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.
- calc_terminal/__init__.py +14 -0
- calc_terminal/__main__.py +14 -0
- calc_terminal/activity.py +1334 -0
- calc_terminal/agent.py +3387 -0
- calc_terminal/agent_runtime.py +519 -0
- calc_terminal/ai_context.py +447 -0
- calc_terminal/ai_modes.py +752 -0
- calc_terminal/ai_personalization.py +286 -0
- calc_terminal/ai_preview_feedback.py +213 -0
- calc_terminal/aicore.py +2572 -0
- calc_terminal/anim.py +367 -0
- calc_terminal/app.py +3685 -0
- calc_terminal/art.py +639 -0
- calc_terminal/atomsim.py +368 -0
- calc_terminal/attachments.py +743 -0
- calc_terminal/benchmark_system.py +414 -0
- calc_terminal/browser/__init__.py +36 -0
- calc_terminal/browser/browser_state.py +346 -0
- calc_terminal/browser/devserver.py +176 -0
- calc_terminal/browser/engine.py +494 -0
- calc_terminal/browser/navigation.py +84 -0
- calc_terminal/browser/preview.py +429 -0
- calc_terminal/browser/preview_entry.py +95 -0
- calc_terminal/browser/project_detector.py +144 -0
- calc_terminal/browser/server.py +449 -0
- calc_terminal/browser/state.py +75 -0
- calc_terminal/browser/watcher.py +99 -0
- calc_terminal/browser_gui/__init__.py +1 -0
- calc_terminal/browser_gui/__main__.py +3 -0
- calc_terminal/browser_gui/launcher.py +173 -0
- calc_terminal/browser_gui/playwright_browser.py +117 -0
- calc_terminal/browser_gui/qt_browser.py +1501 -0
- calc_terminal/browser_gui/webview_browser.py +57 -0
- calc_terminal/capabilities/__init__.py +35 -0
- calc_terminal/capabilities/adapters/__init__.py +33 -0
- calc_terminal/capabilities/adapters/bioinformatics.py +204 -0
- calc_terminal/capabilities/adapters/browser_adapter.py +205 -0
- calc_terminal/capabilities/adapters/filesystem.py +206 -0
- calc_terminal/capabilities/adapters/git_adapter.py +202 -0
- calc_terminal/capabilities/adapters/jupyter_adapter.py +138 -0
- calc_terminal/capabilities/adapters/ml_frameworks.py +158 -0
- calc_terminal/capabilities/adapters/platforms.py +200 -0
- calc_terminal/capabilities/adapters/python_exec.py +93 -0
- calc_terminal/capabilities/adapters/quantum_adapter.py +150 -0
- calc_terminal/capabilities/adapters/scientific_comp.py +123 -0
- calc_terminal/capabilities/adapters/structural_bio.py +161 -0
- calc_terminal/capabilities/adapters/terminal.py +99 -0
- calc_terminal/capabilities/bus.py +178 -0
- calc_terminal/capabilities/discovery.py +207 -0
- calc_terminal/capabilities/schema.py +221 -0
- calc_terminal/cat.ico +0 -0
- calc_terminal/cat_browser.py +2018 -0
- calc_terminal/chat_store.py +703 -0
- calc_terminal/cli.py +1178 -0
- calc_terminal/code_editor.py +640 -0
- calc_terminal/collaboration.py +723 -0
- calc_terminal/commands_data.py +139 -0
- calc_terminal/compatibility_engine.py +352 -0
- calc_terminal/compute/__init__.py +31 -0
- calc_terminal/compute/fabric.py +350 -0
- calc_terminal/config.py +227 -0
- calc_terminal/core/__init__.py +41 -0
- calc_terminal/core/checkpoint.py +156 -0
- calc_terminal/core/input/__init__.py +45 -0
- calc_terminal/core/mode_registry.py +300 -0
- calc_terminal/core/project_graph.py +172 -0
- calc_terminal/core/recovery.py +129 -0
- calc_terminal/core/security_layer.py +112 -0
- calc_terminal/core/task_graph.py +202 -0
- calc_terminal/core/unified_runtime.py +184 -0
- calc_terminal/core/verification.py +257 -0
- calc_terminal/customization.py +1566 -0
- calc_terminal/derivations.py +153 -0
- calc_terminal/device_control.py +263 -0
- calc_terminal/diagnostics/__init__.py +27 -0
- calc_terminal/diagnostics/doctor_engine.py +382 -0
- calc_terminal/diagnostics/self_test.py +247 -0
- calc_terminal/doctor.py +519 -0
- calc_terminal/easter_eggs.py +274 -0
- calc_terminal/editor/__init__.py +1 -0
- calc_terminal/editor/actions.py +263 -0
- calc_terminal/editor/commands.py +160 -0
- calc_terminal/editor/shortcuts.py +226 -0
- calc_terminal/engine.py +259 -0
- calc_terminal/errors.py +120 -0
- calc_terminal/event_stream.py +146 -0
- calc_terminal/eventbus.py +133 -0
- calc_terminal/extensions.py +733 -0
- calc_terminal/fallback_cli.py +1321 -0
- calc_terminal/first_run.py +265 -0
- calc_terminal/fomoji_auth.py +1043 -0
- calc_terminal/formulas.py +82 -0
- calc_terminal/fs_cache.py +121 -0
- calc_terminal/fs_watcher.py +277 -0
- calc_terminal/game.py +193 -0
- calc_terminal/gen1.py +5 -0
- calc_terminal/generators.py +245 -0
- calc_terminal/gestures/__init__.py +42 -0
- calc_terminal/gestures/bindings.py +175 -0
- calc_terminal/gestures/manager.py +477 -0
- calc_terminal/goodbye.py +363 -0
- calc_terminal/gpu3d.py +290 -0
- calc_terminal/graphs.py +358 -0
- calc_terminal/hardware_analyzer.py +440 -0
- calc_terminal/host/__init__.py +30 -0
- calc_terminal/host/browser_manager.py +187 -0
- calc_terminal/host/desktop.py +1386 -0
- calc_terminal/host/launcher.py +395 -0
- calc_terminal/host/terminal.py +279 -0
- calc_terminal/identity.py +216 -0
- calc_terminal/input/__init__.py +54 -0
- calc_terminal/input/capabilities.py +258 -0
- calc_terminal/input/focus.py +87 -0
- calc_terminal/input/gestures.py +64 -0
- calc_terminal/input/pointer.py +114 -0
- calc_terminal/input/touch.py +345 -0
- calc_terminal/keys.py +84 -0
- calc_terminal/live_automation.py +165 -0
- calc_terminal/mathtext.py +433 -0
- calc_terminal/mcp.py +386 -0
- calc_terminal/memory.py +337 -0
- calc_terminal/memory_v2.py +479 -0
- calc_terminal/metrics.py +333 -0
- calc_terminal/mode_detection.py +146 -0
- calc_terminal/model.py +2431 -0
- calc_terminal/model_router.py +665 -0
- calc_terminal/models/__init__.py +0 -0
- calc_terminal/models/active_state.py +187 -0
- calc_terminal/models/dynamic_registry.py +584 -0
- calc_terminal/models/manager.py +781 -0
- calc_terminal/models/model_metadata.json +3526 -0
- calc_terminal/models/profiles.py +194 -0
- calc_terminal/models/registry.py +265 -0
- calc_terminal/models/schema.py +197 -0
- calc_terminal/models/validator.py +287 -0
- calc_terminal/models/verification_engine.py +368 -0
- calc_terminal/native_picker.py +215 -0
- calc_terminal/ollama_catalog.py +279 -0
- calc_terminal/ollama_download.py +233 -0
- calc_terminal/orchestrator.py +304 -0
- calc_terminal/package_research.py +322 -0
- calc_terminal/packages.py +1024 -0
- calc_terminal/pc_specs.py +116 -0
- calc_terminal/permissions.py +334 -0
- calc_terminal/pet.py +106 -0
- calc_terminal/pipeline.py +505 -0
- calc_terminal/platform/__init__.py +491 -0
- calc_terminal/platform/desktop.py +491 -0
- calc_terminal/platform/web.py +781 -0
- calc_terminal/preview/__init__.py +1 -0
- calc_terminal/preview/dev_server.py +303 -0
- calc_terminal/preview/diagnostics.py +131 -0
- calc_terminal/preview/live_reload.py +66 -0
- calc_terminal/preview/manager.py +129 -0
- calc_terminal/project_stats.py +209 -0
- calc_terminal/projects.py +328 -0
- calc_terminal/providers/__init__.py +0 -0
- calc_terminal/providers/adapters/__init__.py +80 -0
- calc_terminal/providers/adapters/anthropic_adapter.py +127 -0
- calc_terminal/providers/adapters/base.py +106 -0
- calc_terminal/providers/adapters/chinese_adapters.py +420 -0
- calc_terminal/providers/adapters/gemini_adapter.py +101 -0
- calc_terminal/providers/adapters/ollama_adapter.py +83 -0
- calc_terminal/providers/adapters/openai_adapter.py +159 -0
- calc_terminal/providers/adapters/other_adapters.py +246 -0
- calc_terminal/providers/anthropic_provider.py +172 -0
- calc_terminal/providers/auto_update.py +416 -0
- calc_terminal/providers/base_provider.py +105 -0
- calc_terminal/providers/discovery_manager.py +207 -0
- calc_terminal/providers/gemini_provider.py +178 -0
- calc_terminal/providers/lifecycle.py +767 -0
- calc_terminal/providers/ollama_adapter.py +707 -0
- calc_terminal/providers/openai_provider.py +254 -0
- calc_terminal/providers/provider_manager.py +1827 -0
- calc_terminal/providers/providers.json +4075 -0
- calc_terminal/reactionsim.py +279 -0
- calc_terminal/registry.py +337 -0
- calc_terminal/report.py +162 -0
- calc_terminal/research/__init__.py +45 -0
- calc_terminal/research/artifact_intel.py +126 -0
- calc_terminal/research/data_lineage.py +123 -0
- calc_terminal/research/experiment_ledger.py +303 -0
- calc_terminal/research/reproducibility.py +131 -0
- calc_terminal/resilience/__init__.py +47 -0
- calc_terminal/resilience/agent_state.py +121 -0
- calc_terminal/resilience/capability_matcher.py +174 -0
- calc_terminal/resilience/circuit_breaker.py +158 -0
- calc_terminal/resilience/failover_engine.py +230 -0
- calc_terminal/resilience/health_monitor.py +192 -0
- calc_terminal/resilience/ollama_adapter.py +125 -0
- calc_terminal/resilience/orchestrator.py +312 -0
- calc_terminal/resilience/types.py +134 -0
- calc_terminal/sandbox.py +98 -0
- calc_terminal/scires.py +558 -0
- calc_terminal/security_scanner.py +126 -0
- calc_terminal/session.py +294 -0
- calc_terminal/sim3d.py +206 -0
- calc_terminal/solver.py +276 -0
- calc_terminal/sound.py +127 -0
- calc_terminal/task_reports.py +287 -0
- calc_terminal/terminal_host.py +201 -0
- calc_terminal/terminal_identity.py +411 -0
- calc_terminal/test_ai_mode_reliability.py +344 -0
- calc_terminal/test_browser.py +368 -0
- calc_terminal/test_code_editor_upgrade.py +485 -0
- calc_terminal/test_customization.py +1148 -0
- calc_terminal/test_customization_ui.py +612 -0
- calc_terminal/test_dynamic_registry.py +304 -0
- calc_terminal/test_extensions.py +436 -0
- calc_terminal/test_overhaul.py +557 -0
- calc_terminal/test_project_detect.py +255 -0
- calc_terminal/test_root_cause_fix.py +527 -0
- calc_terminal/test_stability.py +532 -0
- calc_terminal/test_terminal_identity.py +132 -0
- calc_terminal/test_v079_speed.py +460 -0
- calc_terminal/theme.py +1107 -0
- calc_terminal/timeline.py +139 -0
- calc_terminal/todos.py +246 -0
- calc_terminal/tool_call_normalizer.py +419 -0
- calc_terminal/tui.py +104 -0
- calc_terminal/ui/__init__.py +8 -0
- calc_terminal/ui/activity_panel.py +231 -0
- calc_terminal/ui/activity_stream_panel.py +238 -0
- calc_terminal/ui/animations.py +122 -0
- calc_terminal/ui/app.py +7271 -0
- calc_terminal/ui/attach_panel.py +597 -0
- calc_terminal/ui/attachments.py +424 -0
- calc_terminal/ui/backup_panel.py +810 -0
- calc_terminal/ui/browser_shell.py +887 -0
- calc_terminal/ui/cat_agent.py +357 -0
- calc_terminal/ui/chats_panel.py +899 -0
- calc_terminal/ui/command_palette.py +125 -0
- calc_terminal/ui/command_palette_modal.py +166 -0
- calc_terminal/ui/composer.py +1141 -0
- calc_terminal/ui/context_menu.py +197 -0
- calc_terminal/ui/conversation.py +1435 -0
- calc_terminal/ui/customization_panel.py +1229 -0
- calc_terminal/ui/dashboard.py +404 -0
- calc_terminal/ui/design_system.py +557 -0
- calc_terminal/ui/diff_panel.py +213 -0
- calc_terminal/ui/editor.py +2102 -0
- calc_terminal/ui/empty_state.py +302 -0
- calc_terminal/ui/events.py +487 -0
- calc_terminal/ui/extensions_panel.py +815 -0
- calc_terminal/ui/footer.py +166 -0
- calc_terminal/ui/gestures_panel.py +383 -0
- calc_terminal/ui/goodbye_screen.py +100 -0
- calc_terminal/ui/header.py +1034 -0
- calc_terminal/ui/help_panel.py +254 -0
- calc_terminal/ui/live_activities.py +914 -0
- calc_terminal/ui/mcp_panel.py +570 -0
- calc_terminal/ui/memory_center.py +524 -0
- calc_terminal/ui/mode_colors_panel.py +525 -0
- calc_terminal/ui/nav_screens.py +747 -0
- calc_terminal/ui/ollama_panel.py +536 -0
- calc_terminal/ui/palette.py +221 -0
- calc_terminal/ui/permission_panel.py +269 -0
- calc_terminal/ui/personalization_panel.py +517 -0
- calc_terminal/ui/personalize_center.py +1568 -0
- calc_terminal/ui/preview_panel.py +441 -0
- calc_terminal/ui/resizers.py +402 -0
- calc_terminal/ui/sidebar.py +1285 -0
- calc_terminal/ui/statusbar.py +168 -0
- calc_terminal/ui/theme_css.py +1396 -0
- calc_terminal/ui/thinking.py +226 -0
- calc_terminal/ui/timeline_panel.py +102 -0
- calc_terminal/ui/todo_panel.py +193 -0
- calc_terminal/ui/viewport.py +136 -0
- calc_terminal/ui/vision_panel.py +489 -0
- calc_terminal/ui/welcome_modal.py +343 -0
- calc_terminal/ui/widgets.py +160 -0
- calc_terminal/ui/workspace.py +831 -0
- calc_terminal/viewers/__init__.py +1 -0
- calc_terminal/viewers/document_viewer.py +252 -0
- calc_terminal/viewers/image_viewer.py +241 -0
- calc_terminal/viewers/pdf_viewer.py +203 -0
- calc_terminal/viewers/presentation_viewer.py +164 -0
- calc_terminal/viewers/registry.py +120 -0
- calc_terminal/viewers/spreadsheet_viewer.py +204 -0
- calc_terminal/vision/__init__.py +89 -0
- calc_terminal/vision/analysis.py +194 -0
- calc_terminal/vision/annotations.py +297 -0
- calc_terminal/vision/capture.py +171 -0
- calc_terminal/vision/context.py +231 -0
- calc_terminal/vision/correlation.py +169 -0
- calc_terminal/vision/cursor.py +258 -0
- calc_terminal/vision/events.py +66 -0
- calc_terminal/vision/frame_pipeline.py +259 -0
- calc_terminal/vision/priority.py +218 -0
- calc_terminal/vision/provider.py +180 -0
- calc_terminal/vision/safety.py +149 -0
- calc_terminal/vision/session.py +281 -0
- calc_terminal/vision/verify.py +162 -0
- calc_terminal/vision.py +514 -0
- calc_terminal/vscode_integration.py +113 -0
- calc_terminal/web/__init__.py +8 -0
- calc_terminal/web/cat_runtime.py +710 -0
- calc_terminal/web/server.py +2891 -0
- calc_terminal/web/static/css/app.css +3152 -0
- calc_terminal/web/static/icons/badge-72.png +0 -0
- calc_terminal/web/static/icons/cat.ico +0 -0
- calc_terminal/web/static/icons/icon-128.png +0 -0
- calc_terminal/web/static/icons/icon-144.png +0 -0
- calc_terminal/web/static/icons/icon-152.png +0 -0
- calc_terminal/web/static/icons/icon-192.png +0 -0
- calc_terminal/web/static/icons/icon-384.png +0 -0
- calc_terminal/web/static/icons/icon-512.png +0 -0
- calc_terminal/web/static/icons/icon-72.png +0 -0
- calc_terminal/web/static/icons/icon-96.png +0 -0
- calc_terminal/web/static/icons/icon.svg +34 -0
- calc_terminal/web/static/icons/new-project.png +0 -0
- calc_terminal/web/static/icons/open-project.png +0 -0
- calc_terminal/web/static/index.html +734 -0
- calc_terminal/web/static/js/app.js +2403 -0
- calc_terminal/web/static/manifest.json +88 -0
- calc_terminal/web/static/sw.js +230 -0
- calc_terminal/workflow_engine.py +769 -0
- calc_terminal/workspace.py +593 -0
- calc_terminal/workspace_index.py +385 -0
- cct_cli-0.7.9.0.dist-info/METADATA +210 -0
- cct_cli-0.7.9.0.dist-info/RECORD +325 -0
- cct_cli-0.7.9.0.dist-info/WHEEL +5 -0
- cct_cli-0.7.9.0.dist-info/entry_points.txt +4 -0
- cct_cli-0.7.9.0.dist-info/licenses/LICENSE +21 -0
- cct_cli-0.7.9.0.dist-info/top_level.txt +1 -0
calc_terminal/errors.py
ADDED
|
@@ -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()
|