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