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
agents/planner.py ADDED
@@ -0,0 +1,421 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Assistant Planning System — Goal decomposition and autonomous execution.
4
+ Breaks complex goals into subtasks, tracks progress, adapts plans.
5
+ """
6
+
7
+ import os
8
+ import sys
9
+ import json
10
+ import uuid
11
+ from pathlib import Path
12
+ from datetime import datetime
13
+ from typing import Dict, List, Optional, Any
14
+ from enum import Enum
15
+
16
+ # Use user's ~/.hyperclaw directory, not package location
17
+ HYPERCLAW_ROOT = Path(os.environ.get("HYPERCLAW_ROOT", Path.home() / ".hyperclaw"))
18
+ PLANS_DIR = HYPERCLAW_ROOT / "workspace" / "plans"
19
+ PLANS_DIR.mkdir(parents=True, exist_ok=True)
20
+
21
+
22
+ class TaskStatus(str, Enum):
23
+ PENDING = "pending"
24
+ IN_PROGRESS = "in_progress"
25
+ COMPLETED = "completed"
26
+ FAILED = "failed"
27
+ BLOCKED = "blocked"
28
+ SKIPPED = "skipped"
29
+
30
+
31
+ class Task:
32
+ """A single task in a plan."""
33
+
34
+ def __init__(
35
+ self,
36
+ title: str,
37
+ description: str = "",
38
+ depends_on: List[str] = None,
39
+ task_id: str = None
40
+ ):
41
+ self.id = task_id or str(uuid.uuid4())[:8]
42
+ self.title = title
43
+ self.description = description
44
+ self.status = TaskStatus.PENDING
45
+ self.depends_on = depends_on or []
46
+ self.result = None
47
+ self.error = None
48
+ self.started_at = None
49
+ self.completed_at = None
50
+ self.retries = 0
51
+
52
+ def to_dict(self) -> Dict:
53
+ return {
54
+ "id": self.id,
55
+ "title": self.title,
56
+ "description": self.description,
57
+ "status": self.status.value,
58
+ "depends_on": self.depends_on,
59
+ "result": self.result,
60
+ "error": self.error,
61
+ "started_at": self.started_at,
62
+ "completed_at": self.completed_at,
63
+ "retries": self.retries
64
+ }
65
+
66
+ @classmethod
67
+ def from_dict(cls, data: Dict) -> "Task":
68
+ task = cls(
69
+ title=data["title"],
70
+ description=data.get("description", ""),
71
+ depends_on=data.get("depends_on", []),
72
+ task_id=data["id"]
73
+ )
74
+ task.status = TaskStatus(data.get("status", "pending"))
75
+ task.result = data.get("result")
76
+ task.error = data.get("error")
77
+ task.started_at = data.get("started_at")
78
+ task.completed_at = data.get("completed_at")
79
+ task.retries = data.get("retries", 0)
80
+ return task
81
+
82
+
83
+ class Plan:
84
+ """A plan with multiple tasks."""
85
+
86
+ def __init__(self, goal: str, plan_id: str = None):
87
+ self.id = plan_id or str(uuid.uuid4())[:8]
88
+ self.goal = goal
89
+ self.tasks: Dict[str, Task] = {}
90
+ self.task_order: List[str] = []
91
+ self.status = "planning"
92
+ self.created_at = datetime.now().isoformat()
93
+ self.updated_at = self.created_at
94
+ self.metadata: Dict[str, Any] = {}
95
+
96
+ def add_task(self, task: Task) -> str:
97
+ """Add a task to the plan."""
98
+ self.tasks[task.id] = task
99
+ self.task_order.append(task.id)
100
+ self.updated_at = datetime.now().isoformat()
101
+ return task.id
102
+
103
+ def get_next_tasks(self) -> List[Task]:
104
+ """Get tasks that are ready to execute (dependencies met)."""
105
+ ready = []
106
+ for task_id in self.task_order:
107
+ task = self.tasks[task_id]
108
+ if task.status != TaskStatus.PENDING:
109
+ continue
110
+
111
+ # Check dependencies
112
+ deps_met = True
113
+ for dep_id in task.depends_on:
114
+ if dep_id in self.tasks:
115
+ dep_task = self.tasks[dep_id]
116
+ if dep_task.status != TaskStatus.COMPLETED:
117
+ deps_met = False
118
+ break
119
+
120
+ if deps_met:
121
+ ready.append(task)
122
+
123
+ return ready
124
+
125
+ def get_progress(self) -> Dict:
126
+ """Get plan progress."""
127
+ total = len(self.tasks)
128
+ completed = sum(1 for t in self.tasks.values() if t.status == TaskStatus.COMPLETED)
129
+ failed = sum(1 for t in self.tasks.values() if t.status == TaskStatus.FAILED)
130
+ in_progress = sum(1 for t in self.tasks.values() if t.status == TaskStatus.IN_PROGRESS)
131
+
132
+ return {
133
+ "total": total,
134
+ "completed": completed,
135
+ "failed": failed,
136
+ "in_progress": in_progress,
137
+ "pending": total - completed - failed - in_progress,
138
+ "percent": round(completed / total * 100) if total > 0 else 0
139
+ }
140
+
141
+ def is_complete(self) -> bool:
142
+ """Check if plan is complete."""
143
+ return all(
144
+ t.status in [TaskStatus.COMPLETED, TaskStatus.SKIPPED]
145
+ for t in self.tasks.values()
146
+ )
147
+
148
+ def is_blocked(self) -> bool:
149
+ """Check if plan is blocked (failed dependency)."""
150
+ for task in self.tasks.values():
151
+ if task.status == TaskStatus.PENDING:
152
+ for dep_id in task.depends_on:
153
+ if dep_id in self.tasks and self.tasks[dep_id].status == TaskStatus.FAILED:
154
+ return True
155
+ return False
156
+
157
+ def to_dict(self) -> Dict:
158
+ return {
159
+ "id": self.id,
160
+ "goal": self.goal,
161
+ "status": self.status,
162
+ "tasks": {tid: t.to_dict() for tid, t in self.tasks.items()},
163
+ "task_order": self.task_order,
164
+ "created_at": self.created_at,
165
+ "updated_at": self.updated_at,
166
+ "metadata": self.metadata,
167
+ "progress": self.get_progress()
168
+ }
169
+
170
+ @classmethod
171
+ def from_dict(cls, data: Dict) -> "Plan":
172
+ plan = cls(goal=data["goal"], plan_id=data["id"])
173
+ plan.status = data.get("status", "planning")
174
+ plan.task_order = data.get("task_order", [])
175
+ plan.created_at = data.get("created_at")
176
+ plan.updated_at = data.get("updated_at")
177
+ plan.metadata = data.get("metadata", {})
178
+
179
+ for tid, tdata in data.get("tasks", {}).items():
180
+ plan.tasks[tid] = Task.from_dict(tdata)
181
+
182
+ return plan
183
+
184
+ def save(self):
185
+ """Save plan to disk."""
186
+ path = PLANS_DIR / f"{self.id}.json"
187
+ path.write_text(json.dumps(self.to_dict(), indent=2))
188
+
189
+ @classmethod
190
+ def load(cls, plan_id: str) -> Optional["Plan"]:
191
+ """Load plan from disk."""
192
+ path = PLANS_DIR / f"{plan_id}.json"
193
+ if path.exists():
194
+ return cls.from_dict(json.loads(path.read_text()))
195
+ return None
196
+
197
+
198
+ class Planner:
199
+ """The planning engine."""
200
+
201
+ def __init__(self):
202
+ self.active_plan: Optional[Plan] = None
203
+
204
+ def create_plan(self, goal: str, tasks: List[Dict] = None) -> Plan:
205
+ """Create a new plan with optional initial tasks."""
206
+ plan = Plan(goal=goal)
207
+
208
+ if tasks:
209
+ for t in tasks:
210
+ task = Task(
211
+ title=t["title"],
212
+ description=t.get("description", ""),
213
+ depends_on=t.get("depends_on", [])
214
+ )
215
+ plan.add_task(task)
216
+
217
+ plan.status = "ready"
218
+ plan.save()
219
+ self.active_plan = plan
220
+ return plan
221
+
222
+ def decompose_goal(self, goal: str) -> List[Dict]:
223
+ """Use AI to decompose a goal into tasks.
224
+ This would call Claude to generate the task breakdown."""
225
+ # For now, return a simple template
226
+ # In production, this would call the Claude API
227
+ return [
228
+ {"title": f"Analyze: {goal}", "description": "Understand the requirements"},
229
+ {"title": "Research", "description": "Gather necessary information"},
230
+ {"title": "Plan approach", "description": "Design the solution"},
231
+ {"title": "Execute", "description": "Implement the solution"},
232
+ {"title": "Verify", "description": "Check the results", "depends_on": []},
233
+ ]
234
+
235
+ def start_task(self, plan_id: str, task_id: str) -> str:
236
+ """Mark a task as started."""
237
+ plan = Plan.load(plan_id)
238
+ if not plan or task_id not in plan.tasks:
239
+ return "Task not found"
240
+
241
+ task = plan.tasks[task_id]
242
+ task.status = TaskStatus.IN_PROGRESS
243
+ task.started_at = datetime.now().isoformat()
244
+ plan.updated_at = datetime.now().isoformat()
245
+ plan.save()
246
+ return f"Started: {task.title}"
247
+
248
+ def complete_task(self, plan_id: str, task_id: str, result: str = None) -> str:
249
+ """Mark a task as completed."""
250
+ plan = Plan.load(plan_id)
251
+ if not plan or task_id not in plan.tasks:
252
+ return "Task not found"
253
+
254
+ task = plan.tasks[task_id]
255
+ task.status = TaskStatus.COMPLETED
256
+ task.result = result
257
+ task.completed_at = datetime.now().isoformat()
258
+ plan.updated_at = datetime.now().isoformat()
259
+
260
+ # Check if plan is complete
261
+ if plan.is_complete():
262
+ plan.status = "completed"
263
+
264
+ plan.save()
265
+ return f"Completed: {task.title}"
266
+
267
+ def fail_task(self, plan_id: str, task_id: str, error: str = None) -> str:
268
+ """Mark a task as failed."""
269
+ plan = Plan.load(plan_id)
270
+ if not plan or task_id not in plan.tasks:
271
+ return "Task not found"
272
+
273
+ task = plan.tasks[task_id]
274
+ task.status = TaskStatus.FAILED
275
+ task.error = error
276
+ task.completed_at = datetime.now().isoformat()
277
+ plan.updated_at = datetime.now().isoformat()
278
+
279
+ if plan.is_blocked():
280
+ plan.status = "blocked"
281
+
282
+ plan.save()
283
+ return f"Failed: {task.title}"
284
+
285
+ def retry_task(self, plan_id: str, task_id: str) -> str:
286
+ """Retry a failed task."""
287
+ plan = Plan.load(plan_id)
288
+ if not plan or task_id not in plan.tasks:
289
+ return "Task not found"
290
+
291
+ task = plan.tasks[task_id]
292
+ task.status = TaskStatus.PENDING
293
+ task.error = None
294
+ task.retries += 1
295
+ plan.status = "ready"
296
+ plan.save()
297
+ return f"Retrying: {task.title} (attempt {task.retries + 1})"
298
+
299
+ def get_plan(self, plan_id: str) -> Optional[Dict]:
300
+ """Get plan details."""
301
+ plan = Plan.load(plan_id)
302
+ return plan.to_dict() if plan else None
303
+
304
+ def list_plans(self) -> List[Dict]:
305
+ """List all plans."""
306
+ plans = []
307
+ for path in PLANS_DIR.glob("*.json"):
308
+ try:
309
+ plan = Plan.load(path.stem)
310
+ if plan:
311
+ plans.append({
312
+ "id": plan.id,
313
+ "goal": plan.goal[:50] + "..." if len(plan.goal) > 50 else plan.goal,
314
+ "status": plan.status,
315
+ "progress": plan.get_progress()
316
+ })
317
+ except:
318
+ continue
319
+ return plans
320
+
321
+ def delete_plan(self, plan_id: str) -> str:
322
+ """Delete a plan."""
323
+ path = PLANS_DIR / f"{plan_id}.json"
324
+ if path.exists():
325
+ path.unlink()
326
+ return f"Deleted plan {plan_id}"
327
+ return "Plan not found"
328
+
329
+
330
+ # Global planner instance
331
+ planner = Planner()
332
+
333
+
334
+ # CLI functions for TUI integration
335
+ def plan_create(goal: str, tasks: List[Dict] = None) -> Dict:
336
+ """Create a new plan."""
337
+ if not tasks:
338
+ # Auto-decompose goal into tasks
339
+ tasks = planner.decompose_goal(goal)
340
+
341
+ plan = planner.create_plan(goal, tasks)
342
+ return plan.to_dict()
343
+
344
+
345
+ def plan_add_task(plan_id: str, title: str, description: str = "", depends_on: List[str] = None) -> str:
346
+ """Add a task to a plan."""
347
+ plan = Plan.load(plan_id)
348
+ if not plan:
349
+ return "Plan not found"
350
+
351
+ task = Task(title=title, description=description, depends_on=depends_on or [])
352
+ plan.add_task(task)
353
+ plan.save()
354
+ return f"Added task {task.id}: {title}"
355
+
356
+
357
+ def plan_status(plan_id: str) -> Dict:
358
+ """Get plan status."""
359
+ return planner.get_plan(plan_id) or {"error": "Plan not found"}
360
+
361
+
362
+ def plan_next(plan_id: str) -> List[Dict]:
363
+ """Get next tasks ready to execute."""
364
+ plan = Plan.load(plan_id)
365
+ if not plan:
366
+ return []
367
+
368
+ tasks = plan.get_next_tasks()
369
+ return [t.to_dict() for t in tasks]
370
+
371
+
372
+ def plan_update(plan_id: str, task_id: str, status: str, result: str = None) -> str:
373
+ """Update a task status."""
374
+ if status == "completed":
375
+ return planner.complete_task(plan_id, task_id, result)
376
+ elif status == "failed":
377
+ return planner.fail_task(plan_id, task_id, result)
378
+ elif status == "in_progress":
379
+ return planner.start_task(plan_id, task_id)
380
+ elif status == "retry":
381
+ return planner.retry_task(plan_id, task_id)
382
+ return f"Unknown status: {status}"
383
+
384
+
385
+ def plan_list() -> List[Dict]:
386
+ """List all plans."""
387
+ return planner.list_plans()
388
+
389
+
390
+ def plan_delete(plan_id: str) -> str:
391
+ """Delete a plan."""
392
+ return planner.delete_plan(plan_id)
393
+
394
+
395
+ if __name__ == "__main__":
396
+ import sys
397
+
398
+ if len(sys.argv) < 2:
399
+ print("Usage: planner.py <create|status|list|next> [args]")
400
+ sys.exit(1)
401
+
402
+ cmd = sys.argv[1]
403
+
404
+ if cmd == "create":
405
+ goal = " ".join(sys.argv[2:]) or "Test goal"
406
+ result = plan_create(goal)
407
+ print(json.dumps(result, indent=2))
408
+
409
+ elif cmd == "status":
410
+ plan_id = sys.argv[2] if len(sys.argv) > 2 else ""
411
+ print(json.dumps(plan_status(plan_id), indent=2))
412
+
413
+ elif cmd == "list":
414
+ print(json.dumps(plan_list(), indent=2))
415
+
416
+ elif cmd == "next":
417
+ plan_id = sys.argv[2] if len(sys.argv) > 2 else ""
418
+ print(json.dumps(plan_next(plan_id), indent=2))
419
+
420
+ else:
421
+ print(f"Unknown command: {cmd}")