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,112 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Watchers (``agent_start``), owner-only.
|
|
3
|
+
|
|
4
|
+
Watchers are ON again in rc5, inside one constraint (Vaughn's spec):
|
|
5
|
+
|
|
6
|
+
- they only ever message their OWNER (the owner Telegram chat(s) from
|
|
7
|
+
``chat_scope.owner_ids``; never TELEGRAM_CHAT_ID from the environment, never
|
|
8
|
+
anyone else);
|
|
9
|
+
- in ONE fixed format, one line per alert: ``<who>: <what> - <one line>``
|
|
10
|
+
(``format_alert``). Markdown, line breaks and every web address are
|
|
11
|
+
removed, link previews are off: a watcher alert never opens a link and
|
|
12
|
+
never makes Telegram open one;
|
|
13
|
+
- they never act: anything other than this one-line alert to the owner
|
|
14
|
+
(a reply, a forward, a post, a fetch of something they saw) is a normal
|
|
15
|
+
request that waits in the approval queue for Send.
|
|
16
|
+
|
|
17
|
+
Starting them is itself an ``ask``-tier tool call: the card says exactly what
|
|
18
|
+
the watchers will do (``START_CARD``) and nothing runs until the owner
|
|
19
|
+
presses Send. Because every later message has this fixed shape and goes only
|
|
20
|
+
to the owner, what they would send CAN be shown up front, which is why
|
|
21
|
+
agent_start is no longer tier "never" (tools/catalog.py).
|
|
22
|
+
"""
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
import logging
|
|
26
|
+
import re
|
|
27
|
+
from typing import Callable, Dict, Iterable, List, Optional
|
|
28
|
+
|
|
29
|
+
log = logging.getLogger("hyperclaw.team.watchers")
|
|
30
|
+
|
|
31
|
+
LINE_MAX = 160
|
|
32
|
+
_URL = re.compile(r"(?i)\b(?:https?://|ftp://|www\.)\S+|\b[\w.-]+\.(?:com|net|org|io|co|ai|app|"
|
|
33
|
+
r"dev|ly|gl|me|info|biz|us|uk)(?:/\S*)?\b")
|
|
34
|
+
_MARK = re.compile(r"[*_`~<>#\[\]()|]+")
|
|
35
|
+
_SPACE = re.compile(r"\s+")
|
|
36
|
+
|
|
37
|
+
START_CARD = ("Start background watchers. They read only what they watch (unread mail "
|
|
38
|
+
"subjects and senders, prices, news headlines), and they only ever message YOU, "
|
|
39
|
+
"one line each: who, what, one line. They never open links and never reply, "
|
|
40
|
+
"post or act; anything else waits for your Send.")
|
|
41
|
+
|
|
42
|
+
WHO = {"email_monitor": "Email watcher", "market_monitor": "Market watcher",
|
|
43
|
+
"news_monitor": "News watcher"}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def clean(text: str, limit: int = LINE_MAX) -> str:
|
|
47
|
+
"""One plain line: no addresses, no markdown, no line breaks."""
|
|
48
|
+
t = _URL.sub("\x00", str(text or ""))
|
|
49
|
+
t = _MARK.sub(" ", t).replace("\x00", "[link removed]")
|
|
50
|
+
t = _SPACE.sub(" ", t).strip()
|
|
51
|
+
if len(t) > limit:
|
|
52
|
+
t = t[:limit - 1].rstrip() + "…"
|
|
53
|
+
return t
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def format_alert(who: str, what: str, line: str) -> str:
|
|
57
|
+
"""The one fixed format: '<who>: <what> - <one line>'."""
|
|
58
|
+
return f"{clean(who, 40)}: {clean(what, 40)} - {clean(line)}"
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def alert_lines(agent_name: str, result: dict) -> List[str]:
|
|
62
|
+
"""The fixed-format lines for one watcher result (at most 3)."""
|
|
63
|
+
who = WHO.get(agent_name, agent_name.replace("_", " ").capitalize())
|
|
64
|
+
out: List[str] = []
|
|
65
|
+
for e in (result.get("emails") or [])[:3]:
|
|
66
|
+
out.append(format_alert(who, "important email",
|
|
67
|
+
f"from {e.get('sender', '?')}: {e.get('subject', '')}"))
|
|
68
|
+
for m in (result.get("moves") or [])[:3]:
|
|
69
|
+
out.append(format_alert(who, f"{m.get('symbol', '?')} moved",
|
|
70
|
+
f"{m.get('move', '')} to {m.get('price', '')}"))
|
|
71
|
+
for n in (result.get("news") or [])[:3]:
|
|
72
|
+
out.append(format_alert(who, "news", n.get("title", "")))
|
|
73
|
+
if not out:
|
|
74
|
+
out.append(format_alert(who, "alert", "something needs your attention (no detail)"))
|
|
75
|
+
return out
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def owner_chats() -> List[int]:
|
|
79
|
+
from hyperclaw.chat_scope import owner_ids
|
|
80
|
+
out = []
|
|
81
|
+
for c in sorted(owner_ids("telegram")):
|
|
82
|
+
try:
|
|
83
|
+
out.append(int(c))
|
|
84
|
+
except (TypeError, ValueError):
|
|
85
|
+
continue
|
|
86
|
+
return out
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def send_owner_alert(agent_name: str, result: dict, *,
|
|
90
|
+
send: Optional[Callable[[int, str], bool]] = None,
|
|
91
|
+
chats: Optional[Iterable[int]] = None) -> List[int]:
|
|
92
|
+
"""Send the fixed-format lines to the owner chat(s) only. Returns the
|
|
93
|
+
chats reached. Plain text, link previews off (brief.service.telegram_send)."""
|
|
94
|
+
if send is None:
|
|
95
|
+
from hyperclaw.brief.service import telegram_send as send
|
|
96
|
+
text = "\n".join(alert_lines(agent_name, result))
|
|
97
|
+
reached = []
|
|
98
|
+
for chat in (owner_chats() if chats is None else list(chats)):
|
|
99
|
+
try:
|
|
100
|
+
if send(int(chat), text):
|
|
101
|
+
reached.append(int(chat))
|
|
102
|
+
except Exception as e:
|
|
103
|
+
log.warning("watcher alert not delivered (%s)", type(e).__name__)
|
|
104
|
+
if not reached:
|
|
105
|
+
log.info("watcher alert: no owner chat reached")
|
|
106
|
+
return reached
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
async def owner_alert_callback(agent_name: str, result: Dict) -> None:
|
|
110
|
+
"""The AgentManager alert callback (agents/background_monitor.py)."""
|
|
111
|
+
import asyncio
|
|
112
|
+
await asyncio.to_thread(send_owner_alert, agent_name, result)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Remote team onboarding (a team member setting up their own install;
|
|
2
|
+
not the team of agents in hyperclaw.team) (docs/TEAM-ONBOARDING.md).
|
|
3
|
+
|
|
4
|
+
A team member sets up HyperAgents alone, from one 1Password item:
|
|
5
|
+
|
|
6
|
+
- ``item`` the versioned item format and its parser (a Hyper-signed team
|
|
7
|
+
profile plus their personal secrets);
|
|
8
|
+
- ``secrets`` the OS secret store (macOS keychain, Linux Secret Service),
|
|
9
|
+
else an owner-only 0600 file;
|
|
10
|
+
- ``profile`` the verified team profile on this install (read by
|
|
11
|
+
``hyperclaw.managed_config`` as the install's managed settings);
|
|
12
|
+
- ``slack_oauth`` / ``google`` / ``checks`` sign-ins and the minimal
|
|
13
|
+
authenticated call that proves each connection;
|
|
14
|
+
- ``onboard`` the resumable step runner behind ``hyperclaw onboard --team``;
|
|
15
|
+
- ``service`` the one background service at login (Telegram + scheduler);
|
|
16
|
+
- ``selfcheck`` the redacted report for ``hyperclaw self-check``.
|
|
17
|
+
"""
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
"""One minimal authenticated call per connected piece, for the "✓ Connected"
|
|
2
|
+
lines of ``hyperclaw onboard --team`` and for ``hyperclaw self-check``.
|
|
3
|
+
|
|
4
|
+
Every check returns a ``Check`` with a plain line: ``✓ Connected: <piece> ...``
|
|
5
|
+
or ``✗ <piece>: <reason>. Fix: <what to do>``. No line ever holds a key, a
|
|
6
|
+
token, an email address or message text. Calls are read-only.
|
|
7
|
+
|
|
8
|
+
- model: ``GET /v1/models/<id>`` on the Anthropic API (no tokens used);
|
|
9
|
+
- Telegram: ``getMe`` (and the owner chat is paired);
|
|
10
|
+
- Google: ``tokeninfo`` for the stored access (refreshed when needed);
|
|
11
|
+
- Slack: ``auth.test`` (hyperclaw.teamsetup.slack_oauth);
|
|
12
|
+
- GitLab: ``personal_access_tokens/self`` + ``user`` (hyperclaw.digest.gitlab).
|
|
13
|
+
"""
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import logging
|
|
17
|
+
import os
|
|
18
|
+
from dataclasses import dataclass
|
|
19
|
+
from typing import Optional
|
|
20
|
+
|
|
21
|
+
import httpx
|
|
22
|
+
|
|
23
|
+
log = logging.getLogger("hyperclaw.teamsetup.checks")
|
|
24
|
+
|
|
25
|
+
TIMEOUT = 15.0
|
|
26
|
+
ANTHROPIC_API = "https://api.anthropic.com"
|
|
27
|
+
ANTHROPIC_VERSION = "2023-06-01"
|
|
28
|
+
GOOGLE_TOKENINFO = "https://oauth2.googleapis.com/tokeninfo"
|
|
29
|
+
RERUN = "hyperclaw onboard --team --step {step}"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class Check:
|
|
34
|
+
piece: str
|
|
35
|
+
ok: bool
|
|
36
|
+
line: str
|
|
37
|
+
step: str = ""
|
|
38
|
+
code: str = ""
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def ok(piece: str, detail: str = "", step: str = "") -> Check:
|
|
42
|
+
return Check(piece, True, f"✓ Connected: {piece}" + (f" ({detail})" if detail else ""), step)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def fail(piece: str, reason: str, fix: str, step: str = "", code: str = "") -> Check:
|
|
46
|
+
reason = reason[:1].upper() + reason[1:]
|
|
47
|
+
return Check(piece, False, f"✗ {piece}: {reason.rstrip('.')}. Fix: {fix}", step, code)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _client(http):
|
|
51
|
+
return http or httpx.Client(timeout=TIMEOUT, follow_redirects=False)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
# ── model ─────────────────────────────────────────────────────────────────
|
|
55
|
+
|
|
56
|
+
def model_label(provider: str, model: str) -> str:
|
|
57
|
+
from hyperclaw.model_policy import pretty_model_name
|
|
58
|
+
from hyperclaw import managed_config
|
|
59
|
+
name = pretty_model_name(provider, model)
|
|
60
|
+
return f"{name}, via Hyper" if managed_config.hyper_issued(provider) else name
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def check_model(provider: str, model: str, key: str = "", *,
|
|
64
|
+
http: Optional[httpx.Client] = None) -> Check:
|
|
65
|
+
label = model_label(provider, model)
|
|
66
|
+
step = "item"
|
|
67
|
+
if provider != "anthropic":
|
|
68
|
+
return fail(label, "this install's model isn't one onboarding can check",
|
|
69
|
+
"tell Vaughn", step, "unsupported")
|
|
70
|
+
key = key or os.environ.get("ANTHROPIC_API_KEY", "")
|
|
71
|
+
if not key:
|
|
72
|
+
return fail(label, "no model key is stored", RERUN.format(step="item"), step, "no_key")
|
|
73
|
+
base = (os.environ.get("ANTHROPIC_BASE_URL", "").strip() or ANTHROPIC_API).rstrip("/")
|
|
74
|
+
c = _client(http)
|
|
75
|
+
try:
|
|
76
|
+
r = c.get(f"{base}/v1/models/{model}",
|
|
77
|
+
headers={"x-api-key": key, "anthropic-version": ANTHROPIC_VERSION})
|
|
78
|
+
except httpx.HTTPError as e:
|
|
79
|
+
log.warning("model check: network error (%s)", type(e).__name__)
|
|
80
|
+
return fail(label, "the model service couldn't be reached",
|
|
81
|
+
"check your internet connection, then run hyperclaw self-check", step,
|
|
82
|
+
"unreachable")
|
|
83
|
+
finally:
|
|
84
|
+
if http is None:
|
|
85
|
+
c.close()
|
|
86
|
+
if r.status_code == 200:
|
|
87
|
+
return ok(label, "", step)
|
|
88
|
+
log.warning("model check: HTTP %s", r.status_code)
|
|
89
|
+
if r.status_code in (401, 403):
|
|
90
|
+
return fail(label, "the model key in your 1Password item was not accepted",
|
|
91
|
+
"tell Vaughn; he will send a new item", step, "auth")
|
|
92
|
+
if r.status_code == 404:
|
|
93
|
+
return fail(label, f"your key works, but {model} isn't available to it",
|
|
94
|
+
"tell Vaughn", step, "model")
|
|
95
|
+
return fail(label, "the model service isn't available right now",
|
|
96
|
+
"try again in a few minutes with hyperclaw self-check", step, "unavailable")
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
# ── Telegram ──────────────────────────────────────────────────────────────
|
|
100
|
+
|
|
101
|
+
def check_telegram(*, client=None) -> Check:
|
|
102
|
+
from hyperclaw import telegram_pairing as tp
|
|
103
|
+
step = "telegram"
|
|
104
|
+
token = os.environ.get("TELEGRAM_BOT_TOKEN", "").strip()
|
|
105
|
+
owner = os.environ.get("TELEGRAM_OWNER_CHAT_ID", "").strip()
|
|
106
|
+
if not token:
|
|
107
|
+
return fail("Telegram", "no bot is connected", RERUN.format(step=step), step,
|
|
108
|
+
"not_connected")
|
|
109
|
+
name = tp.bot_username(token, client=client)
|
|
110
|
+
if not name:
|
|
111
|
+
return fail("Telegram", "Telegram didn't accept the bot token (or couldn't be reached)",
|
|
112
|
+
RERUN.format(step=step), step, "token")
|
|
113
|
+
if not owner:
|
|
114
|
+
return fail("Telegram", f"the bot @{name} works but isn't paired to your chat yet",
|
|
115
|
+
RERUN.format(step=step), step, "unpaired")
|
|
116
|
+
return ok("Telegram", f"@{name}, paired to your chat", step)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
# ── Google ──────────────────────────────────────────────────────────────
|
|
120
|
+
|
|
121
|
+
GOOGLE_NAMES = "Gmail, Calendar, Meet, Drive, Docs, Sheets and Slides"
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def check_google(*, http: Optional[httpx.Client] = None) -> Check:
|
|
125
|
+
"""The Workspace sign-in (hyperclaw.google_workspace): refresh if needed,
|
|
126
|
+
then one tokeninfo call; the granted scopes must be ones we asked for."""
|
|
127
|
+
from hyperclaw.google_workspace import auth as ga
|
|
128
|
+
from hyperclaw.google_workspace.errors import GoogleError
|
|
129
|
+
step = "google"
|
|
130
|
+
if not ga.connected():
|
|
131
|
+
return fail("Google", "you haven't signed in to Google yet", RERUN.format(step=step),
|
|
132
|
+
step, "not_connected")
|
|
133
|
+
try:
|
|
134
|
+
tok = ga.access_token(http=http)
|
|
135
|
+
except GoogleError as e:
|
|
136
|
+
return fail("Google", e.text.split(". ")[0], RERUN.format(step=step), step,
|
|
137
|
+
e.reason.value)
|
|
138
|
+
c = _client(http)
|
|
139
|
+
try:
|
|
140
|
+
r = c.post(GOOGLE_TOKENINFO, data={"access_token": tok})
|
|
141
|
+
except httpx.HTTPError:
|
|
142
|
+
return fail("Google", "Google couldn't be reached",
|
|
143
|
+
"check your internet connection, then run hyperclaw self-check", step,
|
|
144
|
+
"unreachable")
|
|
145
|
+
finally:
|
|
146
|
+
if http is None:
|
|
147
|
+
c.close()
|
|
148
|
+
if r.status_code != 200:
|
|
149
|
+
return fail("Google", "Google didn't accept the stored sign-in",
|
|
150
|
+
RERUN.format(step=step), step, "rejected")
|
|
151
|
+
try:
|
|
152
|
+
granted = ga.check_scopes(str((r.json() or {}).get("scope") or ""))
|
|
153
|
+
except (GoogleError, ValueError) as e:
|
|
154
|
+
return fail("Google", getattr(e, "text", "Google's answer couldn't be read").split(". ")[0],
|
|
155
|
+
RERUN.format(step=step), step, "scopes")
|
|
156
|
+
missing = [s.rsplit("/", 1)[-1] for s in ga.SCOPES if s not in granted]
|
|
157
|
+
detail = GOOGLE_NAMES + ("; not granted: " + ", ".join(missing) if missing else "")
|
|
158
|
+
return ok("Google", detail, step)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
# ── Slack ─────────────────────────────────────────────────────────────────
|
|
162
|
+
|
|
163
|
+
def check_slack(cfg: dict, *, http: Optional[httpx.Client] = None) -> Check:
|
|
164
|
+
from hyperclaw.teamsetup import slack_oauth as so
|
|
165
|
+
step = "slack"
|
|
166
|
+
try:
|
|
167
|
+
so.expected_workspace(cfg)
|
|
168
|
+
except so.SlackAuthError as e:
|
|
169
|
+
return fail("Slack", e.text, e.fix, step, e.code)
|
|
170
|
+
if not so.connected():
|
|
171
|
+
return fail("Slack", "you haven't signed in to Slack yet", RERUN.format(step=step),
|
|
172
|
+
step, "not_connected")
|
|
173
|
+
try:
|
|
174
|
+
info = so.verify(cfg, http=http)
|
|
175
|
+
except so.SlackAuthError as e:
|
|
176
|
+
if e.code == "expired":
|
|
177
|
+
return fail("Slack", "your Slack sign-in has expired or was removed",
|
|
178
|
+
"sign in again: " + RERUN.format(step=step), step, e.code)
|
|
179
|
+
return fail("Slack", e.text, e.fix, step, e.code)
|
|
180
|
+
return ok("Slack", f"workspace {info['team']}; posts only when you press Send" if info["team"]
|
|
181
|
+
else "posts only when you press Send",
|
|
182
|
+
step)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
# ── GitLab ────────────────────────────────────────────────────────────────
|
|
186
|
+
|
|
187
|
+
def check_gitlab(*, client=None) -> Check:
|
|
188
|
+
from hyperclaw.digest import state, tokens
|
|
189
|
+
from hyperclaw.digest.errors import DigestError
|
|
190
|
+
from hyperclaw.digest.gitlab import GitLabCollector
|
|
191
|
+
step = "gitlab"
|
|
192
|
+
meta = state.connections().get("gitlab") or {}
|
|
193
|
+
try:
|
|
194
|
+
token = tokens.store(meta.get("store") or "").get("gitlab")
|
|
195
|
+
except DigestError:
|
|
196
|
+
token = ""
|
|
197
|
+
if not token:
|
|
198
|
+
return fail("GitLab", "GitLab isn't connected (optional)", RERUN.format(step=step),
|
|
199
|
+
step, "not_connected")
|
|
200
|
+
col = GitLabCollector(token, url=meta.get("url") or "", client=client)
|
|
201
|
+
try:
|
|
202
|
+
info = col.verify()
|
|
203
|
+
except DigestError as e:
|
|
204
|
+
return fail("GitLab", e.text, e.fix or RERUN.format(step=step), step, e.reason.value)
|
|
205
|
+
finally:
|
|
206
|
+
if client is None:
|
|
207
|
+
col.close()
|
|
208
|
+
return ok("GitLab", f"{info.get('account', '')}, read_api", step)
|