dulus 0.2.49__tar.gz → 0.2.51__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.
- {dulus-0.2.49/dulus.egg-info → dulus-0.2.51}/PKG-INFO +1 -1
- {dulus-0.2.49 → dulus-0.2.51}/backend/server.py +25 -0
- dulus-0.2.51/data/context.json +153 -0
- {dulus-0.2.49 → dulus-0.2.51/dulus.egg-info}/PKG-INFO +1 -1
- {dulus-0.2.49 → dulus-0.2.51}/dulus.egg-info/SOURCES.txt +2 -2
- {dulus-0.2.49 → dulus-0.2.51}/dulus.py +15 -22
- {dulus-0.2.49 → dulus-0.2.51}/dulus_gui.py +54 -21
- {dulus-0.2.49 → dulus-0.2.51}/gui/agent_bridge.py +2 -2
- {dulus-0.2.49 → dulus-0.2.51}/gui/session_utils.py +58 -51
- {dulus-0.2.49 → dulus-0.2.51}/gui/sidebar.py +79 -53
- {dulus-0.2.49 → dulus-0.2.51}/pyproject.toml +1 -1
- dulus-0.2.51/sandbox/dist/assets/index-DE51D6wI.css +1 -0
- dulus-0.2.49/sandbox/dist/assets/index-BQlB1Z_S.js → dulus-0.2.51/sandbox/dist/assets/index-DMCCNE9Y.js +42 -40
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/dist/index.html +2 -2
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/apps/Chat.tsx +7 -1
- dulus-0.2.51/sandbox/src/apps/MemoryManager.tsx +263 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/hooks/useSkillBridge.ts +27 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/lib/dulus-api.ts +27 -0
- {dulus-0.2.49 → dulus-0.2.51}/webchat_server.py +40 -1
- dulus-0.2.49/data/context.json +0 -222
- dulus-0.2.49/sandbox/dist/assets/index-CsIO61nW.css +0 -1
- dulus-0.2.49/sandbox/src/apps/MemoryManager.tsx +0 -248
- {dulus-0.2.49 → dulus-0.2.51}/LICENSE +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/MANIFEST.in +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/README.md +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/agent.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/backend/__init__.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/backend/agents_bridge.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/backend/compressor.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/backend/context.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/backend/githook.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/backend/marketplace.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/backend/mempalace_bridge.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/backend/personas.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/backend/plugins.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/backend/tasks.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/batch_api.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/checkpoint/__init__.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/checkpoint/hooks.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/checkpoint/store.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/checkpoint/types.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/claude_code_watcher.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/clipboard_utils.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/cloudsave.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/common.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/compaction.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/config.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/context.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/data/__init__.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/data/active_persona.json +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/data/marketplace.json +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/data/personas.json +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/data/plugins/__init__.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/data/plugins/composio/__init__.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/data/plugins/composio/composio_plugin/__init__.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/data/plugins/composio/composio_plugin/session_manager.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/data/plugins/composio/composio_plugin/tool_generator.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/data/plugins/composio/plugin.json +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/data/plugins/composio/plugin_tool.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/data/tasks.json +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/docs/README.md +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/docs/__init__.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/docs/api.html +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/docs/architecture.md +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/docs/azure-speech-template.json +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/docs/dashboard/index.html +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/docs/divider.svg +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/docs/generate.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/docs/hero.svg +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/docs/index.html +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/docs/news.md +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/docs/nvidia-models.svg +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/docs/particle-playground.html +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/docs/personas/index.html +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/docs/poetry-banner.png +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/docs/preview.html +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/docs/sec-agents.svg +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/docs/sec-brainstorm.svg +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/docs/sec-bridges.svg +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/docs/sec-features.svg +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/docs/sec-freetier.svg +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/docs/sec-memory.svg +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/docs/sec-models.svg +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/docs/sec-perms.svg +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/docs/sec-plugins.svg +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/docs/sec-quickstart.svg +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/docs/sec-ssj.svg +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/docs/spinners.svg +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/docs/split-pane.svg +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/docs/terminal-boot.svg +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/docs/uploads/particle-playground.html +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/dulus.egg-info/dependency_links.txt +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/dulus.egg-info/entry_points.txt +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/dulus.egg-info/requires.txt +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/dulus.egg-info/top_level.txt +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/dulus_mcp/__init__.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/dulus_mcp/client.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/dulus_mcp/config.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/dulus_mcp/tools.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/dulus_mcp/types.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/gui/__init__.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/gui/chat_widget.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/gui/main_window.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/gui/personas.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/gui/settings_dialog.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/gui/tasks_view.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/gui/themes.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/gui/tool_panel.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/input.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/license_manager.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/memory/__init__.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/memory/audit.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/memory/consolidator.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/memory/context.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/memory/offload.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/memory/palace.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/memory/scan.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/memory/sessions.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/memory/store.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/memory/tools.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/memory/types.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/memory/vector_search.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/multi_agent/__init__.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/multi_agent/subagent.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/multi_agent/tools.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/offload_helper.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/plugin/__init__.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/plugin/autoadapter.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/plugin/loader.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/plugin/recommend.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/plugin/store.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/plugin/types.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/providers.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/README.md +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/__init__.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/components.json +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/dist/wallpaper-default.jpg +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/dist/wallpapers/default.jpeg +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/dist/wallpapers/light.jpeg +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/dist/wallpapers/nature.jpeg +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/dist/wallpapers/tech.jpeg +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/eslint.config.js +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/index.html +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/info.md +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/package-lock.json +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/package.json +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/postcss.config.js +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/public/wallpaper-default.jpg +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/public/wallpapers/default.jpeg +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/public/wallpapers/light.jpeg +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/public/wallpapers/nature.jpeg +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/public/wallpapers/tech.jpeg +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/App.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/apps/AgentMonitor.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/apps/ApiTester.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/apps/AppRouter.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/apps/ArchiveManager.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/apps/AsciiArt.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/apps/Base64Tool.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/apps/Browser.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/apps/Calculator.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/apps/Calendar.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/apps/Chess.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/apps/Clock.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/apps/CodeEditor.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/apps/ColorPalette.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/apps/ColorPicker.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/apps/Contacts.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/apps/DocumentViewer.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/apps/Drawing.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/apps/Email.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/apps/FileManager.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/apps/FlappyBird.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/apps/FtpClient.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/apps/Game2048.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/apps/GitClient.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/apps/ImageGallery.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/apps/ImageViewer.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/apps/JsonFormatter.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/apps/MarkdownPreview.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/apps/MatrixRain.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/apps/MediaConverter.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/apps/Memory.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/apps/Minesweeper.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/apps/MusicPlayer.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/apps/NetworkTools.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/apps/Notes.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/apps/PasswordManager.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/apps/PhotoEditor.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/apps/Pong.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/apps/RegexTester.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/apps/Reminders.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/apps/RssReader.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/apps/ScreenRecorder.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/apps/Settings.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/apps/SkillsLauncher.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/apps/Snake.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/apps/Solitaire.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/apps/Spreadsheet.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/apps/Sudoku.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/apps/SystemMonitor.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/apps/TaskManager.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/apps/Terminal.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/apps/Tetris.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/apps/TextEditor.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/apps/TicTacToe.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/apps/Todo.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/apps/VideoPlayer.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/apps/VoiceRecorder.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/apps/Weather.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/apps/Whiteboard.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/apps/registry.ts +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/components/AppContainer.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/components/AppLauncher.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/components/BootSequence.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/components/ContextMenu.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/components/Desktop.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/components/Dock.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/components/LoginScreen.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/components/NotImplemented.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/components/NotificationCenter.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/components/NotificationSystem.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/components/TopPanel.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/components/WindowFrame.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/components/WindowManager.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/components/ui/accordion.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/components/ui/alert-dialog.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/components/ui/alert.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/components/ui/aspect-ratio.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/components/ui/avatar.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/components/ui/badge.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/components/ui/breadcrumb.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/components/ui/button-group.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/components/ui/button.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/components/ui/calendar.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/components/ui/card.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/components/ui/carousel.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/components/ui/chart.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/components/ui/checkbox.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/components/ui/collapsible.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/components/ui/command.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/components/ui/context-menu.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/components/ui/dialog.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/components/ui/drawer.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/components/ui/dropdown-menu.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/components/ui/empty.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/components/ui/field.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/components/ui/form.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/components/ui/hover-card.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/components/ui/input-group.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/components/ui/input-otp.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/components/ui/input.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/components/ui/item.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/components/ui/kbd.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/components/ui/label.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/components/ui/menubar.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/components/ui/navigation-menu.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/components/ui/pagination.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/components/ui/popover.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/components/ui/progress.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/components/ui/radio-group.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/components/ui/resizable.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/components/ui/scroll-area.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/components/ui/select.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/components/ui/separator.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/components/ui/sheet.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/components/ui/sidebar.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/components/ui/skeleton.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/components/ui/slider.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/components/ui/sonner.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/components/ui/spinner.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/components/ui/switch.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/components/ui/table.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/components/ui/tabs.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/components/ui/textarea.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/components/ui/toggle-group.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/components/ui/toggle.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/components/ui/tooltip.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/hooks/index.ts +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/hooks/use-mobile.ts +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/hooks/useAutoOpenChat.ts +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/hooks/useDulusAgents.ts +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/hooks/useDulusChat.ts +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/hooks/useDulusEvents.ts +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/hooks/useDulusHealth.ts +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/hooks/useDulusMemory.ts +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/hooks/useDulusSkills.ts +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/hooks/useDulusTasks.ts +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/hooks/useFileSystem.ts +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/hooks/useOSStore.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/hooks/useSystemBattery.ts +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/hooks/useSystemNetwork.ts +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/hooks/useSystemVolume.ts +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/index.css +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/lib/utils.ts +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/main.tsx +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/types/index.ts +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/src/utils/assets.ts +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/tailwind.config.js +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/tsconfig.app.json +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/tsconfig.json +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/tsconfig.node.json +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/sandbox/vite.config.ts +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/setup.cfg +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/skill/__init__.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/skill/builtin.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/skill/clawhub.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/skill/executor.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/skill/loader.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/skill/tools.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/skills.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/spinner.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/string_utils.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/subagent.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/task/__init__.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/task/store.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/task/tools.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/task/types.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/tests/test_afk_yolo.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/tests/test_approval_runtime.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/tests/test_background_task_tools.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/tests/test_background_tasks.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/tests/test_checkpoint.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/tests/test_clipboard_utils.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/tests/test_compaction.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/tests/test_diff_view.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/tests/test_diff_visualization.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/tests/test_display_blocks.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/tests/test_export_import.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/tests/test_hook_engine.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/tests/test_injection_fix.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/tests/test_license.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/tests/test_mcp.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/tests/test_memory.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/tests/test_notification_manager.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/tests/test_plugin.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/tests/test_session_fork.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/tests/test_shell_mode.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/tests/test_skills.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/tests/test_steer_input.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/tests/test_subagent.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/tests/test_task.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/tests/test_telegram_buffer.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/tests/test_think_tool.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/tests/test_todo_tool.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/tests/test_todo_visualization.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/tests/test_tool_registry.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/tests/test_voice.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/tests/test_wire_events.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/tmux_offloader.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/tmux_tools.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/tool_registry.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/tools.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/ui/__init__.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/ui/input.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/ui/render.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/voice/__init__.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/voice/keyterms.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/voice/recorder.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/voice/stt.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/voice/tts.py +0 -0
- {dulus-0.2.49 → dulus-0.2.51}/webchat.py +0 -0
|
@@ -272,6 +272,31 @@ class DulusHandler(SimpleHTTPRequestHandler):
|
|
|
272
272
|
self._error(f"Wings error: {e}", 500)
|
|
273
273
|
return
|
|
274
274
|
|
|
275
|
+
# ── Memory file list (disk-direct, mirrors /memory CLI) ──
|
|
276
|
+
if path == "/api/memory/files":
|
|
277
|
+
try:
|
|
278
|
+
from memory.store import load_index
|
|
279
|
+
scope = parse_qs(urlparse(self.path).query).get("scope", ["all"])[0]
|
|
280
|
+
entries = load_index(scope)
|
|
281
|
+
self._json_response([
|
|
282
|
+
{
|
|
283
|
+
"name": e.name,
|
|
284
|
+
"description": e.description,
|
|
285
|
+
"type": e.type,
|
|
286
|
+
"scope": e.scope,
|
|
287
|
+
"hall": e.hall,
|
|
288
|
+
"created": e.created,
|
|
289
|
+
"confidence": e.confidence,
|
|
290
|
+
"gold": e.gold,
|
|
291
|
+
"file_path": e.file_path,
|
|
292
|
+
"content": e.content,
|
|
293
|
+
}
|
|
294
|
+
for e in entries
|
|
295
|
+
])
|
|
296
|
+
except Exception as exc:
|
|
297
|
+
self._error(f"Memory files error: {exc}", 500)
|
|
298
|
+
return
|
|
299
|
+
|
|
275
300
|
# ── Skills ──
|
|
276
301
|
if path == "/api/skills":
|
|
277
302
|
try:
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
{
|
|
2
|
+
"session": {
|
|
3
|
+
"mode": "proactive",
|
|
4
|
+
"agent": "Dulus",
|
|
5
|
+
"agent_id": "dulus",
|
|
6
|
+
"user": "KevRojo",
|
|
7
|
+
"location": "RD"
|
|
8
|
+
},
|
|
9
|
+
"project": {
|
|
10
|
+
"name": "Dulus Command Center",
|
|
11
|
+
"repo_stats": {
|
|
12
|
+
"files": 432,
|
|
13
|
+
"lines": 204971,
|
|
14
|
+
"languages": {
|
|
15
|
+
".example": 5,
|
|
16
|
+
"no_ext": 1424,
|
|
17
|
+
".py": 60404,
|
|
18
|
+
".html": 14944,
|
|
19
|
+
".in": 3,
|
|
20
|
+
".toml": 129,
|
|
21
|
+
".md": 2161,
|
|
22
|
+
".txt": 463,
|
|
23
|
+
".lock": 1,
|
|
24
|
+
".json": 9521,
|
|
25
|
+
".whl": 21605,
|
|
26
|
+
".gz": 20876,
|
|
27
|
+
".svg": 1123,
|
|
28
|
+
".png": 31639,
|
|
29
|
+
".sh": 144,
|
|
30
|
+
".js": 475,
|
|
31
|
+
".ts": 2440,
|
|
32
|
+
".log": 1,
|
|
33
|
+
".jpg": 1076,
|
|
34
|
+
".tsx": 31283,
|
|
35
|
+
".css": 170,
|
|
36
|
+
".jpeg": 5084
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"recent_commits": [
|
|
40
|
+
{
|
|
41
|
+
"hash": "fae4455",
|
|
42
|
+
"subject": "feat: MemoryManager overhaul, skill bridge polish, server sync",
|
|
43
|
+
"author": "KevRojo",
|
|
44
|
+
"date": "2026-05-12"
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"hash": "bf6455f",
|
|
48
|
+
"subject": "feat: skill-to-chat bridge, MemPalace search API, sandbox UX polish",
|
|
49
|
+
"author": "KevRojo",
|
|
50
|
+
"date": "2026-05-12"
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"hash": "9803f97",
|
|
54
|
+
"subject": "fix: include sandbox/src and sandbox/public in wheel",
|
|
55
|
+
"author": "KevRojo",
|
|
56
|
+
"date": "2026-05-12"
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"hash": "a157622",
|
|
60
|
+
"subject": "fix: include missing sandbox JS bundle and anchor dist/build ignore to root",
|
|
61
|
+
"author": "KevRojo",
|
|
62
|
+
"date": "2026-05-12"
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"hash": "cfa4e4e",
|
|
66
|
+
"subject": "bump: 0.2.45 → 0.2.46",
|
|
67
|
+
"author": "KevRojo",
|
|
68
|
+
"date": "2026-05-12"
|
|
69
|
+
}
|
|
70
|
+
],
|
|
71
|
+
"recent_changes": [
|
|
72
|
+
"backend/server.py",
|
|
73
|
+
"data/context.json",
|
|
74
|
+
"pyproject.toml",
|
|
75
|
+
"sandbox/dist/assets/index-CsIO61nW.css",
|
|
76
|
+
"sandbox/dist/assets/index-DE51D6wI.css",
|
|
77
|
+
"sandbox/dist/assets/index-DMCCNE9Y.js",
|
|
78
|
+
"sandbox/dist/index.html",
|
|
79
|
+
"sandbox/src/apps/Chat.tsx",
|
|
80
|
+
"sandbox/src/apps/MemoryManager.tsx",
|
|
81
|
+
"sandbox/src/hooks/useSkillBridge.ts",
|
|
82
|
+
"sandbox/src/lib/dulus-api.ts",
|
|
83
|
+
"webchat_server.py"
|
|
84
|
+
]
|
|
85
|
+
},
|
|
86
|
+
"tasks": {
|
|
87
|
+
"active": [
|
|
88
|
+
{
|
|
89
|
+
"id": "T-009",
|
|
90
|
+
"subject": "Test Coverage Expansion",
|
|
91
|
+
"status": "in_progress",
|
|
92
|
+
"owner": "kimi-code",
|
|
93
|
+
"phase": "Quality"
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"id": "T-010",
|
|
97
|
+
"subject": "Multi-Agent Mesa Redonda",
|
|
98
|
+
"status": "in_progress",
|
|
99
|
+
"owner": "Dulus",
|
|
100
|
+
"phase": "Core"
|
|
101
|
+
}
|
|
102
|
+
],
|
|
103
|
+
"total": 2
|
|
104
|
+
},
|
|
105
|
+
"agents": [
|
|
106
|
+
{
|
|
107
|
+
"name": "Dulus",
|
|
108
|
+
"role": "primary",
|
|
109
|
+
"color": "#ff6b1f",
|
|
110
|
+
"status": "active",
|
|
111
|
+
"avatar": "[F]",
|
|
112
|
+
"active": true
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
"name": "kimi-code",
|
|
116
|
+
"role": "coder",
|
|
117
|
+
"color": "#7ab6ff",
|
|
118
|
+
"status": "idle",
|
|
119
|
+
"avatar": "[K1]",
|
|
120
|
+
"active": false
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
"name": "kimi-code2",
|
|
124
|
+
"role": "designer",
|
|
125
|
+
"color": "#b388ff",
|
|
126
|
+
"status": "idle",
|
|
127
|
+
"avatar": "[K2]",
|
|
128
|
+
"active": false
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
"name": "kimi-code3",
|
|
132
|
+
"role": "integrator",
|
|
133
|
+
"color": "#7cffb5",
|
|
134
|
+
"status": "idle",
|
|
135
|
+
"avatar": "[K3]",
|
|
136
|
+
"active": false
|
|
137
|
+
}
|
|
138
|
+
],
|
|
139
|
+
"persona": {
|
|
140
|
+
"id": "dulus",
|
|
141
|
+
"name": "Dulus",
|
|
142
|
+
"role": "primary",
|
|
143
|
+
"color": "#ff6b1f",
|
|
144
|
+
"avatar": "[F]",
|
|
145
|
+
"tone": "dominicano_coder"
|
|
146
|
+
},
|
|
147
|
+
"memory": {
|
|
148
|
+
"connected": false,
|
|
149
|
+
"wings": [],
|
|
150
|
+
"count": 0,
|
|
151
|
+
"memories": []
|
|
152
|
+
}
|
|
153
|
+
}
|
|
@@ -145,8 +145,8 @@ sandbox/tsconfig.node.json
|
|
|
145
145
|
sandbox/vite.config.ts
|
|
146
146
|
sandbox/dist/index.html
|
|
147
147
|
sandbox/dist/wallpaper-default.jpg
|
|
148
|
-
sandbox/dist/assets/index-
|
|
149
|
-
sandbox/dist/assets/index-
|
|
148
|
+
sandbox/dist/assets/index-DE51D6wI.css
|
|
149
|
+
sandbox/dist/assets/index-DMCCNE9Y.js
|
|
150
150
|
sandbox/dist/wallpapers/default.jpeg
|
|
151
151
|
sandbox/dist/wallpapers/light.jpeg
|
|
152
152
|
sandbox/dist/wallpapers/nature.jpeg
|
|
@@ -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.
|
|
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
|
|
1170
|
-
|
|
1171
|
-
save_path =
|
|
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
|
|
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("
|
|
1217
|
-
history_limit = cfg.get("
|
|
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
|
-
|
|
1225
|
-
latest_path =
|
|
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
|
|
@@ -1267,22 +1267,16 @@ def save_latest(args: str, state, config=None, mode: str = "full") -> bool:
|
|
|
1267
1267
|
ok(f" → {SESSION_HIST_FILE} ({len(hist['sessions'])} sessions / {hist['total_turns']} total turns)")
|
|
1268
1268
|
return True
|
|
1269
1269
|
def cmd_load(args: str, state, config) -> bool:
|
|
1270
|
-
from config import SESSIONS_DIR,
|
|
1270
|
+
from config import SESSIONS_DIR, DAILY_DIR
|
|
1271
1271
|
|
|
1272
1272
|
path = None
|
|
1273
1273
|
if not args.strip():
|
|
1274
|
-
# Collect sessions from daily/ folders
|
|
1274
|
+
# Collect sessions from daily/ folders only (single source of truth for listing)
|
|
1275
1275
|
sessions: list[Path] = []
|
|
1276
1276
|
if DAILY_DIR.exists():
|
|
1277
1277
|
for day_dir in sorted(DAILY_DIR.iterdir(), reverse=True):
|
|
1278
1278
|
if day_dir.is_dir():
|
|
1279
1279
|
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
1280
|
|
|
1287
1281
|
if not sessions:
|
|
1288
1282
|
info("No saved sessions found.")
|
|
@@ -1403,8 +1397,7 @@ def cmd_load(args: str, state, config) -> bool:
|
|
|
1403
1397
|
fname = args.strip()
|
|
1404
1398
|
path = Path(fname) if "/" in fname or "\\" in fname else SESSIONS_DIR / fname
|
|
1405
1399
|
if not path.exists() and ("/" not in fname and "\\" not in fname):
|
|
1406
|
-
for alt in [
|
|
1407
|
-
*(d / fname for d in DAILY_DIR.iterdir()
|
|
1400
|
+
for alt in [*(d / fname for d in DAILY_DIR.iterdir()
|
|
1408
1401
|
if DAILY_DIR.exists() and d.is_dir())]:
|
|
1409
1402
|
if alt.exists():
|
|
1410
1403
|
path = alt
|
|
@@ -1422,16 +1415,16 @@ def cmd_load(args: str, state, config) -> bool:
|
|
|
1422
1415
|
return True
|
|
1423
1416
|
|
|
1424
1417
|
def cmd_resume(args: str, state, config) -> bool:
|
|
1425
|
-
from config import
|
|
1418
|
+
from config import SESSIONS_DIR
|
|
1426
1419
|
|
|
1427
1420
|
if not args.strip():
|
|
1428
|
-
path =
|
|
1421
|
+
path = SESSIONS_DIR / "session_latest.json"
|
|
1429
1422
|
if not path.exists():
|
|
1430
1423
|
info("No auto-saved sessions found.")
|
|
1431
1424
|
return True
|
|
1432
1425
|
else:
|
|
1433
1426
|
fname = args.strip()
|
|
1434
|
-
path = Path(fname) if "/" in fname else
|
|
1427
|
+
path = Path(fname) if "/" in fname else SESSIONS_DIR / fname
|
|
1435
1428
|
|
|
1436
1429
|
if not path.exists():
|
|
1437
1430
|
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
|
|
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
|
-
|
|
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
|
-
|
|
195
|
-
|
|
225
|
+
_refresh_sidebar_async()
|
|
226
|
+
|
|
196
227
|
app.hide_thinking()
|
|
197
|
-
|
|
198
|
-
# 1.
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
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
|
-
|
|
237
|
+
session_path = s.get("path")
|
|
205
238
|
break
|
|
206
|
-
|
|
207
|
-
if not
|
|
239
|
+
|
|
240
|
+
if not session_path:
|
|
208
241
|
return
|
|
209
242
|
|
|
210
|
-
# 2.
|
|
211
|
-
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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,60 +19,71 @@ 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
|
+
}
|
|
55
|
+
_scan_cache[key] = (mtime, result)
|
|
56
|
+
return result
|
|
57
|
+
except Exception:
|
|
58
|
+
return None
|
|
59
|
+
|
|
60
|
+
|
|
24
61
|
def scan_sessions() -> list[dict]:
|
|
25
|
-
"""Scan session directories and return sorted list of metadata.
|
|
62
|
+
"""Scan daily session directories and return sorted list of metadata.
|
|
63
|
+
|
|
64
|
+
Single source of truth for listing: only daily/ folder is scanned.
|
|
65
|
+
Other locations (root sessions/, checkpoints) continue to exist for
|
|
66
|
+
internal use but are not listed to avoid duplicates.
|
|
67
|
+
"""
|
|
26
68
|
sessions: list[dict] = []
|
|
27
69
|
seen: set[str] = set()
|
|
28
70
|
files: list[Path] = []
|
|
29
71
|
|
|
30
|
-
# Daily sessions (newest first)
|
|
72
|
+
# Daily sessions only (newest first)
|
|
31
73
|
if DAILY_DIR.exists():
|
|
32
74
|
for day_dir in sorted(DAILY_DIR.iterdir(), reverse=True):
|
|
33
75
|
if day_dir.is_dir():
|
|
34
76
|
files.extend(sorted(day_dir.glob("session_*.json"), reverse=True))
|
|
35
77
|
|
|
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
78
|
for path in files:
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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:
|
|
79
|
+
meta = _read_session_meta(path)
|
|
80
|
+
if not meta:
|
|
81
|
+
continue
|
|
82
|
+
sid = meta["id"]
|
|
83
|
+
if sid in seen:
|
|
71
84
|
continue
|
|
85
|
+
seen.add(sid)
|
|
86
|
+
sessions.append(meta)
|
|
72
87
|
|
|
73
88
|
# Sort all found sessions by saved_at DESC
|
|
74
89
|
sessions.sort(key=lambda x: x.get("saved_at", ""), reverse=True)
|
|
@@ -101,8 +116,8 @@ def save_session(state, config: dict, session_id: str | None = None) -> str:
|
|
|
101
116
|
payload = json.dumps(data, indent=2, default=str)
|
|
102
117
|
|
|
103
118
|
# 2. Save latest for /resume
|
|
104
|
-
|
|
105
|
-
(
|
|
119
|
+
SESSIONS_DIR.mkdir(parents=True, exist_ok=True)
|
|
120
|
+
(SESSIONS_DIR / "session_latest.json").write_text(payload, encoding="utf-8")
|
|
106
121
|
|
|
107
122
|
# 3. Save to daily folder
|
|
108
123
|
day_dir = DAILY_DIR / date_str
|
|
@@ -155,16 +170,8 @@ def delete_session(session_id: str) -> bool:
|
|
|
155
170
|
return False
|
|
156
171
|
|
|
157
172
|
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
173
|
|
|
167
|
-
#
|
|
174
|
+
# 1. Daily sessions
|
|
168
175
|
if DAILY_DIR.exists():
|
|
169
176
|
for d in DAILY_DIR.iterdir():
|
|
170
177
|
if d.is_dir():
|
|
@@ -174,7 +181,7 @@ def delete_session(session_id: str) -> bool:
|
|
|
174
181
|
deleted = True
|
|
175
182
|
except: pass
|
|
176
183
|
|
|
177
|
-
#
|
|
184
|
+
# 2. Root sessions (includes session_latest.json and manual /save files)
|
|
178
185
|
if SESSIONS_DIR.exists():
|
|
179
186
|
for p in SESSIONS_DIR.glob(f"*{session_id}*"):
|
|
180
187
|
try:
|
|
@@ -182,7 +189,7 @@ def delete_session(session_id: str) -> bool:
|
|
|
182
189
|
deleted = True
|
|
183
190
|
except: pass
|
|
184
191
|
|
|
185
|
-
#
|
|
192
|
+
# 3. Update history.json
|
|
186
193
|
if SESSION_HIST_FILE.exists():
|
|
187
194
|
try:
|
|
188
195
|
hist = json.loads(SESSION_HIST_FILE.read_text())
|