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
civilization/store.py ADDED
@@ -0,0 +1,177 @@
1
+ """
2
+ CivilizationStore — all DB operations for civilization nodes.
3
+ Wraps asyncpg pool (or stub when pool=None for testing).
4
+ """
5
+ from __future__ import annotations
6
+ import json
7
+ import logging
8
+ from uuid import UUID
9
+ from datetime import datetime
10
+ from typing import Any
11
+
12
+ from hyperclaw.taint import training_sink
13
+
14
+ from .schema import CivilizationNode, NodeType, NodeStatus
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ class CivilizationStore:
20
+ def __init__(self, pool=None):
21
+ self.pool = pool # asyncpg pool; None = no-DB mode (tests)
22
+
23
+ @training_sink("civilization.store.save")
24
+ async def save(self, node: CivilizationNode) -> CivilizationNode:
25
+ """Upsert a node into civilization_nodes."""
26
+ if not self.pool:
27
+ return node
28
+ content = node.model_dump(mode="json", exclude={"embedding"})
29
+ async with self.pool.acquire() as conn:
30
+ await conn.execute("""
31
+ INSERT INTO civilization_nodes
32
+ (id, org_id, node_type, title, status, version, owner_id,
33
+ tags, source, source_url, content, embedding, metadata,
34
+ created_at, updated_at)
35
+ VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12::vector,$13,$14,$15)
36
+ ON CONFLICT (id) DO UPDATE SET
37
+ title=EXCLUDED.title, status=EXCLUDED.status,
38
+ version=EXCLUDED.version, content=EXCLUDED.content,
39
+ embedding=EXCLUDED.embedding, updated_at=EXCLUDED.updated_at
40
+ """,
41
+ node.id, node.org_id, node.node_type.value, node.title,
42
+ node.status.value, node.version, node.owner_id,
43
+ node.tags, node.source, node.source_url,
44
+ json.dumps(content),
45
+ node.embedding,
46
+ json.dumps(node.metadata),
47
+ node.created_at, node.updated_at)
48
+ return node
49
+
50
+ async def get(self, node_id: UUID, org_id: str) -> CivilizationNode | None:
51
+ if not self.pool:
52
+ return None
53
+ async with self.pool.acquire() as conn:
54
+ row = await conn.fetchrow(
55
+ "SELECT * FROM civilization_nodes WHERE id=$1 AND org_id=$2",
56
+ node_id, org_id
57
+ )
58
+ if not row:
59
+ return None
60
+ return self._row_to_node(row)
61
+
62
+ async def list_by_type(self, org_id: str, node_type: NodeType,
63
+ limit: int = 100) -> list[CivilizationNode]:
64
+ if not self.pool:
65
+ return []
66
+ async with self.pool.acquire() as conn:
67
+ rows = await conn.fetch(
68
+ "SELECT * FROM civilization_nodes WHERE org_id=$1 AND node_type=$2 LIMIT $3",
69
+ org_id, node_type.value, limit
70
+ )
71
+ return [self._row_to_node(r) for r in rows]
72
+
73
+ async def list_all(self, org_id: str, limit: int = 500) -> list[CivilizationNode]:
74
+ if not self.pool:
75
+ return []
76
+ async with self.pool.acquire() as conn:
77
+ rows = await conn.fetch(
78
+ "SELECT * FROM civilization_nodes WHERE org_id=$1 LIMIT $2",
79
+ org_id, limit
80
+ )
81
+ return [self._row_to_node(r) for r in rows]
82
+
83
+ async def search_by_embedding(self, org_id: str, embedding: list[float],
84
+ top_k: int = 10,
85
+ node_types: list[NodeType] | None = None) -> list[dict]:
86
+ """Vector similarity search."""
87
+ if not self.pool:
88
+ return []
89
+ type_filter = ""
90
+ args = [org_id, embedding, top_k]
91
+ if node_types:
92
+ placeholders = ",".join(f"${i+4}" for i in range(len(node_types)))
93
+ type_filter = f"AND node_type IN ({placeholders})"
94
+ args.extend([t.value for t in node_types])
95
+ async with self.pool.acquire() as conn:
96
+ rows = await conn.fetch(f"""
97
+ SELECT *, 1 - (embedding <=> $2::vector) AS score
98
+ FROM civilization_nodes
99
+ WHERE org_id=$1 {type_filter}
100
+ ORDER BY embedding <=> $2::vector
101
+ LIMIT $3
102
+ """, *args)
103
+ return [dict(r) for r in rows]
104
+
105
+ async def search_by_tags(self, org_id: str, tags: list[str],
106
+ limit: int = 50) -> list[CivilizationNode]:
107
+ """Find nodes that have any of the specified tags."""
108
+ if not self.pool:
109
+ return []
110
+ async with self.pool.acquire() as conn:
111
+ rows = await conn.fetch(
112
+ "SELECT * FROM civilization_nodes WHERE org_id=$1 AND tags && $2 LIMIT $3",
113
+ org_id, tags, limit
114
+ )
115
+ return [self._row_to_node(r) for r in rows]
116
+
117
+ async def search_by_owner(self, org_id: str, owner_id: str,
118
+ limit: int = 100) -> list[CivilizationNode]:
119
+ """Find all nodes owned by a specific person."""
120
+ if not self.pool:
121
+ return []
122
+ async with self.pool.acquire() as conn:
123
+ rows = await conn.fetch(
124
+ "SELECT * FROM civilization_nodes WHERE org_id=$1 AND owner_id=$2 LIMIT $3",
125
+ org_id, owner_id, limit
126
+ )
127
+ return [self._row_to_node(r) for r in rows]
128
+
129
+ async def count_by_type(self, org_id: str) -> dict[str, int]:
130
+ if not self.pool:
131
+ return {}
132
+ async with self.pool.acquire() as conn:
133
+ rows = await conn.fetch(
134
+ "SELECT node_type, COUNT(*) as cnt FROM civilization_nodes WHERE org_id=$1 GROUP BY node_type",
135
+ org_id
136
+ )
137
+ return {r["node_type"]: r["cnt"] for r in rows}
138
+
139
+ async def delete(self, node_id: UUID, org_id: str) -> bool:
140
+ if not self.pool:
141
+ return True
142
+ async with self.pool.acquire() as conn:
143
+ result = await conn.execute(
144
+ "DELETE FROM civilization_nodes WHERE id=$1 AND org_id=$2",
145
+ node_id, org_id
146
+ )
147
+ return result == "DELETE 1"
148
+
149
+ @training_sink("civilization.store.update_status")
150
+ async def update_status(self, node_id: UUID, org_id: str,
151
+ new_status: NodeStatus) -> bool:
152
+ """Update a node's status."""
153
+ if not self.pool:
154
+ return True
155
+ async with self.pool.acquire() as conn:
156
+ result = await conn.execute(
157
+ "UPDATE civilization_nodes SET status=$3, updated_at=$4 WHERE id=$1 AND org_id=$2",
158
+ node_id, org_id, new_status.value, datetime.utcnow()
159
+ )
160
+ return "UPDATE 1" in result
161
+
162
+ @training_sink("civilization.store.update_embedding")
163
+ async def update_embedding(self, node_id: UUID, org_id: str,
164
+ embedding: list[float]) -> bool:
165
+ """Update a node's embedding vector."""
166
+ if not self.pool:
167
+ return True
168
+ async with self.pool.acquire() as conn:
169
+ result = await conn.execute(
170
+ "UPDATE civilization_nodes SET embedding=$3::vector, updated_at=$4 WHERE id=$1 AND org_id=$2",
171
+ node_id, org_id, embedding, datetime.utcnow()
172
+ )
173
+ return "UPDATE 1" in result
174
+
175
+ def _row_to_node(self, row) -> CivilizationNode:
176
+ content = json.loads(row["content"]) if isinstance(row["content"], str) else row["content"]
177
+ return CivilizationNode(**{**content, "id": row["id"], "org_id": row["org_id"]})
@@ -0,0 +1,16 @@
1
+ """
2
+ Sync module for Civilization Knowledge Layer.
3
+ Handles synchronization with external knowledge management systems.
4
+ """
5
+ from .notion_sync import NotionSync, NotionConfig
6
+ from .gdrive_sync import GDriveSync, GDriveConfig
7
+ from .confluence_sync import ConfluenceSync, ConfluenceConfig
8
+
9
+ __all__ = [
10
+ "NotionSync",
11
+ "NotionConfig",
12
+ "GDriveSync",
13
+ "GDriveConfig",
14
+ "ConfluenceSync",
15
+ "ConfluenceConfig",
16
+ ]
@@ -0,0 +1,317 @@
1
+ """
2
+ Confluence 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
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 ConfluenceConfig:
19
+ """Configuration for Confluence sync."""
20
+ base_url: str = "" # e.g., "https://your-domain.atlassian.net/wiki"
21
+ username: str = ""
22
+ api_token: str = ""
23
+ space_keys: list[str] = field(default_factory=list)
24
+ space_type_mapping: dict[str, NodeType] = field(default_factory=dict)
25
+ sync_interval_minutes: int = 120
26
+ include_attachments: bool = False
27
+ label_filters: list[str] = field(default_factory=list)
28
+
29
+
30
+ @dataclass
31
+ class ConfluencePage:
32
+ """Represents a Confluence page."""
33
+ page_id: str
34
+ title: str
35
+ space_key: str
36
+ web_url: str
37
+ body_html: str
38
+ body_storage: str = "" # Confluence storage format
39
+ labels: list[str] = field(default_factory=list)
40
+ version: int = 1
41
+ last_modified: datetime | None = None
42
+ parent_id: str | None = None
43
+ ancestors: list[str] = field(default_factory=list)
44
+
45
+
46
+ @dataclass
47
+ class ConfluenceSyncResult:
48
+ """Result of a Confluence sync operation."""
49
+ success: bool
50
+ pages_synced: int = 0
51
+ pages_skipped: int = 0
52
+ nodes_created: int = 0
53
+ nodes_updated: int = 0
54
+ errors: list[str] = field(default_factory=list)
55
+ sync_time: datetime = field(default_factory=datetime.utcnow)
56
+
57
+
58
+ class ConfluenceSync:
59
+ """
60
+ Synchronizes Confluence spaces/pages with Civilization Knowledge Layer.
61
+ """
62
+
63
+ LABEL_TYPE_MAPPING = {
64
+ "sop": NodeType.SOP,
65
+ "procedure": NodeType.SOP,
66
+ "checklist": NodeType.CHECKLIST,
67
+ "runbook": NodeType.RUNBOOK,
68
+ "playbook": NodeType.RUNBOOK,
69
+ "role": NodeType.ROLE,
70
+ "job-description": NodeType.JOB_DESCRIPTION,
71
+ "policy": NodeType.POLICY,
72
+ "workflow": NodeType.WORKFLOW,
73
+ }
74
+
75
+ def __init__(
76
+ self,
77
+ org_id: str,
78
+ config: ConfluenceConfig,
79
+ ingestor: DocumentIngestor | None = None,
80
+ ):
81
+ self.org_id = org_id
82
+ self.config = config
83
+ self.ingestor = ingestor
84
+ self._last_sync: datetime | None = None
85
+ self._page_cache: dict[str, int] = {} # page_id -> version
86
+
87
+ async def sync_space(
88
+ self,
89
+ space_key: str,
90
+ node_type: NodeType | None = None,
91
+ ) -> ConfluenceSyncResult:
92
+ """
93
+ Sync a Confluence space to civilization nodes.
94
+
95
+ Args:
96
+ space_key: Confluence space key
97
+ node_type: Optional default node type
98
+
99
+ Returns:
100
+ ConfluenceSyncResult with sync statistics
101
+ """
102
+ result = ConfluenceSyncResult(success=False)
103
+
104
+ try:
105
+ # Fetch pages from space
106
+ pages = await self._list_space_pages(space_key)
107
+
108
+ for page in pages:
109
+ try:
110
+ # Skip if not modified
111
+ if self._should_skip_page(page):
112
+ result.pages_skipped += 1
113
+ continue
114
+
115
+ # Apply label filters
116
+ if self.config.label_filters:
117
+ if not any(l in page.labels for l in self.config.label_filters):
118
+ result.pages_skipped += 1
119
+ continue
120
+
121
+ # Process page
122
+ await self._process_page(page, node_type, result)
123
+ result.pages_synced += 1
124
+
125
+ except Exception as e:
126
+ result.errors.append(f"Error processing page {page.title}: {e}")
127
+
128
+ result.success = True
129
+ self._last_sync = datetime.utcnow()
130
+
131
+ except Exception as e:
132
+ logger.exception("Confluence sync failed")
133
+ result.errors.append(str(e))
134
+
135
+ return result
136
+
137
+ async def sync_all_spaces(self) -> ConfluenceSyncResult:
138
+ """Sync all configured spaces."""
139
+ total_result = ConfluenceSyncResult(success=True)
140
+
141
+ for space_key in self.config.space_keys:
142
+ node_type = self.config.space_type_mapping.get(space_key)
143
+ result = await self.sync_space(space_key, node_type)
144
+
145
+ total_result.pages_synced += result.pages_synced
146
+ total_result.pages_skipped += result.pages_skipped
147
+ total_result.nodes_created += result.nodes_created
148
+ total_result.nodes_updated += result.nodes_updated
149
+ total_result.errors.extend(result.errors)
150
+
151
+ if not result.success:
152
+ total_result.success = False
153
+
154
+ return total_result
155
+
156
+ async def sync_page(self, page_id: str) -> CivilizationNode | None:
157
+ """Sync a single page by ID."""
158
+ page = await self._get_page(page_id)
159
+ if not page:
160
+ return None
161
+
162
+ result = ConfluenceSyncResult(success=False)
163
+ await self._process_page(page, None, result)
164
+
165
+ return None
166
+
167
+ async def sync_by_label(
168
+ self,
169
+ label: str,
170
+ node_type: NodeType | None = None,
171
+ ) -> ConfluenceSyncResult:
172
+ """Sync all pages with a specific label."""
173
+ result = ConfluenceSyncResult(success=False)
174
+
175
+ try:
176
+ pages = await self._search_by_label(label)
177
+
178
+ for page in pages:
179
+ try:
180
+ if self._should_skip_page(page):
181
+ result.pages_skipped += 1
182
+ continue
183
+
184
+ await self._process_page(page, node_type, result)
185
+ result.pages_synced += 1
186
+
187
+ except Exception as e:
188
+ result.errors.append(f"Error processing page {page.title}: {e}")
189
+
190
+ result.success = True
191
+
192
+ except Exception as e:
193
+ logger.exception("Confluence label sync failed")
194
+ result.errors.append(str(e))
195
+
196
+ return result
197
+
198
+ async def _list_space_pages(self, space_key: str) -> list[ConfluencePage]:
199
+ """
200
+ List all pages in a Confluence space.
201
+ This is a stub - actual implementation would use Confluence REST API.
202
+ """
203
+ # In real implementation:
204
+ # import httpx
205
+ # import base64
206
+ #
207
+ # auth = base64.b64encode(
208
+ # f"{self.config.username}:{self.config.api_token}".encode()
209
+ # ).decode()
210
+ #
211
+ # async with httpx.AsyncClient() as client:
212
+ # response = await client.get(
213
+ # f"{self.config.base_url}/rest/api/content",
214
+ # params={"spaceKey": space_key, "expand": "body.storage,version"},
215
+ # headers={"Authorization": f"Basic {auth}"},
216
+ # )
217
+
218
+ logger.info(f"Would list pages from Confluence space: {space_key}")
219
+ return []
220
+
221
+ async def _get_page(self, page_id: str) -> ConfluencePage | None:
222
+ """Fetch a single Confluence page with content."""
223
+ logger.info(f"Would fetch Confluence page: {page_id}")
224
+ return None
225
+
226
+ async def _search_by_label(self, label: str) -> list[ConfluencePage]:
227
+ """Search for pages with a specific label."""
228
+ logger.info(f"Would search Confluence for label: {label}")
229
+ return []
230
+
231
+ def _should_skip_page(self, page: ConfluencePage) -> bool:
232
+ """Check if page should be skipped (not modified)."""
233
+ cached_version = self._page_cache.get(page.page_id)
234
+ if cached_version and page.version <= cached_version:
235
+ return True
236
+ return False
237
+
238
+ async def _process_page(
239
+ self,
240
+ page: ConfluencePage,
241
+ node_type: NodeType | None,
242
+ result: ConfluenceSyncResult,
243
+ ) -> None:
244
+ """Process a Confluence page and create/update civilization node."""
245
+ if not self.ingestor:
246
+ logger.warning("No ingestor configured")
247
+ return
248
+
249
+ # Detect node type from labels if not specified
250
+ detected_type = node_type or self._detect_node_type(page)
251
+
252
+ # Convert HTML to markdown-ish text
253
+ content = self._convert_confluence_content(page)
254
+
255
+ # Ingest
256
+ from hyperclaw.taint import TaintedInput
257
+ try:
258
+ ingest_result = await self.ingestor.ingest(
259
+ content=content,
260
+ filename=f"{page.title}.md",
261
+ source="confluence",
262
+ source_url=page.web_url,
263
+ node_type_hint=detected_type,
264
+ tags=page.labels,
265
+ )
266
+ except TaintedInput as e: # connector content never enters the knowledge layer
267
+ result.errors.append(e.user_message)
268
+ return
269
+
270
+ if ingest_result.success:
271
+ result.nodes_created += 1
272
+ self._page_cache[page.page_id] = page.version
273
+ else:
274
+ result.errors.extend(ingest_result.errors)
275
+
276
+ def _detect_node_type(self, page: ConfluencePage) -> NodeType:
277
+ """Detect node type from page labels."""
278
+ for label in page.labels:
279
+ label_lower = label.lower()
280
+ if label_lower in self.LABEL_TYPE_MAPPING:
281
+ return self.LABEL_TYPE_MAPPING[label_lower]
282
+ return NodeType.KNOWLEDGE_ARTICLE
283
+
284
+ def _convert_confluence_content(self, page: ConfluencePage) -> str:
285
+ """Convert Confluence storage format to plain text/markdown."""
286
+ import re
287
+
288
+ content = page.body_storage or page.body_html
289
+
290
+ # Simple HTML tag stripping (in production, use a proper HTML parser)
291
+ content = re.sub(r"<br\s*/?>", "\n", content)
292
+ content = re.sub(r"<p[^>]*>", "\n", content)
293
+ content = re.sub(r"</p>", "\n", content)
294
+ content = re.sub(r"<li[^>]*>", "- ", content)
295
+ content = re.sub(r"<h(\d)[^>]*>", r"\n#" + r"\1 ", content)
296
+ content = re.sub(r"</h\d>", "\n", content)
297
+ content = re.sub(r"<[^>]+>", "", content)
298
+
299
+ # Clean up whitespace
300
+ content = re.sub(r"\n{3,}", "\n\n", content)
301
+
302
+ # Add title
303
+ return f"# {page.title}\n\n{content.strip()}"
304
+
305
+ def get_sync_status(self) -> dict:
306
+ """Get current sync status."""
307
+ return {
308
+ "last_sync": self._last_sync.isoformat() if self._last_sync else None,
309
+ "configured_spaces": self.config.space_keys,
310
+ "sync_interval_minutes": self.config.sync_interval_minutes,
311
+ "cached_pages": len(self._page_cache),
312
+ "label_filters": self.config.label_filters,
313
+ }
314
+
315
+ def clear_cache(self) -> None:
316
+ """Clear page version cache (forces full re-sync)."""
317
+ self._page_cache.clear()