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,253 @@
1
+ """``hyperclaw upgrade`` / ``--rollback``: signed-only install with a kept
2
+ previous version and automatic rollback (docs/UPDATES.md, "Flows").
3
+
4
+ Every wheel that is installed, including a rollback target, is checked
5
+ against a Hyper-signed manifest immediately before the install.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import datetime as _dt
10
+ import json
11
+ import subprocess
12
+ from dataclasses import dataclass
13
+ from pathlib import Path
14
+ from typing import Callable, Optional, Tuple
15
+
16
+ from hyperclaw import distribution as _dist
17
+ from hyperclaw.updates import index, install as _inst, versioning
18
+ from hyperclaw.updates import manifest as _m
19
+ from hyperclaw.updates.paths import current_version, read_json, releases_dir, updates_dir, write_json
20
+
21
+
22
+ @dataclass
23
+ class Result:
24
+ status: str # up-to-date | checked | declined | upgraded | restored | rolled-back | refused | failed
25
+ message: str
26
+ before: str = ""
27
+ after: str = ""
28
+
29
+ @property
30
+ def ok(self) -> bool:
31
+ return self.status in ("up-to-date", "checked", "declined", "upgraded", "restored")
32
+
33
+
34
+ # ───────────────────────── state + retained releases ─────────────────────────
35
+
36
+ def state_path() -> Path:
37
+ return updates_dir() / "state.json"
38
+
39
+
40
+ def load_state() -> dict:
41
+ s = read_json(state_path(), {})
42
+ return s if isinstance(s, dict) else {}
43
+
44
+
45
+ def _record(action: str, frm: str, to: str, result: str, source: str, **upd) -> None:
46
+ s = load_state()
47
+ s.update(upd)
48
+ hist = s.setdefault("history", [])
49
+ hist.append({"at": _dt.datetime.now(_dt.timezone.utc).isoformat(timespec="seconds"),
50
+ "action": action, "from": frm, "to": to, "result": result, "source": source})
51
+ del hist[:-50]
52
+ write_json(state_path(), s)
53
+
54
+
55
+ def release_dir(version: str) -> Path:
56
+ return releases_dir() / versioning.normalize(version)
57
+
58
+
59
+ def load_retained(version: str) -> Tuple[dict, Path]:
60
+ """Re-verify a retained release (signature + wheel sha256). Raises ManifestError."""
61
+ d = release_dir(version)
62
+ m = _m.load_and_verify(d / "manifest.json", expect_version=version)
63
+ wheel = d / m["wheel"]
64
+ _m.verify_wheel_file(wheel, m)
65
+ return m, wheel
66
+
67
+
68
+ def fetch_and_retain(version: str, *, timeout: float = 60) -> Tuple[dict, Path]:
69
+ """Retained copy of ``version`` (reused if it still verifies, else fetched
70
+ from the index and checked against its signed manifest)."""
71
+ try:
72
+ return load_retained(version)
73
+ except _m.ManifestError:
74
+ pass
75
+ rel = index.release_info(version, timeout=timeout)
76
+ m, env = index.fetch_signed(version, rel, timeout=timeout)
77
+ d = release_dir(version)
78
+ wheel = index.download_wheel(index.wheel_url(rel, m["wheel"]), d, m, timeout=timeout)
79
+ write_json(d / "manifest.json", env)
80
+ return m, wheel
81
+
82
+
83
+ # ───────────────────────── upgrade ─────────────────────────
84
+
85
+ Out = Callable[[str], None]
86
+
87
+
88
+ def _notes(m: dict) -> str:
89
+ return f"HyperClaw {m['version']} release notes:\n\n{m['release_notes'].strip()}\n"
90
+
91
+
92
+ def latest_or_current(current: str, *, timeout: float = 10) -> str:
93
+ """Newest ``hyperagents`` release on the index, or ``current`` when the
94
+ index cannot be reached (offline) or lists nothing usable."""
95
+ try:
96
+ info = index.project_info(timeout=timeout)
97
+ return index.latest_version(info, include_pre=index.include_prereleases(current)) \
98
+ or current
99
+ except Exception: # offline, bad JSON: fall back to the running version
100
+ return current
101
+
102
+
103
+ def legacy_result(current: str, *, timeout: float = 10) -> Result:
104
+ """An install of the old ``hyperclaw`` distribution: one plain line saying
105
+ how to move to ``hyperagents``. Nothing is uninstalled automatically."""
106
+ return Result("refused", _dist.migration_line(latest_or_current(current, timeout=timeout)),
107
+ current, current)
108
+
109
+
110
+ def upgrade(*, target: Optional[str] = None, yes: bool = False, check_only: bool = False,
111
+ allow_no_rollback: bool = False, confirm: Optional[Callable[[str], bool]] = None,
112
+ out: Out = print, method: Optional[_inst.InstallMethod] = None,
113
+ runner=subprocess.run, source: str = "user",
114
+ current: Optional[str] = None, legacy: Optional[bool] = None) -> Result:
115
+ current = current or current_version()
116
+ if _dist.is_legacy_install() if legacy is None else legacy:
117
+ return legacy_result(current)
118
+ try:
119
+ if target is None:
120
+ info = index.project_info()
121
+ target = index.latest_version(info, include_pre=index.include_prereleases(current))
122
+ if not target or not versioning.is_newer(target, current):
123
+ return Result("up-to-date", f"HyperClaw {current} is up to date.", current, current)
124
+ elif not versioning.is_newer(target, current):
125
+ return Result("refused", f"Refused: {target} is not newer than the installed "
126
+ f"{current}. To go back, use 'hyperclaw upgrade --rollback'.",
127
+ current, current)
128
+ rel = index.release_info(target)
129
+ m, _env = index.fetch_signed(target, rel)
130
+ except _m.ManifestError as e:
131
+ return Result("refused", f"Refused: {e}. Only Hyper-signed releases are installed.",
132
+ current, current)
133
+ except index.IndexError_ as e:
134
+ return Result("failed", f"Could not check for updates ({e}).", current, current)
135
+
136
+ target = m["version"]
137
+ mn = versioning.parse(m["min_upgrade_from"])
138
+ if mn and versioning.parse(current) < mn:
139
+ return Result("refused", f"Refused: {target} needs {m['min_upgrade_from']} or later "
140
+ f"installed first (you have {current}). Run 'hyperclaw upgrade "
141
+ f"--version {m['min_upgrade_from']}' first.", current, current)
142
+ out(_notes(m))
143
+ if check_only:
144
+ return Result("checked", f"{target} is available (signed, verified).", current, current)
145
+
146
+ try:
147
+ method = method or _inst.detect()
148
+ except _inst.UnsupportedInstall as e:
149
+ return Result("refused", str(e), current, current)
150
+
151
+ if not yes:
152
+ ask = confirm or (lambda q: input(q).strip().lower() in ("y", "yes"))
153
+ if not ask(f"Install HyperClaw {target} (replacing {current})? [y/N] "):
154
+ return Result("declined", "Nothing installed.", current, current)
155
+
156
+ try:
157
+ m, wheel = fetch_and_retain(target)
158
+ except (_m.ManifestError, index.IndexError_) as e:
159
+ return Result("refused", f"Refused: {e}. Nothing installed.", current, current)
160
+ prev_wheel = None
161
+ try:
162
+ _pm, prev_wheel = fetch_and_retain(current)
163
+ except (_m.ManifestError, index.IndexError_) as e:
164
+ if not allow_no_rollback:
165
+ return Result("refused", f"Refused: the installed version {current} cannot be kept "
166
+ f"for rollback ({e}). Re-run with --allow-no-rollback to upgrade "
167
+ f"without a way back.", current, current)
168
+ out(f"Warning: {current} cannot be kept for rollback ({e}).")
169
+
170
+ out(f"Installing HyperClaw {target} ({method.kind})...")
171
+ ok, log = _inst.install(method, wheel, runner=runner)
172
+ if ok and _inst.verify(method, target, runner=runner):
173
+ upd = {"installed": target, "method": method.kind}
174
+ if prev_wheel is not None:
175
+ upd["previous"] = versioning.normalize(current)
176
+ _record("upgrade", current, target, "ok", source, **upd)
177
+ return Result("upgraded", f"HyperClaw {target} installed and verified "
178
+ f"(previous {current} kept; 'hyperclaw upgrade --rollback' restores it). "
179
+ f"Restart any running HyperClaw services to use it.", current, target)
180
+
181
+ why = "install failed" if not ok else "post-install check failed"
182
+ return _auto_rollback(method, current, target, why, log, runner, source)
183
+
184
+
185
+ def _auto_rollback(method, current, target, why, log, runner, source) -> Result:
186
+ tail = (log or "").strip().splitlines()[-3:]
187
+ detail = (" | " + " / ".join(tail)) if tail and why == "install failed" else ""
188
+ try:
189
+ _pm, prev_wheel = load_retained(current)
190
+ except _m.ManifestError as e:
191
+ _record("upgrade", current, target, f"{why}; no rollback ({e})", source)
192
+ return Result("failed", f"Upgrade to {target} failed ({why}{detail}) and {current} "
193
+ f"could not be restored automatically ({e}). Reinstall with "
194
+ f"'pipx install --force {_dist.DIST_NAME}=={current}'.", current, "")
195
+ ok, _ = _inst.install(method, prev_wheel, force=True, runner=runner)
196
+ back = ok and _inst.verify(method, current, runner=runner)
197
+ _record("upgrade", current, target, f"{why}; auto-rollback {'ok' if back else 'FAILED'}",
198
+ source)
199
+ if back:
200
+ return Result("rolled-back", f"Upgrade to {target} failed ({why}{detail}); "
201
+ f"rolled back to {current} automatically.", current, current)
202
+ return Result("failed", f"Upgrade to {target} failed ({why}{detail}) and the automatic "
203
+ f"rollback to {current} also failed. Reinstall with "
204
+ f"'pipx install --force {_dist.DIST_NAME}=={current}'.", current, "")
205
+
206
+
207
+ # ───────────────────────── rollback ─────────────────────────
208
+
209
+ def rollback(*, yes: bool = False, confirm: Optional[Callable[[str], bool]] = None,
210
+ out: Out = print, method: Optional[_inst.InstallMethod] = None,
211
+ runner=subprocess.run, current: Optional[str] = None) -> Result:
212
+ current = current or current_version()
213
+ prev = load_state().get("previous")
214
+ if not prev:
215
+ return Result("refused", "No previous version is kept; nothing to roll back to.",
216
+ current, current)
217
+ try:
218
+ _pm, wheel = load_retained(prev)
219
+ except _m.ManifestError as e:
220
+ return Result("refused", f"Refused: the kept version {prev} failed verification ({e}). "
221
+ f"Nothing installed.", current, current)
222
+ try:
223
+ method = method or _inst.detect()
224
+ except _inst.UnsupportedInstall as e:
225
+ return Result("refused", str(e), current, current)
226
+ if not yes:
227
+ ask = confirm or (lambda q: input(q).strip().lower() in ("y", "yes"))
228
+ if not ask(f"Roll back HyperClaw {current} to {prev}? [y/N] "):
229
+ return Result("declined", "Nothing installed.", current, current)
230
+ out(f"Restoring HyperClaw {prev} ({method.kind})...")
231
+ ok, _ = _inst.install(method, wheel, force=True, runner=runner)
232
+ if ok and _inst.verify(method, prev, runner=runner):
233
+ _record("rollback", current, prev, "ok", "user", installed=prev,
234
+ previous=versioning.normalize(current))
235
+ return Result("restored", f"Rolled back to HyperClaw {prev} (verified). "
236
+ f"{current} is kept; run --rollback again to return to it.", current, prev)
237
+ # The rollback itself failed: try to put the current version back.
238
+ try:
239
+ _cm, cur_wheel = load_retained(current)
240
+ ok2, _ = _inst.install(method, cur_wheel, force=True, runner=runner)
241
+ restored = ok2 and _inst.verify(method, current, runner=runner)
242
+ except _m.ManifestError:
243
+ restored = False
244
+ _record("rollback", current, prev, f"failed; restore {'ok' if restored else 'FAILED'}", "user")
245
+ return Result("rolled-back" if restored else "failed",
246
+ f"Rollback to {prev} failed; " + (f"{current} restored." if restored else
247
+ f"reinstall with 'pipx install --force "
248
+ f"{_dist.DIST_NAME}=={current}'."),
249
+ current, current if restored else "")
250
+
251
+
252
+ def history_json() -> str:
253
+ return json.dumps(load_state(), indent=2, sort_keys=True)
@@ -0,0 +1,104 @@
1
+ """PEP 440 version parsing/ordering for the subset HyperClaw publishes
2
+ (``X.Y.Z``, ``aN``/``bN``/``rcN``, ``.postN``, ``.devN``, ``+local``).
3
+
4
+ Kept dependency-free so the updater works even if ``packaging`` is absent.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ import re
9
+ from typing import Optional, Tuple
10
+
11
+ _RE = re.compile(
12
+ r"^\s*v?(?P<rel>\d+(?:\.\d+)*)"
13
+ r"(?:[-_.]?(?P<pre>a|alpha|b|beta|c|rc|pre|preview)[-_.]?(?P<pren>\d*))?"
14
+ r"(?:[-_.]?post[-_.]?(?P<post>\d*))?"
15
+ r"(?:[-_.]?dev[-_.]?(?P<dev>\d*))?"
16
+ r"(?:\+(?P<local>[a-z0-9]+(?:[-_.][a-z0-9]+)*))?\s*$",
17
+ re.IGNORECASE,
18
+ )
19
+ _PHASE = {"a": 0, "alpha": 0, "b": 1, "beta": 1, "c": 2, "rc": 2, "pre": 2, "preview": 2}
20
+ _PHASE_NAME = {0: "a", 1: "b", 2: "rc"}
21
+
22
+
23
+ class Version:
24
+ __slots__ = ("release", "pre", "post", "dev", "local")
25
+
26
+ def __init__(self, release: Tuple[int, ...], pre=None, post=None, dev=None, local=None):
27
+ self.release, self.pre, self.post, self.dev, self.local = release, pre, post, dev, local
28
+
29
+ @property
30
+ def is_prerelease(self) -> bool:
31
+ return self.pre is not None or self.dev is not None
32
+
33
+ def _key(self):
34
+ rel = list(self.release)
35
+ while len(rel) > 1 and rel[-1] == 0:
36
+ rel.pop()
37
+ if self.pre is not None:
38
+ pre = self.pre
39
+ elif self.dev is not None and self.post is None:
40
+ pre = (-1, 0) # X.devN sorts before X.aN
41
+ else:
42
+ pre = (3, 0) # final release
43
+ post = self.post if self.post is not None else -1
44
+ dev = self.dev if self.dev is not None else float("inf")
45
+ return (tuple(rel), pre, post, dev)
46
+
47
+ def __eq__(self, o):
48
+ return isinstance(o, Version) and self._key() == o._key()
49
+
50
+ def __lt__(self, o):
51
+ return self._key() < o._key()
52
+
53
+ def __le__(self, o):
54
+ return self._key() <= o._key()
55
+
56
+ def __gt__(self, o):
57
+ return self._key() > o._key()
58
+
59
+ def __ge__(self, o):
60
+ return self._key() >= o._key()
61
+
62
+ def __hash__(self):
63
+ return hash(self._key())
64
+
65
+ def __str__(self):
66
+ s = ".".join(map(str, self.release))
67
+ if self.pre is not None:
68
+ s += f"{_PHASE_NAME[self.pre[0]]}{self.pre[1]}"
69
+ if self.post is not None:
70
+ s += f".post{self.post}"
71
+ if self.dev is not None:
72
+ s += f".dev{self.dev}"
73
+ if self.local:
74
+ s += f"+{self.local}"
75
+ return s
76
+
77
+ __repr__ = __str__
78
+
79
+
80
+ def parse(v) -> Optional[Version]:
81
+ """Parse a version string; None when it is not a valid PEP 440 version."""
82
+ if isinstance(v, Version):
83
+ return v
84
+ m = _RE.match(str(v or ""))
85
+ if not m:
86
+ return None
87
+ rel = tuple(int(x) for x in m.group("rel").split("."))
88
+ pre = None
89
+ if m.group("pre"):
90
+ pre = (_PHASE[m.group("pre").lower()], int(m.group("pren") or 0))
91
+ post = int(m.group("post") or 0) if m.group("post") is not None else None
92
+ dev = int(m.group("dev") or 0) if m.group("dev") is not None else None
93
+ local = m.group("local").lower() if m.group("local") else None
94
+ return Version(rel, pre, post, dev, local)
95
+
96
+
97
+ def normalize(v) -> str:
98
+ p = parse(v)
99
+ return str(p) if p else str(v)
100
+
101
+
102
+ def is_newer(candidate, current) -> bool:
103
+ a, b = parse(candidate), parse(current)
104
+ return bool(a and b and a > b)
@@ -0,0 +1,119 @@
1
+ """The customer's own config: ``config.json`` and ``.env`` under HYPERCLAW_ROOT.
2
+
3
+ One place that knows where these live, so onboarding, the TUI, the Telegram
4
+ bot, doctor and the CLI all read and write the same files. Writes merge;
5
+ they never drop keys they were not asked to change.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import json
10
+ import os
11
+ from pathlib import Path
12
+
13
+
14
+ def root() -> Path:
15
+ return Path(os.environ.get("HYPERCLAW_ROOT", str(Path.home() / ".hyperclaw")))
16
+
17
+
18
+ def config_path() -> Path:
19
+ return root() / "config.json"
20
+
21
+
22
+ def env_path() -> Path:
23
+ return root() / ".env"
24
+
25
+
26
+ def load_config() -> dict:
27
+ p = config_path()
28
+ try:
29
+ if p.exists():
30
+ data = json.loads(p.read_text(encoding="utf-8"))
31
+ if isinstance(data, dict):
32
+ return data
33
+ except Exception:
34
+ pass
35
+ return {}
36
+
37
+
38
+ def update_config(**updates) -> dict:
39
+ """Merge ``updates`` into config.json (a value of None removes the key)."""
40
+ data = load_config()
41
+ for k, v in updates.items():
42
+ if v is None:
43
+ data.pop(k, None)
44
+ else:
45
+ data[k] = v
46
+ p = config_path()
47
+ p.parent.mkdir(parents=True, exist_ok=True)
48
+ tmp = p.with_suffix(".json.tmp")
49
+ tmp.write_text(json.dumps(data, indent=2), encoding="utf-8")
50
+ os.replace(tmp, p)
51
+ return data
52
+
53
+
54
+ # ── .env ────────────────────────────────────────────────────────────────
55
+
56
+ def _parse_env(text: str) -> list:
57
+ """Lines as (key|None, raw_line)."""
58
+ out = []
59
+ for line in text.splitlines():
60
+ s = line.strip()
61
+ if s and not s.startswith("#") and "=" in s:
62
+ out.append((s.split("=", 1)[0].strip(), line))
63
+ else:
64
+ out.append((None, line))
65
+ return out
66
+
67
+
68
+ def env_read() -> dict:
69
+ p = env_path()
70
+ if not p.exists():
71
+ return {}
72
+ out = {}
73
+ for key, line in _parse_env(p.read_text(encoding="utf-8")):
74
+ if key:
75
+ out[key] = line.split("=", 1)[1].strip().strip('"')
76
+ return out
77
+
78
+
79
+ def env_update(values: dict, apply_to_process: bool = True) -> None:
80
+ """Set (value str) or remove (value None) keys in .env, keeping every
81
+ other line. The file is created owner-read/write only (0600)."""
82
+ p = env_path()
83
+ p.parent.mkdir(parents=True, exist_ok=True)
84
+ lines = _parse_env(p.read_text(encoding="utf-8")) if p.exists() else []
85
+ pending = dict(values)
86
+ out = []
87
+ for key, line in lines:
88
+ if key is not None and key in pending:
89
+ v = pending.pop(key)
90
+ if v is not None:
91
+ out.append(f"{key}={v}")
92
+ continue
93
+ out.append(line)
94
+ for key, v in pending.items():
95
+ if v is not None:
96
+ out.append(f"{key}={v}")
97
+ text = "\n".join(out).rstrip("\n") + "\n"
98
+ fd = os.open(str(p), os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600)
99
+ with os.fdopen(fd, "w", encoding="utf-8") as f:
100
+ f.write(text)
101
+ try:
102
+ os.chmod(p, 0o600)
103
+ except OSError:
104
+ pass
105
+ if apply_to_process:
106
+ for key, v in values.items():
107
+ if v is None:
108
+ os.environ.pop(key, None)
109
+ else:
110
+ os.environ[key] = v
111
+
112
+
113
+ def load_env_into_process() -> None:
114
+ """setdefault every .env key into os.environ (never overrides), then the
115
+ secrets a team install keeps in the OS secret store (hyperclaw.teamsetup.secrets)."""
116
+ for k, v in env_read().items():
117
+ os.environ.setdefault(k, v)
118
+ from hyperclaw.teamsetup.secrets import export_env
119
+ export_env()
hyperclaw/xai.py ADDED
@@ -0,0 +1,83 @@
1
+ """xAI as a named provider (Grok), on the existing OpenAI-compatible client.
2
+
3
+ - Base URL https://api.x.ai/v1, Bearer auth with XAI_API_KEY, the chat
4
+ completions endpoint (``POST /v1/chat/completions``).
5
+ - Model ids from xAI's model documentation (read 2026-10-11). Default grok-4.7.
6
+ - The reasoning models reject ``presence_penalty``, ``frequency_penalty`` and
7
+ ``stop``: ``clean_request`` removes them for those models, and every chat
8
+ request to this provider goes through it.
9
+ - Tools only with a Hyper-signed qualification record (hyperclaw.qualification).
10
+ None ships, so Grok is chat-only.
11
+ """
12
+ from __future__ import annotations
13
+
14
+ from urllib.parse import urlsplit
15
+
16
+ PROVIDER_NAME = "xai"
17
+ LABEL = "xAI"
18
+ BASE_URL = "https://api.x.ai/v1"
19
+ HOST = "api.x.ai"
20
+ API_KEY_ENV = "XAI_API_KEY"
21
+ BASE_URL_ENV = "XAI_BASE_URL"
22
+ MODEL_ENV = "XAI_MODEL"
23
+ KEY_PREFIX = "xai-"
24
+
25
+ DEFAULT_MODEL = "grok-4.7"
26
+ MODELS = (
27
+ "grok-4.7",
28
+ "grok-4.6",
29
+ "grok-4.5",
30
+ "grok-4.3",
31
+ "grok-4.20-0309-reasoning",
32
+ "grok-4.20-0309-non-reasoning",
33
+ "grok-4.20-multi-agent-0309",
34
+ "grok-build-0.1",
35
+ )
36
+ # These reject presence_penalty, frequency_penalty and stop.
37
+ REASONING_MODELS = frozenset({"grok-4.7", "grok-4.6", "grok-4.5",
38
+ "grok-4.20-multi-agent-0309"})
39
+ REJECTED_PARAMS = ("presence_penalty", "frequency_penalty", "stop")
40
+
41
+ NAMES = {
42
+ "grok-4.7": "Grok 4.7",
43
+ "grok-4.6": "Grok 4.6",
44
+ "grok-4.5": "Grok 4.5",
45
+ "grok-4.3": "Grok 4.3",
46
+ "grok-4.20-0309-reasoning": "Grok 4.20 (reasoning)",
47
+ "grok-4.20-0309-non-reasoning": "Grok 4.20 (non-reasoning)",
48
+ "grok-4.20-multi-agent-0309": "Grok 4.20 multi-agent",
49
+ "grok-build-0.1": "Grok Build 0.1",
50
+ }
51
+
52
+
53
+ def is_xai(provider_name: str = "", base_url: str = "") -> bool:
54
+ if provider_name == PROVIDER_NAME:
55
+ return True
56
+ try:
57
+ return (urlsplit(base_url or "").hostname or "") == HOST
58
+ except ValueError:
59
+ return False
60
+
61
+
62
+ def rejects_penalties(model: str) -> bool:
63
+ return (model or "").strip() in REASONING_MODELS
64
+
65
+
66
+ def clean_request(provider_name: str, base_url: str, model: str, kwargs: dict) -> dict:
67
+ """Request kwargs safe for this provider + model. For xAI: never the
68
+ parameters its reasoning models reject, and never the vLLM-only
69
+ ``chat_template_kwargs`` switch. Other providers: unchanged."""
70
+ if not is_xai(provider_name, base_url):
71
+ return kwargs
72
+ out = dict(kwargs)
73
+ if rejects_penalties(model):
74
+ for k in REJECTED_PARAMS:
75
+ out.pop(k, None)
76
+ extra = out.get("extra_body")
77
+ if isinstance(extra, dict) and "chat_template_kwargs" in extra:
78
+ extra = {k: v for k, v in extra.items() if k != "chat_template_kwargs"}
79
+ if extra:
80
+ out["extra_body"] = extra
81
+ else:
82
+ out.pop("extra_body")
83
+ return out
@@ -0,0 +1,31 @@
1
+ """
2
+ HyperClaw Integrations Layer — unified connector system for all platforms.
3
+ """
4
+
5
+ from integrations.base import (
6
+ BaseConnector,
7
+ ConnectorCapability,
8
+ ConnectorRegistry,
9
+ ConnectorInfo,
10
+ InboundMessage,
11
+ OutboundMessage,
12
+ ConnectorAuthError,
13
+ ConnectorRateLimitError,
14
+ ConnectorUnavailableError,
15
+ CapabilityNotSupportedError,
16
+ )
17
+ from integrations.gateway import HyperClawGateway
18
+
19
+ __all__ = [
20
+ "BaseConnector",
21
+ "ConnectorCapability",
22
+ "ConnectorRegistry",
23
+ "ConnectorInfo",
24
+ "InboundMessage",
25
+ "OutboundMessage",
26
+ "ConnectorAuthError",
27
+ "ConnectorRateLimitError",
28
+ "ConnectorUnavailableError",
29
+ "CapabilityNotSupportedError",
30
+ "HyperClawGateway",
31
+ ]
@@ -0,0 +1 @@
1
+ # Automation integrations
@@ -0,0 +1,87 @@
1
+ """MakeConnector — Make (formerly Integromat) webhook integration."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+
7
+ import httpx
8
+
9
+ from integrations.base import (
10
+ BaseConnector,
11
+ ConnectorCapability,
12
+ ConnectorInfo,
13
+ )
14
+
15
+ logger = logging.getLogger("hyperclaw.integrations.make")
16
+
17
+
18
+ class MakeConnector(BaseConnector):
19
+ """
20
+ Make (Integromat) webhook connector for triggering scenarios.
21
+
22
+ Required config:
23
+ - webhook_url: Make webhook URL
24
+ """
25
+
26
+ def __init__(self, config: dict) -> None:
27
+ self.config = config
28
+ self.webhook_url = config.get("webhook_url", "")
29
+
30
+ if config.get("enabled", False) and not self.webhook_url:
31
+ raise ValueError("MakeConnector requires webhook_url")
32
+
33
+ self._client: httpx.AsyncClient | None = None
34
+
35
+ @property
36
+ def info(self) -> ConnectorInfo:
37
+ return ConnectorInfo(
38
+ connector_id="make",
39
+ platform="make",
40
+ category="automation",
41
+ capabilities=frozenset(
42
+ [
43
+ ConnectorCapability.ACTION,
44
+ ConnectorCapability.WEBHOOKS,
45
+ ]
46
+ ),
47
+ auth_type="webhook_token",
48
+ rate_limit_per_minute=60,
49
+ )
50
+
51
+ async def _get_client(self) -> httpx.AsyncClient:
52
+ if self._client is None:
53
+ self._client = httpx.AsyncClient(
54
+ timeout=30.0,
55
+ headers={"Content-Type": "application/json"},
56
+ )
57
+ return self._client
58
+
59
+ async def health(self) -> bool:
60
+ """Make webhooks don't have a health endpoint."""
61
+ return True
62
+
63
+ async def trigger_scenario(self, data: dict) -> dict:
64
+ """Trigger a Make scenario with the given data."""
65
+ client = await self._get_client()
66
+
67
+ response = await client.post(
68
+ self.webhook_url,
69
+ json=data,
70
+ )
71
+
72
+ # Make webhooks return various status codes
73
+ if response.status_code not in (200, 201, 202):
74
+ raise Exception(f"Make trigger_scenario failed: {response.text}")
75
+
76
+ try:
77
+ return response.json()
78
+ except Exception:
79
+ return {"status": "triggered", "response": response.text}
80
+
81
+ async def _action_impl(self, action_name: str, params: dict) -> dict:
82
+ """Execute Make-specific actions."""
83
+ if action_name == "trigger" or action_name == "trigger_scenario":
84
+ return await self.trigger_scenario(params)
85
+
86
+ # Default: pass all params to trigger
87
+ return await self.trigger_scenario(params)