dulus 0.2.50__tar.gz → 0.2.52__tar.gz

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 (359) hide show
  1. {dulus-0.2.50/dulus.egg-info → dulus-0.2.52}/PKG-INFO +3 -2
  2. {dulus-0.2.50 → dulus-0.2.52}/README.md +2 -1
  3. {dulus-0.2.50 → dulus-0.2.52}/data/context.json +19 -32
  4. {dulus-0.2.50 → dulus-0.2.52/dulus.egg-info}/PKG-INFO +3 -2
  5. {dulus-0.2.50 → dulus-0.2.52}/dulus.py +15 -27
  6. {dulus-0.2.50 → dulus-0.2.52}/dulus_gui.py +54 -21
  7. {dulus-0.2.50 → dulus-0.2.52}/gui/agent_bridge.py +2 -2
  8. {dulus-0.2.50 → dulus-0.2.52}/gui/session_utils.py +60 -52
  9. {dulus-0.2.50 → dulus-0.2.52}/gui/sidebar.py +79 -53
  10. {dulus-0.2.50 → dulus-0.2.52}/pyproject.toml +1 -1
  11. {dulus-0.2.50 → dulus-0.2.52}/webchat_server.py +15 -1
  12. {dulus-0.2.50 → dulus-0.2.52}/LICENSE +0 -0
  13. {dulus-0.2.50 → dulus-0.2.52}/MANIFEST.in +0 -0
  14. {dulus-0.2.50 → dulus-0.2.52}/agent.py +0 -0
  15. {dulus-0.2.50 → dulus-0.2.52}/backend/__init__.py +0 -0
  16. {dulus-0.2.50 → dulus-0.2.52}/backend/agents_bridge.py +0 -0
  17. {dulus-0.2.50 → dulus-0.2.52}/backend/compressor.py +0 -0
  18. {dulus-0.2.50 → dulus-0.2.52}/backend/context.py +0 -0
  19. {dulus-0.2.50 → dulus-0.2.52}/backend/githook.py +0 -0
  20. {dulus-0.2.50 → dulus-0.2.52}/backend/marketplace.py +0 -0
  21. {dulus-0.2.50 → dulus-0.2.52}/backend/mempalace_bridge.py +0 -0
  22. {dulus-0.2.50 → dulus-0.2.52}/backend/personas.py +0 -0
  23. {dulus-0.2.50 → dulus-0.2.52}/backend/plugins.py +0 -0
  24. {dulus-0.2.50 → dulus-0.2.52}/backend/server.py +0 -0
  25. {dulus-0.2.50 → dulus-0.2.52}/backend/tasks.py +0 -0
  26. {dulus-0.2.50 → dulus-0.2.52}/batch_api.py +0 -0
  27. {dulus-0.2.50 → dulus-0.2.52}/checkpoint/__init__.py +0 -0
  28. {dulus-0.2.50 → dulus-0.2.52}/checkpoint/hooks.py +0 -0
  29. {dulus-0.2.50 → dulus-0.2.52}/checkpoint/store.py +0 -0
  30. {dulus-0.2.50 → dulus-0.2.52}/checkpoint/types.py +0 -0
  31. {dulus-0.2.50 → dulus-0.2.52}/claude_code_watcher.py +0 -0
  32. {dulus-0.2.50 → dulus-0.2.52}/clipboard_utils.py +0 -0
  33. {dulus-0.2.50 → dulus-0.2.52}/cloudsave.py +0 -0
  34. {dulus-0.2.50 → dulus-0.2.52}/common.py +0 -0
  35. {dulus-0.2.50 → dulus-0.2.52}/compaction.py +0 -0
  36. {dulus-0.2.50 → dulus-0.2.52}/config.py +0 -0
  37. {dulus-0.2.50 → dulus-0.2.52}/context.py +0 -0
  38. {dulus-0.2.50 → dulus-0.2.52}/data/__init__.py +0 -0
  39. {dulus-0.2.50 → dulus-0.2.52}/data/active_persona.json +0 -0
  40. {dulus-0.2.50 → dulus-0.2.52}/data/marketplace.json +0 -0
  41. {dulus-0.2.50 → dulus-0.2.52}/data/personas.json +0 -0
  42. {dulus-0.2.50 → dulus-0.2.52}/data/plugins/__init__.py +0 -0
  43. {dulus-0.2.50 → dulus-0.2.52}/data/plugins/composio/__init__.py +0 -0
  44. {dulus-0.2.50 → dulus-0.2.52}/data/plugins/composio/composio_plugin/__init__.py +0 -0
  45. {dulus-0.2.50 → dulus-0.2.52}/data/plugins/composio/composio_plugin/session_manager.py +0 -0
  46. {dulus-0.2.50 → dulus-0.2.52}/data/plugins/composio/composio_plugin/tool_generator.py +0 -0
  47. {dulus-0.2.50 → dulus-0.2.52}/data/plugins/composio/plugin.json +0 -0
  48. {dulus-0.2.50 → dulus-0.2.52}/data/plugins/composio/plugin_tool.py +0 -0
  49. {dulus-0.2.50 → dulus-0.2.52}/data/tasks.json +0 -0
  50. {dulus-0.2.50 → dulus-0.2.52}/docs/README.md +0 -0
  51. {dulus-0.2.50 → dulus-0.2.52}/docs/__init__.py +0 -0
  52. {dulus-0.2.50 → dulus-0.2.52}/docs/api.html +0 -0
  53. {dulus-0.2.50 → dulus-0.2.52}/docs/architecture.md +0 -0
  54. {dulus-0.2.50 → dulus-0.2.52}/docs/azure-speech-template.json +0 -0
  55. {dulus-0.2.50 → dulus-0.2.52}/docs/dashboard/index.html +0 -0
  56. {dulus-0.2.50 → dulus-0.2.52}/docs/divider.svg +0 -0
  57. {dulus-0.2.50 → dulus-0.2.52}/docs/generate.py +0 -0
  58. {dulus-0.2.50 → dulus-0.2.52}/docs/hero.svg +0 -0
  59. {dulus-0.2.50 → dulus-0.2.52}/docs/index.html +0 -0
  60. {dulus-0.2.50 → dulus-0.2.52}/docs/news.md +0 -0
  61. {dulus-0.2.50 → dulus-0.2.52}/docs/nvidia-models.svg +0 -0
  62. {dulus-0.2.50 → dulus-0.2.52}/docs/particle-playground.html +0 -0
  63. {dulus-0.2.50 → dulus-0.2.52}/docs/personas/index.html +0 -0
  64. {dulus-0.2.50 → dulus-0.2.52}/docs/poetry-banner.png +0 -0
  65. {dulus-0.2.50 → dulus-0.2.52}/docs/preview.html +0 -0
  66. {dulus-0.2.50 → dulus-0.2.52}/docs/sec-agents.svg +0 -0
  67. {dulus-0.2.50 → dulus-0.2.52}/docs/sec-brainstorm.svg +0 -0
  68. {dulus-0.2.50 → dulus-0.2.52}/docs/sec-bridges.svg +0 -0
  69. {dulus-0.2.50 → dulus-0.2.52}/docs/sec-features.svg +0 -0
  70. {dulus-0.2.50 → dulus-0.2.52}/docs/sec-freetier.svg +0 -0
  71. {dulus-0.2.50 → dulus-0.2.52}/docs/sec-memory.svg +0 -0
  72. {dulus-0.2.50 → dulus-0.2.52}/docs/sec-models.svg +0 -0
  73. {dulus-0.2.50 → dulus-0.2.52}/docs/sec-perms.svg +0 -0
  74. {dulus-0.2.50 → dulus-0.2.52}/docs/sec-plugins.svg +0 -0
  75. {dulus-0.2.50 → dulus-0.2.52}/docs/sec-quickstart.svg +0 -0
  76. {dulus-0.2.50 → dulus-0.2.52}/docs/sec-ssj.svg +0 -0
  77. {dulus-0.2.50 → dulus-0.2.52}/docs/spinners.svg +0 -0
  78. {dulus-0.2.50 → dulus-0.2.52}/docs/split-pane.svg +0 -0
  79. {dulus-0.2.50 → dulus-0.2.52}/docs/terminal-boot.svg +0 -0
  80. {dulus-0.2.50 → dulus-0.2.52}/docs/uploads/particle-playground.html +0 -0
  81. {dulus-0.2.50 → dulus-0.2.52}/dulus.egg-info/SOURCES.txt +0 -0
  82. {dulus-0.2.50 → dulus-0.2.52}/dulus.egg-info/dependency_links.txt +0 -0
  83. {dulus-0.2.50 → dulus-0.2.52}/dulus.egg-info/entry_points.txt +0 -0
  84. {dulus-0.2.50 → dulus-0.2.52}/dulus.egg-info/requires.txt +0 -0
  85. {dulus-0.2.50 → dulus-0.2.52}/dulus.egg-info/top_level.txt +0 -0
  86. {dulus-0.2.50 → dulus-0.2.52}/dulus_mcp/__init__.py +0 -0
  87. {dulus-0.2.50 → dulus-0.2.52}/dulus_mcp/client.py +0 -0
  88. {dulus-0.2.50 → dulus-0.2.52}/dulus_mcp/config.py +0 -0
  89. {dulus-0.2.50 → dulus-0.2.52}/dulus_mcp/tools.py +0 -0
  90. {dulus-0.2.50 → dulus-0.2.52}/dulus_mcp/types.py +0 -0
  91. {dulus-0.2.50 → dulus-0.2.52}/gui/__init__.py +0 -0
  92. {dulus-0.2.50 → dulus-0.2.52}/gui/chat_widget.py +0 -0
  93. {dulus-0.2.50 → dulus-0.2.52}/gui/main_window.py +0 -0
  94. {dulus-0.2.50 → dulus-0.2.52}/gui/personas.py +0 -0
  95. {dulus-0.2.50 → dulus-0.2.52}/gui/settings_dialog.py +0 -0
  96. {dulus-0.2.50 → dulus-0.2.52}/gui/tasks_view.py +0 -0
  97. {dulus-0.2.50 → dulus-0.2.52}/gui/themes.py +0 -0
  98. {dulus-0.2.50 → dulus-0.2.52}/gui/tool_panel.py +0 -0
  99. {dulus-0.2.50 → dulus-0.2.52}/input.py +0 -0
  100. {dulus-0.2.50 → dulus-0.2.52}/license_manager.py +0 -0
  101. {dulus-0.2.50 → dulus-0.2.52}/memory/__init__.py +0 -0
  102. {dulus-0.2.50 → dulus-0.2.52}/memory/audit.py +0 -0
  103. {dulus-0.2.50 → dulus-0.2.52}/memory/consolidator.py +0 -0
  104. {dulus-0.2.50 → dulus-0.2.52}/memory/context.py +0 -0
  105. {dulus-0.2.50 → dulus-0.2.52}/memory/offload.py +0 -0
  106. {dulus-0.2.50 → dulus-0.2.52}/memory/palace.py +0 -0
  107. {dulus-0.2.50 → dulus-0.2.52}/memory/scan.py +0 -0
  108. {dulus-0.2.50 → dulus-0.2.52}/memory/sessions.py +0 -0
  109. {dulus-0.2.50 → dulus-0.2.52}/memory/store.py +0 -0
  110. {dulus-0.2.50 → dulus-0.2.52}/memory/tools.py +0 -0
  111. {dulus-0.2.50 → dulus-0.2.52}/memory/types.py +0 -0
  112. {dulus-0.2.50 → dulus-0.2.52}/memory/vector_search.py +0 -0
  113. {dulus-0.2.50 → dulus-0.2.52}/multi_agent/__init__.py +0 -0
  114. {dulus-0.2.50 → dulus-0.2.52}/multi_agent/subagent.py +0 -0
  115. {dulus-0.2.50 → dulus-0.2.52}/multi_agent/tools.py +0 -0
  116. {dulus-0.2.50 → dulus-0.2.52}/offload_helper.py +0 -0
  117. {dulus-0.2.50 → dulus-0.2.52}/plugin/__init__.py +0 -0
  118. {dulus-0.2.50 → dulus-0.2.52}/plugin/autoadapter.py +0 -0
  119. {dulus-0.2.50 → dulus-0.2.52}/plugin/loader.py +0 -0
  120. {dulus-0.2.50 → dulus-0.2.52}/plugin/recommend.py +0 -0
  121. {dulus-0.2.50 → dulus-0.2.52}/plugin/store.py +0 -0
  122. {dulus-0.2.50 → dulus-0.2.52}/plugin/types.py +0 -0
  123. {dulus-0.2.50 → dulus-0.2.52}/providers.py +0 -0
  124. {dulus-0.2.50 → dulus-0.2.52}/sandbox/README.md +0 -0
  125. {dulus-0.2.50 → dulus-0.2.52}/sandbox/__init__.py +0 -0
  126. {dulus-0.2.50 → dulus-0.2.52}/sandbox/components.json +0 -0
  127. {dulus-0.2.50 → dulus-0.2.52}/sandbox/dist/assets/index-DE51D6wI.css +0 -0
  128. {dulus-0.2.50 → dulus-0.2.52}/sandbox/dist/assets/index-DMCCNE9Y.js +0 -0
  129. {dulus-0.2.50 → dulus-0.2.52}/sandbox/dist/index.html +0 -0
  130. {dulus-0.2.50 → dulus-0.2.52}/sandbox/dist/wallpaper-default.jpg +0 -0
  131. {dulus-0.2.50 → dulus-0.2.52}/sandbox/dist/wallpapers/default.jpeg +0 -0
  132. {dulus-0.2.50 → dulus-0.2.52}/sandbox/dist/wallpapers/light.jpeg +0 -0
  133. {dulus-0.2.50 → dulus-0.2.52}/sandbox/dist/wallpapers/nature.jpeg +0 -0
  134. {dulus-0.2.50 → dulus-0.2.52}/sandbox/dist/wallpapers/tech.jpeg +0 -0
  135. {dulus-0.2.50 → dulus-0.2.52}/sandbox/eslint.config.js +0 -0
  136. {dulus-0.2.50 → dulus-0.2.52}/sandbox/index.html +0 -0
  137. {dulus-0.2.50 → dulus-0.2.52}/sandbox/info.md +0 -0
  138. {dulus-0.2.50 → dulus-0.2.52}/sandbox/package-lock.json +0 -0
  139. {dulus-0.2.50 → dulus-0.2.52}/sandbox/package.json +0 -0
  140. {dulus-0.2.50 → dulus-0.2.52}/sandbox/postcss.config.js +0 -0
  141. {dulus-0.2.50 → dulus-0.2.52}/sandbox/public/wallpaper-default.jpg +0 -0
  142. {dulus-0.2.50 → dulus-0.2.52}/sandbox/public/wallpapers/default.jpeg +0 -0
  143. {dulus-0.2.50 → dulus-0.2.52}/sandbox/public/wallpapers/light.jpeg +0 -0
  144. {dulus-0.2.50 → dulus-0.2.52}/sandbox/public/wallpapers/nature.jpeg +0 -0
  145. {dulus-0.2.50 → dulus-0.2.52}/sandbox/public/wallpapers/tech.jpeg +0 -0
  146. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/App.tsx +0 -0
  147. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/apps/AgentMonitor.tsx +0 -0
  148. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/apps/ApiTester.tsx +0 -0
  149. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/apps/AppRouter.tsx +0 -0
  150. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/apps/ArchiveManager.tsx +0 -0
  151. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/apps/AsciiArt.tsx +0 -0
  152. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/apps/Base64Tool.tsx +0 -0
  153. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/apps/Browser.tsx +0 -0
  154. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/apps/Calculator.tsx +0 -0
  155. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/apps/Calendar.tsx +0 -0
  156. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/apps/Chat.tsx +0 -0
  157. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/apps/Chess.tsx +0 -0
  158. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/apps/Clock.tsx +0 -0
  159. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/apps/CodeEditor.tsx +0 -0
  160. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/apps/ColorPalette.tsx +0 -0
  161. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/apps/ColorPicker.tsx +0 -0
  162. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/apps/Contacts.tsx +0 -0
  163. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/apps/DocumentViewer.tsx +0 -0
  164. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/apps/Drawing.tsx +0 -0
  165. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/apps/Email.tsx +0 -0
  166. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/apps/FileManager.tsx +0 -0
  167. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/apps/FlappyBird.tsx +0 -0
  168. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/apps/FtpClient.tsx +0 -0
  169. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/apps/Game2048.tsx +0 -0
  170. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/apps/GitClient.tsx +0 -0
  171. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/apps/ImageGallery.tsx +0 -0
  172. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/apps/ImageViewer.tsx +0 -0
  173. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/apps/JsonFormatter.tsx +0 -0
  174. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/apps/MarkdownPreview.tsx +0 -0
  175. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/apps/MatrixRain.tsx +0 -0
  176. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/apps/MediaConverter.tsx +0 -0
  177. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/apps/Memory.tsx +0 -0
  178. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/apps/MemoryManager.tsx +0 -0
  179. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/apps/Minesweeper.tsx +0 -0
  180. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/apps/MusicPlayer.tsx +0 -0
  181. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/apps/NetworkTools.tsx +0 -0
  182. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/apps/Notes.tsx +0 -0
  183. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/apps/PasswordManager.tsx +0 -0
  184. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/apps/PhotoEditor.tsx +0 -0
  185. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/apps/Pong.tsx +0 -0
  186. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/apps/RegexTester.tsx +0 -0
  187. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/apps/Reminders.tsx +0 -0
  188. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/apps/RssReader.tsx +0 -0
  189. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/apps/ScreenRecorder.tsx +0 -0
  190. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/apps/Settings.tsx +0 -0
  191. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/apps/SkillsLauncher.tsx +0 -0
  192. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/apps/Snake.tsx +0 -0
  193. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/apps/Solitaire.tsx +0 -0
  194. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/apps/Spreadsheet.tsx +0 -0
  195. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/apps/Sudoku.tsx +0 -0
  196. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/apps/SystemMonitor.tsx +0 -0
  197. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/apps/TaskManager.tsx +0 -0
  198. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/apps/Terminal.tsx +0 -0
  199. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/apps/Tetris.tsx +0 -0
  200. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/apps/TextEditor.tsx +0 -0
  201. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/apps/TicTacToe.tsx +0 -0
  202. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/apps/Todo.tsx +0 -0
  203. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/apps/VideoPlayer.tsx +0 -0
  204. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/apps/VoiceRecorder.tsx +0 -0
  205. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/apps/Weather.tsx +0 -0
  206. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/apps/Whiteboard.tsx +0 -0
  207. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/apps/registry.ts +0 -0
  208. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/components/AppContainer.tsx +0 -0
  209. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/components/AppLauncher.tsx +0 -0
  210. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/components/BootSequence.tsx +0 -0
  211. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/components/ContextMenu.tsx +0 -0
  212. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/components/Desktop.tsx +0 -0
  213. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/components/Dock.tsx +0 -0
  214. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/components/LoginScreen.tsx +0 -0
  215. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/components/NotImplemented.tsx +0 -0
  216. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/components/NotificationCenter.tsx +0 -0
  217. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/components/NotificationSystem.tsx +0 -0
  218. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/components/TopPanel.tsx +0 -0
  219. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/components/WindowFrame.tsx +0 -0
  220. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/components/WindowManager.tsx +0 -0
  221. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/components/ui/accordion.tsx +0 -0
  222. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/components/ui/alert-dialog.tsx +0 -0
  223. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/components/ui/alert.tsx +0 -0
  224. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/components/ui/aspect-ratio.tsx +0 -0
  225. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/components/ui/avatar.tsx +0 -0
  226. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/components/ui/badge.tsx +0 -0
  227. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/components/ui/breadcrumb.tsx +0 -0
  228. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/components/ui/button-group.tsx +0 -0
  229. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/components/ui/button.tsx +0 -0
  230. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/components/ui/calendar.tsx +0 -0
  231. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/components/ui/card.tsx +0 -0
  232. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/components/ui/carousel.tsx +0 -0
  233. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/components/ui/chart.tsx +0 -0
  234. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/components/ui/checkbox.tsx +0 -0
  235. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/components/ui/collapsible.tsx +0 -0
  236. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/components/ui/command.tsx +0 -0
  237. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/components/ui/context-menu.tsx +0 -0
  238. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/components/ui/dialog.tsx +0 -0
  239. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/components/ui/drawer.tsx +0 -0
  240. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/components/ui/dropdown-menu.tsx +0 -0
  241. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/components/ui/empty.tsx +0 -0
  242. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/components/ui/field.tsx +0 -0
  243. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/components/ui/form.tsx +0 -0
  244. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/components/ui/hover-card.tsx +0 -0
  245. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/components/ui/input-group.tsx +0 -0
  246. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/components/ui/input-otp.tsx +0 -0
  247. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/components/ui/input.tsx +0 -0
  248. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/components/ui/item.tsx +0 -0
  249. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/components/ui/kbd.tsx +0 -0
  250. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/components/ui/label.tsx +0 -0
  251. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/components/ui/menubar.tsx +0 -0
  252. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/components/ui/navigation-menu.tsx +0 -0
  253. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/components/ui/pagination.tsx +0 -0
  254. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/components/ui/popover.tsx +0 -0
  255. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/components/ui/progress.tsx +0 -0
  256. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/components/ui/radio-group.tsx +0 -0
  257. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/components/ui/resizable.tsx +0 -0
  258. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/components/ui/scroll-area.tsx +0 -0
  259. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/components/ui/select.tsx +0 -0
  260. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/components/ui/separator.tsx +0 -0
  261. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/components/ui/sheet.tsx +0 -0
  262. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/components/ui/sidebar.tsx +0 -0
  263. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/components/ui/skeleton.tsx +0 -0
  264. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/components/ui/slider.tsx +0 -0
  265. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/components/ui/sonner.tsx +0 -0
  266. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/components/ui/spinner.tsx +0 -0
  267. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/components/ui/switch.tsx +0 -0
  268. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/components/ui/table.tsx +0 -0
  269. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/components/ui/tabs.tsx +0 -0
  270. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/components/ui/textarea.tsx +0 -0
  271. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/components/ui/toggle-group.tsx +0 -0
  272. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/components/ui/toggle.tsx +0 -0
  273. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/components/ui/tooltip.tsx +0 -0
  274. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/hooks/index.ts +0 -0
  275. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/hooks/use-mobile.ts +0 -0
  276. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/hooks/useAutoOpenChat.ts +0 -0
  277. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/hooks/useDulusAgents.ts +0 -0
  278. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/hooks/useDulusChat.ts +0 -0
  279. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/hooks/useDulusEvents.ts +0 -0
  280. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/hooks/useDulusHealth.ts +0 -0
  281. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/hooks/useDulusMemory.ts +0 -0
  282. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/hooks/useDulusSkills.ts +0 -0
  283. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/hooks/useDulusTasks.ts +0 -0
  284. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/hooks/useFileSystem.ts +0 -0
  285. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/hooks/useOSStore.tsx +0 -0
  286. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/hooks/useSkillBridge.ts +0 -0
  287. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/hooks/useSystemBattery.ts +0 -0
  288. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/hooks/useSystemNetwork.ts +0 -0
  289. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/hooks/useSystemVolume.ts +0 -0
  290. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/index.css +0 -0
  291. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/lib/dulus-api.ts +0 -0
  292. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/lib/utils.ts +0 -0
  293. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/main.tsx +0 -0
  294. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/types/index.ts +0 -0
  295. {dulus-0.2.50 → dulus-0.2.52}/sandbox/src/utils/assets.ts +0 -0
  296. {dulus-0.2.50 → dulus-0.2.52}/sandbox/tailwind.config.js +0 -0
  297. {dulus-0.2.50 → dulus-0.2.52}/sandbox/tsconfig.app.json +0 -0
  298. {dulus-0.2.50 → dulus-0.2.52}/sandbox/tsconfig.json +0 -0
  299. {dulus-0.2.50 → dulus-0.2.52}/sandbox/tsconfig.node.json +0 -0
  300. {dulus-0.2.50 → dulus-0.2.52}/sandbox/vite.config.ts +0 -0
  301. {dulus-0.2.50 → dulus-0.2.52}/setup.cfg +0 -0
  302. {dulus-0.2.50 → dulus-0.2.52}/skill/__init__.py +0 -0
  303. {dulus-0.2.50 → dulus-0.2.52}/skill/builtin.py +0 -0
  304. {dulus-0.2.50 → dulus-0.2.52}/skill/clawhub.py +0 -0
  305. {dulus-0.2.50 → dulus-0.2.52}/skill/executor.py +0 -0
  306. {dulus-0.2.50 → dulus-0.2.52}/skill/loader.py +0 -0
  307. {dulus-0.2.50 → dulus-0.2.52}/skill/tools.py +0 -0
  308. {dulus-0.2.50 → dulus-0.2.52}/skills.py +0 -0
  309. {dulus-0.2.50 → dulus-0.2.52}/spinner.py +0 -0
  310. {dulus-0.2.50 → dulus-0.2.52}/string_utils.py +0 -0
  311. {dulus-0.2.50 → dulus-0.2.52}/subagent.py +0 -0
  312. {dulus-0.2.50 → dulus-0.2.52}/task/__init__.py +0 -0
  313. {dulus-0.2.50 → dulus-0.2.52}/task/store.py +0 -0
  314. {dulus-0.2.50 → dulus-0.2.52}/task/tools.py +0 -0
  315. {dulus-0.2.50 → dulus-0.2.52}/task/types.py +0 -0
  316. {dulus-0.2.50 → dulus-0.2.52}/tests/test_afk_yolo.py +0 -0
  317. {dulus-0.2.50 → dulus-0.2.52}/tests/test_approval_runtime.py +0 -0
  318. {dulus-0.2.50 → dulus-0.2.52}/tests/test_background_task_tools.py +0 -0
  319. {dulus-0.2.50 → dulus-0.2.52}/tests/test_background_tasks.py +0 -0
  320. {dulus-0.2.50 → dulus-0.2.52}/tests/test_checkpoint.py +0 -0
  321. {dulus-0.2.50 → dulus-0.2.52}/tests/test_clipboard_utils.py +0 -0
  322. {dulus-0.2.50 → dulus-0.2.52}/tests/test_compaction.py +0 -0
  323. {dulus-0.2.50 → dulus-0.2.52}/tests/test_diff_view.py +0 -0
  324. {dulus-0.2.50 → dulus-0.2.52}/tests/test_diff_visualization.py +0 -0
  325. {dulus-0.2.50 → dulus-0.2.52}/tests/test_display_blocks.py +0 -0
  326. {dulus-0.2.50 → dulus-0.2.52}/tests/test_export_import.py +0 -0
  327. {dulus-0.2.50 → dulus-0.2.52}/tests/test_hook_engine.py +0 -0
  328. {dulus-0.2.50 → dulus-0.2.52}/tests/test_injection_fix.py +0 -0
  329. {dulus-0.2.50 → dulus-0.2.52}/tests/test_license.py +0 -0
  330. {dulus-0.2.50 → dulus-0.2.52}/tests/test_mcp.py +0 -0
  331. {dulus-0.2.50 → dulus-0.2.52}/tests/test_memory.py +0 -0
  332. {dulus-0.2.50 → dulus-0.2.52}/tests/test_notification_manager.py +0 -0
  333. {dulus-0.2.50 → dulus-0.2.52}/tests/test_plugin.py +0 -0
  334. {dulus-0.2.50 → dulus-0.2.52}/tests/test_session_fork.py +0 -0
  335. {dulus-0.2.50 → dulus-0.2.52}/tests/test_shell_mode.py +0 -0
  336. {dulus-0.2.50 → dulus-0.2.52}/tests/test_skills.py +0 -0
  337. {dulus-0.2.50 → dulus-0.2.52}/tests/test_steer_input.py +0 -0
  338. {dulus-0.2.50 → dulus-0.2.52}/tests/test_subagent.py +0 -0
  339. {dulus-0.2.50 → dulus-0.2.52}/tests/test_task.py +0 -0
  340. {dulus-0.2.50 → dulus-0.2.52}/tests/test_telegram_buffer.py +0 -0
  341. {dulus-0.2.50 → dulus-0.2.52}/tests/test_think_tool.py +0 -0
  342. {dulus-0.2.50 → dulus-0.2.52}/tests/test_todo_tool.py +0 -0
  343. {dulus-0.2.50 → dulus-0.2.52}/tests/test_todo_visualization.py +0 -0
  344. {dulus-0.2.50 → dulus-0.2.52}/tests/test_tool_registry.py +0 -0
  345. {dulus-0.2.50 → dulus-0.2.52}/tests/test_voice.py +0 -0
  346. {dulus-0.2.50 → dulus-0.2.52}/tests/test_wire_events.py +0 -0
  347. {dulus-0.2.50 → dulus-0.2.52}/tmux_offloader.py +0 -0
  348. {dulus-0.2.50 → dulus-0.2.52}/tmux_tools.py +0 -0
  349. {dulus-0.2.50 → dulus-0.2.52}/tool_registry.py +0 -0
  350. {dulus-0.2.50 → dulus-0.2.52}/tools.py +0 -0
  351. {dulus-0.2.50 → dulus-0.2.52}/ui/__init__.py +0 -0
  352. {dulus-0.2.50 → dulus-0.2.52}/ui/input.py +0 -0
  353. {dulus-0.2.50 → dulus-0.2.52}/ui/render.py +0 -0
  354. {dulus-0.2.50 → dulus-0.2.52}/voice/__init__.py +0 -0
  355. {dulus-0.2.50 → dulus-0.2.52}/voice/keyterms.py +0 -0
  356. {dulus-0.2.50 → dulus-0.2.52}/voice/recorder.py +0 -0
  357. {dulus-0.2.50 → dulus-0.2.52}/voice/stt.py +0 -0
  358. {dulus-0.2.50 → dulus-0.2.52}/voice/tts.py +0 -0
  359. {dulus-0.2.50 → dulus-0.2.52}/webchat.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dulus
3
- Version: 0.2.50
3
+ Version: 0.2.52
4
4
  Summary: Spanish-first multi-provider AI CLI — 14 NVIDIA models free, Mesa Redonda, voice, TTS, RTK token reducer, MemPalace
5
5
  Author: KevRojo
6
6
  License: GPL-3.0
@@ -69,7 +69,7 @@ SET /sticky_input ON since the first run for the best experience!
69
69
  <a href="https://pypi.org/project/dulus/"><img src="https://static.pepy.tech/badge/dulus?style=flat-square" alt="downloads"/></a>
70
70
  <img src="https://img.shields.io/badge/python-3.11+-ff6b1f?style=flat-square&labelColor=07070a" alt="python"/>
71
71
  <img src="https://img.shields.io/badge/license-GPLv3-ff6b1f?style=flat-square&labelColor=07070a" alt="license"/>
72
- <img src="https://img.shields.io/badge/version-v0.2.44-ff6b1f?style=flat-square&labelColor=07070a" alt="version"/>
72
+ <img src="https://img.shields.io/badge/version-v0.2.52-ff6b1f?style=flat-square&labelColor=07070a" alt="version"/>
73
73
  <img src="https://img.shields.io/badge/providers-11-ff6b1f?style=flat-square&labelColor=07070a" alt="providers"/>
74
74
  <img src="https://img.shields.io/badge/tools-27-ff6b1f?style=flat-square&labelColor=07070a" alt="tools"/>
75
75
  <img src="https://img.shields.io/badge/tests-263+-ff6b1f?style=flat-square&labelColor=07070a" alt="tests"/>
@@ -125,6 +125,7 @@ Dulus adapt any python repository <3
125
125
 
126
126
  Dulus is a **lightweight Python reimplementation of Claude Code** that isn't locked to Claude. It ships the whole loop — REPL, tool dispatch, streaming, context compaction, checkpoints, sub-agents, voice, Telegram bridge, MCP, plugins — in roughly **12K lines you can actually read**. Fork it. Bend it. Run it offline against Qwen on your M2.
127
127
 
128
+ > **v0.2.52 — May 12, 2026** — Session system overhaul: removed 50-session scan limit, metadata now includes message counts, eliminated daily prune from save path. **Recommendation:** wipe old sessions and start clean — only sessions, your memories stay intact.
128
129
  > **v0.2.22 — May 9, 2026** — `/bg start` spawns one detached Dulus daemon that serves CLI (IPC), Web (browser at `127.0.0.1:5000`), and Telegram simultaneously — all sharing the same session. WebChat now defaults to **loopback-only** (opt-in to LAN exposure with `/webchat lan on`).
129
130
  > **v0.2.21 — May 9, 2026** — Console-freeze fix: system prompt now tells the model SleepTimer is for user reminders only — pauses inside command pipelines must use `sleep N` inside the Bash command itself.
130
131
  > **v0.2.20 — May 9, 2026** — Windows IPC port-collision fix: switched to `SO_EXCLUSIVEADDRUSE` so a second Dulus instance correctly cedes the port and acts as client. End-to-end tested.
@@ -22,7 +22,7 @@ SET /sticky_input ON since the first run for the best experience!
22
22
  <a href="https://pypi.org/project/dulus/"><img src="https://static.pepy.tech/badge/dulus?style=flat-square" alt="downloads"/></a>
23
23
  <img src="https://img.shields.io/badge/python-3.11+-ff6b1f?style=flat-square&labelColor=07070a" alt="python"/>
24
24
  <img src="https://img.shields.io/badge/license-GPLv3-ff6b1f?style=flat-square&labelColor=07070a" alt="license"/>
25
- <img src="https://img.shields.io/badge/version-v0.2.44-ff6b1f?style=flat-square&labelColor=07070a" alt="version"/>
25
+ <img src="https://img.shields.io/badge/version-v0.2.52-ff6b1f?style=flat-square&labelColor=07070a" alt="version"/>
26
26
  <img src="https://img.shields.io/badge/providers-11-ff6b1f?style=flat-square&labelColor=07070a" alt="providers"/>
27
27
  <img src="https://img.shields.io/badge/tools-27-ff6b1f?style=flat-square&labelColor=07070a" alt="tools"/>
28
28
  <img src="https://img.shields.io/badge/tests-263+-ff6b1f?style=flat-square&labelColor=07070a" alt="tests"/>
@@ -78,6 +78,7 @@ Dulus adapt any python repository <3
78
78
 
79
79
  Dulus is a **lightweight Python reimplementation of Claude Code** that isn't locked to Claude. It ships the whole loop — REPL, tool dispatch, streaming, context compaction, checkpoints, sub-agents, voice, Telegram bridge, MCP, plugins — in roughly **12K lines you can actually read**. Fork it. Bend it. Run it offline against Qwen on your M2.
80
80
 
81
+ > **v0.2.52 — May 12, 2026** — Session system overhaul: removed 50-session scan limit, metadata now includes message counts, eliminated daily prune from save path. **Recommendation:** wipe old sessions and start clean — only sessions, your memories stay intact.
81
82
  > **v0.2.22 — May 9, 2026** — `/bg start` spawns one detached Dulus daemon that serves CLI (IPC), Web (browser at `127.0.0.1:5000`), and Telegram simultaneously — all sharing the same session. WebChat now defaults to **loopback-only** (opt-in to LAN exposure with `/webchat lan on`).
82
83
  > **v0.2.21 — May 9, 2026** — Console-freeze fix: system prompt now tells the model SleepTimer is for user reminders only — pauses inside command pipelines must use `sleep N` inside the Bash command itself.
83
84
  > **v0.2.20 — May 9, 2026** — Windows IPC port-collision fix: switched to `SO_EXCLUSIVEADDRUSE` so a second Dulus instance correctly cedes the port and acts as client. End-to-end tested.
@@ -9,21 +9,21 @@
9
9
  "project": {
10
10
  "name": "Dulus Command Center",
11
11
  "repo_stats": {
12
- "files": 433,
13
- "lines": 205053,
12
+ "files": 432,
13
+ "lines": 205188,
14
14
  "languages": {
15
15
  ".example": 5,
16
16
  "no_ext": 1424,
17
- ".py": 60404,
17
+ ".py": 60473,
18
18
  ".html": 14944,
19
19
  ".in": 3,
20
20
  ".toml": 129,
21
21
  ".md": 2161,
22
22
  ".txt": 463,
23
23
  ".lock": 1,
24
- ".json": 9532,
25
- ".whl": 21581,
26
- ".gz": 20971,
24
+ ".json": 9513,
25
+ ".whl": 21729,
26
+ ".gz": 20908,
27
27
  ".svg": 1123,
28
28
  ".png": 31639,
29
29
  ".sh": 144,
@@ -38,54 +38,41 @@
38
38
  },
39
39
  "recent_commits": [
40
40
  {
41
- "hash": "bf6455f",
42
- "subject": "feat: skill-to-chat bridge, MemPalace search API, sandbox UX polish",
41
+ "hash": "7fc805e",
42
+ "subject": "chore: bump version 0.2.50 -> 0.2.51",
43
43
  "author": "KevRojo",
44
44
  "date": "2026-05-12"
45
45
  },
46
46
  {
47
- "hash": "9803f97",
48
- "subject": "fix: include sandbox/src and sandbox/public in wheel",
47
+ "hash": "360badc",
48
+ "subject": "fix: GUI session responsiveness, sidebar incremental diff, async scan, default session IDs",
49
49
  "author": "KevRojo",
50
50
  "date": "2026-05-12"
51
51
  },
52
52
  {
53
- "hash": "a157622",
54
- "subject": "fix: include missing sandbox JS bundle and anchor dist/build ignore to root",
53
+ "hash": "fae4455",
54
+ "subject": "feat: MemoryManager overhaul, skill bridge polish, server sync",
55
55
  "author": "KevRojo",
56
56
  "date": "2026-05-12"
57
57
  },
58
58
  {
59
- "hash": "cfa4e4e",
60
- "subject": "bump: 0.2.45 → 0.2.46",
59
+ "hash": "bf6455f",
60
+ "subject": "feat: skill-to-chat bridge, MemPalace search API, sandbox UX polish",
61
61
  "author": "KevRojo",
62
62
  "date": "2026-05-12"
63
63
  },
64
64
  {
65
- "hash": "bc4cf5c",
66
- "subject": "chore: pre-release sync — backend agents, sandbox updates, explorer HTML",
65
+ "hash": "9803f97",
66
+ "subject": "fix: include sandbox/src and sandbox/public in wheel",
67
67
  "author": "KevRojo",
68
68
  "date": "2026-05-12"
69
69
  }
70
70
  ],
71
71
  "recent_changes": [
72
- ".gitignore",
73
- "backend/server.py",
74
72
  "data/context.json",
75
- "pyproject.toml",
76
- "sandbox/dist/assets/index-CsIO61nW.css",
77
- "sandbox/dist/assets/index-nQkFq-oq.js",
78
- "sandbox/dist/index.html",
79
- "sandbox/src/apps/AppRouter.tsx",
80
- "sandbox/src/apps/Chat.tsx",
81
- "sandbox/src/apps/MemoryManager.tsx",
82
- "sandbox/src/apps/SkillsLauncher.tsx",
83
- "sandbox/src/hooks/index.ts",
84
- "sandbox/src/hooks/useAutoOpenChat.ts",
85
- "sandbox/src/hooks/useOSStore.tsx",
86
- "sandbox/src/hooks/useSkillBridge.ts",
87
- "sandbox/src/lib/dulus-api.ts",
88
- "webchat_server.py"
73
+ "dulus.py",
74
+ "gui/session_utils.py",
75
+ "pyproject.toml"
89
76
  ]
90
77
  },
91
78
  "tasks": {
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dulus
3
- Version: 0.2.50
3
+ Version: 0.2.52
4
4
  Summary: Spanish-first multi-provider AI CLI — 14 NVIDIA models free, Mesa Redonda, voice, TTS, RTK token reducer, MemPalace
5
5
  Author: KevRojo
6
6
  License: GPL-3.0
@@ -69,7 +69,7 @@ SET /sticky_input ON since the first run for the best experience!
69
69
  <a href="https://pypi.org/project/dulus/"><img src="https://static.pepy.tech/badge/dulus?style=flat-square" alt="downloads"/></a>
70
70
  <img src="https://img.shields.io/badge/python-3.11+-ff6b1f?style=flat-square&labelColor=07070a" alt="python"/>
71
71
  <img src="https://img.shields.io/badge/license-GPLv3-ff6b1f?style=flat-square&labelColor=07070a" alt="license"/>
72
- <img src="https://img.shields.io/badge/version-v0.2.44-ff6b1f?style=flat-square&labelColor=07070a" alt="version"/>
72
+ <img src="https://img.shields.io/badge/version-v0.2.52-ff6b1f?style=flat-square&labelColor=07070a" alt="version"/>
73
73
  <img src="https://img.shields.io/badge/providers-11-ff6b1f?style=flat-square&labelColor=07070a" alt="providers"/>
74
74
  <img src="https://img.shields.io/badge/tools-27-ff6b1f?style=flat-square&labelColor=07070a" alt="tools"/>
75
75
  <img src="https://img.shields.io/badge/tests-263+-ff6b1f?style=flat-square&labelColor=07070a" alt="tests"/>
@@ -125,6 +125,7 @@ Dulus adapt any python repository <3
125
125
 
126
126
  Dulus is a **lightweight Python reimplementation of Claude Code** that isn't locked to Claude. It ships the whole loop — REPL, tool dispatch, streaming, context compaction, checkpoints, sub-agents, voice, Telegram bridge, MCP, plugins — in roughly **12K lines you can actually read**. Fork it. Bend it. Run it offline against Qwen on your M2.
127
127
 
128
+ > **v0.2.52 — May 12, 2026** — Session system overhaul: removed 50-session scan limit, metadata now includes message counts, eliminated daily prune from save path. **Recommendation:** wipe old sessions and start clean — only sessions, your memories stay intact.
128
129
  > **v0.2.22 — May 9, 2026** — `/bg start` spawns one detached Dulus daemon that serves CLI (IPC), Web (browser at `127.0.0.1:5000`), and Telegram simultaneously — all sharing the same session. WebChat now defaults to **loopback-only** (opt-in to LAN exposure with `/webchat lan on`).
129
130
  > **v0.2.21 — May 9, 2026** — Console-freeze fix: system prompt now tells the model SleepTimer is for user reminders only — pauses inside command pipelines must use `sleep N` inside the Bash command itself.
130
131
  > **v0.2.20 — May 9, 2026** — Windows IPC port-collision fix: switched to `SO_EXCLUSIVEADDRUSE` so a second Dulus instance correctly cedes the port and acts as client. End-to-end tested.
@@ -1158,7 +1158,7 @@ def _atomic_write_json(path: Path, data) -> None:
1158
1158
  def _save_roundtable_session(log: list, save_path=None):
1159
1159
  """Save the full roundtable session log to a JSON file.
1160
1160
 
1161
- Sessions go under config.MR_SESSION_DIR (~/.dulus/sessions/mr_sessions/),
1161
+ Sessions go under config.SESSIONS_DIR (~/.dulus/sessions/),
1162
1162
  consistent with /save and other session artifacts. Pass an explicit
1163
1163
  save_path to override (used to keep all turns of one debate in one file).
1164
1164
  """
@@ -1166,9 +1166,9 @@ def _save_roundtable_session(log: list, save_path=None):
1166
1166
  return
1167
1167
  if save_path is None:
1168
1168
  from datetime import datetime as _dt
1169
- from config import MR_SESSION_DIR
1170
- MR_SESSION_DIR.mkdir(parents=True, exist_ok=True)
1171
- save_path = MR_SESSION_DIR / f"round_table_{_dt.now().strftime('%Y%m%d_%H%M%S')}.json"
1169
+ from config import SESSIONS_DIR
1170
+ SESSIONS_DIR.mkdir(parents=True, exist_ok=True)
1171
+ save_path = SESSIONS_DIR / f"round_table_{_dt.now().strftime('%Y%m%d_%H%M%S')}.json"
1172
1172
  try:
1173
1173
  _atomic_write_json(save_path, log)
1174
1174
  ok(f"Sesión de Mesa Redonda guardada en: {save_path}")
@@ -1193,7 +1193,7 @@ def save_latest(args: str, state, config=None, mode: str = "full") -> bool:
1193
1193
  mode="full" → session_latest.json + daily/ copy + append to history.json (REPL default)
1194
1194
  mode="daemon"→ only overwrite SESSIONS_DIR/session_<sid>.json, skip latest/history/daily.
1195
1195
  """
1196
- from config import MR_SESSION_DIR, DAILY_DIR, SESSION_HIST_FILE, SESSIONS_DIR
1196
+ from config import DAILY_DIR, SESSION_HIST_FILE, SESSIONS_DIR
1197
1197
  if not state.messages:
1198
1198
  return True
1199
1199
 
@@ -1213,16 +1213,16 @@ def save_latest(args: str, state, config=None, mode: str = "full") -> bool:
1213
1213
  return True
1214
1214
 
1215
1215
  # ── Full mode (REPL exit) ──
1216
- daily_limit = cfg.get("session_daily_limit", 5)
1217
- history_limit = cfg.get("session_history_limit", 100)
1216
+ daily_limit = cfg.get("session_limit_daily", 10)
1217
+ history_limit = cfg.get("session_limit_history", 200)
1218
1218
 
1219
1219
  now = datetime.now()
1220
1220
  ts = now.strftime("%H%M%S")
1221
1221
  date_str = now.strftime("%Y-%m-%d")
1222
1222
 
1223
1223
  # 1. session_latest.json — always overwrite for quick /resume
1224
- MR_SESSION_DIR.mkdir(parents=True, exist_ok=True)
1225
- latest_path = MR_SESSION_DIR / "session_latest.json"
1224
+ SESSIONS_DIR.mkdir(parents=True, exist_ok=True)
1225
+ latest_path = SESSIONS_DIR / "session_latest.json"
1226
1226
  latest_path.write_text(payload)
1227
1227
 
1228
1228
  # 2. daily/YYYY-MM-DD/session_HHMMSS_sid.json
@@ -1239,11 +1239,6 @@ def save_latest(args: str, state, config=None, mode: str = "full") -> bool:
1239
1239
  daily_path = day_dir / f"session_{ts}_{sid}.json"
1240
1240
  daily_path.write_text(payload)
1241
1241
 
1242
- # Prune daily folder: keep only the latest `daily_limit` files
1243
- daily_files = sorted(day_dir.glob("session_*.json"))
1244
- for old in daily_files[:-daily_limit]:
1245
- old.unlink(missing_ok=True)
1246
-
1247
1242
  # 3. Append to history.json (master file)
1248
1243
  if SESSION_HIST_FILE.exists():
1249
1244
  try:
@@ -1267,22 +1262,16 @@ def save_latest(args: str, state, config=None, mode: str = "full") -> bool:
1267
1262
  ok(f" → {SESSION_HIST_FILE} ({len(hist['sessions'])} sessions / {hist['total_turns']} total turns)")
1268
1263
  return True
1269
1264
  def cmd_load(args: str, state, config) -> bool:
1270
- from config import SESSIONS_DIR, MR_SESSION_DIR, DAILY_DIR
1265
+ from config import SESSIONS_DIR, DAILY_DIR
1271
1266
 
1272
1267
  path = None
1273
1268
  if not args.strip():
1274
- # Collect sessions from daily/ folders, newest first
1269
+ # Collect sessions from daily/ folders only (single source of truth for listing)
1275
1270
  sessions: list[Path] = []
1276
1271
  if DAILY_DIR.exists():
1277
1272
  for day_dir in sorted(DAILY_DIR.iterdir(), reverse=True):
1278
1273
  if day_dir.is_dir():
1279
1274
  sessions.extend(sorted(day_dir.glob("session_*.json"), reverse=True))
1280
- # Fall back to legacy mr_sessions/ if daily/ is empty
1281
- if not sessions and MR_SESSION_DIR.exists():
1282
- sessions = [s for s in sorted(MR_SESSION_DIR.glob("*.json"), reverse=True)
1283
- if s.name != "session_latest.json"]
1284
- # Also include manually /save'd sessions from SESSIONS_DIR root
1285
- sessions.extend(sorted(SESSIONS_DIR.glob("session_*.json"), reverse=True))
1286
1275
 
1287
1276
  if not sessions:
1288
1277
  info("No saved sessions found.")
@@ -1403,8 +1392,7 @@ def cmd_load(args: str, state, config) -> bool:
1403
1392
  fname = args.strip()
1404
1393
  path = Path(fname) if "/" in fname or "\\" in fname else SESSIONS_DIR / fname
1405
1394
  if not path.exists() and ("/" not in fname and "\\" not in fname):
1406
- for alt in [MR_SESSION_DIR / fname,
1407
- *(d / fname for d in DAILY_DIR.iterdir()
1395
+ for alt in [*(d / fname for d in DAILY_DIR.iterdir()
1408
1396
  if DAILY_DIR.exists() and d.is_dir())]:
1409
1397
  if alt.exists():
1410
1398
  path = alt
@@ -1422,16 +1410,16 @@ def cmd_load(args: str, state, config) -> bool:
1422
1410
  return True
1423
1411
 
1424
1412
  def cmd_resume(args: str, state, config) -> bool:
1425
- from config import MR_SESSION_DIR
1413
+ from config import SESSIONS_DIR
1426
1414
 
1427
1415
  if not args.strip():
1428
- path = MR_SESSION_DIR / "session_latest.json"
1416
+ path = SESSIONS_DIR / "session_latest.json"
1429
1417
  if not path.exists():
1430
1418
  info("No auto-saved sessions found.")
1431
1419
  return True
1432
1420
  else:
1433
1421
  fname = args.strip()
1434
- path = Path(fname) if "/" in fname else MR_SESSION_DIR / fname
1422
+ path = Path(fname) if "/" in fname else SESSIONS_DIR / fname
1435
1423
 
1436
1424
  if not path.exists():
1437
1425
  err(f"File not found: {path}")
@@ -7,8 +7,10 @@ Usage:
7
7
  from __future__ import annotations
8
8
 
9
9
  import datetime
10
+ import json
10
11
  import queue
11
12
  import sys
13
+ import threading
12
14
  import traceback
13
15
  from pathlib import Path
14
16
  from typing import Callable
@@ -27,7 +29,7 @@ from gui.themes import get_theme, set_theme
27
29
  from gui.session_utils import scan_sessions
28
30
 
29
31
  # Session directories
30
- from config import SESSIONS_DIR, DAILY_DIR, MR_SESSION_DIR
32
+ from config import SESSIONS_DIR, DAILY_DIR
31
33
 
32
34
 
33
35
  # ── Helpers ───────────────────────────────────────────────────────────────────
@@ -163,6 +165,35 @@ def launch_gui(config: dict | None = None, initial_prompt: str | None = None) ->
163
165
  # Wire bridge into sidebar so context bar / model list work
164
166
  app.sidebar.bridge = bridge
165
167
 
168
+ # ── Sidebar refresh (non-blocking) ────────────────────────────────────────
169
+ _sidebar_refresh_pending = False
170
+
171
+ def _refresh_sidebar_async() -> None:
172
+ """Run scan_sessions in a background thread so the UI never freezes."""
173
+ nonlocal _sidebar_refresh_pending
174
+ if _sidebar_refresh_pending:
175
+ return
176
+ _sidebar_refresh_pending = True
177
+
178
+ def _do_scan():
179
+ try:
180
+ data = scan_sessions()
181
+ # Update UI from main thread
182
+ app.after(0, lambda: app.set_sessions(data))
183
+ finally:
184
+ nonlocal _sidebar_refresh_pending
185
+ _sidebar_refresh_pending = False
186
+
187
+ threading.Thread(target=_do_scan, daemon=True).start()
188
+
189
+ def _load_session_messages(path: str) -> list[dict]:
190
+ """Load messages directly from a session file."""
191
+ try:
192
+ data = json.loads(Path(path).read_text(encoding="utf-8", errors="replace"))
193
+ return data.get("messages", [])
194
+ except Exception:
195
+ return []
196
+
166
197
  # ── Wire callbacks ────────────────────────────────────────────────────────
167
198
 
168
199
  def _on_send(text: str) -> None:
@@ -176,8 +207,8 @@ def launch_gui(config: dict | None = None, initial_prompt: str | None = None) ->
176
207
  sid = bridge.save_current_session()
177
208
  if sid:
178
209
  # If a new session was created, refresh sidebar to show it
179
- app.set_sessions(scan_sessions())
180
-
210
+ _refresh_sidebar_async()
211
+
181
212
  app.hide_thinking()
182
213
  app.chat.clear_chat()
183
214
  bridge.clear_session()
@@ -188,36 +219,38 @@ def launch_gui(config: dict | None = None, initial_prompt: str | None = None) ->
188
219
  def _on_session_select(session_id: str) -> None:
189
220
  # Save current session before switching to ensure no loss
190
221
  sid = bridge.save_current_session()
191
-
222
+
192
223
  # If we were in a new chat that just got saved, refresh sidebar to show it
193
224
  if sid:
194
- app.set_sessions(scan_sessions())
195
-
225
+ _refresh_sidebar_async()
226
+
196
227
  app.hide_thinking()
197
-
198
- # 1. Use cached data from sidebar for instant switching
199
- session_data = app.sidebar._session_cache.get(session_id)
200
- if not session_data:
201
- # Fallback to scanning if cache missed (rare)
228
+
229
+ # 1. Find the session file path (from cache or scan)
230
+ session_path = None
231
+ cached = app.sidebar._session_cache.get(session_id)
232
+ if cached:
233
+ session_path = cached.get("path")
234
+ if not session_path:
202
235
  for s in scan_sessions():
203
236
  if s["id"] == session_id:
204
- session_data = s
237
+ session_path = s.get("path")
205
238
  break
206
-
207
- if not session_data:
239
+
240
+ if not session_path:
208
241
  return
209
242
 
210
- # 2. Update UI instantly (fluid)
211
- messages = session_data.get("messages", [])
243
+ # 2. Load messages directly from disk (avoids keeping all messages in memory)
244
+ messages = _load_session_messages(session_path)
212
245
  app.chat.load_messages(messages)
213
-
246
+
214
247
  # 3. Defer bridge loading until first message (user request)
215
248
  bridge.pending_history = messages
216
249
  bridge.session_id = session_id
217
250
  # Important: clear actual AI state so it's fresh until sync
218
251
  from agent import AgentState
219
252
  bridge.state = AgentState()
220
-
253
+
221
254
  app.set_active_session(session_id)
222
255
  app.sidebar.update_context_bar()
223
256
  app.set_status("Sesión lista (Contexto diferido)", t["success"])
@@ -236,8 +269,8 @@ def launch_gui(config: dict | None = None, initial_prompt: str | None = None) ->
236
269
  app.on_model_change = _on_model_change
237
270
  app.on_session_select = _on_session_select
238
271
 
239
- # Load existing sessions into sidebar
240
- app.set_sessions(scan_sessions())
272
+ # Load existing sessions into sidebar (async so GUI shows immediately)
273
+ _refresh_sidebar_async()
241
274
  app.sidebar._refresh_model_list()
242
275
  app.sidebar.update_context_bar()
243
276
 
@@ -291,7 +324,7 @@ def launch_gui(config: dict | None = None, initial_prompt: str | None = None) ->
291
324
  # Rebuilding after every message causes annoying flicker.
292
325
  sid = event.get("session_id")
293
326
  if sid and sid not in app.sidebar._session_buttons:
294
- app.set_sessions(scan_sessions())
327
+ _refresh_sidebar_async()
295
328
  if sid:
296
329
  app.set_active_session(sid)
297
330
 
@@ -162,9 +162,9 @@ class DulusBridge:
162
162
 
163
163
  def _process_turn(self, user_message: str) -> None:
164
164
  # Assign session_id immediately to prevent UI duplication during turn
165
+ # Use "default" as fallback so New Chat doesn't spawn random UUIDs
165
166
  if not self.session_id:
166
- import uuid
167
- self.session_id = uuid.uuid4().hex[:8]
167
+ self.session_id = "default"
168
168
 
169
169
  # ── Skill inject (one-shot) ────────────────────────────────────────
170
170
  skill_body = self._skill_inject
@@ -3,7 +3,11 @@ import json
3
3
  import datetime
4
4
  import uuid
5
5
  from pathlib import Path
6
- from config import SESSIONS_DIR, DAILY_DIR, MR_SESSION_DIR, SESSION_HIST_FILE
6
+ from config import SESSIONS_DIR, DAILY_DIR, SESSION_HIST_FILE
7
+
8
+ # File-mtime cache: path -> (mtime, result) to avoid re-reading unchanged files
9
+ _scan_cache: dict[str, tuple[float, dict]] = {}
10
+
7
11
 
8
12
  def build_title(messages: list[dict]) -> str:
9
13
  """Generate a descriptive title from the first user message."""
@@ -15,64 +19,76 @@ def build_title(messages: list[dict]) -> str:
15
19
  text = " ".join(part.get("text", "") for part in content if isinstance(part, dict))
16
20
  else:
17
21
  text = str(content)
18
-
22
+
19
23
  if text.strip():
20
24
  clean = text.strip().replace("\n", " ")
21
25
  return clean[:40] + ("..." if len(clean) > 40 else "")
22
26
  return "Nueva conversación"
23
27
 
28
+
29
+ def _read_session_meta(path: Path) -> dict | None:
30
+ """Read session metadata with mtime caching."""
31
+ global _scan_cache
32
+ try:
33
+ mtime = path.stat().st_mtime
34
+ key = str(path)
35
+ if key in _scan_cache:
36
+ cached_mtime, cached = _scan_cache[key]
37
+ if cached_mtime == mtime:
38
+ return cached
39
+
40
+ data = json.loads(path.read_text(encoding="utf-8", errors="replace"))
41
+ sid = data.get("session_id", path.stem)
42
+ messages = data.get("messages", [])
43
+ title = build_title(messages)
44
+ saved_at = data.get("saved_at", "")
45
+ if saved_at and len(saved_at) >= 19:
46
+ title = f"{saved_at[11:16]} {title}"
47
+
48
+ result = {
49
+ "id": sid,
50
+ "title": title,
51
+ "path": str(path),
52
+ "saved_at": saved_at,
53
+ "turn_count": data.get("turn_count", len(messages) // 2),
54
+ "messages": messages,
55
+ }
56
+ _scan_cache[key] = (mtime, result)
57
+ return result
58
+ except Exception:
59
+ return None
60
+
61
+
24
62
  def scan_sessions() -> list[dict]:
25
- """Scan session directories and return sorted list of metadata."""
63
+ """Scan daily session directories and return sorted list of metadata.
64
+
65
+ Single source of truth for listing: only daily/ folder is scanned.
66
+ Other locations (root sessions/, checkpoints) continue to exist for
67
+ internal use but are not listed to avoid duplicates.
68
+ """
26
69
  sessions: list[dict] = []
27
70
  seen: set[str] = set()
28
71
  files: list[Path] = []
29
72
 
30
- # Daily sessions (newest first)
73
+ # Daily sessions only (newest first)
31
74
  if DAILY_DIR.exists():
32
75
  for day_dir in sorted(DAILY_DIR.iterdir(), reverse=True):
33
76
  if day_dir.is_dir():
34
77
  files.extend(sorted(day_dir.glob("session_*.json"), reverse=True))
35
78
 
36
- # MR sessions
37
- if MR_SESSION_DIR.exists():
38
- files.extend(
39
- s for s in sorted(MR_SESSION_DIR.glob("*.json"), reverse=True)
40
- if s.name != "session_latest.json"
41
- )
42
-
43
- # Root sessions
44
- if SESSIONS_DIR.exists():
45
- files.extend(sorted(SESSIONS_DIR.glob("session_*.json"), reverse=True))
46
-
47
79
  for path in files:
48
- try:
49
- data = json.loads(path.read_text(encoding="utf-8", errors="replace"))
50
- sid = data.get("session_id", path.stem)
51
- if sid in seen:
52
- continue
53
- seen.add(sid)
54
-
55
- messages = data.get("messages", [])
56
- title = build_title(messages)
57
-
58
- saved_at = data.get("saved_at", "")
59
- if saved_at and len(saved_at) >= 19:
60
- # Add time prefix: "HH:MM Title"
61
- title = f"{saved_at[11:16]} {title}"
62
-
63
- sessions.append({
64
- "id": sid,
65
- "title": title,
66
- "path": str(path),
67
- "messages": messages,
68
- "saved_at": saved_at
69
- })
70
- except Exception:
80
+ meta = _read_session_meta(path)
81
+ if not meta:
82
+ continue
83
+ sid = meta["id"]
84
+ if sid in seen:
71
85
  continue
86
+ seen.add(sid)
87
+ sessions.append(meta)
72
88
 
73
89
  # Sort all found sessions by saved_at DESC
74
90
  sessions.sort(key=lambda x: x.get("saved_at", ""), reverse=True)
75
- return sessions[:50]
91
+ return sessions
76
92
 
77
93
  def save_session(state, config: dict, session_id: str | None = None) -> str:
78
94
  """Save AgentState to disk in standard Dulus format. Returns the session_id."""
@@ -101,8 +117,8 @@ def save_session(state, config: dict, session_id: str | None = None) -> str:
101
117
  payload = json.dumps(data, indent=2, default=str)
102
118
 
103
119
  # 2. Save latest for /resume
104
- MR_SESSION_DIR.mkdir(parents=True, exist_ok=True)
105
- (MR_SESSION_DIR / "session_latest.json").write_text(payload, encoding="utf-8")
120
+ SESSIONS_DIR.mkdir(parents=True, exist_ok=True)
121
+ (SESSIONS_DIR / "session_latest.json").write_text(payload, encoding="utf-8")
106
122
 
107
123
  # 3. Save to daily folder
108
124
  day_dir = DAILY_DIR / date_str
@@ -155,16 +171,8 @@ def delete_session(session_id: str) -> bool:
155
171
  return False
156
172
 
157
173
  deleted = False
158
-
159
- # 1. Scan and delete in MR_SESSION_DIR (except latest maybe?)
160
- if MR_SESSION_DIR.exists():
161
- for p in MR_SESSION_DIR.glob(f"*{session_id}*"):
162
- try:
163
- p.unlink()
164
- deleted = True
165
- except: pass
166
174
 
167
- # 2. Daily sessions
175
+ # 1. Daily sessions
168
176
  if DAILY_DIR.exists():
169
177
  for d in DAILY_DIR.iterdir():
170
178
  if d.is_dir():
@@ -174,7 +182,7 @@ def delete_session(session_id: str) -> bool:
174
182
  deleted = True
175
183
  except: pass
176
184
 
177
- # 3. Root sessions
185
+ # 2. Root sessions (includes session_latest.json and manual /save files)
178
186
  if SESSIONS_DIR.exists():
179
187
  for p in SESSIONS_DIR.glob(f"*{session_id}*"):
180
188
  try:
@@ -182,7 +190,7 @@ def delete_session(session_id: str) -> bool:
182
190
  deleted = True
183
191
  except: pass
184
192
 
185
- # 4. Update history.json
193
+ # 3. Update history.json
186
194
  if SESSION_HIST_FILE.exists():
187
195
  try:
188
196
  hist = json.loads(SESSION_HIST_FILE.read_text())