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,262 @@
1
+ """
2
+ Google Drive integration for syncing organizational documents.
3
+ """
4
+ from __future__ import annotations
5
+ import logging
6
+ from dataclasses import dataclass, field
7
+ from datetime import datetime
8
+ from typing import Any
9
+ from uuid import UUID
10
+
11
+ from ..schema import CivilizationNode, NodeType
12
+ from ..ingestion.document_ingestor import DocumentIngestor
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ @dataclass
18
+ class GDriveConfig:
19
+ """Configuration for Google Drive sync."""
20
+ credentials_path: str = ""
21
+ folder_ids: list[str] = field(default_factory=list)
22
+ folder_type_mapping: dict[str, NodeType] = field(default_factory=dict)
23
+ sync_interval_minutes: int = 120
24
+ file_types: list[str] = field(default_factory=lambda: [".md", ".txt", ".docx", ".pdf"])
25
+ max_file_size_mb: int = 10
26
+
27
+
28
+ @dataclass
29
+ class GDriveFile:
30
+ """Represents a Google Drive file."""
31
+ file_id: str
32
+ name: str
33
+ mime_type: str
34
+ folder_id: str
35
+ web_view_link: str
36
+ modified_time: datetime
37
+ size_bytes: int = 0
38
+ content: bytes | None = None
39
+
40
+
41
+ @dataclass
42
+ class GDriveSyncResult:
43
+ """Result of a Google Drive sync operation."""
44
+ success: bool
45
+ files_processed: int = 0
46
+ files_skipped: int = 0
47
+ nodes_created: int = 0
48
+ nodes_updated: int = 0
49
+ errors: list[str] = field(default_factory=list)
50
+ sync_time: datetime = field(default_factory=datetime.utcnow)
51
+
52
+
53
+ class GDriveSync:
54
+ """
55
+ Synchronizes Google Drive documents with Civilization Knowledge Layer.
56
+ """
57
+
58
+ SUPPORTED_MIME_TYPES = {
59
+ "application/vnd.google-apps.document": "gdoc",
60
+ "text/plain": "txt",
61
+ "text/markdown": "md",
62
+ "application/pdf": "pdf",
63
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document": "docx",
64
+ }
65
+
66
+ def __init__(
67
+ self,
68
+ org_id: str,
69
+ config: GDriveConfig,
70
+ ingestor: DocumentIngestor | None = None,
71
+ ):
72
+ self.org_id = org_id
73
+ self.config = config
74
+ self.ingestor = ingestor
75
+ self._last_sync: datetime | None = None
76
+ self._file_cache: dict[str, datetime] = {} # file_id -> last_modified
77
+
78
+ async def sync_folder(
79
+ self,
80
+ folder_id: str,
81
+ node_type: NodeType | None = None,
82
+ recursive: bool = True,
83
+ ) -> GDriveSyncResult:
84
+ """
85
+ Sync a Google Drive folder to civilization nodes.
86
+
87
+ Args:
88
+ folder_id: Google Drive folder ID
89
+ node_type: Optional node type to assign to all files
90
+ recursive: Whether to sync subfolders
91
+
92
+ Returns:
93
+ GDriveSyncResult with sync statistics
94
+ """
95
+ result = GDriveSyncResult(success=False)
96
+
97
+ try:
98
+ # Fetch files from folder
99
+ files = await self._list_folder_files(folder_id, recursive)
100
+
101
+ for file in files:
102
+ try:
103
+ # Skip if not modified since last sync
104
+ if self._should_skip_file(file):
105
+ result.files_skipped += 1
106
+ continue
107
+
108
+ # Process file
109
+ await self._process_file(file, node_type, result)
110
+ result.files_processed += 1
111
+
112
+ except Exception as e:
113
+ result.errors.append(f"Error processing file {file.name}: {e}")
114
+
115
+ result.success = True
116
+ self._last_sync = datetime.utcnow()
117
+
118
+ except Exception as e:
119
+ logger.exception("Google Drive sync failed")
120
+ result.errors.append(str(e))
121
+
122
+ return result
123
+
124
+ async def sync_all_folders(self) -> GDriveSyncResult:
125
+ """Sync all configured folders."""
126
+ total_result = GDriveSyncResult(success=True)
127
+
128
+ for folder_id in self.config.folder_ids:
129
+ node_type = self.config.folder_type_mapping.get(folder_id)
130
+ result = await self.sync_folder(folder_id, node_type)
131
+
132
+ total_result.files_processed += result.files_processed
133
+ total_result.files_skipped += result.files_skipped
134
+ total_result.nodes_created += result.nodes_created
135
+ total_result.nodes_updated += result.nodes_updated
136
+ total_result.errors.extend(result.errors)
137
+
138
+ if not result.success:
139
+ total_result.success = False
140
+
141
+ return total_result
142
+
143
+ async def sync_file(self, file_id: str) -> CivilizationNode | None:
144
+ """Sync a single file by ID."""
145
+ file = await self._get_file(file_id)
146
+ if not file:
147
+ return None
148
+
149
+ result = GDriveSyncResult(success=False)
150
+ await self._process_file(file, None, result)
151
+
152
+ return None # Would return created/updated node
153
+
154
+ async def _list_folder_files(
155
+ self,
156
+ folder_id: str,
157
+ recursive: bool,
158
+ ) -> list[GDriveFile]:
159
+ """
160
+ List files in a Google Drive folder.
161
+ This is a stub - actual implementation would use Google Drive API.
162
+ """
163
+ # In real implementation:
164
+ # from google.oauth2 import service_account
165
+ # from googleapiclient.discovery import build
166
+ #
167
+ # creds = service_account.Credentials.from_service_account_file(
168
+ # self.config.credentials_path
169
+ # )
170
+ # service = build('drive', 'v3', credentials=creds)
171
+ # results = service.files().list(
172
+ # q=f"'{folder_id}' in parents",
173
+ # pageSize=100,
174
+ # fields="files(id, name, mimeType, modifiedTime, size, webViewLink)"
175
+ # ).execute()
176
+
177
+ logger.info(f"Would list files from Google Drive folder: {folder_id}")
178
+ return []
179
+
180
+ async def _get_file(self, file_id: str) -> GDriveFile | None:
181
+ """Fetch a single file with content."""
182
+ logger.info(f"Would fetch Google Drive file: {file_id}")
183
+ return None
184
+
185
+ async def _download_file_content(self, file: GDriveFile) -> bytes | None:
186
+ """Download file content."""
187
+ # Check size limit
188
+ max_size = self.config.max_file_size_mb * 1024 * 1024
189
+ if file.size_bytes > max_size:
190
+ logger.warning(f"File {file.name} exceeds size limit")
191
+ return None
192
+
193
+ # In real implementation, would download file content
194
+ logger.info(f"Would download content for file: {file.name}")
195
+ return None
196
+
197
+ def _should_skip_file(self, file: GDriveFile) -> bool:
198
+ """Check if file should be skipped (not modified)."""
199
+ # Check mime type
200
+ if file.mime_type not in self.SUPPORTED_MIME_TYPES:
201
+ return True
202
+
203
+ # Check extension
204
+ ext = "." + file.name.split(".")[-1].lower() if "." in file.name else ""
205
+ if ext and ext not in self.config.file_types:
206
+ return True
207
+
208
+ # Check if modified since last seen
209
+ last_seen = self._file_cache.get(file.file_id)
210
+ if last_seen and file.modified_time <= last_seen:
211
+ return True
212
+
213
+ return False
214
+
215
+ async def _process_file(
216
+ self,
217
+ file: GDriveFile,
218
+ node_type: NodeType | None,
219
+ result: GDriveSyncResult,
220
+ ) -> None:
221
+ """Process a Google Drive file and create/update civilization node."""
222
+ if not self.ingestor:
223
+ logger.warning("No ingestor configured")
224
+ return
225
+
226
+ # Download content
227
+ content = await self._download_file_content(file)
228
+ if not content:
229
+ return
230
+
231
+ # Ingest
232
+ from hyperclaw.taint import TaintedInput
233
+ try:
234
+ ingest_result = await self.ingestor.ingest(
235
+ content=content,
236
+ filename=file.name,
237
+ source="gdrive",
238
+ source_url=file.web_view_link,
239
+ node_type_hint=node_type,
240
+ )
241
+ except TaintedInput as e: # connector content never enters the knowledge layer
242
+ result.errors.append(e.user_message)
243
+ return
244
+
245
+ if ingest_result.success:
246
+ result.nodes_created += 1
247
+ self._file_cache[file.file_id] = file.modified_time
248
+ else:
249
+ result.errors.extend(ingest_result.errors)
250
+
251
+ def get_sync_status(self) -> dict:
252
+ """Get current sync status."""
253
+ return {
254
+ "last_sync": self._last_sync.isoformat() if self._last_sync else None,
255
+ "configured_folders": self.config.folder_ids,
256
+ "sync_interval_minutes": self.config.sync_interval_minutes,
257
+ "cached_files": len(self._file_cache),
258
+ }
259
+
260
+ def clear_cache(self) -> None:
261
+ """Clear the file modification cache (forces full re-sync)."""
262
+ self._file_cache.clear()
@@ -0,0 +1,237 @@
1
+ """
2
+ Notion integration for syncing organizational knowledge.
3
+ """
4
+ from __future__ import annotations
5
+ import logging
6
+ from dataclasses import dataclass, field
7
+ from datetime import datetime
8
+ from typing import Any
9
+ from uuid import UUID, uuid4
10
+
11
+ from ..schema import CivilizationNode, NodeType, NodeStatus
12
+ from ..ingestion.document_ingestor import DocumentIngestor
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ @dataclass
18
+ class NotionConfig:
19
+ """Configuration for Notion sync."""
20
+ api_key: str = ""
21
+ workspace_id: str | None = None
22
+ database_ids: dict[NodeType, str] = field(default_factory=dict)
23
+ sync_interval_minutes: int = 60
24
+ auto_create_nodes: bool = True
25
+ bidirectional: bool = False # If True, also push changes back to Notion
26
+
27
+
28
+ @dataclass
29
+ class NotionPage:
30
+ """Represents a Notion page."""
31
+ page_id: str
32
+ title: str
33
+ url: str
34
+ content: str
35
+ properties: dict = field(default_factory=dict)
36
+ last_edited: datetime | None = None
37
+ parent_id: str | None = None
38
+
39
+
40
+ @dataclass
41
+ class SyncResult:
42
+ """Result of a sync operation."""
43
+ success: bool
44
+ pages_synced: int = 0
45
+ nodes_created: int = 0
46
+ nodes_updated: int = 0
47
+ errors: list[str] = field(default_factory=list)
48
+ sync_time: datetime = field(default_factory=datetime.utcnow)
49
+
50
+
51
+ class NotionSync:
52
+ """
53
+ Synchronizes Notion workspace content with Civilization Knowledge Layer.
54
+ """
55
+
56
+ def __init__(
57
+ self,
58
+ org_id: str,
59
+ config: NotionConfig,
60
+ ingestor: DocumentIngestor | None = None,
61
+ ):
62
+ self.org_id = org_id
63
+ self.config = config
64
+ self.ingestor = ingestor
65
+ self._last_sync: datetime | None = None
66
+ self._sync_cursor: dict[str, str] = {}
67
+
68
+ async def sync_database(
69
+ self,
70
+ database_id: str,
71
+ node_type: NodeType | None = None,
72
+ ) -> SyncResult:
73
+ """
74
+ Sync a Notion database to civilization nodes.
75
+
76
+ Args:
77
+ database_id: Notion database ID
78
+ node_type: Optional node type to assign
79
+
80
+ Returns:
81
+ SyncResult with sync statistics
82
+ """
83
+ result = SyncResult(success=False)
84
+
85
+ try:
86
+ # Fetch pages from Notion
87
+ pages = await self._fetch_database_pages(database_id)
88
+ result.pages_synced = len(pages)
89
+
90
+ # Process each page
91
+ for page in pages:
92
+ try:
93
+ await self._process_page(page, node_type, result)
94
+ except Exception as e:
95
+ result.errors.append(f"Error processing page {page.page_id}: {e}")
96
+
97
+ result.success = True
98
+ self._last_sync = datetime.utcnow()
99
+
100
+ except Exception as e:
101
+ logger.exception("Notion sync failed")
102
+ result.errors.append(str(e))
103
+
104
+ return result
105
+
106
+ async def sync_all_databases(self) -> SyncResult:
107
+ """Sync all configured databases."""
108
+ total_result = SyncResult(success=True)
109
+
110
+ for node_type, database_id in self.config.database_ids.items():
111
+ result = await self.sync_database(database_id, node_type)
112
+ total_result.pages_synced += result.pages_synced
113
+ total_result.nodes_created += result.nodes_created
114
+ total_result.nodes_updated += result.nodes_updated
115
+ total_result.errors.extend(result.errors)
116
+ if not result.success:
117
+ total_result.success = False
118
+
119
+ return total_result
120
+
121
+ async def sync_page(self, page_id: str) -> CivilizationNode | None:
122
+ """Sync a single Notion page."""
123
+ page = await self._fetch_page(page_id)
124
+ if not page:
125
+ return None
126
+
127
+ result = SyncResult(success=False)
128
+ await self._process_page(page, None, result)
129
+
130
+ return None # Would return the created/updated node
131
+
132
+ async def _fetch_database_pages(self, database_id: str) -> list[NotionPage]:
133
+ """
134
+ Fetch all pages from a Notion database.
135
+ This is a stub - actual implementation would use Notion API.
136
+ """
137
+ # In real implementation:
138
+ # async with httpx.AsyncClient() as client:
139
+ # response = await client.post(
140
+ # f"https://api.notion.com/v1/databases/{database_id}/query",
141
+ # headers={
142
+ # "Authorization": f"Bearer {self.config.api_key}",
143
+ # "Notion-Version": "2022-06-28",
144
+ # },
145
+ # json={"page_size": 100},
146
+ # )
147
+ # ...
148
+
149
+ logger.info(f"Would fetch pages from Notion database: {database_id}")
150
+ return []
151
+
152
+ async def _fetch_page(self, page_id: str) -> NotionPage | None:
153
+ """
154
+ Fetch a single Notion page with content.
155
+ This is a stub - actual implementation would use Notion API.
156
+ """
157
+ logger.info(f"Would fetch Notion page: {page_id}")
158
+ return None
159
+
160
+ async def _process_page(
161
+ self,
162
+ page: NotionPage,
163
+ node_type: NodeType | None,
164
+ result: SyncResult,
165
+ ) -> None:
166
+ """Process a Notion page and create/update civilization node."""
167
+ if not self.ingestor:
168
+ logger.warning("No ingestor configured, skipping page processing")
169
+ return
170
+
171
+ # Determine node type from page properties or use provided type
172
+ detected_type = node_type or self._detect_node_type(page)
173
+
174
+ # Ingest the page content
175
+ from hyperclaw.taint import TaintedInput
176
+ try:
177
+ ingest_result = await self.ingestor.ingest(
178
+ content=page.content,
179
+ filename=f"{page.title}.md",
180
+ source="notion",
181
+ source_url=page.url,
182
+ node_type_hint=detected_type,
183
+ )
184
+ except TaintedInput as e: # connector content never enters the knowledge layer
185
+ result.errors.append(e.user_message)
186
+ return
187
+
188
+ if ingest_result.success:
189
+ result.nodes_created += 1
190
+ else:
191
+ result.errors.extend(ingest_result.errors)
192
+
193
+ def _detect_node_type(self, page: NotionPage) -> NodeType:
194
+ """Detect node type from page properties."""
195
+ # Check for type property
196
+ type_prop = page.properties.get("Type", {}).get("select", {}).get("name", "")
197
+ type_map = {
198
+ "SOP": NodeType.SOP,
199
+ "Procedure": NodeType.SOP,
200
+ "Checklist": NodeType.CHECKLIST,
201
+ "Runbook": NodeType.RUNBOOK,
202
+ "Role": NodeType.ROLE,
203
+ "Job Description": NodeType.JOB_DESCRIPTION,
204
+ "Policy": NodeType.POLICY,
205
+ }
206
+ return type_map.get(type_prop, NodeType.KNOWLEDGE_ARTICLE)
207
+
208
+ async def push_to_notion(
209
+ self,
210
+ node: CivilizationNode,
211
+ database_id: str | None = None,
212
+ ) -> str | None:
213
+ """
214
+ Push a civilization node back to Notion.
215
+ Returns the Notion page ID if successful.
216
+ """
217
+ if not self.config.bidirectional:
218
+ logger.warning("Bidirectional sync not enabled")
219
+ return None
220
+
221
+ db_id = database_id or self.config.database_ids.get(node.node_type)
222
+ if not db_id:
223
+ logger.warning(f"No database configured for node type: {node.node_type}")
224
+ return None
225
+
226
+ # In real implementation, would create/update Notion page
227
+ logger.info(f"Would push node {node.id} to Notion database {db_id}")
228
+ return None
229
+
230
+ def get_sync_status(self) -> dict:
231
+ """Get current sync status."""
232
+ return {
233
+ "last_sync": self._last_sync.isoformat() if self._last_sync else None,
234
+ "configured_databases": list(self.config.database_ids.keys()),
235
+ "sync_interval_minutes": self.config.sync_interval_minutes,
236
+ "bidirectional": self.config.bidirectional,
237
+ }
@@ -0,0 +1,19 @@
1
+ """
2
+ Versioning module for Civilization Knowledge Layer.
3
+ Handles version tracking, diffs, and staleness detection.
4
+ """
5
+ from .version_manager import VersionManager, NodeVersion, VersionHistory
6
+ from .diff_engine import DiffEngine, NodeDiff, DiffType
7
+ from .staleness_detector import StalenessDetector, StalenessLevel, StalenessReport
8
+
9
+ __all__ = [
10
+ "VersionManager",
11
+ "NodeVersion",
12
+ "VersionHistory",
13
+ "DiffEngine",
14
+ "NodeDiff",
15
+ "DiffType",
16
+ "StalenessDetector",
17
+ "StalenessLevel",
18
+ "StalenessReport",
19
+ ]