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,120 @@
1
+ """
2
+ CCT Error Recovery (spec section 19).
3
+
4
+ Before this module, a failure inside try/except blocks scattered
5
+ across app.py just did `print(theme.red(f"Error: {e}"))` and moved
6
+ on — a technical exception string, no way to retry without re-typing
7
+ the whole command, no way to see more detail, no way to know if it's
8
+ worth trying again. That's real (see the audit in
9
+ CHANGELOG_v0.6.3_architecture.md: 7 call sites doing exactly this).
10
+
11
+ This module gives failures one consistent shape: a themed card with a
12
+ plain-language title + message, and numbered recovery actions —
13
+ Retry (re-runs the same operation), View Details (the actual
14
+ exception message, only surfaced on request, not by default), and
15
+ Dismiss. It does NOT catch anything itself — callers still do their
16
+ own try/except, same as before; this just replaces the "print a red
17
+ line and give up" ending with a real recovery loop, and importantly
18
+ never lets the underlying exception propagate up and crash the REPL.
19
+
20
+ CONTEXTS below covers the failure kinds already wired in (see
21
+ WIRED_INTO at the bottom for the honest current list). Anything not
22
+ in CONTEXTS still gets a real card — just with a generic title.
23
+ """
24
+
25
+ from . import theme
26
+
27
+ WIDTH = 78 # matches app.py's WIDTH; passed in by callers where it differs
28
+
29
+ CONTEXTS = {
30
+ "export": ("Export failed", "CAT couldn't save your export."),
31
+ "render": ("Visualization failed", "CAT couldn't generate that image."),
32
+ "solve": ("Calculation failed", "CAT couldn't solve that with the numbers given."),
33
+ "balance": ("Reaction balancing failed", "CAT couldn't balance that chemical equation."),
34
+ "import": ("Import failed", "CAT couldn't read that file."),
35
+ "ai_provider": ("AI provider unavailable", "CAT couldn't reach the configured AI provider."),
36
+ "workspace": ("Workspace error", "CAT couldn't read or write to the project workspace."),
37
+ }
38
+
39
+
40
+ def error_card(context, exc, retry=None, extra_actions=None, width=None):
41
+ """Show a friendly recovery card for `exc` and act on the user's
42
+ choice.
43
+
44
+ `retry` (zero-arg callable, optional): if given and the user picks
45
+ Retry, calls it and loops — success closes the card, another
46
+ failure reopens it with the new exception.
47
+
48
+ `extra_actions` (list of (label, callback), optional): additional
49
+ numbered options between Retry and View Details, e.g. Reconnect /
50
+ Switch Provider for an AI-unavailable card. Each callback is
51
+ zero-arg. After running one, if `retry` was also given this
52
+ automatically attempts it (reconnecting and then not retrying the
53
+ request would be a dead end); otherwise the card just closes.
54
+
55
+ Returns True if the operation eventually succeeded via retry,
56
+ False if the user dismissed without a successful retry."""
57
+ w = width or WIDTH
58
+ extra_actions = extra_actions or []
59
+ current_exc = exc
60
+ while True:
61
+ title, friendly = CONTEXTS.get(context, ("Something went wrong", "CAT hit an unexpected error."))
62
+ actions = []
63
+ if retry is not None:
64
+ actions.append(("Retry", "retry", None))
65
+ for label, cb in extra_actions:
66
+ actions.append((label, "extra", cb))
67
+ actions.append(("View Details", "details", None))
68
+ actions.append(("Dismiss", "dismiss", None))
69
+
70
+ lines = [theme.red(f"\u26a0 {title}", bold=True), "", theme.text(friendly), ""]
71
+ for i, (label, _, _) in enumerate(actions, 1):
72
+ lines.append(f"{theme.cyan(str(i) + '.')} {label}")
73
+ print()
74
+ print(theme.panel(lines, title="error", color=theme.RED, width=w))
75
+ raw_choice = input(theme.dim(" \u25b8 ")).strip().lower()
76
+
77
+ chosen = None
78
+ for i, (label, act, cb) in enumerate(actions, 1):
79
+ if raw_choice == str(i) or raw_choice == label.lower() or raw_choice == act:
80
+ chosen = (act, cb)
81
+ break
82
+
83
+ if chosen is None:
84
+ return False # unrecognized input or bare Enter dismisses
85
+ act, cb = chosen
86
+
87
+ if act == "details":
88
+ print(theme.faint(f"\n {current_exc}\n"))
89
+ continue
90
+ if act == "extra":
91
+ cb()
92
+ if retry is None:
93
+ return False
94
+ try:
95
+ retry()
96
+ return True
97
+ except Exception as e2:
98
+ current_exc = e2
99
+ continue
100
+ if act == "retry":
101
+ try:
102
+ retry()
103
+ return True
104
+ except Exception as e2:
105
+ current_exc = e2
106
+ continue
107
+ return False # "dismiss"
108
+
109
+
110
+ # What's actually wired into error_card() right now vs. still using a
111
+ # plain print(theme.red(...)) line — read this before assuming every
112
+ # failure in the app shows a recovery card.
113
+ WIRED_INTO = {
114
+ "app.py cmd_export": "reports/PDF export failures",
115
+ "app.py cmd_react/_react_balance": "equation-balancing failures",
116
+ "app.py _solve_library/_solve_custom": "not yet — still a plain red line",
117
+ "app.py cmd_orbitalgrid": "not yet — still a plain red line",
118
+ "app.py cmd_import (zip extraction)": "not yet — still a plain red line",
119
+ "agent.py AI tool calls": "not yet — still returns a plain error string",
120
+ }
@@ -0,0 +1,146 @@
1
+ import time
2
+ import threading
3
+ import logging
4
+ from dataclasses import dataclass, field
5
+ from typing import Any, Callable, Dict, List, Optional
6
+
7
+ logger = logging.getLogger(__name__)
8
+
9
+ MODEL_RESPONSE = "model_response"
10
+ TOOL_REQUEST = "tool_request"
11
+ TOOL_STARTED = "tool_started"
12
+ TOOL_RESULT = "tool_result"
13
+ FILE_CREATED = "file_created"
14
+ FILE_EDITED = "file_edited"
15
+ FILE_DELETED = "file_deleted"
16
+ FILE_MOVED = "file_moved"
17
+ COMMAND_STARTED = "command_started"
18
+ COMMAND_RESULT = "command_result"
19
+ MEMORY_READ = "memory_read"
20
+ MEMORY_CREATED = "memory_created"
21
+ MEMORY_UPDATED = "memory_updated"
22
+ MEMORY_DELETED = "memory_deleted"
23
+ AGENT_STARTED = "agent_started"
24
+ AGENT_COMPLETED = "agent_completed"
25
+ AGENT_FAILED = "agent_failed"
26
+ SESSION_CLEARED = "session_cleared"
27
+ CONTEXT_SWITCHED = "context_switched"
28
+
29
+ # ---------------------------------------------------------------------------
30
+ # v0.7.9.0 — unified real-time activity vocabulary (requirement #2/#29).
31
+ # Every displayed operation must correspond to an actual backend event;
32
+ # these are the topics the whole runtime (model router, agent loop, vision
33
+ # pipeline, multi-AI orchestrator) publishes and the UI subscribes to.
34
+ # ---------------------------------------------------------------------------
35
+ USER_MESSAGE = "user_message" # request entered the pipeline
36
+ ROUTE_DECIDED = "route_decided" # smart router picked a path
37
+ MODEL_SELECTED = "model_selected" # which model/config will answer
38
+ MODEL_REQUEST_STARTED = "model_request_started"
39
+ MODEL_FIRST_TOKEN = "model_first_token" # real first token arrived
40
+ TEXT_DELTA = "text_delta" # streamed text fragment
41
+ AGENT_THINKING = "agent_thinking" # waiting on model reasoning
42
+ TOOL_DETECTED = "tool_detected"
43
+ TOOL_PROGRESS = "tool_progress"
44
+ TOOL_FINISHED = "tool_finished"
45
+ AGENT_CONTINUING = "agent_continuing" # tool result fed back → looping
46
+ AGENT_SWITCHED = "agent_switched"
47
+ MULTI_AGENT_STARTED = "multi_agent_started"
48
+ MULTI_AGENT_PROGRESS = "multi_agent_progress"
49
+ MULTI_AGENT_FINISHED = "multi_agent_finished"
50
+ IMAGE_ANALYSIS_STARTED = "image_analysis_started"
51
+ IMAGE_ANALYSIS_FINISHED = "image_analysis_finished"
52
+ FINAL_RESPONSE = "final_response" # turn truly complete
53
+ ERROR = "error"
54
+
55
+ # Vision events (v0.8.a CAT Vision — spec 41)
56
+ VISION_SESSION_STARTED = "VISION_SESSION_STARTED"
57
+ VISION_SESSION_STOPPED = "VISION_SESSION_STOPPED"
58
+ VISION_CAPTURE_STARTED = "VISION_CAPTURE_STARTED"
59
+ VISION_CAPTURE_ENDED = "VISION_CAPTURE_ENDED"
60
+ VISION_FRAME_SELECTED = "VISION_FRAME_SELECTED"
61
+ VISION_FRAME_ANALYZED = "VISION_FRAME_ANALYZED"
62
+ VISION_CURSOR_EVENT = "VISION_CURSOR_EVENT"
63
+ VISION_ANNOTATION_CREATED = "VISION_ANNOTATION_CREATED"
64
+ VISION_ANNOTATION_UPDATED = "VISION_ANNOTATION_UPDATED"
65
+ VISION_ANNOTATION_DELETED = "VISION_ANNOTATION_DELETED"
66
+ VISION_USER_MESSAGE = "VISION_USER_MESSAGE"
67
+ VISION_AI_ANALYSIS = "VISION_AI_ANALYSIS"
68
+ VISION_FIX_PROPOSED = "VISION_FIX_PROPOSED"
69
+ VISION_FIX_APPLIED = "VISION_FIX_APPLIED"
70
+ VISION_PREVIEW_REFRESHED = "VISION_PREVIEW_REFRESHED"
71
+ VISION_VERIFICATION_COMPLETE = "VISION_VERIFICATION_COMPLETE"
72
+
73
+ # Bounded buffer: a very long session must not grow this without limit.
74
+ _MAX_BUFFER = 500
75
+
76
+
77
+ @dataclass
78
+ class Event:
79
+ event_type: str
80
+ timestamp: float
81
+ data: Dict[str, Any] = field(default_factory=dict)
82
+ source: str = ""
83
+
84
+
85
+ class EventStream:
86
+ def __init__(self):
87
+ self._buffer: List[Event] = []
88
+ self._subscribers: Dict[str, List[Callable]] = {}
89
+ self._lock = threading.Lock()
90
+
91
+ def emit(self, event_type: str, source: str = "", **data: Any) -> Event:
92
+ event = Event(
93
+ event_type=event_type,
94
+ timestamp=time.time(),
95
+ data=data,
96
+ source=source,
97
+ )
98
+ with self._lock:
99
+ self._buffer.append(event)
100
+ if len(self._buffer) > _MAX_BUFFER:
101
+ del self._buffer[:len(self._buffer) - _MAX_BUFFER]
102
+ subscribers = list(self._subscribers.get(event_type, []))
103
+
104
+ for callback in subscribers:
105
+ try:
106
+ callback(event)
107
+ except Exception as e:
108
+ logger.debug("Subscriber error for %s: %s", event_type, e)
109
+
110
+ return event
111
+
112
+ def subscribe(self, event_type: str, callback: Callable) -> Callable:
113
+ with self._lock:
114
+ self._subscribers.setdefault(event_type, []).append(callback)
115
+ return callback
116
+
117
+ def unsubscribe(self, event_type: str, callback: Callable) -> None:
118
+ with self._lock:
119
+ subscribers = self._subscribers.get(event_type, [])
120
+ if callback in subscribers:
121
+ subscribers.remove(callback)
122
+
123
+ def get_recent(self, event_type: Optional[str] = None, limit: int = 50) -> List[Event]:
124
+ with self._lock:
125
+ if event_type is None:
126
+ return list(self._buffer[-limit:])
127
+ return [e for e in self._buffer if e.event_type == event_type][-limit:]
128
+
129
+ def get_all(self, event_type: Optional[str] = None) -> List[Event]:
130
+ with self._lock:
131
+ if event_type is None:
132
+ return list(self._buffer)
133
+ return [e for e in self._buffer if e.event_type == event_type]
134
+
135
+ def clear(self) -> None:
136
+ with self._lock:
137
+ self._buffer.clear()
138
+
139
+ def subscriber_count(self, event_type: Optional[str] = None) -> int:
140
+ with self._lock:
141
+ if event_type is None:
142
+ return sum(len(subs) for subs in self._subscribers.values())
143
+ return len(self._subscribers.get(event_type, []))
144
+
145
+
146
+ stream = EventStream()
@@ -0,0 +1,133 @@
1
+ """
2
+ CCT — eventbus.py: the Workspace Event Bus the v0.7.2 roadmap doc asks
3
+ every backend module to communicate through ("Avoid placing all logic
4
+ inside one file... every module should communicate through the
5
+ Workspace Manager and Event Bus").
6
+
7
+ Deliberately separate from ui/events.py's Textual `Message` classes,
8
+ which stay exactly as they are — those are UI-thread widget-to-widget
9
+ messages, already working, and this pass isn't rewriting that
10
+ architecture. This bus is for the plain-Python backend modules
11
+ (todos.py, timeline.py, and anything future — TodoManager/
12
+ TimelineManager/DashboardManager in the roadmap's own suggested
13
+ naming) that have no reason to depend on Textual at all. CCTApp is the
14
+ one place that bridges the two: it subscribes bus topics to its own UI
15
+ updates in ui/app.py's on_mount, the same way it already turns worker-
16
+ thread results into posted ui/events.py Messages via call_from_thread.
17
+
18
+ Plain pub/sub, synchronous, in-process. No persistence, no cross-
19
+ process delivery, no priority/ordering guarantees beyond "subscribers
20
+ fire in the order they subscribed" — this is a lightweight backbone
21
+ for wiring already-in-process modules together, not a message queue.
22
+ A single module-level `bus` instance is the one every module below
23
+ imports and uses (`from .. import eventbus` / `from . import
24
+ eventbus`, then `eventbus.bus.publish(...)`), mirroring the
25
+ existing single-module-level-store convention projects.py/config.py
26
+ already use instead of a class the caller has to instantiate.
27
+ """
28
+
29
+ if __name__ == "__main__":
30
+ print("This is a library file and is not meant to be run directly.")
31
+ import sys
32
+ sys.exit(1)
33
+
34
+ import time
35
+ import traceback
36
+
37
+ # Topic names — the roadmap's own "Track events such as" list for the
38
+ # Session Timeline doubles as a reasonable topic vocabulary for the
39
+ # whole bus, since nearly everything else (Todo, Dashboard) wants to
40
+ # react to the same underlying happenings.
41
+ FILE_CREATED = "file_created"
42
+ FILE_DELETED = "file_deleted"
43
+ FILE_RENAMED = "file_renamed"
44
+ FILE_SAVED = "file_saved"
45
+ AI_RESPONSE_GENERATED = "ai_response_generated"
46
+ AI_FILE_EDIT = "ai_file_edit"
47
+ TERMINAL_COMMAND = "terminal_command"
48
+ WORKSPACE_OPENED = "workspace_opened"
49
+ WORKSPACE_CLOSED = "workspace_closed"
50
+ TODO_CREATED = "todo_created"
51
+ TODO_COMPLETED = "todo_completed"
52
+ TODO_UPDATED = "todo_updated"
53
+ TODO_DELETED = "todo_deleted"
54
+
55
+ # v0.7.7 autonomous-preview topics.
56
+ PACKAGE_INSTALLED = "package_installed"
57
+ PACKAGE_FAILED = "package_failed"
58
+ PIPELINE_STAGE = "pipeline_stage"
59
+ PIPELINE_COMPLETED = "pipeline_completed"
60
+ MODE_SWITCHED = "mode_switched"
61
+ DEVICE_ACTION = "device_action"
62
+
63
+ # v0.7.9.0 explicit agent-tool filesystem topics (requirement: CAT's own
64
+ # file tools must announce their changes immediately, so the Explorer can
65
+ # react while the OS watcher confirms the real on-disk state):
66
+ TOOL_FILE_CREATED = "tool_file_created"
67
+ TOOL_FILE_DELETED = "tool_file_deleted"
68
+ TOOL_FILE_MOVED = "tool_file_moved"
69
+ TOOL_DIRECTORY_CHANGED = "tool_directory_changed"
70
+
71
+ # Vision topics (v0.8.a — spec 41, mirrored with event_stream names)
72
+ VISION_SESSION_STARTED = "VISION_SESSION_STARTED"
73
+ VISION_SESSION_STOPPED = "VISION_SESSION_STOPPED"
74
+ VISION_CAPTURE_STARTED = "VISION_CAPTURE_STARTED"
75
+ VISION_CAPTURE_ENDED = "VISION_CAPTURE_ENDED"
76
+ VISION_FRAME_SELECTED = "VISION_FRAME_SELECTED"
77
+ VISION_FRAME_ANALYZED = "VISION_FRAME_ANALYZED"
78
+ VISION_CURSOR_EVENT = "VISION_CURSOR_EVENT"
79
+ VISION_ANNOTATION_CREATED = "VISION_ANNOTATION_CREATED"
80
+ VISION_ANNOTATION_UPDATED = "VISION_ANNOTATION_UPDATED"
81
+ VISION_ANNOTATION_DELETED = "VISION_ANNOTATION_DELETED"
82
+ VISION_USER_MESSAGE = "VISION_USER_MESSAGE"
83
+ VISION_AI_ANALYSIS = "VISION_AI_ANALYSIS"
84
+ VISION_FIX_PROPOSED = "VISION_FIX_PROPOSED"
85
+ VISION_FIX_APPLIED = "VISION_FIX_APPLIED"
86
+ VISION_PREVIEW_REFRESHED = "VISION_PREVIEW_REFRESHED"
87
+ VISION_VERIFICATION_COMPLETE = "VISION_VERIFICATION_COMPLETE"
88
+
89
+
90
+ class EventBus:
91
+ def __init__(self):
92
+ self._subs = {} # topic -> list[callable]
93
+
94
+ def subscribe(self, topic, callback):
95
+ """Registers `callback(**data)` for `topic`. Returns `callback`
96
+ unchanged so a caller can do
97
+ `self._sub = bus.subscribe(TOPIC, self._on_topic)` and later
98
+ pass that same reference to unsubscribe()."""
99
+ self._subs.setdefault(topic, []).append(callback)
100
+ return callback
101
+
102
+ def unsubscribe(self, topic, callback):
103
+ subs = self._subs.get(topic)
104
+ if not subs:
105
+ return
106
+ try:
107
+ subs.remove(callback)
108
+ except ValueError:
109
+ pass
110
+
111
+ def publish(self, topic, **data):
112
+ """Fires every subscriber for `topic` synchronously, in
113
+ subscribe order. `data.setdefault("ts", ...)` stamps every
114
+ event with a publish time so a subscriber (Timeline, mainly)
115
+ never has to remember to do it itself. A subscriber that
116
+ raises is logged (via Python's stdlib logging, not print() —
117
+ printing anything to stdout inside a running Textual app
118
+ corrupts the alt-screen, same reason ui/composer.py's own
119
+ error path uses self.log.error() instead) and does not stop
120
+ the remaining subscribers from still running — one broken
121
+ listener (e.g. a Timeline panel mid-teardown) must not be able
122
+ to silently break Todo/Dashboard updates too."""
123
+ data.setdefault("ts", time.time())
124
+ for callback in list(self._subs.get(topic, [])):
125
+ try:
126
+ callback(**data)
127
+ except Exception:
128
+ import logging
129
+ logging.getLogger("cct.eventbus").error(
130
+ "subscriber for %r raised:\n%s", topic, traceback.format_exc())
131
+
132
+
133
+ bus = EventBus()