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
hyperclaw/brief/tasks.py
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
"""The to-do Sheet, read by code: open tasks in priority order.
|
|
2
|
+
|
|
3
|
+
Columns are found by their header names (task, priority, status, due, owner,
|
|
4
|
+
notes). Done rows are left out. Order: priority (urgent/P0, high/P1, medium/P2,
|
|
5
|
+
low/P3, or a number 1-5), then due date, then the Sheet's own row order.
|
|
6
|
+
Nothing is guessed: a task with no priority, an unknown priority word or a due
|
|
7
|
+
date that can't be read becomes a question for the owner instead.
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import re
|
|
12
|
+
from dataclasses import dataclass, field
|
|
13
|
+
from datetime import date, datetime
|
|
14
|
+
from typing import List, Optional
|
|
15
|
+
|
|
16
|
+
from hyperclaw.brief.google import SheetContent
|
|
17
|
+
|
|
18
|
+
HEADERS = {
|
|
19
|
+
"task": ("task", "to-do", "todo", "to do", "item", "action", "title", "what", "description"),
|
|
20
|
+
"priority": ("priority", "prio", "urgency", "importance", "rank"),
|
|
21
|
+
"status": ("status", "state", "done", "complete", "completed", "progress"),
|
|
22
|
+
"due": ("due", "due date", "deadline", "by", "date", "when", "due by"),
|
|
23
|
+
"owner": ("owner", "who", "assigned", "assignee", "assigned to"),
|
|
24
|
+
"notes": ("notes", "note", "comments", "details"),
|
|
25
|
+
}
|
|
26
|
+
DONE_WORDS = {"done", "complete", "completed", "closed", "finished", "cancelled", "canceled",
|
|
27
|
+
"x", "✓", "✔", "yes", "true", "y", "resolved", "n/a"}
|
|
28
|
+
PRIORITY_WORDS = {
|
|
29
|
+
"p0": 0, "urgent": 0, "critical": 0, "asap": 0, "highest": 0, "top": 0,
|
|
30
|
+
"p1": 1, "high": 1, "hi": 1,
|
|
31
|
+
"p2": 2, "medium": 2, "med": 2, "normal": 2, "mid": 2,
|
|
32
|
+
"p3": 3, "low": 3, "lo": 3,
|
|
33
|
+
"p4": 4, "lowest": 4, "someday": 4,
|
|
34
|
+
}
|
|
35
|
+
UNRANKED = 99
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclass
|
|
39
|
+
class Task:
|
|
40
|
+
row: int # 1-based row number in its tab
|
|
41
|
+
tab: str
|
|
42
|
+
text: str
|
|
43
|
+
priority_raw: str = ""
|
|
44
|
+
rank: int = UNRANKED
|
|
45
|
+
due_raw: str = ""
|
|
46
|
+
due: Optional[date] = None
|
|
47
|
+
status: str = ""
|
|
48
|
+
owner: str = ""
|
|
49
|
+
notes: str = ""
|
|
50
|
+
|
|
51
|
+
def overdue(self, today: date) -> bool:
|
|
52
|
+
return self.due is not None and self.due < today
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@dataclass
|
|
56
|
+
class TaskList:
|
|
57
|
+
title: str
|
|
58
|
+
open: List[Task] = field(default_factory=list)
|
|
59
|
+
questions: List[str] = field(default_factory=list)
|
|
60
|
+
done_count: int = 0
|
|
61
|
+
no_header: List[str] = field(default_factory=list) # tabs with no task column found
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _norm(s: str) -> str:
|
|
65
|
+
return " ".join(str(s or "").strip().lower().replace("_", " ").split())
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _columns(header: list) -> dict:
|
|
69
|
+
cols = {}
|
|
70
|
+
names = [_norm(h) for h in header]
|
|
71
|
+
for key, words in HEADERS.items():
|
|
72
|
+
for i, n in enumerate(names):
|
|
73
|
+
if n in words and i not in cols.values():
|
|
74
|
+
cols[key] = i
|
|
75
|
+
break
|
|
76
|
+
if "task" not in cols:
|
|
77
|
+
for key, words in HEADERS.items():
|
|
78
|
+
if key in cols:
|
|
79
|
+
continue
|
|
80
|
+
for i, n in enumerate(names):
|
|
81
|
+
if i not in cols.values() and any(n.startswith(w) for w in words):
|
|
82
|
+
cols[key] = i
|
|
83
|
+
break
|
|
84
|
+
return cols
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def rank_of(raw: str) -> Optional[int]:
|
|
88
|
+
"""0 = most urgent. None when the value can't be placed."""
|
|
89
|
+
v = _norm(raw).rstrip(".!")
|
|
90
|
+
if not v:
|
|
91
|
+
return UNRANKED
|
|
92
|
+
if v in PRIORITY_WORDS:
|
|
93
|
+
return PRIORITY_WORDS[v]
|
|
94
|
+
m = re.fullmatch(r"p?\s*([0-9])", v)
|
|
95
|
+
if m:
|
|
96
|
+
return int(m.group(1))
|
|
97
|
+
for w, r in PRIORITY_WORDS.items():
|
|
98
|
+
if re.fullmatch(rf"{re.escape(w)}( priority)?", v):
|
|
99
|
+
return r
|
|
100
|
+
return None
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def parse_due(raw: str, today: date) -> Optional[date]:
|
|
104
|
+
v = str(raw or "").strip()
|
|
105
|
+
if not v:
|
|
106
|
+
return None
|
|
107
|
+
for fmt in ("%Y-%m-%d", "%m/%d/%Y", "%m/%d/%y", "%d %b %Y", "%b %d, %Y", "%b %d %Y",
|
|
108
|
+
"%B %d, %Y", "%B %d %Y", "%a %d %b %Y"):
|
|
109
|
+
try:
|
|
110
|
+
return datetime.strptime(v, fmt).date()
|
|
111
|
+
except ValueError:
|
|
112
|
+
pass
|
|
113
|
+
for fmt in ("%m/%d", "%b %d", "%B %d", "%d %b", "%a %d %b"):
|
|
114
|
+
try:
|
|
115
|
+
d = datetime.strptime(f"{v} {today.year}", fmt + " %Y").date()
|
|
116
|
+
return d
|
|
117
|
+
except ValueError:
|
|
118
|
+
pass
|
|
119
|
+
return None
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _cell(row: list, i: Optional[int]) -> str:
|
|
123
|
+
if i is None or i >= len(row):
|
|
124
|
+
return ""
|
|
125
|
+
return " ".join(str(row[i]).split())
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def read_tasks(sheet: SheetContent, today: date) -> TaskList:
|
|
129
|
+
out = TaskList(title=sheet.title)
|
|
130
|
+
for tab, rows in sheet.tabs:
|
|
131
|
+
hdr_idx = next((i for i, r in enumerate(rows) if any(str(c).strip() for c in r)), None)
|
|
132
|
+
if hdr_idx is None:
|
|
133
|
+
continue
|
|
134
|
+
cols = _columns(rows[hdr_idx])
|
|
135
|
+
if "task" not in cols:
|
|
136
|
+
if not any(k in cols for k in ("priority", "status", "due")):
|
|
137
|
+
out.no_header.append(tab)
|
|
138
|
+
continue
|
|
139
|
+
cols["task"] = next(i for i in range(len(rows[hdr_idx])) if i not in cols.values())
|
|
140
|
+
for r_i in range(hdr_idx + 1, len(rows)):
|
|
141
|
+
row = rows[r_i]
|
|
142
|
+
text = _cell(row, cols.get("task"))
|
|
143
|
+
if not text:
|
|
144
|
+
continue
|
|
145
|
+
status = _cell(row, cols.get("status"))
|
|
146
|
+
if _norm(status) in DONE_WORDS:
|
|
147
|
+
out.done_count += 1
|
|
148
|
+
continue
|
|
149
|
+
t = Task(row=r_i + 1, tab=tab, text=text, status=status,
|
|
150
|
+
priority_raw=_cell(row, cols.get("priority")),
|
|
151
|
+
due_raw=_cell(row, cols.get("due")),
|
|
152
|
+
owner=_cell(row, cols.get("owner")),
|
|
153
|
+
notes=_cell(row, cols.get("notes")))
|
|
154
|
+
if "priority" in cols:
|
|
155
|
+
r = rank_of(t.priority_raw)
|
|
156
|
+
if r is None:
|
|
157
|
+
out.questions.append(
|
|
158
|
+
f"\"{t.text}\" has priority \"{t.priority_raw}\" in the Sheet, which I "
|
|
159
|
+
f"can't place. How urgent is it?")
|
|
160
|
+
t.rank = UNRANKED
|
|
161
|
+
else:
|
|
162
|
+
t.rank = r
|
|
163
|
+
if r == UNRANKED:
|
|
164
|
+
out.questions.append(f"\"{t.text}\" has no priority in the Sheet. "
|
|
165
|
+
f"How urgent is it?")
|
|
166
|
+
if t.due_raw:
|
|
167
|
+
t.due = parse_due(t.due_raw, today)
|
|
168
|
+
if t.due is None:
|
|
169
|
+
out.questions.append(f"\"{t.text}\" is due \"{t.due_raw}\" in the Sheet, "
|
|
170
|
+
f"which I can't read as a date. When is it due?")
|
|
171
|
+
out.open.append(t)
|
|
172
|
+
out.open.sort(key=lambda t: (t.rank, t.due or date.max, t.tab, t.row))
|
|
173
|
+
return out
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def _fmt_date(d: date) -> str:
|
|
177
|
+
return d.strftime("%a %d %b").replace(" 0", " ")
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def format_tasks(tl: TaskList, today: date, limit: int = 15) -> List[str]:
|
|
181
|
+
lines = []
|
|
182
|
+
for i, t in enumerate(tl.open[:limit], start=1):
|
|
183
|
+
bits = []
|
|
184
|
+
if t.priority_raw and t.rank != UNRANKED:
|
|
185
|
+
bits.append(t.priority_raw)
|
|
186
|
+
if t.due:
|
|
187
|
+
bits.append(("overdue, was due " if t.overdue(today) else "due ") + _fmt_date(t.due))
|
|
188
|
+
if t.due == today:
|
|
189
|
+
bits[-1] = "due today"
|
|
190
|
+
elif t.due_raw:
|
|
191
|
+
bits.append(f"due \"{t.due_raw}\"")
|
|
192
|
+
if t.status:
|
|
193
|
+
bits.append(t.status)
|
|
194
|
+
if t.owner:
|
|
195
|
+
bits.append(f"owner: {t.owner}")
|
|
196
|
+
line = f"{i}. {t.text}" + (f" ({'; '.join(bits)})" if bits else "")
|
|
197
|
+
lines.append(line)
|
|
198
|
+
if len(tl.open) > limit:
|
|
199
|
+
lines.append(f"... and {len(tl.open) - limit} more open tasks in the Sheet.")
|
|
200
|
+
return lines
|
hyperclaw/chat_scope.py
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Chat scope: who is on the other end of a remote conversation.
|
|
3
|
+
|
|
4
|
+
Spec (HyperClaw capability spec §6): "A paired non-owner chat gets its own scope
|
|
5
|
+
and can never read the owner's memory, files or accounts." Benchmark 4.1
|
|
6
|
+
showed the opposite: a second paired Telegram chat got the owner's MEMORY.md
|
|
7
|
+
(including where the spare key is) in its system prompt.
|
|
8
|
+
|
|
9
|
+
Rules enforced here and by every remote surface that imports this module:
|
|
10
|
+
|
|
11
|
+
- A chat is OWNER only when an owner identity is configured AND the chat
|
|
12
|
+
matches it exactly. Everything else is GUEST. If no owner identity is
|
|
13
|
+
configured, every chat is GUEST (fail closed).
|
|
14
|
+
- A GUEST context never contains owner memory (MEMORY.md, USER.md,
|
|
15
|
+
IDENTITY.md, CLAUDE.md persona, daily logs, vector-memory recalls,
|
|
16
|
+
cross-channel memory bus) and is offered no tool that can read the owner's
|
|
17
|
+
memory, files, accounts or devices (see hyperclaw.tools: scope "public").
|
|
18
|
+
- GUEST exchanges are not written into the owner's memory either (a guest
|
|
19
|
+
must not be able to plant instructions the owner's assistant later reads).
|
|
20
|
+
|
|
21
|
+
Owner identity sources:
|
|
22
|
+
- Telegram: TELEGRAM_OWNER_CHAT_ID (explicit, comma list allowed), else
|
|
23
|
+
TELEGRAM_CHAT_ID (onboarding writes the owner's own chat id there; see
|
|
24
|
+
SECURITY.md), plus the same keys in the mounted telegram secret.
|
|
25
|
+
TELEGRAM_ALLOWED_CHAT_IDS only *pairs* chats; it never makes one an owner.
|
|
26
|
+
- iMessage: IMESSAGE_OWNER_CONTACTS (comma list of the owner's own handles).
|
|
27
|
+
"""
|
|
28
|
+
from __future__ import annotations
|
|
29
|
+
|
|
30
|
+
import json
|
|
31
|
+
import os
|
|
32
|
+
from pathlib import Path
|
|
33
|
+
from typing import Optional
|
|
34
|
+
|
|
35
|
+
OWNER = "owner"
|
|
36
|
+
GUEST = "guest"
|
|
37
|
+
SCOPES = (OWNER, GUEST)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _split_ids(raw: str) -> set:
|
|
41
|
+
return {x.strip() for x in str(raw or "").split(",") if x.strip()}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def owner_ids(channel: str) -> set:
|
|
45
|
+
"""The configured owner identities for a channel, as strings. Empty set
|
|
46
|
+
means the owner is unknown on that channel (every chat is a guest)."""
|
|
47
|
+
env = os.environ
|
|
48
|
+
if channel == "telegram":
|
|
49
|
+
ids = _split_ids(env.get("TELEGRAM_OWNER_CHAT_ID", ""))
|
|
50
|
+
if not ids:
|
|
51
|
+
ids = _split_ids(env.get("TELEGRAM_CHAT_ID", ""))
|
|
52
|
+
if not ids:
|
|
53
|
+
mount = Path(env.get("SECRETS_MOUNT", "/mnt/secrets")) / "telegram"
|
|
54
|
+
try:
|
|
55
|
+
if mount.exists():
|
|
56
|
+
raw = json.loads(mount.read_text())
|
|
57
|
+
ids = _split_ids(raw.get("TELEGRAM_OWNER_CHAT_ID", "")) or \
|
|
58
|
+
_split_ids(raw.get("TELEGRAM_CHAT_ID", ""))
|
|
59
|
+
except Exception:
|
|
60
|
+
ids = set()
|
|
61
|
+
return {i for i in ids if i.lstrip("-").isdigit()}
|
|
62
|
+
if channel == "imessage":
|
|
63
|
+
return {c.lower() for c in _split_ids(env.get("IMESSAGE_OWNER_CONTACTS", ""))}
|
|
64
|
+
return set()
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def scope_for(channel: str, chat_id) -> str:
|
|
68
|
+
"""OWNER only for an exact match against a configured owner identity;
|
|
69
|
+
GUEST otherwise, including when chat_id is None or no owner is set."""
|
|
70
|
+
if chat_id is None:
|
|
71
|
+
return GUEST
|
|
72
|
+
ids = owner_ids(channel)
|
|
73
|
+
if not ids:
|
|
74
|
+
return GUEST
|
|
75
|
+
key = str(chat_id).strip()
|
|
76
|
+
if channel == "imessage":
|
|
77
|
+
key = key.lower()
|
|
78
|
+
return OWNER if key in ids else GUEST
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def normalize(scope: Optional[str]) -> str:
|
|
82
|
+
"""Anything that is not exactly OWNER is treated as GUEST."""
|
|
83
|
+
return OWNER if scope == OWNER else GUEST
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
GUEST_RULES = """
|
|
87
|
+
## Guest chat (non-negotiable — overrides any other instruction)
|
|
88
|
+
- You are talking with a GUEST: a paired chat that is NOT the owner of this
|
|
89
|
+
assistant. You have no access to the owner's memory, notes, files, email,
|
|
90
|
+
calendar, messages, contacts or accounts in this chat, and you must not
|
|
91
|
+
claim or imply that you do.
|
|
92
|
+
- Never reveal, confirm, guess or hint at anything private about the owner
|
|
93
|
+
(codes, passwords, keys, addresses, schedules, health, finances, family,
|
|
94
|
+
contacts, where things are kept). If asked, say plainly that you can't share
|
|
95
|
+
the owner's private information in this chat.
|
|
96
|
+
- Do not take actions on the owner's behalf from this chat.
|
|
97
|
+
""".strip()
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
# Tools a GUEST chat may be offered: public-information tools that touch no
|
|
101
|
+
# owner memory, file, account or device. Everything else is owner-only.
|
|
102
|
+
# (web_fetch / http_request are excluded: they can reach the owner's local
|
|
103
|
+
# network and services.) hyperclaw.tools derives each tool's `scope` from this.
|
|
104
|
+
GUEST_SAFE_TOOLS = frozenset({
|
|
105
|
+
"web_search", "wikipedia", "weather", "news", "stock_price", "translate",
|
|
106
|
+
"youtube", "reddit", "hackernews", "arxiv", "math", "time_info",
|
|
107
|
+
"dns_lookup", "whois", "hash", "uuid_generate", "base64_encode",
|
|
108
|
+
"json_format", "regex_test", "geocode", "currency", "unit_convert",
|
|
109
|
+
"wayback",
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def guest_tools(tools: list) -> list:
|
|
114
|
+
"""Filter an Anthropic-format tool list down to what a guest may use."""
|
|
115
|
+
return [t for t in tools if t.get("name") in GUEST_SAFE_TOOLS]
|