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.
- agents/__init__.py +31 -0
- agents/background_monitor.py +453 -0
- agents/learning.py +460 -0
- agents/planner.py +421 -0
- agents/transcriber.py +341 -0
- civilization/__init__.py +51 -0
- civilization/api/__init__.py +7 -0
- civilization/api/civilization_api.py +361 -0
- civilization/graph/__init__.py +19 -0
- civilization/graph/knowledge_linker.py +348 -0
- civilization/graph/org_graph.py +348 -0
- civilization/graph/process_graph.py +386 -0
- civilization/ingestion/__init__.py +20 -0
- civilization/ingestion/chunker.py +273 -0
- civilization/ingestion/document_ingestor.py +358 -0
- civilization/ingestion/embedder.py +262 -0
- civilization/ingestion/extractor.py +224 -0
- civilization/interview/__init__.py +19 -0
- civilization/interview/gap_detector.py +380 -0
- civilization/interview/interviewer.py +323 -0
- civilization/interview/questioner.py +363 -0
- civilization/retrieval/__init__.py +17 -0
- civilization/retrieval/civilization_rag.py +316 -0
- civilization/retrieval/context_injector.py +342 -0
- civilization/retrieval/relevance_ranker.py +287 -0
- civilization/schema.py +299 -0
- civilization/store.py +177 -0
- civilization/sync/__init__.py +16 -0
- civilization/sync/confluence_sync.py +317 -0
- civilization/sync/gdrive_sync.py +262 -0
- civilization/sync/notion_sync.py +237 -0
- civilization/versioning/__init__.py +19 -0
- civilization/versioning/diff_engine.py +351 -0
- civilization/versioning/staleness_detector.py +303 -0
- civilization/versioning/version_manager.py +293 -0
- cli/__init__.py +1 -0
- cli/doctor.py +334 -0
- cli/hyperclaw.py +1357 -0
- cli/onboarding.py +469 -0
- core/__init__.py +1 -0
- core/connectors/__init__.py +119 -0
- core/connectors/infrastructure.py +772 -0
- core/connectors/messaging.py +586 -0
- core/connectors/productivity.py +764 -0
- core/connectors/smarthome.py +528 -0
- core/connectors/social.py +730 -0
- core/hyperrouter/__init__.py +23 -0
- core/hyperrouter/bandit.py +218 -0
- core/hyperrouter/fast_loop.py +68 -0
- core/hyperrouter/slow_loop.py +143 -0
- core/hyperstate/__init__.py +24 -0
- core/hyperstate/certifier.py +154 -0
- core/hyperstate/schema.py +160 -0
- core/hyperstate/state_manager.py +103 -0
- core/hyperstate/store.py +163 -0
- core/providers/__init__.py +121 -0
- core/providers/embeddings.py +389 -0
- core/providers/image.py +534 -0
- core/providers/llm.py +1935 -0
- core/providers/search.py +608 -0
- core/providers/voice.py +474 -0
- hyperagents-1.2.3rc5.post3.dist-info/METADATA +469 -0
- hyperagents-1.2.3rc5.post3.dist-info/RECORD +361 -0
- hyperagents-1.2.3rc5.post3.dist-info/WHEEL +5 -0
- hyperagents-1.2.3rc5.post3.dist-info/entry_points.txt +6 -0
- hyperagents-1.2.3rc5.post3.dist-info/licenses/LICENSE +21 -0
- hyperagents-1.2.3rc5.post3.dist-info/top_level.txt +13 -0
- hyperclaw/__init__.py +67 -0
- hyperclaw/__main__.py +220 -0
- hyperclaw/agent.py +775 -0
- hyperclaw/agent_coordinator.py +577 -0
- hyperclaw/anthropic_params.py +225 -0
- hyperclaw/api_utils.py +85 -0
- hyperclaw/approval_telegram.py +209 -0
- hyperclaw/approval_terminal.py +80 -0
- hyperclaw/attachments.py +177 -0
- hyperclaw/branding.py +55 -0
- hyperclaw/brief/__init__.py +11 -0
- hyperclaw/brief/cli.py +204 -0
- hyperclaw/brief/collect.py +146 -0
- hyperclaw/brief/compose.py +333 -0
- hyperclaw/brief/diff.py +110 -0
- hyperclaw/brief/errors.py +99 -0
- hyperclaw/brief/google.py +269 -0
- hyperclaw/brief/google_auth.py +294 -0
- hyperclaw/brief/links.py +124 -0
- hyperclaw/brief/service.py +424 -0
- hyperclaw/brief/store.py +205 -0
- hyperclaw/brief/tasks.py +200 -0
- hyperclaw/chat_scope.py +115 -0
- hyperclaw/cli_tui.py +497 -0
- hyperclaw/client_guard.py +717 -0
- hyperclaw/connection_check.py +113 -0
- hyperclaw/cost_guard.py +1728 -0
- hyperclaw/default_config/agents.yaml +534 -0
- hyperclaw/default_config/hyperclaw.yaml +27 -0
- hyperclaw/default_config/models.yaml +161 -0
- hyperclaw/deployment.py +39 -0
- hyperclaw/digest/__init__.py +9 -0
- hyperclaw/digest/cli.py +117 -0
- hyperclaw/digest/errors.py +100 -0
- hyperclaw/digest/gitlab.py +187 -0
- hyperclaw/digest/http.py +113 -0
- hyperclaw/digest/redact.py +32 -0
- hyperclaw/digest/service.py +271 -0
- hyperclaw/digest/slack.py +223 -0
- hyperclaw/digest/state.py +124 -0
- hyperclaw/digest/summarize.py +198 -0
- hyperclaw/digest/tokens.py +143 -0
- hyperclaw/distribution.py +46 -0
- hyperclaw/documents.py +351 -0
- hyperclaw/feeds.py +339 -0
- hyperclaw/google_workspace/__init__.py +5 -0
- hyperclaw/google_workspace/api.py +151 -0
- hyperclaw/google_workspace/auth.py +397 -0
- hyperclaw/google_workspace/calendar.py +157 -0
- hyperclaw/google_workspace/cli.py +72 -0
- hyperclaw/google_workspace/drive.py +236 -0
- hyperclaw/google_workspace/errors.py +58 -0
- hyperclaw/google_workspace/gmail.py +295 -0
- hyperclaw/google_workspace/meet.py +122 -0
- hyperclaw/google_workspace/tools.py +319 -0
- hyperclaw/hyperspeed.py +57 -0
- hyperclaw/integrations_layer.py +1427 -0
- hyperclaw/live_state.py +188 -0
- hyperclaw/managed_config.py +245 -0
- hyperclaw/media_hub.py +179 -0
- hyperclaw/memory_manager.py +584 -0
- hyperclaw/model_catalog.py +551 -0
- hyperclaw/model_policy.py +211 -0
- hyperclaw/model_router.py +595 -0
- hyperclaw/model_selector.py +72 -0
- hyperclaw/naming.py +284 -0
- hyperclaw/notices.py +65 -0
- hyperclaw/oai_stream.py +106 -0
- hyperclaw/orchestrator.py +591 -0
- hyperclaw/outbox.py +82 -0
- hyperclaw/persona.py +367 -0
- hyperclaw/prometheus.py +219 -0
- hyperclaw/prompt_assembly.py +157 -0
- hyperclaw/provider_setup.py +146 -0
- hyperclaw/providers.py +340 -0
- hyperclaw/qualification.py +341 -0
- hyperclaw/qualification_keys/hyper-d292fd127cef5a3e.pub +1 -0
- hyperclaw/qualification_records/anthropic--claude-sonnet-5-5.json +14 -0
- hyperclaw/reasoning_display.py +35 -0
- hyperclaw/scheduler.py +454 -0
- hyperclaw/server.py +847 -0
- hyperclaw/setup.py +627 -0
- hyperclaw/skills.py +717 -0
- hyperclaw/solomon.py +442 -0
- hyperclaw/swarm.py +533 -0
- hyperclaw/taint.py +832 -0
- hyperclaw/team/__init__.py +28 -0
- hyperclaw/team/dispatcher.py +431 -0
- hyperclaw/team/genesis.py +106 -0
- hyperclaw/team/lead.py +110 -0
- hyperclaw/team/model.py +135 -0
- hyperclaw/team/morning.py +238 -0
- hyperclaw/team/repos.py +66 -0
- hyperclaw/team/roster.py +264 -0
- hyperclaw/team/specialist.py +235 -0
- hyperclaw/team/status.py +163 -0
- hyperclaw/team/watchers.py +112 -0
- hyperclaw/teamsetup/__init__.py +17 -0
- hyperclaw/teamsetup/checks.py +208 -0
- hyperclaw/teamsetup/item.py +391 -0
- hyperclaw/teamsetup/oauth_loopback.py +84 -0
- hyperclaw/teamsetup/onboard.py +550 -0
- hyperclaw/teamsetup/profile.py +82 -0
- hyperclaw/teamsetup/secrets.py +313 -0
- hyperclaw/teamsetup/selfcheck.py +295 -0
- hyperclaw/teamsetup/service.py +495 -0
- hyperclaw/teamsetup/slack_oauth.py +442 -0
- hyperclaw/teamsetup/tty.py +62 -0
- hyperclaw/telegram_bot.py +1106 -0
- hyperclaw/telegram_intake.py +126 -0
- hyperclaw/telegram_pairing.py +360 -0
- hyperclaw/terminal_provider.py +323 -0
- hyperclaw/tools/__init__.py +1 -0
- hyperclaw/tools/approval.py +949 -0
- hyperclaw/tools/args.py +212 -0
- hyperclaw/tools/catalog.py +244 -0
- hyperclaw/tools/classify.py +266 -0
- hyperclaw/tools/effects.py +724 -0
- hyperclaw/tools/egress.py +210 -0
- hyperclaw/tools/envelope.py +188 -0
- hyperclaw/tools/guard.py +367 -0
- hyperclaw/tools/ledger.py +339 -0
- hyperclaw/tools/receipts.py +70 -0
- hyperclaw/tools/registry.py +318 -0
- hyperclaw/tools/service_guard.py +151 -0
- hyperclaw/tools/surfaces.py +54 -0
- hyperclaw/tools/workspace.py +87 -0
- hyperclaw/trading/__init__.py +77 -0
- hyperclaw/truth_guard.py +791 -0
- hyperclaw/tts.py +124 -0
- hyperclaw/tui.py +4255 -0
- hyperclaw/tui_bridge.py +938 -0
- hyperclaw/unattended.py +60 -0
- hyperclaw/updates/__init__.py +54 -0
- hyperclaw/updates/check.py +131 -0
- hyperclaw/updates/index.py +178 -0
- hyperclaw/updates/install.py +110 -0
- hyperclaw/updates/managed.py +161 -0
- hyperclaw/updates/manifest.py +138 -0
- hyperclaw/updates/notices.py +86 -0
- hyperclaw/updates/paths.py +53 -0
- hyperclaw/updates/release.py +146 -0
- hyperclaw/updates/upgrade.py +253 -0
- hyperclaw/updates/versioning.py +104 -0
- hyperclaw/userconfig.py +119 -0
- hyperclaw/xai.py +83 -0
- integrations/__init__.py +31 -0
- integrations/automation/__init__.py +1 -0
- integrations/automation/make.py +87 -0
- integrations/automation/zapier.py +87 -0
- integrations/base.py +476 -0
- integrations/communication/__init__.py +1 -0
- integrations/communication/twilio.py +279 -0
- integrations/communication/zoom.py +290 -0
- integrations/data/__init__.py +1 -0
- integrations/data/google_analytics.py +135 -0
- integrations/data/postgres.py +182 -0
- integrations/data/supabase_connector.py +202 -0
- integrations/data/weather_feeds.py +450 -0
- integrations/developer/__init__.py +9 -0
- integrations/developer/github.py +406 -0
- integrations/developer/gitlab.py +298 -0
- integrations/enterprise/__init__.py +13 -0
- integrations/enterprise/confluence.py +266 -0
- integrations/enterprise/hubspot.py +293 -0
- integrations/enterprise/jira.py +314 -0
- integrations/enterprise/salesforce.py +286 -0
- integrations/finance/__init__.py +1 -0
- integrations/finance/polymarket_client.py +247 -0
- integrations/finance/quickbooks.py +281 -0
- integrations/finance/stripe.py +261 -0
- integrations/gateway.py +238 -0
- integrations/messaging/__init__.py +32 -0
- integrations/messaging/discord.py +159 -0
- integrations/messaging/email.py +401 -0
- integrations/messaging/imessage.py +443 -0
- integrations/messaging/signal.py +175 -0
- integrations/messaging/slack.py +217 -0
- integrations/messaging/sms.py +175 -0
- integrations/messaging/teams.py +208 -0
- integrations/messaging/telegram.py +260 -0
- integrations/messaging/whatsapp.py +203 -0
- integrations/productivity/__init__.py +17 -0
- integrations/productivity/airtable.py +217 -0
- integrations/productivity/asana.py +248 -0
- integrations/productivity/google/__init__.py +19 -0
- integrations/productivity/google/base.py +89 -0
- integrations/productivity/google/calendar.py +272 -0
- integrations/productivity/google/docs.py +202 -0
- integrations/productivity/google/drive.py +268 -0
- integrations/productivity/google/gmail.py +279 -0
- integrations/productivity/google/meet.py +218 -0
- integrations/productivity/google/sheets.py +222 -0
- integrations/productivity/google/tasks.py +199 -0
- integrations/productivity/linear.py +279 -0
- integrations/productivity/microsoft/__init__.py +13 -0
- integrations/productivity/microsoft/base.py +93 -0
- integrations/productivity/microsoft/calendar.py +207 -0
- integrations/productivity/microsoft/onedrive.py +222 -0
- integrations/productivity/microsoft/outlook.py +241 -0
- integrations/productivity/microsoft/sharepoint.py +211 -0
- integrations/productivity/notion.py +243 -0
- integrations/productivity/todoist.py +250 -0
- integrations/productivity/trello.py +244 -0
- integrations/storage/__init__.py +1 -0
- integrations/storage/box.py +279 -0
- integrations/storage/dropbox.py +215 -0
- integrations/telegram_inbound.py +179 -0
- integrations/telegram_poller.py +151 -0
- memory/__init__.py +1 -0
- memory/causal_graph.py +125 -0
- memory/impact_tracker.py +88 -0
- memory/vector_memory.py +436 -0
- models/__init__.py +12 -0
- models/chatjimmy_client.py +210 -0
- models/claude_client.py +235 -0
- models/claude_code_subagent.py +192 -0
- models/openai_client.py +120 -0
- models/router.py +162 -0
- recursive/discovery_loop.py +118 -0
- recursive/optimization_loop.py +135 -0
- recursive/validation_loop.py +38 -0
- security/__init__.py +1 -0
- security/audit_logger.py +106 -0
- security/filesystem_guard.py +100 -0
- security/hypershield.py +99 -0
- security/network_guard.py +97 -0
- security/policy_engine.py +198 -0
- swarm/__init__.py +1 -0
- swarm/agents/__init__.py +0 -0
- swarm/agents/base.py +230 -0
- swarm/agents/business/__init__.py +0 -0
- swarm/agents/business/counsel.py +31 -0
- swarm/agents/business/herald.py +32 -0
- swarm/agents/business/ledger.py +26 -0
- swarm/agents/business/nexus.py +30 -0
- swarm/agents/business/ops.py +30 -0
- swarm/agents/business/pipeline.py +28 -0
- swarm/agents/business/revenue.py +30 -0
- swarm/agents/business/sovereign.py +31 -0
- swarm/agents/business/strategos.py +27 -0
- swarm/agents/business/talent.py +27 -0
- swarm/agents/business/venture.py +32 -0
- swarm/agents/comms/__init__.py +1 -0
- swarm/agents/comms/cipher.py +32 -0
- swarm/agents/comms/echo_comms.py +31 -0
- swarm/agents/comms/envoy.py +31 -0
- swarm/agents/comms/herald_scheduler.py +33 -0
- swarm/agents/comms/pulse.py +29 -0
- swarm/agents/creative/__init__.py +0 -0
- swarm/agents/creative/author.py +27 -0
- swarm/agents/creative/lens.py +25 -0
- swarm/agents/executive/__init__.py +1 -0
- swarm/agents/intelligence/__init__.py +1 -0
- swarm/agents/intelligence/arbiter.py +30 -0
- swarm/agents/intelligence/sentinel.py +30 -0
- swarm/agents/media/__init__.py +0 -0
- swarm/agents/personal/__init__.py +0 -0
- swarm/agents/personal/atlas.py +27 -0
- swarm/agents/personal/hearth.py +26 -0
- swarm/agents/personal/midas.py +27 -0
- swarm/agents/personal/navigator.py +27 -0
- swarm/agents/personal/nourish.py +31 -0
- swarm/agents/personal/vitals.py +33 -0
- swarm/agents/recursive/__init__.py +0 -0
- swarm/agents/recursive/alchemist.py +73 -0
- swarm/agents/recursive/calibrator.py +45 -0
- swarm/agents/recursive/scout.py +97 -0
- swarm/agents/scientific/__init__.py +0 -0
- swarm/agents/scientific/cosmos.py +36 -0
- swarm/agents/scientific/gaia.py +26 -0
- swarm/agents/scientific/medicus.py +33 -0
- swarm/agents/scientific/oracle_agent.py +39 -0
- swarm/agents/scientific/scribe.py +39 -0
- swarm/agents/talent/__init__.py +0 -0
- swarm/agents/talent/deal.py +28 -0
- swarm/agents/talent/roster.py +29 -0
- swarm/agents/talent/scout.py +29 -0
- swarm/agents/talent/stage.py +27 -0
- swarm/agents/tech/__init__.py +0 -0
- swarm/agents/tech/aegis.py +28 -0
- swarm/agents/tech/bridge.py +28 -0
- swarm/agents/tech/forge.py +28 -0
- swarm/agents/trading/__init__.py +1 -0
- swarm/agents/trading/global_prediction_engine.py +611 -0
- swarm/agents/trading/polymarket_trader.py +165 -0
- swarm/agents/trading/prediction_strategist.py +218 -0
- swarm/autogen_bridge.py +68 -0
- swarm/bid_protocol.py +196 -0
- swarm/dispatcher.py +527 -0
- swarm/nexus.py +181 -0
- swarm/registry.py +112 -0
- telegram_listener.py +403 -0
- 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)
|
hyperclaw/userconfig.py
ADDED
|
@@ -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
|
integrations/__init__.py
ADDED
|
@@ -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)
|