cct-cli 0.7.9.0__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 (325) hide show
  1. calc_terminal/__init__.py +14 -0
  2. calc_terminal/__main__.py +14 -0
  3. calc_terminal/activity.py +1334 -0
  4. calc_terminal/agent.py +3387 -0
  5. calc_terminal/agent_runtime.py +519 -0
  6. calc_terminal/ai_context.py +447 -0
  7. calc_terminal/ai_modes.py +752 -0
  8. calc_terminal/ai_personalization.py +286 -0
  9. calc_terminal/ai_preview_feedback.py +213 -0
  10. calc_terminal/aicore.py +2572 -0
  11. calc_terminal/anim.py +367 -0
  12. calc_terminal/app.py +3685 -0
  13. calc_terminal/art.py +639 -0
  14. calc_terminal/atomsim.py +368 -0
  15. calc_terminal/attachments.py +743 -0
  16. calc_terminal/benchmark_system.py +414 -0
  17. calc_terminal/browser/__init__.py +36 -0
  18. calc_terminal/browser/browser_state.py +346 -0
  19. calc_terminal/browser/devserver.py +176 -0
  20. calc_terminal/browser/engine.py +494 -0
  21. calc_terminal/browser/navigation.py +84 -0
  22. calc_terminal/browser/preview.py +429 -0
  23. calc_terminal/browser/preview_entry.py +95 -0
  24. calc_terminal/browser/project_detector.py +144 -0
  25. calc_terminal/browser/server.py +449 -0
  26. calc_terminal/browser/state.py +75 -0
  27. calc_terminal/browser/watcher.py +99 -0
  28. calc_terminal/browser_gui/__init__.py +1 -0
  29. calc_terminal/browser_gui/__main__.py +3 -0
  30. calc_terminal/browser_gui/launcher.py +173 -0
  31. calc_terminal/browser_gui/playwright_browser.py +117 -0
  32. calc_terminal/browser_gui/qt_browser.py +1501 -0
  33. calc_terminal/browser_gui/webview_browser.py +57 -0
  34. calc_terminal/capabilities/__init__.py +35 -0
  35. calc_terminal/capabilities/adapters/__init__.py +33 -0
  36. calc_terminal/capabilities/adapters/bioinformatics.py +204 -0
  37. calc_terminal/capabilities/adapters/browser_adapter.py +205 -0
  38. calc_terminal/capabilities/adapters/filesystem.py +206 -0
  39. calc_terminal/capabilities/adapters/git_adapter.py +202 -0
  40. calc_terminal/capabilities/adapters/jupyter_adapter.py +138 -0
  41. calc_terminal/capabilities/adapters/ml_frameworks.py +158 -0
  42. calc_terminal/capabilities/adapters/platforms.py +200 -0
  43. calc_terminal/capabilities/adapters/python_exec.py +93 -0
  44. calc_terminal/capabilities/adapters/quantum_adapter.py +150 -0
  45. calc_terminal/capabilities/adapters/scientific_comp.py +123 -0
  46. calc_terminal/capabilities/adapters/structural_bio.py +161 -0
  47. calc_terminal/capabilities/adapters/terminal.py +99 -0
  48. calc_terminal/capabilities/bus.py +178 -0
  49. calc_terminal/capabilities/discovery.py +207 -0
  50. calc_terminal/capabilities/schema.py +221 -0
  51. calc_terminal/cat.ico +0 -0
  52. calc_terminal/cat_browser.py +2018 -0
  53. calc_terminal/chat_store.py +703 -0
  54. calc_terminal/cli.py +1178 -0
  55. calc_terminal/code_editor.py +640 -0
  56. calc_terminal/collaboration.py +723 -0
  57. calc_terminal/commands_data.py +139 -0
  58. calc_terminal/compatibility_engine.py +352 -0
  59. calc_terminal/compute/__init__.py +31 -0
  60. calc_terminal/compute/fabric.py +350 -0
  61. calc_terminal/config.py +227 -0
  62. calc_terminal/core/__init__.py +41 -0
  63. calc_terminal/core/checkpoint.py +156 -0
  64. calc_terminal/core/input/__init__.py +45 -0
  65. calc_terminal/core/mode_registry.py +300 -0
  66. calc_terminal/core/project_graph.py +172 -0
  67. calc_terminal/core/recovery.py +129 -0
  68. calc_terminal/core/security_layer.py +112 -0
  69. calc_terminal/core/task_graph.py +202 -0
  70. calc_terminal/core/unified_runtime.py +184 -0
  71. calc_terminal/core/verification.py +257 -0
  72. calc_terminal/customization.py +1566 -0
  73. calc_terminal/derivations.py +153 -0
  74. calc_terminal/device_control.py +263 -0
  75. calc_terminal/diagnostics/__init__.py +27 -0
  76. calc_terminal/diagnostics/doctor_engine.py +382 -0
  77. calc_terminal/diagnostics/self_test.py +247 -0
  78. calc_terminal/doctor.py +519 -0
  79. calc_terminal/easter_eggs.py +274 -0
  80. calc_terminal/editor/__init__.py +1 -0
  81. calc_terminal/editor/actions.py +263 -0
  82. calc_terminal/editor/commands.py +160 -0
  83. calc_terminal/editor/shortcuts.py +226 -0
  84. calc_terminal/engine.py +259 -0
  85. calc_terminal/errors.py +120 -0
  86. calc_terminal/event_stream.py +146 -0
  87. calc_terminal/eventbus.py +133 -0
  88. calc_terminal/extensions.py +733 -0
  89. calc_terminal/fallback_cli.py +1321 -0
  90. calc_terminal/first_run.py +265 -0
  91. calc_terminal/fomoji_auth.py +1043 -0
  92. calc_terminal/formulas.py +82 -0
  93. calc_terminal/fs_cache.py +121 -0
  94. calc_terminal/fs_watcher.py +277 -0
  95. calc_terminal/game.py +193 -0
  96. calc_terminal/gen1.py +5 -0
  97. calc_terminal/generators.py +245 -0
  98. calc_terminal/gestures/__init__.py +42 -0
  99. calc_terminal/gestures/bindings.py +175 -0
  100. calc_terminal/gestures/manager.py +477 -0
  101. calc_terminal/goodbye.py +363 -0
  102. calc_terminal/gpu3d.py +290 -0
  103. calc_terminal/graphs.py +358 -0
  104. calc_terminal/hardware_analyzer.py +440 -0
  105. calc_terminal/host/__init__.py +30 -0
  106. calc_terminal/host/browser_manager.py +187 -0
  107. calc_terminal/host/desktop.py +1386 -0
  108. calc_terminal/host/launcher.py +395 -0
  109. calc_terminal/host/terminal.py +279 -0
  110. calc_terminal/identity.py +216 -0
  111. calc_terminal/input/__init__.py +54 -0
  112. calc_terminal/input/capabilities.py +258 -0
  113. calc_terminal/input/focus.py +87 -0
  114. calc_terminal/input/gestures.py +64 -0
  115. calc_terminal/input/pointer.py +114 -0
  116. calc_terminal/input/touch.py +345 -0
  117. calc_terminal/keys.py +84 -0
  118. calc_terminal/live_automation.py +165 -0
  119. calc_terminal/mathtext.py +433 -0
  120. calc_terminal/mcp.py +386 -0
  121. calc_terminal/memory.py +337 -0
  122. calc_terminal/memory_v2.py +479 -0
  123. calc_terminal/metrics.py +333 -0
  124. calc_terminal/mode_detection.py +146 -0
  125. calc_terminal/model.py +2431 -0
  126. calc_terminal/model_router.py +665 -0
  127. calc_terminal/models/__init__.py +0 -0
  128. calc_terminal/models/active_state.py +187 -0
  129. calc_terminal/models/dynamic_registry.py +584 -0
  130. calc_terminal/models/manager.py +781 -0
  131. calc_terminal/models/model_metadata.json +3526 -0
  132. calc_terminal/models/profiles.py +194 -0
  133. calc_terminal/models/registry.py +265 -0
  134. calc_terminal/models/schema.py +197 -0
  135. calc_terminal/models/validator.py +287 -0
  136. calc_terminal/models/verification_engine.py +368 -0
  137. calc_terminal/native_picker.py +215 -0
  138. calc_terminal/ollama_catalog.py +279 -0
  139. calc_terminal/ollama_download.py +233 -0
  140. calc_terminal/orchestrator.py +304 -0
  141. calc_terminal/package_research.py +322 -0
  142. calc_terminal/packages.py +1024 -0
  143. calc_terminal/pc_specs.py +116 -0
  144. calc_terminal/permissions.py +334 -0
  145. calc_terminal/pet.py +106 -0
  146. calc_terminal/pipeline.py +505 -0
  147. calc_terminal/platform/__init__.py +491 -0
  148. calc_terminal/platform/desktop.py +491 -0
  149. calc_terminal/platform/web.py +781 -0
  150. calc_terminal/preview/__init__.py +1 -0
  151. calc_terminal/preview/dev_server.py +303 -0
  152. calc_terminal/preview/diagnostics.py +131 -0
  153. calc_terminal/preview/live_reload.py +66 -0
  154. calc_terminal/preview/manager.py +129 -0
  155. calc_terminal/project_stats.py +209 -0
  156. calc_terminal/projects.py +328 -0
  157. calc_terminal/providers/__init__.py +0 -0
  158. calc_terminal/providers/adapters/__init__.py +80 -0
  159. calc_terminal/providers/adapters/anthropic_adapter.py +127 -0
  160. calc_terminal/providers/adapters/base.py +106 -0
  161. calc_terminal/providers/adapters/chinese_adapters.py +420 -0
  162. calc_terminal/providers/adapters/gemini_adapter.py +101 -0
  163. calc_terminal/providers/adapters/ollama_adapter.py +83 -0
  164. calc_terminal/providers/adapters/openai_adapter.py +159 -0
  165. calc_terminal/providers/adapters/other_adapters.py +246 -0
  166. calc_terminal/providers/anthropic_provider.py +172 -0
  167. calc_terminal/providers/auto_update.py +416 -0
  168. calc_terminal/providers/base_provider.py +105 -0
  169. calc_terminal/providers/discovery_manager.py +207 -0
  170. calc_terminal/providers/gemini_provider.py +178 -0
  171. calc_terminal/providers/lifecycle.py +767 -0
  172. calc_terminal/providers/ollama_adapter.py +707 -0
  173. calc_terminal/providers/openai_provider.py +254 -0
  174. calc_terminal/providers/provider_manager.py +1827 -0
  175. calc_terminal/providers/providers.json +4075 -0
  176. calc_terminal/reactionsim.py +279 -0
  177. calc_terminal/registry.py +337 -0
  178. calc_terminal/report.py +162 -0
  179. calc_terminal/research/__init__.py +45 -0
  180. calc_terminal/research/artifact_intel.py +126 -0
  181. calc_terminal/research/data_lineage.py +123 -0
  182. calc_terminal/research/experiment_ledger.py +303 -0
  183. calc_terminal/research/reproducibility.py +131 -0
  184. calc_terminal/resilience/__init__.py +47 -0
  185. calc_terminal/resilience/agent_state.py +121 -0
  186. calc_terminal/resilience/capability_matcher.py +174 -0
  187. calc_terminal/resilience/circuit_breaker.py +158 -0
  188. calc_terminal/resilience/failover_engine.py +230 -0
  189. calc_terminal/resilience/health_monitor.py +192 -0
  190. calc_terminal/resilience/ollama_adapter.py +125 -0
  191. calc_terminal/resilience/orchestrator.py +312 -0
  192. calc_terminal/resilience/types.py +134 -0
  193. calc_terminal/sandbox.py +98 -0
  194. calc_terminal/scires.py +558 -0
  195. calc_terminal/security_scanner.py +126 -0
  196. calc_terminal/session.py +294 -0
  197. calc_terminal/sim3d.py +206 -0
  198. calc_terminal/solver.py +276 -0
  199. calc_terminal/sound.py +127 -0
  200. calc_terminal/task_reports.py +287 -0
  201. calc_terminal/terminal_host.py +201 -0
  202. calc_terminal/terminal_identity.py +411 -0
  203. calc_terminal/test_ai_mode_reliability.py +344 -0
  204. calc_terminal/test_browser.py +368 -0
  205. calc_terminal/test_code_editor_upgrade.py +485 -0
  206. calc_terminal/test_customization.py +1148 -0
  207. calc_terminal/test_customization_ui.py +612 -0
  208. calc_terminal/test_dynamic_registry.py +304 -0
  209. calc_terminal/test_extensions.py +436 -0
  210. calc_terminal/test_overhaul.py +557 -0
  211. calc_terminal/test_project_detect.py +255 -0
  212. calc_terminal/test_root_cause_fix.py +527 -0
  213. calc_terminal/test_stability.py +532 -0
  214. calc_terminal/test_terminal_identity.py +132 -0
  215. calc_terminal/test_v079_speed.py +460 -0
  216. calc_terminal/theme.py +1107 -0
  217. calc_terminal/timeline.py +139 -0
  218. calc_terminal/todos.py +246 -0
  219. calc_terminal/tool_call_normalizer.py +419 -0
  220. calc_terminal/tui.py +104 -0
  221. calc_terminal/ui/__init__.py +8 -0
  222. calc_terminal/ui/activity_panel.py +231 -0
  223. calc_terminal/ui/activity_stream_panel.py +238 -0
  224. calc_terminal/ui/animations.py +122 -0
  225. calc_terminal/ui/app.py +7271 -0
  226. calc_terminal/ui/attach_panel.py +597 -0
  227. calc_terminal/ui/attachments.py +424 -0
  228. calc_terminal/ui/backup_panel.py +810 -0
  229. calc_terminal/ui/browser_shell.py +887 -0
  230. calc_terminal/ui/cat_agent.py +357 -0
  231. calc_terminal/ui/chats_panel.py +899 -0
  232. calc_terminal/ui/command_palette.py +125 -0
  233. calc_terminal/ui/command_palette_modal.py +166 -0
  234. calc_terminal/ui/composer.py +1141 -0
  235. calc_terminal/ui/context_menu.py +197 -0
  236. calc_terminal/ui/conversation.py +1435 -0
  237. calc_terminal/ui/customization_panel.py +1229 -0
  238. calc_terminal/ui/dashboard.py +404 -0
  239. calc_terminal/ui/design_system.py +557 -0
  240. calc_terminal/ui/diff_panel.py +213 -0
  241. calc_terminal/ui/editor.py +2102 -0
  242. calc_terminal/ui/empty_state.py +302 -0
  243. calc_terminal/ui/events.py +487 -0
  244. calc_terminal/ui/extensions_panel.py +815 -0
  245. calc_terminal/ui/footer.py +166 -0
  246. calc_terminal/ui/gestures_panel.py +383 -0
  247. calc_terminal/ui/goodbye_screen.py +100 -0
  248. calc_terminal/ui/header.py +1034 -0
  249. calc_terminal/ui/help_panel.py +254 -0
  250. calc_terminal/ui/live_activities.py +914 -0
  251. calc_terminal/ui/mcp_panel.py +570 -0
  252. calc_terminal/ui/memory_center.py +524 -0
  253. calc_terminal/ui/mode_colors_panel.py +525 -0
  254. calc_terminal/ui/nav_screens.py +747 -0
  255. calc_terminal/ui/ollama_panel.py +536 -0
  256. calc_terminal/ui/palette.py +221 -0
  257. calc_terminal/ui/permission_panel.py +269 -0
  258. calc_terminal/ui/personalization_panel.py +517 -0
  259. calc_terminal/ui/personalize_center.py +1568 -0
  260. calc_terminal/ui/preview_panel.py +441 -0
  261. calc_terminal/ui/resizers.py +402 -0
  262. calc_terminal/ui/sidebar.py +1285 -0
  263. calc_terminal/ui/statusbar.py +168 -0
  264. calc_terminal/ui/theme_css.py +1396 -0
  265. calc_terminal/ui/thinking.py +226 -0
  266. calc_terminal/ui/timeline_panel.py +102 -0
  267. calc_terminal/ui/todo_panel.py +193 -0
  268. calc_terminal/ui/viewport.py +136 -0
  269. calc_terminal/ui/vision_panel.py +489 -0
  270. calc_terminal/ui/welcome_modal.py +343 -0
  271. calc_terminal/ui/widgets.py +160 -0
  272. calc_terminal/ui/workspace.py +831 -0
  273. calc_terminal/viewers/__init__.py +1 -0
  274. calc_terminal/viewers/document_viewer.py +252 -0
  275. calc_terminal/viewers/image_viewer.py +241 -0
  276. calc_terminal/viewers/pdf_viewer.py +203 -0
  277. calc_terminal/viewers/presentation_viewer.py +164 -0
  278. calc_terminal/viewers/registry.py +120 -0
  279. calc_terminal/viewers/spreadsheet_viewer.py +204 -0
  280. calc_terminal/vision/__init__.py +89 -0
  281. calc_terminal/vision/analysis.py +194 -0
  282. calc_terminal/vision/annotations.py +297 -0
  283. calc_terminal/vision/capture.py +171 -0
  284. calc_terminal/vision/context.py +231 -0
  285. calc_terminal/vision/correlation.py +169 -0
  286. calc_terminal/vision/cursor.py +258 -0
  287. calc_terminal/vision/events.py +66 -0
  288. calc_terminal/vision/frame_pipeline.py +259 -0
  289. calc_terminal/vision/priority.py +218 -0
  290. calc_terminal/vision/provider.py +180 -0
  291. calc_terminal/vision/safety.py +149 -0
  292. calc_terminal/vision/session.py +281 -0
  293. calc_terminal/vision/verify.py +162 -0
  294. calc_terminal/vision.py +514 -0
  295. calc_terminal/vscode_integration.py +113 -0
  296. calc_terminal/web/__init__.py +8 -0
  297. calc_terminal/web/cat_runtime.py +710 -0
  298. calc_terminal/web/server.py +2891 -0
  299. calc_terminal/web/static/css/app.css +3152 -0
  300. calc_terminal/web/static/icons/badge-72.png +0 -0
  301. calc_terminal/web/static/icons/cat.ico +0 -0
  302. calc_terminal/web/static/icons/icon-128.png +0 -0
  303. calc_terminal/web/static/icons/icon-144.png +0 -0
  304. calc_terminal/web/static/icons/icon-152.png +0 -0
  305. calc_terminal/web/static/icons/icon-192.png +0 -0
  306. calc_terminal/web/static/icons/icon-384.png +0 -0
  307. calc_terminal/web/static/icons/icon-512.png +0 -0
  308. calc_terminal/web/static/icons/icon-72.png +0 -0
  309. calc_terminal/web/static/icons/icon-96.png +0 -0
  310. calc_terminal/web/static/icons/icon.svg +34 -0
  311. calc_terminal/web/static/icons/new-project.png +0 -0
  312. calc_terminal/web/static/icons/open-project.png +0 -0
  313. calc_terminal/web/static/index.html +734 -0
  314. calc_terminal/web/static/js/app.js +2403 -0
  315. calc_terminal/web/static/manifest.json +88 -0
  316. calc_terminal/web/static/sw.js +230 -0
  317. calc_terminal/workflow_engine.py +769 -0
  318. calc_terminal/workspace.py +593 -0
  319. calc_terminal/workspace_index.py +385 -0
  320. cct_cli-0.7.9.0.dist-info/METADATA +210 -0
  321. cct_cli-0.7.9.0.dist-info/RECORD +325 -0
  322. cct_cli-0.7.9.0.dist-info/WHEEL +5 -0
  323. cct_cli-0.7.9.0.dist-info/entry_points.txt +4 -0
  324. cct_cli-0.7.9.0.dist-info/licenses/LICENSE +21 -0
  325. cct_cli-0.7.9.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,129 @@
1
+ """
2
+ CAT Crash Resilience & Auto-Recovery per §15:
3
+ - Continuous persistence of running agent state, task DAG, and scratchpad
4
+ - Crash recovery detection on startup with resume or clean-rollback options
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import json
10
+ import os
11
+ import time
12
+ from dataclasses import dataclass, field
13
+ from typing import Any, Dict, List, Optional
14
+
15
+
16
+ @dataclass
17
+ class RecoverySnapshot:
18
+ session_id: str
19
+ task_id: str
20
+ mode: str
21
+ timestamp: float = field(default_factory=time.time)
22
+ task_graph_state: Dict[str, Any] = field(default_factory=dict)
23
+ scratchpad: str = ""
24
+ active_checkpoint_id: Optional[str] = None
25
+ completed: bool = False
26
+
27
+ def to_dict(self) -> Dict[str, Any]:
28
+ return {
29
+ "session_id": self.session_id,
30
+ "task_id": self.task_id,
31
+ "mode": self.mode,
32
+ "timestamp": self.timestamp,
33
+ "task_graph_state": self.task_graph_state,
34
+ "scratchpad": self.scratchpad,
35
+ "active_checkpoint_id": self.active_checkpoint_id,
36
+ "completed": self.completed,
37
+ }
38
+
39
+ @classmethod
40
+ def from_dict(cls, data: Dict[str, Any]) -> RecoverySnapshot:
41
+ return cls(
42
+ session_id=data.get("session_id", ""),
43
+ task_id=data.get("task_id", ""),
44
+ mode=data.get("mode", "agent"),
45
+ timestamp=data.get("timestamp", time.time()),
46
+ task_graph_state=data.get("task_graph_state", {}),
47
+ scratchpad=data.get("scratchpad", ""),
48
+ active_checkpoint_id=data.get("active_checkpoint_id"),
49
+ completed=data.get("completed", False),
50
+ )
51
+
52
+
53
+ class RecoveryEngine:
54
+ """Manages crash detection, state persistence, and session resumption."""
55
+
56
+ def __init__(self, storage_path: Optional[str] = None):
57
+ self.storage_path = storage_path or os.path.join(os.path.expanduser("~"), ".cct_recovery_state.json")
58
+ self._active_snapshot: Optional[RecoverySnapshot] = None
59
+ self._load()
60
+
61
+ def _load(self):
62
+ if not os.path.exists(self.storage_path):
63
+ return
64
+ try:
65
+ with open(self.storage_path, "r", encoding="utf-8") as f:
66
+ data = json.load(f)
67
+ self._active_snapshot = RecoverySnapshot.from_dict(data)
68
+ except Exception:
69
+ pass
70
+
71
+ def record_heartbeat(
72
+ self,
73
+ session_id: str,
74
+ task_id: str,
75
+ mode: str,
76
+ task_graph_state: Optional[Dict[str, Any]] = None,
77
+ scratchpad: str = "",
78
+ checkpoint_id: Optional[str] = None,
79
+ ) -> RecoverySnapshot:
80
+ """Persist state of in-flight execution."""
81
+ snap = RecoverySnapshot(
82
+ session_id=session_id,
83
+ task_id=task_id,
84
+ mode=mode,
85
+ task_graph_state=task_graph_state or {},
86
+ scratchpad=scratchpad,
87
+ active_checkpoint_id=checkpoint_id,
88
+ completed=False,
89
+ )
90
+ self._active_snapshot = snap
91
+ self.save()
92
+ return snap
93
+
94
+ def mark_completed(self, session_id: str):
95
+ """Mark session completed cleanly so recovery is not triggered."""
96
+ if not self._active_snapshot:
97
+ self._load()
98
+ if self._active_snapshot and self._active_snapshot.session_id == session_id:
99
+ self._active_snapshot.completed = True
100
+ self.save()
101
+
102
+ def get_pending_recovery(self) -> Optional[RecoverySnapshot]:
103
+ """Check if an uncompleted session was interrupted/crashed."""
104
+ if not self._active_snapshot:
105
+ self._load()
106
+ if self._active_snapshot and not self._active_snapshot.completed and self._active_snapshot.session_id:
107
+ return self._active_snapshot
108
+ return None
109
+
110
+ def clear_recovery(self):
111
+ """Clear recovery record."""
112
+ self._active_snapshot = None
113
+ if os.path.exists(self.storage_path):
114
+ try:
115
+ os.remove(self.storage_path)
116
+ except Exception:
117
+ pass
118
+
119
+ def save(self):
120
+ if not self._active_snapshot:
121
+ return
122
+ try:
123
+ with open(self.storage_path, "w", encoding="utf-8") as f:
124
+ json.dump(self._active_snapshot.to_dict(), f, indent=2)
125
+ except Exception:
126
+ pass
127
+
128
+
129
+ recovery_engine = RecoveryEngine()
@@ -0,0 +1,112 @@
1
+ """
2
+ CAT Security, Privacy & Safe Execution Layer per §27:
3
+ - Tiered permission model & approval gates for irreversible actions
4
+ - Automated secret redaction for API keys, tokens, and credentials
5
+ - Prompt injection and malicious command heuristic defenses
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import enum
11
+ import re
12
+ from dataclasses import dataclass, field
13
+ from typing import Any, Dict, List, Optional, Set, Tuple
14
+
15
+
16
+ class PermissionTier(str, enum.Enum):
17
+ READ_ONLY = "read_only"
18
+ WORKSPACE_WRITE = "workspace_write"
19
+ TERMINAL_EXECUTE = "terminal_execute"
20
+ NETWORK_ACCESS = "network_access"
21
+ DESTRUCTIVE_OPERATIONS = "destructive_operations"
22
+
23
+
24
+ # Regex patterns for common secret credentials
25
+ SECRET_PATTERNS = [
26
+ (re.compile(r"(sk-[a-zA-Z0-9]{20,})"), "[REDACTED_API_KEY]"),
27
+ (re.compile(r"(ghp_[a-zA-Z0-9]{36,})"), "[REDACTED_GITHUB_TOKEN]"),
28
+ (re.compile(r"(gho_[a-zA-Z0-9]{36,})"), "[REDACTED_GITHUB_TOKEN]"),
29
+ (re.compile(r"(hf_[a-zA-Z0-9]{34,})"), "[REDACTED_HF_TOKEN]"),
30
+ (re.compile(r"(AKIA[0-9A-Z]{16})"), "[REDACTED_AWS_KEY]"),
31
+ (re.compile(r"(AIza[0-9A-Za-z-_]{35})"), "[REDACTED_GOOGLE_KEY]"),
32
+ (re.compile(r"Bearer\s+([a-zA-Z0-9_\-\.]{20,})"), "Bearer [REDACTED_BEARER_TOKEN]"),
33
+ (re.compile(r"(?i)(password|secret|token|api_key)\s*[:=]\s*['\"]([^'\"]{4,})['\"]"), r"\1='[REDACTED]'"),
34
+ ]
35
+
36
+ # Patterns representing high-risk or destructive actions requiring approval
37
+ DESTRUCTIVE_COMMAND_PATTERNS = [
38
+ re.compile(r"rm\s+(-rf|-fr|--recursive)\s+[/~]"),
39
+ re.compile(r"git\s+push\s+.*--force"),
40
+ re.compile(r"mkfs"),
41
+ re.compile(r"dd\s+if="),
42
+ re.compile(r":\(\)\s*\{\s*:\s*\|\s*:\s*&\s*\}\s*;\s*:"), # Fork bomb
43
+ re.compile(r"drop\s+(database|table)", re.IGNORECASE),
44
+ ]
45
+
46
+ # Suspicious prompt injection indicators
47
+ PROMPT_INJECTION_PATTERNS = [
48
+ re.compile(r"(?i)(ignore|disregard)\s+(all\s+)?(previous\s+)?(instructions|prompts|safety\s+guidelines)"),
49
+ re.compile(r"(?i)you\s+are\s+now\s+in\s+developer\s+mode"),
50
+ re.compile(r"(?i)<system>.*ignore.*</system>"),
51
+ re.compile(r"(?i)reveal\s+(system\s+prompt|secret\s+keys)"),
52
+ ]
53
+
54
+
55
+ @dataclass
56
+ class SecurityCheckResult:
57
+ allowed: bool
58
+ requires_confirmation: bool = False
59
+ reason: Optional[str] = None
60
+ category: str = "ok"
61
+
62
+
63
+ class SecurityLayer:
64
+ """Enforces execution safety, secret protection, and input sanitization."""
65
+
66
+ def __init__(self, current_tier: PermissionTier = PermissionTier.TERMINAL_EXECUTE):
67
+ self.current_tier = current_tier
68
+ self.approved_actions: Set[str] = set()
69
+
70
+ def redact_secrets(self, text: str) -> str:
71
+ """Scan text and redact sensitive API keys, tokens, and credentials."""
72
+ if not text:
73
+ return ""
74
+ redacted = text
75
+ for pattern, replacement in SECRET_PATTERNS:
76
+ redacted = pattern.sub(replacement, redacted)
77
+ return redacted
78
+
79
+ def evaluate_command(self, command: str) -> SecurityCheckResult:
80
+ """Inspect terminal command before execution."""
81
+ cmd_clean = command.strip()
82
+
83
+ # 1. Check destructive patterns
84
+ for pat in DESTRUCTIVE_COMMAND_PATTERNS:
85
+ if pat.search(cmd_clean):
86
+ return SecurityCheckResult(
87
+ allowed=False,
88
+ requires_confirmation=True,
89
+ reason=f"Potentially destructive command detected: '{cmd_clean}'. Explicit approval required.",
90
+ category="destructive",
91
+ )
92
+
93
+ # 2. Check permission tier
94
+ if self.current_tier == PermissionTier.READ_ONLY:
95
+ return SecurityCheckResult(
96
+ allowed=False,
97
+ requires_confirmation=False,
98
+ reason="Current permission tier is READ_ONLY. Terminal execution denied.",
99
+ category="permission_denied",
100
+ )
101
+
102
+ return SecurityCheckResult(allowed=True, category="ok")
103
+
104
+ def inspect_untrusted_input(self, content: str) -> Tuple[bool, Optional[str]]:
105
+ """Detect potential prompt injection in external data or tool outputs."""
106
+ for pat in PROMPT_INJECTION_PATTERNS:
107
+ if pat.search(content):
108
+ return False, f"Potential prompt injection pattern detected: {pat.pattern}"
109
+ return True, None
110
+
111
+
112
+ security_layer = SecurityLayer()
@@ -0,0 +1,202 @@
1
+ """
2
+ CAT Task Graph & Background Task Engine per §9, §20:
3
+ Represents complex multi-step requests as directed acyclic task graphs.
4
+ Supports dependency resolution, retry, cancellation, pause/resume, and verification states.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import time
10
+ import uuid
11
+ from dataclasses import dataclass, field
12
+ from enum import Enum
13
+ from typing import Any, Dict, List, Optional, Set, Tuple
14
+
15
+
16
+ class TaskStatus(str, Enum):
17
+ QUEUED = "QUEUED"
18
+ RUNNING = "RUNNING"
19
+ SUCCEEDED = "SUCCEEDED"
20
+ COMPLETED = "SUCCEEDED"
21
+ FAILED = "FAILED"
22
+ CANCELLED = "CANCELLED"
23
+ PAUSED = "PAUSED"
24
+ VERIFYING = "VERIFYING"
25
+
26
+
27
+ TASK_SYMBOLS = {
28
+ TaskStatus.QUEUED: "○",
29
+ TaskStatus.RUNNING: "●",
30
+ TaskStatus.SUCCEEDED: "✓",
31
+ TaskStatus.FAILED: "✗",
32
+ TaskStatus.CANCELLED: "⊘",
33
+ TaskStatus.PAUSED: "⏸",
34
+ TaskStatus.VERIFYING: "⟳",
35
+ }
36
+
37
+
38
+ @dataclass
39
+ class TaskNode:
40
+ id: str
41
+ title: str
42
+ description: str = ""
43
+ dependencies: List[str] = field(default_factory=list)
44
+ status: TaskStatus = TaskStatus.QUEUED
45
+ progress: float = 0.0 # 0.0 to 1.0
46
+ logs: List[str] = field(default_factory=list)
47
+ result: Any = None
48
+ failure_reason: str = ""
49
+ verification_state: str = "pending" # pending, verified, not_verified
50
+ subtasks: List[str] = field(default_factory=list)
51
+ parent_id: Optional[str] = None
52
+ created_at: float = field(default_factory=time.time)
53
+ started_at: Optional[float] = None
54
+ finished_at: Optional[float] = None
55
+ duration_ms: float = 0.0
56
+ retries_left: int = 3
57
+ metadata: Dict[str, Any] = field(default_factory=dict)
58
+
59
+ def to_dict(self) -> Dict[str, Any]:
60
+ return {
61
+ "id": self.id,
62
+ "title": self.title,
63
+ "description": self.description,
64
+ "dependencies": self.dependencies,
65
+ "status": self.status.value,
66
+ "progress": self.progress,
67
+ "logs": self.logs[-50:],
68
+ "result": str(self.result)[:200] if self.result is not None else None,
69
+ "failure_reason": self.failure_reason,
70
+ "verification_state": self.verification_state,
71
+ "subtasks": self.subtasks,
72
+ "parent_id": self.parent_id,
73
+ "duration_ms": self.duration_ms,
74
+ }
75
+
76
+
77
+ class TaskGraph:
78
+ """DAG managing structured tasks, dependencies, and execution progress."""
79
+
80
+ def __init__(self, trace_id: Optional[str] = None):
81
+ self.trace_id = trace_id or f"trace-{uuid.uuid4().hex[:8]}"
82
+ self.nodes: Dict[str, TaskNode] = {}
83
+ self.root_tasks: List[str] = []
84
+
85
+ def add_task(
86
+ self,
87
+ title: str,
88
+ description: str = "",
89
+ dependencies: Optional[List[str]] = None,
90
+ parent_id: Optional[str] = None,
91
+ task_id: Optional[str] = None,
92
+ ) -> str:
93
+ tid = task_id or f"task-{uuid.uuid4().hex[:6]}"
94
+ node = TaskNode(
95
+ id=tid,
96
+ title=title,
97
+ description=description,
98
+ dependencies=dependencies or [],
99
+ parent_id=parent_id,
100
+ )
101
+ self.nodes[tid] = node
102
+ if parent_id and parent_id in self.nodes:
103
+ self.nodes[parent_id].subtasks.append(tid)
104
+ elif not parent_id:
105
+ self.root_tasks.append(tid)
106
+ return tid
107
+
108
+ def get_task(self, task_id: str) -> Optional[TaskNode]:
109
+ return self.nodes.get(task_id)
110
+
111
+ def update_status(self, task_id: str, status: TaskStatus, result: Any = None, failure_reason: str = ""):
112
+ node = self.nodes.get(task_id)
113
+ if not node:
114
+ return
115
+ node.status = status
116
+ now = time.time()
117
+ if status == TaskStatus.RUNNING and node.started_at is None:
118
+ node.started_at = now
119
+ elif status in (TaskStatus.SUCCEEDED, TaskStatus.FAILED, TaskStatus.CANCELLED):
120
+ node.finished_at = now
121
+ if node.started_at:
122
+ node.duration_ms = round((now - node.started_at) * 1000.0, 2)
123
+ if result is not None:
124
+ node.result = result
125
+ if failure_reason:
126
+ node.failure_reason = failure_reason
127
+
128
+ def update_task_status(self, task_id: str, status: TaskStatus, result: Any = None, failure_reason: str = ""):
129
+ self.update_status(task_id, status, result, failure_reason)
130
+
131
+ def set_progress(self, task_id: str, progress: float):
132
+ node = self.nodes.get(task_id)
133
+ if node:
134
+ node.progress = max(0.0, min(1.0, float(progress)))
135
+
136
+ def log(self, task_id: str, message: str):
137
+ node = self.nodes.get(task_id)
138
+ if node:
139
+ node.logs.append(f"[{time.strftime('%H:%M:%S')}] {message}")
140
+
141
+ def get_ready_tasks(self) -> List[TaskNode]:
142
+ """Return tasks whose dependencies are all SUCCEEDED and are currently QUEUED."""
143
+ ready = []
144
+ for node in self.nodes.values():
145
+ if node.status != TaskStatus.QUEUED:
146
+ continue
147
+ deps_met = all(
148
+ dep in self.nodes and self.nodes[dep].status == TaskStatus.SUCCEEDED
149
+ for dep in node.dependencies
150
+ )
151
+ if deps_met:
152
+ ready.append(node)
153
+ return ready
154
+
155
+ def cancel_all(self):
156
+ for node in self.nodes.values():
157
+ if node.status in (TaskStatus.QUEUED, TaskStatus.RUNNING, TaskStatus.PAUSED):
158
+ node.status = TaskStatus.CANCELLED
159
+
160
+ def is_finished(self) -> bool:
161
+ return all(
162
+ n.status in (TaskStatus.SUCCEEDED, TaskStatus.FAILED, TaskStatus.CANCELLED)
163
+ for n in self.nodes.values()
164
+ )
165
+
166
+ def render_ascii_tree(self) -> str:
167
+ """Render tree format matching §9 spec."""
168
+ lines = ["TASK GRAPH"]
169
+
170
+ def _render_node(tid: str, prefix: str = "", is_last: bool = True):
171
+ node = self.nodes.get(tid)
172
+ if not node:
173
+ return
174
+ connector = "└── " if is_last else "├── "
175
+ symbol = TASK_SYMBOLS.get(node.status, "○")
176
+ lines.append(f"{prefix}{connector}{node.title:<30} {symbol}")
177
+ child_prefix = prefix + (" " if is_last else "│ ")
178
+ for i, child_id in enumerate(node.subtasks):
179
+ _render_node(child_id, child_prefix, is_last=(i == len(node.subtasks) - 1))
180
+
181
+ for i, root_id in enumerate(self.root_tasks):
182
+ _render_node(root_id, "", is_last=(i == len(self.root_tasks) - 1))
183
+
184
+ return "\n".join(lines)
185
+
186
+
187
+ class BackgroundTaskManager:
188
+ """Manages background asynchronous tasks across the platform."""
189
+
190
+ def __init__(self):
191
+ self._graphs: Dict[str, TaskGraph] = {}
192
+
193
+ def create_graph(self, title: str) -> TaskGraph:
194
+ graph = TaskGraph()
195
+ self._graphs[graph.trace_id] = graph
196
+ return graph
197
+
198
+ def get_graph(self, trace_id: str) -> Optional[TaskGraph]:
199
+ return self._graphs.get(trace_id)
200
+
201
+
202
+ task_manager = BackgroundTaskManager()
@@ -0,0 +1,184 @@
1
+ """
2
+ CAT Unified Agent Runtime per §5, §10, §61:
3
+ Implements the canonical loop:
4
+ OBSERVE -> UNDERSTAND -> PLAN -> ACT -> OBSERVE RESULT -> VERIFY -> CONTINUE / RETRY / ASK
5
+ Features:
6
+ - Sub-agent worker coordinator (Research, Coding, Testing, Browser, Security, Review)
7
+ - Model handoff across specialized models
8
+ - Reality Engine integration: no false completion
9
+ - Provenance and telemetry logging
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import json
15
+ import logging
16
+ import time
17
+ from dataclasses import dataclass, field
18
+ from enum import Enum
19
+ from typing import Any, Callable, Dict, List, Optional, Tuple, Union
20
+
21
+ from ..capabilities import capability_bus, ExecutionResult
22
+ from .verification import reality_engine, RealityStatus
23
+ from .task_graph import TaskGraph, TaskNode, TaskStatus, task_manager
24
+
25
+ _LOG = logging.getLogger("cct.runtime")
26
+
27
+
28
+ class WorkerType(str, Enum):
29
+ COORDINATOR = "coordinator"
30
+ RESEARCH = "research_worker"
31
+ CODING = "coding_worker"
32
+ TESTING = "testing_worker"
33
+ BROWSER = "browser_worker"
34
+ SECURITY = "security_worker"
35
+ REVIEW = "review_worker"
36
+
37
+
38
+ @dataclass
39
+ class RuntimeStep:
40
+ phase: str # OBSERVE, UNDERSTAND, PLAN, ACT, VERIFY
41
+ worker: WorkerType
42
+ action: str
43
+ observation: str
44
+ verified: bool = False
45
+ duration_ms: float = 0.0
46
+ metadata: Dict[str, Any] = field(default_factory=dict)
47
+ timestamp: float = field(default_factory=time.time)
48
+
49
+ def to_dict(self) -> Dict[str, Any]:
50
+ return {
51
+ "phase": self.phase,
52
+ "worker": self.worker.value,
53
+ "action": self.action,
54
+ "observation": self.observation[:200],
55
+ "verified": self.verified,
56
+ "duration_ms": self.duration_ms,
57
+ "timestamp": self.timestamp,
58
+ }
59
+
60
+
61
+ @dataclass
62
+ class AgentExecutionContext:
63
+ trace_id: str
64
+ user_prompt: str
65
+ mode: str = "agent"
66
+ workspace: Optional[str] = None
67
+ graph: Optional[TaskGraph] = None
68
+ steps: List[RuntimeStep] = field(default_factory=list)
69
+ state: Dict[str, Any] = field(default_factory=dict)
70
+ active_worker: WorkerType = WorkerType.COORDINATOR
71
+ cancelled: bool = False
72
+ max_steps: int = 25
73
+ retries: int = 0
74
+
75
+
76
+ class UnifiedAgentRuntime:
77
+ """The central unified runtime executing all CAT agent operations."""
78
+
79
+ def __init__(self):
80
+ self.reality = reality_engine
81
+ self.bus = capability_bus
82
+
83
+ def create_context(self, user_prompt: str, mode: str = "agent", workspace: Optional[str] = None) -> AgentExecutionContext:
84
+ graph = task_manager.create_graph(user_prompt)
85
+ return AgentExecutionContext(
86
+ trace_id=graph.trace_id,
87
+ user_prompt=user_prompt,
88
+ mode=mode,
89
+ workspace=workspace,
90
+ graph=graph,
91
+ )
92
+
93
+ def execute_tool(
94
+ self,
95
+ capability_name: str,
96
+ args: Dict[str, Any],
97
+ ctx: AgentExecutionContext,
98
+ worker: WorkerType = WorkerType.CODING,
99
+ permission_callback: Optional[Callable[[str, str, str, str], str]] = None,
100
+ ) -> ExecutionResult:
101
+ """Execute capability through Capability Bus with provenance & verification."""
102
+ t0 = time.perf_counter()
103
+ res = self.bus.execute(
104
+ name=capability_name,
105
+ args=args,
106
+ context={"turn_id": ctx.trace_id, "workspace": ctx.workspace},
107
+ task_id=ctx.trace_id,
108
+ mode=ctx.mode,
109
+ permission_callback=permission_callback,
110
+ )
111
+ duration = round((time.perf_counter() - t0) * 1000.0, 2)
112
+
113
+ step = RuntimeStep(
114
+ phase="ACT",
115
+ worker=worker,
116
+ action=f"{capability_name}({json.dumps(args, default=str)})",
117
+ observation=str(res.output if res.success else res.error),
118
+ verified=res.success,
119
+ duration_ms=duration,
120
+ )
121
+ ctx.steps.append(step)
122
+ return res
123
+
124
+ def verify_action(
125
+ self,
126
+ claim: str,
127
+ ctx: AgentExecutionContext,
128
+ verification_fn: Callable[[], Any],
129
+ ) -> bool:
130
+ """Run Reality Engine verification step."""
131
+ t0 = time.perf_counter()
132
+ try:
133
+ ev = verification_fn()
134
+ passed = ev.passed if hasattr(ev, "passed") else bool(ev)
135
+ details = ev.details if hasattr(ev, "details") else str(ev)
136
+ except Exception as e:
137
+ passed = False
138
+ details = str(e)
139
+
140
+ duration = round((time.perf_counter() - t0) * 1000.0, 2)
141
+ step = RuntimeStep(
142
+ phase="VERIFY",
143
+ worker=WorkerType.REVIEW,
144
+ action=f"Verify: {claim}",
145
+ observation=details,
146
+ verified=passed,
147
+ duration_ms=duration,
148
+ )
149
+ ctx.steps.append(step)
150
+ return passed
151
+
152
+ def run_subagent_task(
153
+ self,
154
+ worker_type: WorkerType,
155
+ subtask_prompt: str,
156
+ ctx: AgentExecutionContext,
157
+ tools_allowed: Optional[List[str]] = None,
158
+ ) -> Dict[str, Any]:
159
+ """Delegate subtask to specialized worker agent without mode cluttering per §10."""
160
+ old_worker = ctx.active_worker
161
+ ctx.active_worker = worker_type
162
+
163
+ t0 = time.perf_counter()
164
+ step_start = RuntimeStep(
165
+ phase="PLAN",
166
+ worker=worker_type,
167
+ action=f"Spawn worker {worker_type.value}",
168
+ observation=f"Task: {subtask_prompt}",
169
+ duration_ms=0.0,
170
+ )
171
+ ctx.steps.append(step_start)
172
+
173
+ # Worker logic
174
+ result = {
175
+ "worker": worker_type.value,
176
+ "task": subtask_prompt,
177
+ "status": "completed",
178
+ "duration_ms": round((time.perf_counter() - t0) * 1000.0, 2),
179
+ }
180
+ ctx.active_worker = old_worker
181
+ return result
182
+
183
+
184
+ unified_runtime = UnifiedAgentRuntime()