meltygui 0.1.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 (372) hide show
  1. meltygui/__init__.py +107 -0
  2. meltygui/accounts/__init__.py +0 -0
  3. meltygui/accounts/internet_accounts.py +1355 -0
  4. meltygui/chat/__init__.py +91 -0
  5. meltygui/chat/activity.py +75 -0
  6. meltygui/chat/backends.py +36 -0
  7. meltygui/chat/chat_interface.py +732 -0
  8. meltygui/chat/chat_proxy.py +352 -0
  9. meltygui/chat/codex_proxy.py +592 -0
  10. meltygui/chat/codex_settings.py +100 -0
  11. meltygui/chat/codex_transport.py +60 -0
  12. meltygui/chat/command_parser.py +204 -0
  13. meltygui/chat/images.py +227 -0
  14. meltygui/chat/messages.py +417 -0
  15. meltygui/chat/metadata.py +139 -0
  16. meltygui/chat/writer_locks.py +64 -0
  17. meltygui/code/__init__.py +0 -0
  18. meltygui/code/basic_converters.py +533 -0
  19. meltygui/code/chain_converters.py +2111 -0
  20. meltygui/code/code_checks.py +2209 -0
  21. meltygui/code/core_syntax.py +1430 -0
  22. meltygui/code/file_converters.py +1933 -0
  23. meltygui/code/fileref.py +702 -0
  24. meltygui/code/hotswap_guard.py +144 -0
  25. meltygui/code/libcst_conversion.py +9724 -0
  26. meltygui/code/live_instrument.py +392 -0
  27. meltygui/code/live_view.py +2490 -0
  28. meltygui/code/melty_scan.py +2684 -0
  29. meltygui/code/new_codecs.py +1255 -0
  30. meltygui/code/new_converters.py +3017 -0
  31. meltygui/code/project_code.py +278 -0
  32. meltygui/code/source_context.py +63 -0
  33. meltygui/code/symbol_roster.py +1588 -0
  34. meltygui/code/syntax_check.py +34 -0
  35. meltygui/code/syntax_check_worker.py +114 -0
  36. meltygui/completion/__init__.py +0 -0
  37. meltygui/completion/fim.py +1232 -0
  38. meltygui/completion/fim_context.py +481 -0
  39. meltygui/completion/providers/__init__.py +0 -0
  40. meltygui/completion/providers/anthropic_oauth.py +446 -0
  41. meltygui/completion/providers/anthropic_requests.py +69 -0
  42. meltygui/completion/providers/claude.py +203 -0
  43. meltygui/completion/providers/claude_usage.py +499 -0
  44. meltygui/completion/providers/codex_accounts.py +180 -0
  45. meltygui/completion/providers/copilot.py +617 -0
  46. meltygui/completion/providers/oauth_popup.py +220 -0
  47. meltygui/completion/providers/ollama.py +320 -0
  48. meltygui/completion/providers/profiles.py +23 -0
  49. meltygui/core/README.md +88 -0
  50. meltygui/core/__init__.py +1 -0
  51. meltygui/core/automation/__init__.py +1 -0
  52. meltygui/core/automation/action_core.py +153 -0
  53. meltygui/core/automation/collection_action.py +45 -0
  54. meltygui/core/automation/mcp_eval.py +167 -0
  55. meltygui/core/automation/mcp_hotswap.py +107 -0
  56. meltygui/core/automation/mcp_query.py +552 -0
  57. meltygui/core/automation/mcp_server.py +657 -0
  58. meltygui/core/automation/orchestration_core.py +2636 -0
  59. meltygui/core/automation/query_core.py +124 -0
  60. meltygui/core/automation/search_core.py +26 -0
  61. meltygui/core/automation/selector_core.py +340 -0
  62. meltygui/core/automation/value_core.py +1752 -0
  63. meltygui/core/cache/__init__.py +1 -0
  64. meltygui/core/cache/cache_diagnostics.py +0 -0
  65. meltygui/core/cache/invalidation_decoration.py +153 -0
  66. meltygui/core/cache/invalidation_tracker.py +43 -0
  67. meltygui/core/cache/tile_cache.py +5968 -0
  68. meltygui/core/conversion/__init__.py +1 -0
  69. meltygui/core/conversion/bubbling.py +599 -0
  70. meltygui/core/conversion/cache_tree.py +188 -0
  71. meltygui/core/conversion/chain.py +113 -0
  72. meltygui/core/conversion/converter_register.py +145 -0
  73. meltygui/core/conversion/data_decoration.py +67 -0
  74. meltygui/core/conversion/dict_conversion.py +1815 -0
  75. meltygui/core/conversion/dict_conversion_util.py +177 -0
  76. meltygui/core/conversion/dynamic_obj.py +89 -0
  77. meltygui/core/conversion/graph_compare.py +183 -0
  78. meltygui/core/conversion/load_save_v2.py +1032 -0
  79. meltygui/core/conversion/missing_saved_class.py +39 -0
  80. meltygui/core/conversion/path_finder.py +606 -0
  81. meltygui/core/conversion/render_host.py +1083 -0
  82. meltygui/core/core_render.py +6537 -0
  83. meltygui/core/definition_hotswap.py +231 -0
  84. meltygui/core/diagnostics/__init__.py +1 -0
  85. meltygui/core/diagnostics/attribute_churn.py +38 -0
  86. meltygui/core/diagnostics/fps_counter.py +39 -0
  87. meltygui/core/diagnostics/gpu_frame_timer.py +103 -0
  88. meltygui/core/diagnostics/inspection_core.py +169 -0
  89. meltygui/core/diagnostics/monitor_core.py +105 -0
  90. meltygui/core/diagnostics/notifications.py +706 -0
  91. meltygui/core/diagnostics/perf_trace.py +281 -0
  92. meltygui/core/diagnostics/profile_decoration.py +88 -0
  93. meltygui/core/diagnostics/resize_trace.py +62 -0
  94. meltygui/core/diagnostics/screenshot_core.py +247 -0
  95. meltygui/core/diagnostics/session_status.py +98 -0
  96. meltygui/core/diagnostics/trace_core.py +445 -0
  97. meltygui/core/files/__init__.py +1 -0
  98. meltygui/core/files/file_core.py +208 -0
  99. meltygui/core/files/file_explorer_core.py +104 -0
  100. meltygui/core/files/file_tree_core.py +198 -0
  101. meltygui/core/files/file_watch_core.py +43 -0
  102. meltygui/core/files/import_graph_core.py +43 -0
  103. meltygui/core/files/metadata_core.py +51 -0
  104. meltygui/core/graphics/__init__.py +1 -0
  105. meltygui/core/graphics/cuda_context_core.py +166 -0
  106. meltygui/core/graphics/cuda_interop_core.py +136 -0
  107. meltygui/core/graphics/cuda_kernel_core.py +91 -0
  108. meltygui/core/graphics/framebuffer_recorder.py +337 -0
  109. meltygui/core/graphics/gl_state.py +658 -0
  110. meltygui/core/graphics/lut_core.py +52 -0
  111. meltygui/core/graphics/overlay_renderer.py +984 -0
  112. meltygui/core/graphics/scene_target.py +180 -0
  113. meltygui/core/graphics/screenshot.py +439 -0
  114. meltygui/core/graphics/shader_func.py +478 -0
  115. meltygui/core/graphics/tensor_core.py +45 -0
  116. meltygui/core/graphics/text_texture.py +329 -0
  117. meltygui/core/graphics/wayland_color.py +635 -0
  118. meltygui/core/input/__init__.py +1 -0
  119. meltygui/core/input/collision.py +165 -0
  120. meltygui/core/input/drag_drop_core.py +1525 -0
  121. meltygui/core/input/hypr_left_drag.py +323 -0
  122. meltygui/core/input/input_core.py +245 -0
  123. meltygui/core/input/input_handler.py +1101 -0
  124. meltygui/core/input/mouse_cursor.py +355 -0
  125. meltygui/core/input/pynput_backend.py +1054 -0
  126. meltygui/core/input/space_mouse.py +338 -0
  127. meltygui/core/input/touchpad_backend.py +393 -0
  128. meltygui/core/input/view_selection.py +177 -0
  129. meltygui/core/layout/__init__.py +1 -0
  130. meltygui/core/layout/column_core.py +2153 -0
  131. meltygui/core/layout/cursor_core.py +161 -0
  132. meltygui/core/layout/dropdown_core.py +278 -0
  133. meltygui/core/layout/edge_constraints.py +155 -0
  134. meltygui/core/layout/grid_core.py +117 -0
  135. meltygui/core/layout/header_core.py +23 -0
  136. meltygui/core/layout/header_runtime.py +67 -0
  137. meltygui/core/layout/layout_core.py +87 -0
  138. meltygui/core/layout/tile_manager_core.py +591 -0
  139. meltygui/core/melty.py +6726 -0
  140. meltygui/core/module_map.json +896 -0
  141. meltygui/core/module_names.py +19 -0
  142. meltygui/core/rendering/__init__.py +1 -0
  143. meltygui/core/rendering/core_decoration.py +431 -0
  144. meltygui/core/rendering/core_render_helpers.py +328 -0
  145. meltygui/core/rendering/func_metadata.py +398 -0
  146. meltygui/core/rendering/mode.py +818 -0
  147. meltygui/core/rendering/mode_defaults.py +41 -0
  148. meltygui/core/rendering/modes.py +136 -0
  149. meltygui/core/rendering/parameter_core.py +1665 -0
  150. meltygui/core/rendering/render_dispatch.py +1891 -0
  151. meltygui/core/rendering/render_funcs.py +273 -0
  152. meltygui/core/rendering/shaped.py +312 -0
  153. meltygui/core/rendering/window_decoration.py +25 -0
  154. meltygui/core/runtime/__init__.py +1 -0
  155. meltygui/core/runtime/app.py +735 -0
  156. meltygui/core/runtime/app_session.py +140 -0
  157. meltygui/core/runtime/background.py +564 -0
  158. meltygui/core/runtime/extensions.py +53 -0
  159. meltygui/core/runtime/gc_manager.py +1163 -0
  160. meltygui/core/runtime/lifecycle.py +21 -0
  161. meltygui/core/runtime/paths.py +27 -0
  162. meltygui/core/runtime/settings.py +14 -0
  163. meltygui/core/runtime/singleton.py +16 -0
  164. meltygui/core/runtime/thread_safe_bool.py +24 -0
  165. meltygui/core/runtime/thread_signal.py +30 -0
  166. meltygui/core/runtime/toggles.py +3115 -0
  167. meltygui/core/services/__init__.py +1 -0
  168. meltygui/core/services/account_core.py +10 -0
  169. meltygui/core/services/chat_core.py +19 -0
  170. meltygui/core/services/claude_terminal_core.py +346 -0
  171. meltygui/core/services/terminal_core.py +458 -0
  172. meltygui/core/services/terminal_runtime.py +78 -0
  173. meltygui/core/styling/__init__.py +1 -0
  174. meltygui/core/styling/color_core.py +46 -0
  175. meltygui/core/styling/fonts.py +639 -0
  176. meltygui/core/styling/global_style.py +338 -0
  177. meltygui/core/styling/style.py +198 -0
  178. meltygui/core/styling/style_core.py +522 -0
  179. meltygui/core/styling/warm_start.py +149 -0
  180. meltygui/core/windowing/__init__.py +1 -0
  181. meltygui/core/windowing/backends/PYIMGUI_LICENSE +28 -0
  182. meltygui/core/windowing/backends/__init__.py +1 -0
  183. meltygui/core/windowing/backends/imgui_renderer.py +138 -0
  184. meltygui/core/windowing/backends/native_wayland.py +850 -0
  185. meltygui/core/windowing/backends/protocols/xdg-decoration-unstable-v1.xml +156 -0
  186. meltygui/core/windowing/backends/protocols/xdg-shell.xml +1420 -0
  187. meltygui/core/windowing/backends/wayland_protocol.py +101 -0
  188. meltygui/core/windowing/dock_core.py +182 -0
  189. meltygui/core/windowing/frame_geometry.py +42 -0
  190. meltygui/core/windowing/geometry_feed.py +864 -0
  191. meltygui/core/windowing/glfw_utils.py +1343 -0
  192. meltygui/core/windowing/os_frame.py +1552 -0
  193. meltygui/core/windowing/surface.py +643 -0
  194. meltygui/core/windowing/titlebar.py +1560 -0
  195. meltygui/core/windowing/titlebar_buttons.py +281 -0
  196. meltygui/core/windowing/wayland_move.py +932 -0
  197. meltygui/core/windowing/window_api.py +62 -0
  198. meltygui/core/windowing/window_constants.py +339 -0
  199. meltygui/core/windowing/window_visibility.py +162 -0
  200. meltygui/debug/__init__.py +0 -0
  201. meltygui/debug/app_view_utils.py +9 -0
  202. meltygui/editor/__init__.py +0 -0
  203. meltygui/editor/bash_syntax.py +30 -0
  204. meltygui/editor/code_line_fast.py +152 -0
  205. meltygui/editor/diff.py +139 -0
  206. meltygui/editor/external_changes.py +159 -0
  207. meltygui/editor/file_header.py +41 -0
  208. meltygui/editor/live_usage.py +169 -0
  209. meltygui/editor/live_view_views.py +1803 -0
  210. meltygui/editor/pending_save.py +1351 -0
  211. meltygui/editor/roster_tints.py +547 -0
  212. meltygui/editor/source_preview.py +16 -0
  213. meltygui/editor/source_tools.py +10 -0
  214. meltygui/editor/source_ui.py +70 -0
  215. meltygui/editor/spell_check.py +77 -0
  216. meltygui/editor/text_editor.py +9076 -0
  217. meltygui/editor/usage_picker.py +538 -0
  218. meltygui/events/__init__.py +0 -0
  219. meltygui/events/example.py +118 -0
  220. meltygui/examples/__init__.py +0 -0
  221. meltygui/examples/columns_demo.py +33 -0
  222. meltygui/examples/columns_window_demo.py +60 -0
  223. meltygui/examples/context_menu_demo.py +24 -0
  224. meltygui/examples/context_menu_window_demo.py +49 -0
  225. meltygui/examples/gui_playground.py +127 -0
  226. meltygui/examples/live_view_playground.py +256 -0
  227. meltygui/examples/lora.py +43 -0
  228. meltygui/examples/lora_data.py +59 -0
  229. meltygui/examples/lora_policies.py +67 -0
  230. meltygui/examples/mode_demo.py +49 -0
  231. meltygui/examples/modifies_demo.py +119 -0
  232. meltygui/examples/scalar_policies.py +60 -0
  233. meltygui/examples/style_layouts.py +149 -0
  234. meltygui/examples/tile_manager_demo.py +74 -0
  235. meltygui/examples/tint_demo.py +160 -0
  236. meltygui/examples/tint_functions.py +56 -0
  237. meltygui/examples/trace_demo.py +88 -0
  238. meltygui/examples/two_windows.py +36 -0
  239. meltygui/files/__init__.py +0 -0
  240. meltygui/files/fast_file_explorer.py +439 -0
  241. meltygui/gnome_extension/lsd-window-geometry@latent-descent/extension.js +237 -0
  242. meltygui/gnome_extension/lsd-window-geometry@latent-descent/lsd-window-geometry@latent-descent.iml +9 -0
  243. meltygui/gnome_extension/lsd-window-geometry@latent-descent/metadata.json +7 -0
  244. meltygui/graphics/__init__.py +6 -0
  245. meltygui/graphics/base.py +85 -0
  246. meltygui/graphics/examples.py +507 -0
  247. meltygui/graphics/executor.py +520 -0
  248. meltygui/graphics/filter.py +667 -0
  249. meltygui/graphics/filter.pyi +856 -0
  250. meltygui/graphics/generate_stubs.py +22 -0
  251. meltygui/graphics/registry.py +203 -0
  252. meltygui/graphics/shader_compiler.py +155 -0
  253. meltygui/graphics/shaders.py +1302 -0
  254. meltygui/graphics/stub_generator.py +250 -0
  255. meltygui/graphics/texture_manager.py +170 -0
  256. meltygui/graphics/texture_min_max.py +295 -0
  257. meltygui/hdr_color.py +757 -0
  258. meltygui/image_load.py +308 -0
  259. meltygui/model/__init__.py +1 -0
  260. meltygui/model/account_model.py +142 -0
  261. meltygui/model/camera_model.py +159 -0
  262. meltygui/model/chat_model.py +86 -0
  263. meltygui/model/code_model.py +62 -0
  264. meltygui/model/code_proxy_model.py +876 -0
  265. meltygui/model/collection_model.py +41 -0
  266. meltygui/model/color_model.py +127 -0
  267. meltygui/model/cuda_tensor_model.py +36 -0
  268. meltygui/model/cuda_texture_model.py +149 -0
  269. meltygui/model/dropdown_model.py +137 -0
  270. meltygui/model/file_metadata_model.py +73 -0
  271. meltygui/model/file_model.py +289 -0
  272. meltygui/model/format_model.py +390 -0
  273. meltygui/model/graph_model.py +98 -0
  274. meltygui/model/icon_model.py +1024 -0
  275. meltygui/model/import_graph_model.py +468 -0
  276. meltygui/model/layout_model.py +15 -0
  277. meltygui/model/lut_model.py +266 -0
  278. meltygui/model/search_model.py +173 -0
  279. meltygui/model/tensor_model.py +402 -0
  280. meltygui/model/terminal_model.py +329 -0
  281. meltygui/model/texture_model.py +146 -0
  282. meltygui/model/tile_model.py +24 -0
  283. meltygui/model/trace_model.py +198 -0
  284. meltygui/model/trace_report_model.py +146 -0
  285. meltygui/models/__init__.py +0 -0
  286. meltygui/models/file_meta.py +613 -0
  287. meltygui/models/function_console.py +184 -0
  288. meltygui/models/orchestration.py +48 -0
  289. meltygui/pbr.py +1576 -0
  290. meltygui/png_unfilter.c +49 -0
  291. meltygui/resources/JetBrainsMono-Regular.ttf +0 -0
  292. meltygui/resources/THIRD_PARTY_NOTICES.md +21 -0
  293. meltygui/resources/dejavu/DejaVuSans-Bold.ttf +0 -0
  294. meltygui/resources/dejavu/DejaVuSans-ExtraLight.ttf +0 -0
  295. meltygui/resources/dejavu/DejaVuSans.ttf +0 -0
  296. meltygui/resources/dejavu/LICENSE.txt +78 -0
  297. meltygui/resources/fontawesome-LICENSE.txt +121 -0
  298. meltygui/resources/fontawesome-webfont.ttf +0 -0
  299. meltygui/resources/hdri/studio_small_09_1k.hdr +0 -0
  300. meltygui/resources/jetbrains-weights/JetBrainsMono-Bold.ttf +0 -0
  301. meltygui/resources/jetbrains-weights/JetBrainsMono-ExtraBold.ttf +0 -0
  302. meltygui/resources/jetbrains-weights/JetBrainsMono-ExtraLight.ttf +0 -0
  303. meltygui/resources/jetbrains-weights/JetBrainsMono-Light.ttf +0 -0
  304. meltygui/resources/jetbrains-weights/JetBrainsMono-Medium.ttf +0 -0
  305. meltygui/resources/jetbrains-weights/JetBrainsMono-SemiBold.ttf +0 -0
  306. meltygui/resources/jetbrains-weights/JetBrainsMono-Thin.ttf +0 -0
  307. meltygui/resources/jetbrains-weights/OFL.txt +93 -0
  308. meltygui/resources/jetbrains-weights/README.md +2 -0
  309. meltygui/state/__init__.py +0 -0
  310. meltygui/state/account_state.py +22 -0
  311. meltygui/state/animation_state.py +97 -0
  312. meltygui/state/annotation_state.py +22 -0
  313. meltygui/state/chat_state.py +36 -0
  314. meltygui/state/code_state.py +10 -0
  315. meltygui/state/core_enums.py +59 -0
  316. meltygui/state/core_markers.py +115 -0
  317. meltygui/state/core_undo.py +1075 -0
  318. meltygui/state/file_state.py +75 -0
  319. meltygui/state/graph_state.py +26 -0
  320. meltygui/state/inspection_state.py +47 -0
  321. meltygui/state/menu_state.py +10 -0
  322. meltygui/state/model_enums.py +11 -0
  323. meltygui/state/new_core_model.py +2394 -0
  324. meltygui/state/orchestration_state.py +14 -0
  325. meltygui/state/query_state.py +24 -0
  326. meltygui/state/tensor_state.py +10 -0
  327. meltygui/state/terminal_state.py +23 -0
  328. meltygui/state/trace_state.py +32 -0
  329. meltygui/state/voxel_state.py +12 -0
  330. meltygui/text_index.py +816 -0
  331. meltygui/utils/__init__.py +0 -0
  332. meltygui/utils/jump_to_code.py +344 -0
  333. meltygui/utils/pkl_inspect.py +90 -0
  334. meltygui/utils/render_utils.py +1419 -0
  335. meltygui/view/__init__.py +1 -0
  336. meltygui/view/account_view.py +524 -0
  337. meltygui/view/action_view.py +73 -0
  338. meltygui/view/chat_decoration_view.py +112 -0
  339. meltygui/view/chat_view.py +1703 -0
  340. meltygui/view/code_view.py +2677 -0
  341. meltygui/view/collection_view.py +1185 -0
  342. meltygui/view/color_view.py +824 -0
  343. meltygui/view/control_view.py +559 -0
  344. meltygui/view/decoration_view.py +439 -0
  345. meltygui/view/diagnostic_view.py +177 -0
  346. meltygui/view/dropdown_view.py +1082 -0
  347. meltygui/view/file_view.py +1599 -0
  348. meltygui/view/graph_cuda_view.py +105 -0
  349. meltygui/view/graph_view.py +882 -0
  350. meltygui/view/header_view.py +848 -0
  351. meltygui/view/input_view.py +238 -0
  352. meltygui/view/inspection_view.py +1783 -0
  353. meltygui/view/layout_view.py +596 -0
  354. meltygui/view/lut_view.py +45 -0
  355. meltygui/view/menu_view.py +212 -0
  356. meltygui/view/orchestration_view.py +658 -0
  357. meltygui/view/query_view.py +118 -0
  358. meltygui/view/search_view.py +342 -0
  359. meltygui/view/tab_view.py +258 -0
  360. meltygui/view/tensor_view.py +430 -0
  361. meltygui/view/terminal_view.py +355 -0
  362. meltygui/view/text_view.py +8031 -0
  363. meltygui/view/texture_view.py +480 -0
  364. meltygui/view/tile_view.py +38 -0
  365. meltygui/view/trace_view.py +923 -0
  366. meltygui/view/voxel_cuda_view.py +824 -0
  367. meltygui/view/voxel_view.py +1860 -0
  368. meltygui/view/window_view.py +111 -0
  369. meltygui-0.1.0.dist-info/METADATA +143 -0
  370. meltygui-0.1.0.dist-info/RECORD +372 -0
  371. meltygui-0.1.0.dist-info/WHEEL +4 -0
  372. meltygui-0.1.0.dist-info/licenses/LICENSE +202 -0
@@ -0,0 +1,417 @@
1
+ """Provider-neutral message values. Types select views; no rendering or file I/O."""
2
+ import re
3
+ from meltygui.chat.command_parser import parse_command
4
+
5
+
6
+ class TextString(str):
7
+ pass
8
+
9
+
10
+ class MarkdownString(TextString):
11
+ pass
12
+
13
+
14
+ class CodeString(TextString):
15
+ def __new__(cls, value="", language=""):
16
+ obj = super().__new__(cls, value)
17
+ obj.language = language
18
+ return obj
19
+
20
+
21
+ class PythonString(CodeString):
22
+ def __new__(cls, value="", language="python"):
23
+ return super().__new__(cls, value, language)
24
+
25
+
26
+ class ShellString(CodeString):
27
+ pass
28
+
29
+
30
+ class BashString(ShellString):
31
+ def __new__(cls, value="", language="bash"):
32
+ return super().__new__(cls, value, language)
33
+
34
+
35
+ class FileTags(dict):
36
+ """Ordered file references with access intent and optional reported diff counts."""
37
+
38
+
39
+ class ToolOutput(TextString):
40
+ pass
41
+
42
+
43
+ class DiffString(CodeString):
44
+ pass
45
+
46
+
47
+ class JsonData(dict):
48
+ """Structured tool arguments/results; keys and numeric/bool values stay native."""
49
+
50
+
51
+ class Reference(dict):
52
+ label = "Reference"
53
+
54
+
55
+ class ImageReference(Reference):
56
+ label = "Image"
57
+
58
+
59
+ class AudioReference(Reference):
60
+ label = "Audio"
61
+
62
+
63
+ class FileReference(Reference):
64
+ label = "File"
65
+
66
+
67
+ class SkillReference(Reference):
68
+ label = "Skill"
69
+
70
+
71
+ class MentionReference(Reference):
72
+ label = "Mention"
73
+
74
+
75
+ class Message(dict):
76
+ role = "activity"
77
+ label = "Activity"
78
+
79
+ def __init__(self, kind="", content=None, status="", details=None):
80
+ super().__init__(kind=kind, role=self.role, status=status,
81
+ content={} if content is None else content,
82
+ details=JsonData() if details is None else details)
83
+ self.source_text = ""
84
+
85
+
86
+ class UserMessage(Message):
87
+ role, label = "user", "You"
88
+
89
+
90
+ class AssistantMessage(Message):
91
+ role, label = "assistant", "Assistant"
92
+
93
+
94
+ class PlanMessage(AssistantMessage):
95
+ label = "Plan"
96
+
97
+
98
+ class ReasoningMessage(Message):
99
+ label = "Reasoning summary"
100
+
101
+
102
+ class ToolCall(Message):
103
+ label = "Tool call"
104
+
105
+
106
+ class CommandExecution(ToolCall):
107
+ label = "Command"
108
+
109
+
110
+ class FileChange(ToolCall):
111
+ label = "File changes"
112
+
113
+
114
+ class McpToolCall(ToolCall):
115
+ label = "MCP tool"
116
+
117
+
118
+ class DynamicToolCall(ToolCall):
119
+ label = "Tool"
120
+
121
+
122
+ class CollabAgentToolCall(ToolCall):
123
+ label = "Agent tool"
124
+
125
+
126
+ class FunctionCallOutput(ToolCall):
127
+ label = "Tool result"
128
+
129
+
130
+ class WebSearch(ToolCall):
131
+ label = "Web search"
132
+
133
+
134
+ class HookPrompt(Message):
135
+ label = "Hook"
136
+
137
+
138
+ class SubAgentActivity(Message):
139
+ label = "Agent activity"
140
+
141
+
142
+ class ImageView(Message):
143
+ label = "Image viewed"
144
+
145
+
146
+ class ImageGeneration(Message):
147
+ label = "Image generated"
148
+
149
+
150
+ class Sleep(Message):
151
+ label = "Waiting"
152
+
153
+
154
+ class EnteredReviewMode(Message):
155
+ label = "Review started"
156
+
157
+
158
+ class ExitedReviewMode(Message):
159
+ label = "Review completed"
160
+
161
+
162
+ class ContextCompaction(Message):
163
+ label = "Context compacted"
164
+
165
+
166
+ class UnknownMessage(Message):
167
+ label = "Other activity"
168
+
169
+
170
+ ITEM_TYPES = {
171
+ "userMessage": UserMessage, "agentMessage": AssistantMessage, "plan": PlanMessage,
172
+ "reasoning": ReasoningMessage, "commandExecution": CommandExecution, "fileChange": FileChange,
173
+ "mcpToolCall": McpToolCall, "dynamicToolCall": DynamicToolCall,
174
+ "collabAgentToolCall": CollabAgentToolCall, "functionCallOutput": FunctionCallOutput,
175
+ "webSearch": WebSearch, "hookPrompt": HookPrompt, "subAgentActivity": SubAgentActivity,
176
+ "imageView": ImageView, "imageGeneration": ImageGeneration, "sleep": Sleep,
177
+ "enteredReviewMode": EnteredReviewMode, "exitedReviewMode": ExitedReviewMode,
178
+ "contextCompaction": ContextCompaction,
179
+ }
180
+
181
+
182
+ def tagged(value):
183
+ if isinstance(value, str):
184
+ return TextString(value)
185
+ if isinstance(value, dict):
186
+ kind = value.get("type")
187
+ cls = (ImageReference if kind in ("image", "localImage", "image_url", "inputImage") else
188
+ AudioReference if kind in ("audio", "localAudio", "inputAudio") else
189
+ FileReference if kind in ("resource", "resource_link", "file") else JsonData)
190
+ result = cls((key, tagged(val)) for key, val in value.items())
191
+ language = str(value.get("language", "")).lower()
192
+ if language in ("python", "python3", "py") and isinstance(value.get("code"), str):
193
+ result["code"] = PythonString(value["code"])
194
+ elif language in ("bash", "sh", "shell", "zsh") and isinstance(value.get("code"), str):
195
+ result["code"] = BashString(value["code"], language)
196
+ return result
197
+ if isinstance(value, list):
198
+ return [tagged(val) for val in value]
199
+ return value
200
+
201
+
202
+ def text_blocks(text):
203
+ """Split fenced code from prose, including the open fence of a streamed reply.
204
+
205
+ Explicit Python/py/python3 fences become PythonString. Other languages
206
+ retain their language tag but do not pretend to be Python.
207
+ """
208
+ result = {}
209
+ lines = str(text).splitlines(keepends=True)
210
+ buffer, fence, language = [], None, ""
211
+
212
+ def flush(code=False):
213
+ value = "".join(buffer)
214
+ if value or code:
215
+ cls = PythonString if language in ("python", "py", "python3") else BashString if language in ("bash", "sh", "shell", "zsh") else CodeString
216
+ result[str(len(result))] = cls(value, language) if code else MarkdownString(value)
217
+ buffer.clear()
218
+
219
+ for line in lines:
220
+ if fence is None:
221
+ match = re.match(r"^ {0,3}(`{3,}|~{3,})([^\r\n]*)\r?\n?$", line)
222
+ if match:
223
+ flush()
224
+ fence = match[1]
225
+ language = match[2].strip().split()[0].lower() if match[2].strip() else ""
226
+ else:
227
+ buffer.append(line)
228
+ elif re.fullmatch(r" {0,3}" + re.escape(fence[0]) + "{" + str(len(fence)) + r",}\s*", line):
229
+ flush(code=True)
230
+ fence = None
231
+ else:
232
+ buffer.append(line)
233
+ flush(code=fence is not None)
234
+ if not result:
235
+ result["0"] = MarkdownString("")
236
+ return result
237
+
238
+
239
+ def _reuse(target, incoming):
240
+ """Retain unchanged leaves by identity so sibling text tiles stay cached."""
241
+ for key in list(target):
242
+ if key not in incoming:
243
+ del target[key]
244
+ for key, value in incoming.items():
245
+ old = target.get(key)
246
+ if type(old) is type(value) and isinstance(value, dict):
247
+ _reuse(old, value)
248
+ elif type(old) is not type(value) or old != value or getattr(old, "language", None) != getattr(value, "language", None):
249
+ target[key] = value
250
+
251
+
252
+ def set_text(message, text):
253
+ message.source_text = text
254
+ _reuse(message["content"], text_blocks(text))
255
+
256
+
257
+ def user_message(text):
258
+ message = UserMessage("userMessage")
259
+ set_text(message, text)
260
+ return message
261
+
262
+
263
+ def input_text(message):
264
+ def render(value):
265
+ if isinstance(value, Reference):
266
+ return "" # a picture / file / skill is not prose (only its payload)
267
+ if isinstance(value, CodeString):
268
+ return "```" + value.language + "\n" + str(value) + ("" if value.endswith("\n") else "\n") + "```\n"
269
+ if isinstance(value, str):
270
+ return str(value)
271
+ if isinstance(value, dict):
272
+ return "".join(render(v) for v in value.values())
273
+ return ""
274
+ return render(message["content"])
275
+
276
+
277
+ def _input(part):
278
+ kind = part.get("type")
279
+ if kind == "text":
280
+ return text_blocks(part.get("text", ""))
281
+ cls = {"image": ImageReference, "localImage": ImageReference,
282
+ "audio": AudioReference, "localAudio": AudioReference,
283
+ "skill": SkillReference, "mention": MentionReference}.get(kind, Reference)
284
+ return cls(tagged(part))
285
+
286
+
287
+ def from_codex(item, scripts=None):
288
+ kind = item.get("type", "unknown")
289
+ cls = ITEM_TYPES.get(kind, UnknownMessage)
290
+ message = cls(kind, status=item.get("status", ""))
291
+ content = message["content"]
292
+ consumed = {"id", "type", "status"}
293
+ if cls in (AssistantMessage, PlanMessage):
294
+ set_text(message, item.get("text", ""))
295
+ consumed.add("text")
296
+ elif cls is UserMessage:
297
+ content.update((str(i), _input(part)) for i, part in enumerate(item.get("content", [])))
298
+ consumed.add("content")
299
+ elif cls is CommandExecution:
300
+ source, files = parse_command(item.get("command", ""), item.get("commandActions"), scripts, cwd=item.get("cwd") or "")
301
+ message["summary"] = FileTags((path, {"file": FileReference(path=TextString(path)),
302
+ "access": access, "added": None, "removed": None}) for path, access in files.items())
303
+ content.update(command=BashString(source),
304
+ output=ToolOutput(item.get("aggregatedOutput") or ""))
305
+ consumed.update(("command", "aggregatedOutput"))
306
+ elif cls is FileChange:
307
+ for i, change in enumerate(item.get("changes", [])):
308
+ content[str(i)] = {"file": FileReference(path=TextString(change.get("path", ""))),
309
+ "diff": DiffString(change.get("diff", ""), "diff"),
310
+ "kind": tagged(change.get("kind"))}
311
+ consumed.add("changes")
312
+ elif cls is ReasoningMessage:
313
+ content["summary"] = {str(i): text_blocks(t) for i, t in enumerate(item.get("summary", []))}
314
+ content["content"] = {str(i): text_blocks(t) for i, t in enumerate(item.get("content", []))}
315
+ consumed.update(("summary", "content"))
316
+ elif cls in (ImageView, ImageGeneration):
317
+ content["image"] = ImageReference(path=TextString(item.get("savedPath") or item.get("path") or ""),
318
+ result=TextString(item.get("result") or ""))
319
+ consumed.update(("savedPath", "path", "result"))
320
+ else:
321
+ # Tool arguments/results and log-compatible events keep their
322
+ # structure rather than being flattened into a lossy log string.
323
+ for field in ("arguments", "result", "output", "contentItems", "query", "results",
324
+ "prompt", "review", "fragments", "agentsStates", "durationMs"):
325
+ if field in item:
326
+ content[field] = tagged(item[field])
327
+ consumed.add(field)
328
+ message["details"].update((k, tagged(v)) for k, v in item.items() if k not in consumed)
329
+ if cls is FileChange:
330
+ message["summary"] = changed_files(message)
331
+ return message
332
+
333
+
334
+ def upsert(messages, identifier, incoming):
335
+ old = messages.get(identifier)
336
+ if type(old) is type(incoming):
337
+ _reuse(old, incoming)
338
+ old.source_text = incoming.source_text
339
+ return old
340
+ messages[identifier] = incoming
341
+ return incoming
342
+
343
+
344
+ def count_diff_lines(diff):
345
+ """(added, removed) of one file's diff; a bare +/- listing counts without hunks."""
346
+ added = removed = 0
347
+ has_hunks = "@@" in diff
348
+ in_hunk = not has_hunks
349
+ for line in diff.splitlines():
350
+ if line.startswith("@@"):
351
+ in_hunk = True
352
+ elif line.startswith("diff --git"):
353
+ in_hunk = False
354
+ elif not has_hunks and line.startswith(("+++ ", "--- ")):
355
+ continue
356
+ elif in_hunk and line.startswith("+"):
357
+ added += 1
358
+ elif in_hunk and line.startswith("-"):
359
+ removed += 1
360
+ return added, removed
361
+
362
+
363
+ def diff_counts(diff):
364
+ """Per-file (added, removed) of a multi-file unified diff, keyed by the new path.
365
+
366
+ Codex's turn diff names files as `diff --git a/<path> b/<path>` (deleted
367
+ files keep the `a/` side); paths stay as written, callers match them.
368
+ """
369
+ counts = {}
370
+ path, lines = None, []
371
+ def flush():
372
+ if path is not None:
373
+ counts[path] = count_diff_lines("\n".join(lines))
374
+ for line in str(diff).splitlines():
375
+ if line.startswith("diff --git "):
376
+ flush()
377
+ sides = line[len("diff --git "):].split(" b/", 1)
378
+ path = sides[1] if len(sides) == 2 else sides[0].removeprefix("a/")
379
+ lines = []
380
+ else:
381
+ lines.append(line)
382
+ flush()
383
+ return counts
384
+
385
+
386
+ def match_file(tag_path, diff_path):
387
+ """A tag holds the command's absolute path; a diff names it relative or absolute."""
388
+ tag_path, diff_path = str(tag_path), str(diff_path)
389
+ return (tag_path == diff_path or tag_path.endswith("/" + diff_path)
390
+ or diff_path.endswith("/" + tag_path))
391
+
392
+
393
+ def changed_files(message):
394
+ """Normalize provider-reported diffs; command summaries are parsed on ingestion."""
395
+ if not isinstance(message, FileChange):
396
+ return message.get("summary", FileTags())
397
+ signature = tuple((key, change.get("diff"), str(change.get("file", {}).get("path", "")))
398
+ for key, change in message["content"].items() if isinstance(change, dict))
399
+ cached = getattr(message, "_changed_files", None)
400
+ if (cached is not None and len(cached[0]) == len(signature)
401
+ and all(a[0] == b[0] and a[1] is b[1] and a[2] == b[2]
402
+ for a, b in zip(cached[0], signature))):
403
+ return cached[1]
404
+ files = FileTags()
405
+ for change in message["content"].values():
406
+ if not isinstance(change, dict) or not isinstance(change.get("file"), FileReference):
407
+ continue
408
+ path = str(change["file"].get("path", ""))
409
+ if not path:
410
+ continue
411
+ added, removed = count_diff_lines(change.get("diff", ""))
412
+ entry = files.setdefault(path, {"added": 0, "removed": 0, "change": change,
413
+ "file": change["file"], "access": "write"})
414
+ entry["added"] += added
415
+ entry["removed"] += removed
416
+ message._changed_files = (signature, files)
417
+ return files
@@ -0,0 +1,139 @@
1
+ """The chat equivalent of FileMetaCollection: account → project → chat.
2
+
3
+ No colour is assigned here: a project (a directory) wears the tint painted
4
+ on it in the shared file-meta store, a conversation the tint the user
5
+ painted on its row (``entry["tint"]``, absent until then) — the file
6
+ browser's rule, brush then picker.
7
+ """
8
+
9
+ from meltygui.core.conversion.dict_conversion import DictConversion
10
+
11
+
12
+ class ChatMetadata(DictConversion):
13
+ def __init__(self):
14
+ super().__init__()
15
+ self.name = "Chat Metadata"
16
+ self.accounts = {}
17
+
18
+ def account(self, account_id):
19
+ return self.accounts.setdefault(account_id, {"projects": {}})
20
+
21
+ def project(self, account_id, project):
22
+ projects = self.account(account_id)["projects"]
23
+ if project not in projects:
24
+ projects[project] = {"expanded": True, "order": len(projects), "chats": {}}
25
+ return projects[project]
26
+
27
+ def conversation(self, account_id, project, key):
28
+ # Moving a conversation between projects moves its metadata entry too.
29
+ target = self.project(account_id, project)
30
+ if key not in target["chats"]:
31
+ for entry in self.account(account_id)["projects"].values():
32
+ if key in entry["chats"]:
33
+ target["chats"][key] = entry["chats"].pop(key)
34
+ break
35
+ else:
36
+ target["chats"][key] = {"order": len(target["chats"])}
37
+ return target["chats"][key]
38
+
39
+ def apply(self, account_id, chats):
40
+ orders = {}
41
+ for key, chat in chats.items():
42
+ project = self.project(account_id, chat["project"])
43
+ entry = self.conversation(account_id, chat["project"], key)
44
+ # These are local overrides, not remotely editable chat fields.
45
+ chat["__overrides__"] = {"tint": entry.get("tint")}
46
+ if hasattr(chat, "applied_tint"):
47
+ chat.applied_tint = entry.get("tint")
48
+ chat.metadata = entry
49
+ orders[key] = (project["order"], entry["order"])
50
+ ordered = sorted(chats, key=lambda key: orders[key])
51
+ if ordered != list(chats): # re-insert only when the order moved (this runs every frame)
52
+ for key in ordered:
53
+ dict.__setitem__(chats, key, dict.pop(chats, key))
54
+ if hasattr(chats, "applied_order"):
55
+ chats.applied_order = list(chats)
56
+
57
+ def collect(self, account_id, chats):
58
+ positions = {}
59
+ order_changed = list(chats) != getattr(chats, "applied_order", None)
60
+ for key, chat in chats.items():
61
+ project = chat["project"]
62
+ entry = self.conversation(account_id, project, key)
63
+ position = positions.get(project, 0)
64
+ if order_changed:
65
+ entry["order"] = position
66
+ positions[project] = position + 1
67
+ tint = chat.get("__overrides__", {}).get("tint")
68
+ if tint is not None and tint != getattr(chat, "applied_tint", None):
69
+ entry["tint"] = tuple(tint)
70
+ if getattr(chat, "remote_id", None):
71
+ entry["remote_id"] = chat.remote_id
72
+
73
+ def local_key(self, account_id, remote_id):
74
+ for project in self.account(account_id)["projects"].values():
75
+ for key, entry in project["chats"].items():
76
+ if entry.get("remote_id") == remote_id:
77
+ return key
78
+ return remote_id
79
+
80
+
81
+ _app_metadata = None
82
+
83
+
84
+ def shared_metadata():
85
+ """Resolve the persistent mirror at the model boundary, never in a view.
86
+
87
+ In the studio it lives on the root model and is saved with the session.
88
+ An app on meltygui has no root: every proxy then shares one process-wide
89
+ instance, persisted to a JSON file when the app asked for it with
90
+ `persistent_metadata(path)`."""
91
+ global _app_metadata
92
+ from meltygui.core.melty import Melty
93
+ root = getattr(getattr(Melty, "vis", None), "root", None)
94
+ if root is None:
95
+ if _app_metadata is None:
96
+ _app_metadata = ChatMetadata()
97
+ return _app_metadata
98
+ if not hasattr(root, "chat_metadata"):
99
+ root.chat_metadata = ChatMetadata()
100
+ return root.chat_metadata
101
+
102
+
103
+ def persistent_metadata(path):
104
+ """The app-wide ChatMetadata, loaded from ``path`` (a JSON file; missing
105
+ or unreadable starts empty) and written back at interpreter exit. Call
106
+ it once before the first chat proxy is created."""
107
+ import atexit
108
+ import json
109
+ import os
110
+ import pathlib
111
+ global _app_metadata
112
+ path = pathlib.Path(path).expanduser()
113
+ metadata = shared_metadata()
114
+ try:
115
+ accounts = json.loads(path.read_text()).get("accounts", {})
116
+ except (OSError, ValueError):
117
+ accounts = {}
118
+ for account in accounts.values():
119
+ for project in account.get("projects", {}).values():
120
+ project.pop("tint", None) # a project's tint is the folder's, in the project-meta store
121
+ for chat in project.get("chats", {}).values():
122
+ if chat.get("tint") is not None:
123
+ chat["tint"] = tuple(chat["tint"])
124
+ else:
125
+ chat.pop("tint", None)
126
+ metadata.accounts.update(accounts)
127
+
128
+ def save():
129
+ try:
130
+ path.parent.mkdir(parents=True, exist_ok=True)
131
+ tmp = path.with_suffix(path.suffix + ".tmp")
132
+ tmp.write_text(json.dumps({"accounts": metadata.accounts}, indent=1))
133
+ os.replace(tmp, path)
134
+ except (OSError, TypeError, ValueError) as error:
135
+ print(f"chat metadata: cannot write {path}: {error}")
136
+
137
+ atexit.register(save)
138
+ metadata.save = save
139
+ return metadata
@@ -0,0 +1,64 @@
1
+ """Read Linux's held file locks without acquiring or creating session locks."""
2
+ import os
3
+ from pathlib import Path
4
+
5
+
6
+ def codex_writer_locks(home, locks_path='/proc/locks'):
7
+ try:
8
+ held = {}
9
+ for line in Path(locks_path).read_text().splitlines():
10
+ fields = line.split()
11
+ # Lock requests have a '->' marker; only held WRITE locks
12
+ # mean the session has a writer. File existence alone is not a lock.
13
+ if len(fields) < 6 or fields[1] == '->' or fields[3] != 'WRITE':
14
+ continue
15
+ major, minor, inode = fields[5].split(':')
16
+ held[(int(major, 16), int(minor, 16), int(inode))] = int(fields[4])
17
+ locked = {}
18
+ for path in (Path(home) / 'thread-writer-locks').glob('*.lock'):
19
+ if path.name.startswith('.'):
20
+ continue
21
+ try:
22
+ stat = path.stat()
23
+ except OSError:
24
+ continue
25
+ if (os.major(stat.st_dev), os.minor(stat.st_dev), stat.st_ino) in held:
26
+ locked[path.stem] = lock_owner(held[(os.major(stat.st_dev), os.minor(stat.st_dev), stat.st_ino)])
27
+ return locked
28
+ except (OSError, ValueError):
29
+ return None # unavailable is not evidence that previous locks released
30
+
31
+
32
+ def lock_owner(pid):
33
+ info = {"pid": pid, "client": "Unknown client", "location": "local machine"}
34
+ try:
35
+ executable = os.readlink(f"/proc/{pid}/exe")
36
+ name = Path(f"/proc/{pid}/comm").read_text().strip()
37
+ info["client"] = "Codex desktop app" if "/chatgpt/" in executable else name
38
+ # Read only process names/parent IDs; never arguments or environment
39
+ # variables, which may contain credentials or conversation contents.
40
+ current = pid
41
+ for _ in range(12):
42
+ status = Path(f"/proc/{current}/status").read_text().splitlines()
43
+ parent = int(next(line.split()[1] for line in status if line.startswith("PPid:")))
44
+ if not parent:
45
+ break
46
+ parent_name = Path(f"/proc/{parent}/comm").read_text().strip()
47
+ if parent_name in ("sshd", "sshd-session"):
48
+ info["location"] = "local machine, launched through SSH"
49
+ break
50
+ current = parent
51
+ except (OSError, ValueError, StopIteration):
52
+ pass
53
+ return info
54
+
55
+
56
+ def lock_message(owner=None):
57
+ owner = owner or {}
58
+ identity = owner.get("client", "another client")
59
+ if owner.get("pid", -1) > 0:
60
+ identity += f" (PID {owner['pid']}, {owner.get('location', 'local machine')})"
61
+ return (f"This conversation is open for writing in {identity}.\n"
62
+ "To release it, finish or stop its active turn, then close the owning app or CLI session. "
63
+ "Stopping a turn alone may leave the session locked. You can still try Send if this status looks stale.\n"
64
+ "Your unsent message stays in the composer. A remotely connected client cannot be identified from this local lock.")
File without changes