hyperagents 1.2.3rc5.post3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (361) hide show
  1. agents/__init__.py +31 -0
  2. agents/background_monitor.py +453 -0
  3. agents/learning.py +460 -0
  4. agents/planner.py +421 -0
  5. agents/transcriber.py +341 -0
  6. civilization/__init__.py +51 -0
  7. civilization/api/__init__.py +7 -0
  8. civilization/api/civilization_api.py +361 -0
  9. civilization/graph/__init__.py +19 -0
  10. civilization/graph/knowledge_linker.py +348 -0
  11. civilization/graph/org_graph.py +348 -0
  12. civilization/graph/process_graph.py +386 -0
  13. civilization/ingestion/__init__.py +20 -0
  14. civilization/ingestion/chunker.py +273 -0
  15. civilization/ingestion/document_ingestor.py +358 -0
  16. civilization/ingestion/embedder.py +262 -0
  17. civilization/ingestion/extractor.py +224 -0
  18. civilization/interview/__init__.py +19 -0
  19. civilization/interview/gap_detector.py +380 -0
  20. civilization/interview/interviewer.py +323 -0
  21. civilization/interview/questioner.py +363 -0
  22. civilization/retrieval/__init__.py +17 -0
  23. civilization/retrieval/civilization_rag.py +316 -0
  24. civilization/retrieval/context_injector.py +342 -0
  25. civilization/retrieval/relevance_ranker.py +287 -0
  26. civilization/schema.py +299 -0
  27. civilization/store.py +177 -0
  28. civilization/sync/__init__.py +16 -0
  29. civilization/sync/confluence_sync.py +317 -0
  30. civilization/sync/gdrive_sync.py +262 -0
  31. civilization/sync/notion_sync.py +237 -0
  32. civilization/versioning/__init__.py +19 -0
  33. civilization/versioning/diff_engine.py +351 -0
  34. civilization/versioning/staleness_detector.py +303 -0
  35. civilization/versioning/version_manager.py +293 -0
  36. cli/__init__.py +1 -0
  37. cli/doctor.py +334 -0
  38. cli/hyperclaw.py +1357 -0
  39. cli/onboarding.py +469 -0
  40. core/__init__.py +1 -0
  41. core/connectors/__init__.py +119 -0
  42. core/connectors/infrastructure.py +772 -0
  43. core/connectors/messaging.py +586 -0
  44. core/connectors/productivity.py +764 -0
  45. core/connectors/smarthome.py +528 -0
  46. core/connectors/social.py +730 -0
  47. core/hyperrouter/__init__.py +23 -0
  48. core/hyperrouter/bandit.py +218 -0
  49. core/hyperrouter/fast_loop.py +68 -0
  50. core/hyperrouter/slow_loop.py +143 -0
  51. core/hyperstate/__init__.py +24 -0
  52. core/hyperstate/certifier.py +154 -0
  53. core/hyperstate/schema.py +160 -0
  54. core/hyperstate/state_manager.py +103 -0
  55. core/hyperstate/store.py +163 -0
  56. core/providers/__init__.py +121 -0
  57. core/providers/embeddings.py +389 -0
  58. core/providers/image.py +534 -0
  59. core/providers/llm.py +1935 -0
  60. core/providers/search.py +608 -0
  61. core/providers/voice.py +474 -0
  62. hyperagents-1.2.3rc5.post3.dist-info/METADATA +469 -0
  63. hyperagents-1.2.3rc5.post3.dist-info/RECORD +361 -0
  64. hyperagents-1.2.3rc5.post3.dist-info/WHEEL +5 -0
  65. hyperagents-1.2.3rc5.post3.dist-info/entry_points.txt +6 -0
  66. hyperagents-1.2.3rc5.post3.dist-info/licenses/LICENSE +21 -0
  67. hyperagents-1.2.3rc5.post3.dist-info/top_level.txt +13 -0
  68. hyperclaw/__init__.py +67 -0
  69. hyperclaw/__main__.py +220 -0
  70. hyperclaw/agent.py +775 -0
  71. hyperclaw/agent_coordinator.py +577 -0
  72. hyperclaw/anthropic_params.py +225 -0
  73. hyperclaw/api_utils.py +85 -0
  74. hyperclaw/approval_telegram.py +209 -0
  75. hyperclaw/approval_terminal.py +80 -0
  76. hyperclaw/attachments.py +177 -0
  77. hyperclaw/branding.py +55 -0
  78. hyperclaw/brief/__init__.py +11 -0
  79. hyperclaw/brief/cli.py +204 -0
  80. hyperclaw/brief/collect.py +146 -0
  81. hyperclaw/brief/compose.py +333 -0
  82. hyperclaw/brief/diff.py +110 -0
  83. hyperclaw/brief/errors.py +99 -0
  84. hyperclaw/brief/google.py +269 -0
  85. hyperclaw/brief/google_auth.py +294 -0
  86. hyperclaw/brief/links.py +124 -0
  87. hyperclaw/brief/service.py +424 -0
  88. hyperclaw/brief/store.py +205 -0
  89. hyperclaw/brief/tasks.py +200 -0
  90. hyperclaw/chat_scope.py +115 -0
  91. hyperclaw/cli_tui.py +497 -0
  92. hyperclaw/client_guard.py +717 -0
  93. hyperclaw/connection_check.py +113 -0
  94. hyperclaw/cost_guard.py +1728 -0
  95. hyperclaw/default_config/agents.yaml +534 -0
  96. hyperclaw/default_config/hyperclaw.yaml +27 -0
  97. hyperclaw/default_config/models.yaml +161 -0
  98. hyperclaw/deployment.py +39 -0
  99. hyperclaw/digest/__init__.py +9 -0
  100. hyperclaw/digest/cli.py +117 -0
  101. hyperclaw/digest/errors.py +100 -0
  102. hyperclaw/digest/gitlab.py +187 -0
  103. hyperclaw/digest/http.py +113 -0
  104. hyperclaw/digest/redact.py +32 -0
  105. hyperclaw/digest/service.py +271 -0
  106. hyperclaw/digest/slack.py +223 -0
  107. hyperclaw/digest/state.py +124 -0
  108. hyperclaw/digest/summarize.py +198 -0
  109. hyperclaw/digest/tokens.py +143 -0
  110. hyperclaw/distribution.py +46 -0
  111. hyperclaw/documents.py +351 -0
  112. hyperclaw/feeds.py +339 -0
  113. hyperclaw/google_workspace/__init__.py +5 -0
  114. hyperclaw/google_workspace/api.py +151 -0
  115. hyperclaw/google_workspace/auth.py +397 -0
  116. hyperclaw/google_workspace/calendar.py +157 -0
  117. hyperclaw/google_workspace/cli.py +72 -0
  118. hyperclaw/google_workspace/drive.py +236 -0
  119. hyperclaw/google_workspace/errors.py +58 -0
  120. hyperclaw/google_workspace/gmail.py +295 -0
  121. hyperclaw/google_workspace/meet.py +122 -0
  122. hyperclaw/google_workspace/tools.py +319 -0
  123. hyperclaw/hyperspeed.py +57 -0
  124. hyperclaw/integrations_layer.py +1427 -0
  125. hyperclaw/live_state.py +188 -0
  126. hyperclaw/managed_config.py +245 -0
  127. hyperclaw/media_hub.py +179 -0
  128. hyperclaw/memory_manager.py +584 -0
  129. hyperclaw/model_catalog.py +551 -0
  130. hyperclaw/model_policy.py +211 -0
  131. hyperclaw/model_router.py +595 -0
  132. hyperclaw/model_selector.py +72 -0
  133. hyperclaw/naming.py +284 -0
  134. hyperclaw/notices.py +65 -0
  135. hyperclaw/oai_stream.py +106 -0
  136. hyperclaw/orchestrator.py +591 -0
  137. hyperclaw/outbox.py +82 -0
  138. hyperclaw/persona.py +367 -0
  139. hyperclaw/prometheus.py +219 -0
  140. hyperclaw/prompt_assembly.py +157 -0
  141. hyperclaw/provider_setup.py +146 -0
  142. hyperclaw/providers.py +340 -0
  143. hyperclaw/qualification.py +341 -0
  144. hyperclaw/qualification_keys/hyper-d292fd127cef5a3e.pub +1 -0
  145. hyperclaw/qualification_records/anthropic--claude-sonnet-5-5.json +14 -0
  146. hyperclaw/reasoning_display.py +35 -0
  147. hyperclaw/scheduler.py +454 -0
  148. hyperclaw/server.py +847 -0
  149. hyperclaw/setup.py +627 -0
  150. hyperclaw/skills.py +717 -0
  151. hyperclaw/solomon.py +442 -0
  152. hyperclaw/swarm.py +533 -0
  153. hyperclaw/taint.py +832 -0
  154. hyperclaw/team/__init__.py +28 -0
  155. hyperclaw/team/dispatcher.py +431 -0
  156. hyperclaw/team/genesis.py +106 -0
  157. hyperclaw/team/lead.py +110 -0
  158. hyperclaw/team/model.py +135 -0
  159. hyperclaw/team/morning.py +238 -0
  160. hyperclaw/team/repos.py +66 -0
  161. hyperclaw/team/roster.py +264 -0
  162. hyperclaw/team/specialist.py +235 -0
  163. hyperclaw/team/status.py +163 -0
  164. hyperclaw/team/watchers.py +112 -0
  165. hyperclaw/teamsetup/__init__.py +17 -0
  166. hyperclaw/teamsetup/checks.py +208 -0
  167. hyperclaw/teamsetup/item.py +391 -0
  168. hyperclaw/teamsetup/oauth_loopback.py +84 -0
  169. hyperclaw/teamsetup/onboard.py +550 -0
  170. hyperclaw/teamsetup/profile.py +82 -0
  171. hyperclaw/teamsetup/secrets.py +313 -0
  172. hyperclaw/teamsetup/selfcheck.py +295 -0
  173. hyperclaw/teamsetup/service.py +495 -0
  174. hyperclaw/teamsetup/slack_oauth.py +442 -0
  175. hyperclaw/teamsetup/tty.py +62 -0
  176. hyperclaw/telegram_bot.py +1106 -0
  177. hyperclaw/telegram_intake.py +126 -0
  178. hyperclaw/telegram_pairing.py +360 -0
  179. hyperclaw/terminal_provider.py +323 -0
  180. hyperclaw/tools/__init__.py +1 -0
  181. hyperclaw/tools/approval.py +949 -0
  182. hyperclaw/tools/args.py +212 -0
  183. hyperclaw/tools/catalog.py +244 -0
  184. hyperclaw/tools/classify.py +266 -0
  185. hyperclaw/tools/effects.py +724 -0
  186. hyperclaw/tools/egress.py +210 -0
  187. hyperclaw/tools/envelope.py +188 -0
  188. hyperclaw/tools/guard.py +367 -0
  189. hyperclaw/tools/ledger.py +339 -0
  190. hyperclaw/tools/receipts.py +70 -0
  191. hyperclaw/tools/registry.py +318 -0
  192. hyperclaw/tools/service_guard.py +151 -0
  193. hyperclaw/tools/surfaces.py +54 -0
  194. hyperclaw/tools/workspace.py +87 -0
  195. hyperclaw/trading/__init__.py +77 -0
  196. hyperclaw/truth_guard.py +791 -0
  197. hyperclaw/tts.py +124 -0
  198. hyperclaw/tui.py +4255 -0
  199. hyperclaw/tui_bridge.py +938 -0
  200. hyperclaw/unattended.py +60 -0
  201. hyperclaw/updates/__init__.py +54 -0
  202. hyperclaw/updates/check.py +131 -0
  203. hyperclaw/updates/index.py +178 -0
  204. hyperclaw/updates/install.py +110 -0
  205. hyperclaw/updates/managed.py +161 -0
  206. hyperclaw/updates/manifest.py +138 -0
  207. hyperclaw/updates/notices.py +86 -0
  208. hyperclaw/updates/paths.py +53 -0
  209. hyperclaw/updates/release.py +146 -0
  210. hyperclaw/updates/upgrade.py +253 -0
  211. hyperclaw/updates/versioning.py +104 -0
  212. hyperclaw/userconfig.py +119 -0
  213. hyperclaw/xai.py +83 -0
  214. integrations/__init__.py +31 -0
  215. integrations/automation/__init__.py +1 -0
  216. integrations/automation/make.py +87 -0
  217. integrations/automation/zapier.py +87 -0
  218. integrations/base.py +476 -0
  219. integrations/communication/__init__.py +1 -0
  220. integrations/communication/twilio.py +279 -0
  221. integrations/communication/zoom.py +290 -0
  222. integrations/data/__init__.py +1 -0
  223. integrations/data/google_analytics.py +135 -0
  224. integrations/data/postgres.py +182 -0
  225. integrations/data/supabase_connector.py +202 -0
  226. integrations/data/weather_feeds.py +450 -0
  227. integrations/developer/__init__.py +9 -0
  228. integrations/developer/github.py +406 -0
  229. integrations/developer/gitlab.py +298 -0
  230. integrations/enterprise/__init__.py +13 -0
  231. integrations/enterprise/confluence.py +266 -0
  232. integrations/enterprise/hubspot.py +293 -0
  233. integrations/enterprise/jira.py +314 -0
  234. integrations/enterprise/salesforce.py +286 -0
  235. integrations/finance/__init__.py +1 -0
  236. integrations/finance/polymarket_client.py +247 -0
  237. integrations/finance/quickbooks.py +281 -0
  238. integrations/finance/stripe.py +261 -0
  239. integrations/gateway.py +238 -0
  240. integrations/messaging/__init__.py +32 -0
  241. integrations/messaging/discord.py +159 -0
  242. integrations/messaging/email.py +401 -0
  243. integrations/messaging/imessage.py +443 -0
  244. integrations/messaging/signal.py +175 -0
  245. integrations/messaging/slack.py +217 -0
  246. integrations/messaging/sms.py +175 -0
  247. integrations/messaging/teams.py +208 -0
  248. integrations/messaging/telegram.py +260 -0
  249. integrations/messaging/whatsapp.py +203 -0
  250. integrations/productivity/__init__.py +17 -0
  251. integrations/productivity/airtable.py +217 -0
  252. integrations/productivity/asana.py +248 -0
  253. integrations/productivity/google/__init__.py +19 -0
  254. integrations/productivity/google/base.py +89 -0
  255. integrations/productivity/google/calendar.py +272 -0
  256. integrations/productivity/google/docs.py +202 -0
  257. integrations/productivity/google/drive.py +268 -0
  258. integrations/productivity/google/gmail.py +279 -0
  259. integrations/productivity/google/meet.py +218 -0
  260. integrations/productivity/google/sheets.py +222 -0
  261. integrations/productivity/google/tasks.py +199 -0
  262. integrations/productivity/linear.py +279 -0
  263. integrations/productivity/microsoft/__init__.py +13 -0
  264. integrations/productivity/microsoft/base.py +93 -0
  265. integrations/productivity/microsoft/calendar.py +207 -0
  266. integrations/productivity/microsoft/onedrive.py +222 -0
  267. integrations/productivity/microsoft/outlook.py +241 -0
  268. integrations/productivity/microsoft/sharepoint.py +211 -0
  269. integrations/productivity/notion.py +243 -0
  270. integrations/productivity/todoist.py +250 -0
  271. integrations/productivity/trello.py +244 -0
  272. integrations/storage/__init__.py +1 -0
  273. integrations/storage/box.py +279 -0
  274. integrations/storage/dropbox.py +215 -0
  275. integrations/telegram_inbound.py +179 -0
  276. integrations/telegram_poller.py +151 -0
  277. memory/__init__.py +1 -0
  278. memory/causal_graph.py +125 -0
  279. memory/impact_tracker.py +88 -0
  280. memory/vector_memory.py +436 -0
  281. models/__init__.py +12 -0
  282. models/chatjimmy_client.py +210 -0
  283. models/claude_client.py +235 -0
  284. models/claude_code_subagent.py +192 -0
  285. models/openai_client.py +120 -0
  286. models/router.py +162 -0
  287. recursive/discovery_loop.py +118 -0
  288. recursive/optimization_loop.py +135 -0
  289. recursive/validation_loop.py +38 -0
  290. security/__init__.py +1 -0
  291. security/audit_logger.py +106 -0
  292. security/filesystem_guard.py +100 -0
  293. security/hypershield.py +99 -0
  294. security/network_guard.py +97 -0
  295. security/policy_engine.py +198 -0
  296. swarm/__init__.py +1 -0
  297. swarm/agents/__init__.py +0 -0
  298. swarm/agents/base.py +230 -0
  299. swarm/agents/business/__init__.py +0 -0
  300. swarm/agents/business/counsel.py +31 -0
  301. swarm/agents/business/herald.py +32 -0
  302. swarm/agents/business/ledger.py +26 -0
  303. swarm/agents/business/nexus.py +30 -0
  304. swarm/agents/business/ops.py +30 -0
  305. swarm/agents/business/pipeline.py +28 -0
  306. swarm/agents/business/revenue.py +30 -0
  307. swarm/agents/business/sovereign.py +31 -0
  308. swarm/agents/business/strategos.py +27 -0
  309. swarm/agents/business/talent.py +27 -0
  310. swarm/agents/business/venture.py +32 -0
  311. swarm/agents/comms/__init__.py +1 -0
  312. swarm/agents/comms/cipher.py +32 -0
  313. swarm/agents/comms/echo_comms.py +31 -0
  314. swarm/agents/comms/envoy.py +31 -0
  315. swarm/agents/comms/herald_scheduler.py +33 -0
  316. swarm/agents/comms/pulse.py +29 -0
  317. swarm/agents/creative/__init__.py +0 -0
  318. swarm/agents/creative/author.py +27 -0
  319. swarm/agents/creative/lens.py +25 -0
  320. swarm/agents/executive/__init__.py +1 -0
  321. swarm/agents/intelligence/__init__.py +1 -0
  322. swarm/agents/intelligence/arbiter.py +30 -0
  323. swarm/agents/intelligence/sentinel.py +30 -0
  324. swarm/agents/media/__init__.py +0 -0
  325. swarm/agents/personal/__init__.py +0 -0
  326. swarm/agents/personal/atlas.py +27 -0
  327. swarm/agents/personal/hearth.py +26 -0
  328. swarm/agents/personal/midas.py +27 -0
  329. swarm/agents/personal/navigator.py +27 -0
  330. swarm/agents/personal/nourish.py +31 -0
  331. swarm/agents/personal/vitals.py +33 -0
  332. swarm/agents/recursive/__init__.py +0 -0
  333. swarm/agents/recursive/alchemist.py +73 -0
  334. swarm/agents/recursive/calibrator.py +45 -0
  335. swarm/agents/recursive/scout.py +97 -0
  336. swarm/agents/scientific/__init__.py +0 -0
  337. swarm/agents/scientific/cosmos.py +36 -0
  338. swarm/agents/scientific/gaia.py +26 -0
  339. swarm/agents/scientific/medicus.py +33 -0
  340. swarm/agents/scientific/oracle_agent.py +39 -0
  341. swarm/agents/scientific/scribe.py +39 -0
  342. swarm/agents/talent/__init__.py +0 -0
  343. swarm/agents/talent/deal.py +28 -0
  344. swarm/agents/talent/roster.py +29 -0
  345. swarm/agents/talent/scout.py +29 -0
  346. swarm/agents/talent/stage.py +27 -0
  347. swarm/agents/tech/__init__.py +0 -0
  348. swarm/agents/tech/aegis.py +28 -0
  349. swarm/agents/tech/bridge.py +28 -0
  350. swarm/agents/tech/forge.py +28 -0
  351. swarm/agents/trading/__init__.py +1 -0
  352. swarm/agents/trading/global_prediction_engine.py +611 -0
  353. swarm/agents/trading/polymarket_trader.py +165 -0
  354. swarm/agents/trading/prediction_strategist.py +218 -0
  355. swarm/autogen_bridge.py +68 -0
  356. swarm/bid_protocol.py +196 -0
  357. swarm/dispatcher.py +527 -0
  358. swarm/nexus.py +181 -0
  359. swarm/registry.py +112 -0
  360. telegram_listener.py +403 -0
  361. ui/tui/app.py +214 -0
@@ -0,0 +1,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
@@ -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]