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,294 @@
1
+ """
2
+ CCT chat session state — a plain data model for the conversation, with
3
+ no rendering and no widget dependencies. calc_terminal/ui/ renders this;
4
+ this module owns it.
5
+
6
+ Deliberately separate from `App.history` (calc_terminal/app.py), which
7
+ is the list of *solved notebooks* (numerical results the calculator
8
+ commands produce) — a different, older concept this refactor doesn't
9
+ touch. `ChatSession` is specifically the turn-by-turn chat transcript
10
+ the primary UI shows.
11
+ """
12
+
13
+ if __name__ == "__main__":
14
+ print("This is a library file and is not meant to be run directly.")
15
+ import sys
16
+ sys.exit(1)
17
+
18
+ import itertools
19
+ import time
20
+
21
+ from . import ai_modes
22
+
23
+ _id_counter = itertools.count(1)
24
+
25
+
26
+ def next_turn_id():
27
+ """One monotonically increasing id per process — good enough to key
28
+ widgets and correlate MessageStarted/Chunk/Finished events; not
29
+ persisted, so it resets each run."""
30
+ return f"turn-{next(_id_counter)}"
31
+
32
+
33
+ class Turn:
34
+ """One conversation turn: a user message, or an assistant reply
35
+ (streaming or finished), or a system/permission note.
36
+
37
+ `role` is one of "user", "assistant", "system".
38
+ `status` is one of "complete" (default), "streaming", "error".
39
+
40
+ Every Turn is also a permanent snapshot of the AI mode that created
41
+ it. `mode` and `mode_snapshot` are captured exactly once, in
42
+ __init__, from whatever ai_modes.current_mode() is *at that
43
+ instant* — never re-read afterwards. That's deliberate: this is the
44
+ fix for the old bug where switching AI modes (Notebook -> Agent ->
45
+ Build -> ...) repainted every message already on screen, not just
46
+ new ones. A Turn's mode never changes after creation, so anything
47
+ that renders from `turn.mode_snapshot` (see ui/conversation.py)
48
+ renders the mode the message was actually created in, forever,
49
+ regardless of how many times the user switches modes afterwards.
50
+ `mode_snapshot` is a plain dict from ai_modes.snapshot() — label,
51
+ icon, accent hex/rgb — with no live reference back to ai_modes, so
52
+ it can't drift even if ai_modes.MODE_META itself is ever mutated.
53
+
54
+ `provider`/`model`/`workspace`/`simulation_id` are likewise
55
+ point-in-time metadata the caller may supply at creation (e.g. the
56
+ AI provider/model configured when an assistant reply was
57
+ generated, or the active workspace) — plain data, not live lookups.
58
+
59
+ `parent_turn_id` is the "Conversation Architecture" metadata the
60
+ v0.7.2 context-menu spec asks for: for an assistant turn, the id
61
+ of the user turn that prompted it (set by
62
+ ui/app.py._begin_assistant_turn). This is what lets "Try Again in
63
+ <mode>" retrieve the original prompt reliably by id instead of
64
+ guessing from list position, which breaks the moment any turn is
65
+ removed (see Revert) or reordered.
66
+ """
67
+
68
+ __slots__ = ("turn_id", "role", "text", "status", "created_at",
69
+ "duration", "usage", "attachments",
70
+ "mode", "mode_snapshot", "provider", "model",
71
+ "workspace", "simulation_id", "parent_turn_id")
72
+
73
+ def __init__(self, turn_id, role, text="", status="complete",
74
+ attachments=None, mode=None, provider=None, model=None,
75
+ workspace=None, simulation_id=None, parent_turn_id=None):
76
+ self.turn_id = turn_id
77
+ self.role = role
78
+ self.text = text
79
+ self.status = status
80
+ self.created_at = time.time()
81
+ self.duration = 0.0
82
+ self.usage = {}
83
+ self.attachments = attachments or []
84
+ # Frozen at creation — see class docstring. `mode` defaults to
85
+ # whatever's active right now (the normal case); callers that
86
+ # already know the mode a turn belongs to (e.g. restoring one)
87
+ # can pass it explicitly instead.
88
+ self.mode = mode if mode in ai_modes.MODE_META else ai_modes.current_mode()
89
+ self.mode_snapshot = ai_modes.snapshot(self.mode)
90
+ self.provider = provider
91
+ self.model = model
92
+ self.workspace = workspace
93
+ self.simulation_id = simulation_id
94
+ self.parent_turn_id = parent_turn_id
95
+
96
+ def append(self, fragment):
97
+ self.text += fragment
98
+
99
+ def finish(self, full_text=None, duration=0.0, usage=None):
100
+ if full_text is not None:
101
+ self.text = full_text
102
+ self.status = "complete"
103
+ self.duration = duration
104
+ self.usage = usage or {}
105
+
106
+
107
+ class ChatSession:
108
+ """The full transcript for one run of the primary UI. Holds every
109
+ `Turn` in order; the UI only ever appends to what it mounts — this
110
+ object is the record it appends *from*, so scrollback beyond what's
111
+ currently mounted can be regenerated instead of staying resident as
112
+ live widgets (see ui/conversation.py's virtualization notes)."""
113
+
114
+ # How many recent turns stay verbatim in every AI request. Once the
115
+ # un-folded backlog grows past KEEP_VERBATIM + FOLD_HEADROOM, the
116
+ # oldest excess turns get condensed into `older_summary` instead of
117
+ # being sent (or dropped) raw — see summarize_older_turns().
118
+ KEEP_VERBATIM = 16
119
+ FOLD_HEADROOM = 8
120
+
121
+ def __init__(self):
122
+ self.turns = []
123
+ self.notebook_mode = "NOTEBOOK"
124
+ self.workspace = None
125
+ self.model_name = None
126
+ self.older_summary = "" # condensed memory of turns older than what's kept verbatim
127
+ self._folded_count = 0 # how many leading non-system turns are already folded in
128
+
129
+ def add_user_turn(self, text, attachments=None, mode=None, workspace=None):
130
+ turn = Turn(next_turn_id(), "user", text, status="complete",
131
+ attachments=attachments, mode=mode, workspace=workspace)
132
+ self.turns.append(turn)
133
+ return turn
134
+
135
+ def start_assistant_turn(self, mode=None, provider=None, model=None,
136
+ workspace=None, simulation_id=None, parent_turn_id=None):
137
+ turn = Turn(next_turn_id(), "assistant", "", status="streaming",
138
+ mode=mode, provider=provider, model=model,
139
+ workspace=workspace, simulation_id=simulation_id,
140
+ parent_turn_id=parent_turn_id)
141
+ self.turns.append(turn)
142
+ return turn
143
+
144
+ def index_of(self, turn_id):
145
+ """Position of `turn_id` in self.turns, or None. Used by Revert
146
+ to truncate the transcript at the right point."""
147
+ for i, t in enumerate(self.turns):
148
+ if t.turn_id == turn_id:
149
+ return i
150
+ return None
151
+
152
+ def add_system_turn(self, text, mode=None):
153
+ turn = Turn(next_turn_id(), "system", text, status="complete", mode=mode)
154
+ self.turns.append(turn)
155
+ return turn
156
+
157
+ def get(self, turn_id):
158
+ for t in self.turns:
159
+ if t.turn_id == turn_id:
160
+ return t
161
+ return None
162
+
163
+ def clear(self):
164
+ self.turns = []
165
+ self.older_summary = ""
166
+ self._folded_count = 0
167
+
168
+ def __len__(self):
169
+ return len(self.turns)
170
+
171
+ def fork_upto(self, turn_id):
172
+ """Fork Conversation (context-menu spec): returns a brand-new
173
+ ChatSession carrying a shallow copy of every turn up to and
174
+ including `turn_id`, plus the same workspace/model_name/
175
+ older_summary state — so the fork starts with identical
176
+ context instead of an empty transcript. `self` (the original
177
+ session) is left completely untouched, which is what makes
178
+ this a real fork rather than a rename/move of the current one.
179
+ Returns None if turn_id isn't found."""
180
+ idx = self.index_of(turn_id)
181
+ if idx is None:
182
+ return None
183
+ new_session = ChatSession()
184
+ new_session.turns = list(self.turns[:idx + 1])
185
+ new_session.notebook_mode = self.notebook_mode
186
+ new_session.workspace = self.workspace
187
+ new_session.model_name = self.model_name
188
+ new_session.older_summary = self.older_summary
189
+ new_session._folded_count = min(self._folded_count, len(new_session.turns))
190
+ return new_session
191
+
192
+ def as_prompt_history(self, limit=None, before_turn_id=None,
193
+ include_attachment_context=True, mode=None):
194
+ """Plain (role, text) pairs for callers (e.g. aicore) that want
195
+ conversational context rather than a single one-shot prompt.
196
+ Business logic reads this; it does not itself talk to the AI.
197
+
198
+ v0.7.9.6 (AI Reliability Contract - Mode Isolation):
199
+ When `mode` is specified, turns from unrelated AI modes (such as
200
+ large research papers, debugger stack traces, or planning trees)
201
+ are excluded to prevent cross-mode context contamination.
202
+ """
203
+ non_system = [t for t in self.turns if t.role != "system"]
204
+ if before_turn_id is not None:
205
+ idx = next((i for i, t in enumerate(non_system) if t.turn_id == before_turn_id),
206
+ len(non_system))
207
+ non_system = non_system[:idx]
208
+ unfolded = non_system[self._folded_count:]
209
+
210
+ # Mode isolation filtering
211
+ if mode:
212
+ target_mode = str(mode).lower()
213
+ compatible = {target_mode}
214
+ if target_mode in ("chat", "notebook"):
215
+ compatible.update({"chat", "notebook"})
216
+ elif target_mode in ("code", "build"):
217
+ compatible.update({"code", "build"})
218
+
219
+ # Filter turns to compatible modes only
220
+ isolated = []
221
+ for t in unfolded:
222
+ t_mode = str(getattr(t, "mode", "chat") or "chat").lower()
223
+ if t_mode in compatible:
224
+ isolated.append(t)
225
+ elif len(t.text or "") < 200 and t_mode in ("chat", "notebook"):
226
+ # Allow short conversational pleasantries across modes
227
+ isolated.append(t)
228
+ unfolded = isolated
229
+
230
+ if limit is not None:
231
+ unfolded = unfolded[-limit:]
232
+ out = []
233
+ for t in unfolded:
234
+ if not t.text and not t.attachments:
235
+ continue
236
+ text = t.text
237
+ if include_attachment_context and t.role == "user" and t.attachments:
238
+ from . import attachments as _att
239
+ ctx = _att.AttachmentManager.build_context(t.attachments)
240
+ if ctx:
241
+ text = (text + "\n\n" + ctx).strip() if text else ctx
242
+ out.append((t.role, text))
243
+ if self.older_summary and (mode is None or str(mode).lower() in ("chat", "notebook")):
244
+ out.insert(0, ("user", f"[Summary of earlier conversation, for context — "
245
+ f"do not repeat it back verbatim]\n{self.older_summary}"))
246
+ return out
247
+
248
+ def needs_summarization(self):
249
+ """True once there's enough un-folded backlog to be worth the
250
+ extra summarization call — leaves headroom so this doesn't
251
+ trigger on every single message once it crosses the line."""
252
+ non_system = [t for t in self.turns if t.role != "system"]
253
+ unfolded_count = len(non_system) - self._folded_count
254
+ return unfolded_count > (self.KEEP_VERBATIM + self.FOLD_HEADROOM)
255
+
256
+ def summarize_older_turns(self, summarizer):
257
+ """Folds everything but the most recent KEEP_VERBATIM turns into
258
+ `older_summary`, using `summarizer(prompt, system_prompt=...)`
259
+ (e.g. aicore.query_ai) to condense them. Safe to call on every
260
+ turn — it's a no-op unless needs_summarization() is True, and
261
+ it never touches self.turns, so the UI's scrollback is
262
+ unaffected either way; only what gets *sent* to the model
263
+ changes. Best-effort: if the summarizer call fails or returns
264
+ an error string, the un-folded turns are simply left as-is and
265
+ get sent verbatim (or re-attempted next turn) instead of being
266
+ silently lost."""
267
+ if not self.needs_summarization():
268
+ return
269
+ non_system = [t for t in self.turns if t.role != "system"]
270
+ unfolded = non_system[self._folded_count:]
271
+ to_fold = unfolded[:-self.KEEP_VERBATIM]
272
+ if not to_fold:
273
+ return
274
+ transcript = "\n".join(f"{t.role}: {t.text}" for t in to_fold)
275
+ prior = f"Existing summary so far:\n{self.older_summary}\n\n" if self.older_summary else ""
276
+ prompt = (
277
+ f"{prior}Condense the following older chat turns into a short, factual "
278
+ f"summary (plain text, under 200 words) that preserves names, numbers, "
279
+ f"decisions, and any options/lists the assistant offered — so a later "
280
+ f"reply like 'option 3' or 'the second one' still makes sense against "
281
+ f"it:\n\n{transcript}"
282
+ )
283
+ try:
284
+ summary = summarizer(
285
+ prompt, system_prompt="You compress chat history into a short, factual summary. "
286
+ "Be concise; preserve specifics, not vibes.")
287
+ except Exception:
288
+ return # best-effort — verbatim turns stay un-folded and get sent as-is
289
+ if not summary or summary.startswith((
290
+ "Error", "AI not configured", "Could not reach", "The AI request timed out",
291
+ "Provider '", "The 'requests' library")):
292
+ return # looks like an error string from aicore, not a real summary
293
+ self.older_summary = summary.strip()
294
+ self._folded_count += len(to_fold)
calc_terminal/sim3d.py ADDED
@@ -0,0 +1,206 @@
1
+ """
2
+ Real-time 3D Atom, Electron, and Proton Simulation for CCT [BETA].
3
+ Uses terminal-based 3D projection and creative ASCII/ANSI design.
4
+ """
5
+
6
+ import math
7
+ import random
8
+ import sys
9
+ import time
10
+ from . import theme
11
+ from . import keys
12
+ from .atomsim import ELEMENTS, resolve_element, SHELL_NAMES
13
+
14
+ # 3D Projection Constants
15
+ FOV = 250
16
+ CAMERA_Z = 5.0
17
+
18
+ def project_3d(x, y, z, width, height, scale=1.0):
19
+ """Simple 3D to 2D perspective projection."""
20
+ factor = FOV / (z + CAMERA_Z)
21
+ x_2d = x * factor * scale * 2.0 # Terminal char aspect ratio correction
22
+ y_2d = y * factor * scale
23
+ return int(width // 2 + x_2d), int(height // 2 - y_2d)
24
+
25
+ def rotate_y(x, y, z, angle):
26
+ """Rotate around Y axis."""
27
+ cos_a = math.cos(angle)
28
+ sin_a = math.sin(angle)
29
+ return x * cos_a + z * sin_a, y, -x * sin_a + z * cos_a
30
+
31
+ def rotate_x(x, y, z, angle):
32
+ """Rotate around X axis."""
33
+ cos_a = math.cos(angle)
34
+ sin_a = math.sin(angle)
35
+ return x, y * cos_a - z * sin_a, y * sin_a + z * cos_a
36
+
37
+ def get_3d_graph_data(z_num, angle):
38
+ """Generate 3D graph data points for a potential field or electron density."""
39
+ points = []
40
+ # Real-time calculation of potential field V(r) = -kZe^2/r
41
+ k = 1.0 # simplified units
42
+ for i in range(-12, 13, 2):
43
+ for j in range(-12, 13, 2):
44
+ x = i / 4.0
45
+ y = j / 4.0
46
+ r = math.sqrt(x*x + y*y)
47
+ # Electrostatic potential calculation
48
+ val = -k * z_num / (r + 0.8)
49
+ # Add wave function interference simulation
50
+ val += 0.3 * math.sin(r * 3 - angle * 1.5)
51
+ points.append((x, val, y))
52
+ return points
53
+
54
+ def calculate_consequences(z_num, shells):
55
+ """Calculate physical consequences based on the atomic configuration."""
56
+ # 1. Bohr Radius for the outermost shell
57
+ n_max = len(shells)
58
+ rn = 0.529 * (n_max**2) / z_num # in Angstroms
59
+
60
+ # 2. Ionization Energy (simplified)
61
+ ie = 13.6 * (z_num**2) / (n_max**2) # in eV
62
+
63
+ # 3. Effective Nuclear Charge (Slater's rule approximation)
64
+ s = 0
65
+ if n_max > 1:
66
+ s = sum(shells[:-1]) * 0.85 + (shells[-1]-1) * 0.35
67
+ else:
68
+ s = (shells[0]-1) * 0.3
69
+ z_eff = z_num - s
70
+
71
+ return [
72
+ f"Outer Radius: {rn:.3f} \u00c5",
73
+ f"Ionization E: {ie:.2f} eV",
74
+ f"Z-effective: {z_eff:.2f} (Slater)"
75
+ ]
76
+
77
+ def atom_simulation_3d(z_num, out=sys.stdout, max_frames=1000, frame_delay=0.05):
78
+ if z_num not in ELEMENTS:
79
+ print(theme.red(f" No shell data for element Z={z_num} (supported: 1-36)."))
80
+ return
81
+
82
+ sym, name, shells, mass = ELEMENTS[z_num]
83
+ neutrons = mass - z_num
84
+
85
+ angle_y = 0.0
86
+ angle_x = 0.2
87
+ speed = 0.1
88
+ paused = False
89
+ show_graph = False
90
+
91
+ width, height = 70, 26
92
+ interactive = keys.stdin_is_interactive()
93
+
94
+ theme.hide_cursor()
95
+ try:
96
+ theme.clear_screen()
97
+ frame = 0
98
+ while frame < max_frames:
99
+ grid = [[" "] * width for _ in range(height)]
100
+
101
+ # 1. Draw 3D Graph if enabled
102
+ if show_graph:
103
+ graph_pts = get_3d_graph_data(z_num, angle_y)
104
+ for gx, gy, gz in graph_pts:
105
+ rx, ry, rz = rotate_y(gx, gy, gz, angle_y * 0.5)
106
+ rx, ry, rz = rotate_x(rx, ry, rz, angle_x)
107
+ px, py = project_3d(rx, ry, rz, width, height, scale=1.5)
108
+ if 0 <= px < width and 0 <= py < height:
109
+ grid[py][px] = theme.faint("\u00b7")
110
+
111
+ # 2. Draw Nucleus (Protons + Neutrons)
112
+ random.seed(z_num * 13)
113
+ total_nucleons = z_num + neutrons
114
+ for i in range(min(total_nucleons, 50)):
115
+ # Random position in a sphere
116
+ phi = random.uniform(0, 2 * math.pi)
117
+ costheta = random.uniform(-1, 1)
118
+ u = random.uniform(0, 1)
119
+ theta = math.acos(costheta)
120
+ r = 0.4 * (u ** (1/3))
121
+
122
+ nx = r * math.sin(theta) * math.cos(phi)
123
+ ny = r * math.sin(theta) * math.sin(phi)
124
+ nz = r * math.cos(theta)
125
+
126
+ # Rotate nucleus
127
+ rx, ry, rz = rotate_y(nx, ny, nz, angle_y * 0.2)
128
+ rx, ry, rz = rotate_x(rx, ry, rz, angle_x)
129
+ px, py = project_3d(rx, ry, rz, width, height)
130
+
131
+ if 0 <= px < width and 0 <= py < height:
132
+ is_proton = (i < z_num)
133
+ ch = "P" if is_proton else "n"
134
+ color = theme.red if is_proton else theme.dim
135
+ grid[py][px] = color(ch, bold=True)
136
+
137
+ # 3. Draw Electrons in Shells
138
+ for si, count in enumerate(shells):
139
+ radius = (si + 1) * 1.2
140
+ e_speed = 1.0 / (si + 1) ** 0.5
141
+ for e in range(count):
142
+ phase = 2 * math.pi * e / count
143
+ # Orbit in XZ plane initially
144
+ ex = radius * math.cos(angle_y * e_speed + phase)
145
+ ez = radius * math.sin(angle_y * e_speed + phase)
146
+ ey = 0
147
+
148
+ # Tilt orbits slightly for "3D" feel
149
+ tilt = (si * 0.4)
150
+ ex, ey, ez = rotate_x(ex, ey, ez, tilt)
151
+
152
+ # Apply global rotation
153
+ rx, ry, rz = rotate_y(ex, ey, ez, angle_y)
154
+ rx, ry, rz = rotate_x(rx, ry, rz, angle_x)
155
+
156
+ px, py = project_3d(rx, ry, rz, width, height)
157
+ if 0 <= px < width and 0 <= py < height:
158
+ grid[py][px] = theme.cyan("\u25cf", bold=True)
159
+
160
+ # Render frame
161
+ theme.cursor_home()
162
+ consequences = calculate_consequences(z_num, shells)
163
+ info = [
164
+ theme.badge("BETA", theme.BG_WARN) + " " + theme.purple(f"█ {name} ({sym}) 3D SIMULATION █", bold=True),
165
+ theme.dim(f"Z: {z_num} ┃ MASS: ≈{mass} ┃ NUCLEONS: {total_nucleons}"),
166
+ theme.dim("REAL-TIME: ") + theme.green(" ┃ ".join(consequences)),
167
+ theme.dim("STATUS: ") + theme.orange("QUANTUM POTENTIAL MAPPING" if show_graph else "STABLE ELECTRON CONFIG"),
168
+ ""
169
+ ]
170
+ lines = ["".join(row) for row in grid]
171
+ out.write(theme.panel(info + lines, title="3D ATOM SIM", color=theme.CYAN, width=width + 4))
172
+
173
+ # Controls
174
+ out.write("\n" + theme.faint(
175
+ " [q] quit [space] pause [g] toggle 3D graph [+/-] speed [arrows] rotate") + "\n")
176
+ out.flush()
177
+
178
+ if not paused:
179
+ angle_y += speed
180
+ frame += 1
181
+
182
+ if not interactive:
183
+ out.write(theme.dim(
184
+ "\n (no interactive terminal detected — showing a single "
185
+ "frame instead of the live view)\n"))
186
+ out.flush()
187
+ break
188
+
189
+ if keys.key_available():
190
+ k = keys.read_key()
191
+ if k == "q": break
192
+ elif k == " ": paused = not paused
193
+ elif k == "g": show_graph = not show_graph
194
+ elif k == "+": speed = min(0.5, speed + 0.02)
195
+ elif k == "-": speed = max(0.01, speed - 0.02)
196
+ # Arrow keys and W/A/S/D for rotation
197
+ elif k in ("w", "\x1b[A"): angle_x += 0.1
198
+ elif k in ("s", "\x1b[B"): angle_x -= 0.1
199
+ elif k in ("a", "\x1b[D"): angle_y -= 0.1
200
+ elif k in ("d", "\x1b[C"): angle_y += 0.1
201
+
202
+ time.sleep(frame_delay)
203
+
204
+ finally:
205
+ theme.show_cursor()
206
+ print()