dulus 3.10.76__tar.gz → 3.10.78__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-3.10.76/dulus.egg-info → dulus-3.10.78}/PKG-INFO +9 -1
- {dulus-3.10.76 → dulus-3.10.78}/README.md +8 -0
- {dulus-3.10.76 → dulus-3.10.78}/checkpoint/__init__.py +3 -1
- {dulus-3.10.76 → dulus-3.10.78}/checkpoint/store.py +125 -4
- {dulus-3.10.76 → dulus-3.10.78}/docs/news.md +4 -0
- {dulus-3.10.76 → dulus-3.10.78/dulus.egg-info}/PKG-INFO +9 -1
- {dulus-3.10.76 → dulus-3.10.78}/dulus.py +15 -1
- {dulus-3.10.76 → dulus-3.10.78}/pyproject.toml +1 -1
- {dulus-3.10.76 → dulus-3.10.78}/tests/test_checkpoint.py +75 -0
- {dulus-3.10.76 → dulus-3.10.78}/LICENSE +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/MANIFEST.in +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/agent.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/algolia_search.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/analytics.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/backend/__init__.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/backend/agents_bridge.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/backend/compressor.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/backend/context.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/backend/githook.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/backend/marketplace.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/backend/mempalace_bridge.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/backend/paths.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/backend/personas.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/backend/plugins.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/backend/server.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/backend/tasks.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/banners/__init__.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/banners/banner_rotator.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/banners/banners_dulus_ai.txt +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/batch_api.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/checkpoint/hooks.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/checkpoint/types.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/cli_animations/__init__.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/cli_animations/__main__.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/cli_animations/ansi.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/cli_animations/assets/ascii/dulus_logo.txt +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/cli_animations/assets/ascii/eagle.txt +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/cli_animations/assets/ascii/terminal_art.txt +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/cli_animations/banners.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/cli_animations/demo.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/cli_animations/effects.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/cli_animations/effort_slider.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/cli_animations/progress.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/cli_animations/py.typed +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/cli_animations/spinners.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/cli_animations/status.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/clipboard_utils.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/cloudsave.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/common.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/compaction.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/config.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/context.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/data/__init__.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/data/active_persona.json +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/data/marketplace.json +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/data/personas.json +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/data/plugins/__init__.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/data/plugins/composio/__init__.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/data/plugins/composio/composio_plugin/__init__.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/data/plugins/composio/composio_plugin/session_manager.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/data/plugins/composio/composio_plugin/tool_generator.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/data/plugins/composio/plugin.json +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/data/plugins/composio/plugin_tool.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/data/tasks.json +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/display_blocks.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/docs/API.md +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/docs/BRAND_GUIDE.md +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/docs/CHANGELOG.md +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/docs/CONTRIBUTING.md +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/docs/DEPLOYMENT.md +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/docs/Dulus_AI_Whitepaper_(v2.0).pdf +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/docs/FAQ.md +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/docs/GETTING_STARTED.md +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/docs/README.md +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/docs/README_AR.md +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/docs/README_EN.md +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/docs/README_ES.md +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/docs/README_FR.md +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/docs/README_JA.md +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/docs/README_KO.md +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/docs/README_PT.md +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/docs/README_RU.md +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/docs/README_ZH.md +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/docs/SECURITY.md +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/docs/__init__.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/docs/api.html +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/docs/architecture.md +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/docs/azure-speech-template.json +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/docs/dashboard/index.html +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/docs/divider.svg +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/docs/dulus-bird.png +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/docs/generate.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/docs/index.html +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/docs/nvidia-models.svg +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/docs/particle-playground.html +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/docs/personas/index.html +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/docs/poetry-banner.png +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/docs/preview.html +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/docs/python-console.md +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/docs/readme/auto-adapter.png +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/docs/readme/dulus-os.png +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/docs/readme/hero.png +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/docs/readme/lookback_two_windows.png +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/docs/readme/pyc_heap.svg +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/docs/readme/pyc_receipts.svg +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/docs/readme/pyc_trap.svg +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/docs/readme/roundtable.png +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/docs/readme/task-board.png +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/docs/readme/webchat.png +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/docs/sec-agents.svg +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/docs/sec-brainstorm.svg +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/docs/sec-bridges.svg +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/docs/sec-features.svg +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/docs/sec-freetier.svg +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/docs/sec-memory.svg +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/docs/sec-models.svg +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/docs/sec-perms.svg +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/docs/sec-plugins.svg +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/docs/sec-quickstart.svg +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/docs/sec-ssj.svg +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/docs/spinners.svg +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/docs/split-pane.svg +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/docs/terminal-boot.svg +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/docs/termux-edge.md +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/dulus.egg-info/SOURCES.txt +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/dulus.egg-info/dependency_links.txt +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/dulus.egg-info/entry_points.txt +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/dulus.egg-info/requires.txt +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/dulus.egg-info/top_level.txt +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/dulus_bar_client.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/dulus_gui.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/dulus_mcp/__init__.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/dulus_mcp/client.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/dulus_mcp/commands.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/dulus_mcp/config.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/dulus_mcp/hub.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/dulus_mcp/tools.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/dulus_mcp/types.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/dulus_quick_menu.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/dulus_tools/__init__.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/dulus_tools/add_dir_manager.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/dulus_tools/afk_mode.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/dulus_tools/approval_runtime.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/dulus_tools/background_tasks.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/dulus_tools/clipboard_utils.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/dulus_tools/diff_visualizer.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/dulus_tools/export_import.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/dulus_tools/hook_engine.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/dulus_tools/notification_manager.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/dulus_tools/session_fork.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/dulus_tools/shell_mode.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/dulus_tools/todo_visualizer.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/dulus_tools/tree_ls.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/dulus_tools/wire_events.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/dulus_tools/yolo_mode.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/file_filter.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/fs_compass.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/governance.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/gui/__init__.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/gui/agent_bridge.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/gui/chat_widget.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/gui/main_window.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/gui/personas.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/gui/sandbox_server.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/gui/session_utils.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/gui/settings_dialog.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/gui/sidebar.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/gui/tasks_view.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/gui/themes.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/gui/tool_panel.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/input.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/license_manager.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/lookback.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/memory/__init__.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/memory/audit.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/memory/consolidator.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/memory/context.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/memory/mempalace_bridge.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/memory/offload.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/memory/palace.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/memory/scan.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/memory/seeds/insights/adaptation_guides.md +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/memory/seeds/insights/askuserquestion_after_numbered_list.md +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/memory/seeds/insights/background_jobs_no_extra_timers.md +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/memory/seeds/insights/cleverness_over_resources.md +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/memory/seeds/insights/corporate_tls_breaks_curl_cffi.md +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/memory/seeds/insights/hardened_rules.md +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/memory/seeds/insights/investigator_workflow_heuristic.md +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/memory/seeds/insights/native_code_over_sketchy_plugins.md +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/memory/seeds/insights/no_personal_info_in_code.md +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/memory/seeds/insights/searches_include_modified_date.md +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/memory/seeds/insights/tmux_send_keys_power_patterns.md +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/memory/seeds/insights/token_optimization_technique.md +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/memory/seeds/insights/tool_chaining_capability.md +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/memory/seeds/insights/webbridge_evaluate_first_pattern.md +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/memory/seeds/short_memory.md +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/memory/sessions.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/memory/store.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/memory/tools.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/memory/types.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/memory/vector_search.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/model_params.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/multi_agent/__init__.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/multi_agent/subagent.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/multi_agent/tools.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/paste_placeholders.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/personality_config.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/plugin/__init__.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/plugin/autoadapter.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/plugin/loader.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/plugin/recommend.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/plugin/store.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/plugin/types.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/profiles.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/providers.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/pyconsole.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/sandbox/README.md +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/sandbox/__init__.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/sandbox/components.json +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/sandbox/dist/assets/index-DE51D6wI.css +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/sandbox/dist/assets/index-DMCCNE9Y.js +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/sandbox/dist/index.html +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/sandbox/dist/wallpaper-default.jpg +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/sandbox/dist/wallpapers/default.jpeg +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/sandbox/dist/wallpapers/light.jpeg +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/sandbox/dist/wallpapers/nature.jpeg +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/sandbox/dist/wallpapers/tech.jpeg +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/sandbox/eslint.config.js +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/sandbox/index.html +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/sandbox/info.md +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/sandbox/package-lock.json +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/sandbox/package.json +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/sandbox/postcss.config.js +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/sandbox/tailwind.config.js +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/sandbox/tsconfig.app.json +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/sandbox/tsconfig.json +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/sandbox/tsconfig.node.json +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/sandbox/vite.config.ts +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/sandbox_bootstrap.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/setup.cfg +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/skill/__init__.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/skill/builtin.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/skill/bundled/LICENSE-obsidian-skills +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/skill/bundled/README.md +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/skill/bundled/defuddle/SKILL.md +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/skill/bundled/json-canvas/SKILL.md +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/skill/bundled/json-canvas/references/EXAMPLES.md +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/skill/bundled/obsidian-bases/SKILL.md +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/skill/bundled/obsidian-bases/references/FUNCTIONS_REFERENCE.md +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/skill/bundled/obsidian-cli/SKILL.md +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/skill/bundled/obsidian-markdown/SKILL.md +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/skill/bundled/obsidian-markdown/references/CALLOUTS.md +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/skill/bundled/obsidian-markdown/references/EMBEDS.md +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/skill/bundled/obsidian-markdown/references/PROPERTIES.md +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/skill/bundled/unbroker/README.md +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/skill/bundled/unbroker/SKILL.md +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/skill/bundled/unbroker/references/brokers/addresses.json +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/skill/bundled/unbroker/references/brokers/advancedbackgroundchecks.json +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/skill/bundled/unbroker/references/brokers/beenverified.json +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/skill/bundled/unbroker/references/brokers/clustal.json +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/skill/bundled/unbroker/references/brokers/clustrmaps.json +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/skill/bundled/unbroker/references/brokers/cyberbackgroundchecks.json +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/skill/bundled/unbroker/references/brokers/familytreenow.json +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/skill/bundled/unbroker/references/brokers/fastpeoplesearch.json +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/skill/bundled/unbroker/references/brokers/intelius.json +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/skill/bundled/unbroker/references/brokers/mylife.json +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/skill/bundled/unbroker/references/brokers/nuwber.json +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/skill/bundled/unbroker/references/brokers/peekyou.json +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/skill/bundled/unbroker/references/brokers/peoplefinders.json +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/skill/bundled/unbroker/references/brokers/radaris.json +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/skill/bundled/unbroker/references/brokers/rehold.json +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/skill/bundled/unbroker/references/brokers/searchpeoplefree.json +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/skill/bundled/unbroker/references/brokers/socialcatfish.json +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/skill/bundled/unbroker/references/brokers/spokeo.json +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/skill/bundled/unbroker/references/brokers/thatsthem.json +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/skill/bundled/unbroker/references/brokers/truepeoplesearch.json +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/skill/bundled/unbroker/references/brokers/usphonebook.json +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/skill/bundled/unbroker/references/brokers/whitepages.json +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/skill/bundled/unbroker/references/legal/ccpa.md +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/skill/bundled/unbroker/references/legal/drop.md +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/skill/bundled/unbroker/references/legal/gdpr.md +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/skill/bundled/unbroker/references/methods.md +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/skill/bundled/unbroker/references/site-playbooks.md +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/skill/bundled/unbroker/references/state-machine.md +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/skill/bundled/unbroker/scripts/autopilot.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/skill/bundled/unbroker/scripts/badbool.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/skill/bundled/unbroker/scripts/brokers.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/skill/bundled/unbroker/scripts/cdp.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/skill/bundled/unbroker/scripts/config.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/skill/bundled/unbroker/scripts/crypto.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/skill/bundled/unbroker/scripts/dossier.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/skill/bundled/unbroker/scripts/email_modes.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/skill/bundled/unbroker/scripts/emailer.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/skill/bundled/unbroker/scripts/ledger.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/skill/bundled/unbroker/scripts/legal.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/skill/bundled/unbroker/scripts/paths.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/skill/bundled/unbroker/scripts/pdd.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/skill/bundled/unbroker/scripts/registry.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/skill/bundled/unbroker/scripts/report.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/skill/bundled/unbroker/scripts/scan.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/skill/bundled/unbroker/scripts/storage.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/skill/bundled/unbroker/scripts/tiers.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/skill/bundled/unbroker/scripts/vectors.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/skill/bundled/unbroker/templates/consent/authorization.md +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/skill/bundled/unbroker/templates/emails/ccpa-authorized-agent.txt +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/skill/bundled/unbroker/templates/emails/ccpa-deletion.txt +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/skill/bundled/unbroker/templates/emails/ccpa-indirect-deletion.txt +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/skill/bundled/unbroker/templates/emails/gdpr-erasure.txt +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/skill/bundled/unbroker/templates/emails/generic-optout.txt +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/skill/clawhub.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/skill/executor.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/skill/loader.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/skill/tools.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/skills.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/soul.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/spinner.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/steer_input.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/subagent.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/task/__init__.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/task/store.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/task/tools.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/task/types.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/tests/test_afk_yolo.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/tests/test_approval_runtime.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/tests/test_background_task_tools.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/tests/test_background_tasks.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/tests/test_chatgpt_oauth.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/tests/test_cli_animations_integration.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/tests/test_clipboard_utils.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/tests/test_compaction.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/tests/test_composio_tool_generator.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/tests/test_diff_view.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/tests/test_diff_visualization.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/tests/test_display_blocks.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/tests/test_export_import.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/tests/test_grok_usage.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/tests/test_hook_engine.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/tests/test_injection_fix.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/tests/test_license.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/tests/test_lookback_cache.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/tests/test_mcp.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/tests/test_mcp_algolia.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/tests/test_memory.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/tests/test_memory_home_resolution.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/tests/test_notification_manager.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/tests/test_plugin.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/tests/test_provider_errors.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/tests/test_session_fork.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/tests/test_shell_mode.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/tests/test_skills.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/tests/test_skillssh_algolia.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/tests/test_steer_input.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/tests/test_subagent.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/tests/test_task.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/tests/test_telegram_buffer.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/tests/test_think_tool.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/tests/test_todo_tool.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/tests/test_todo_visualization.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/tests/test_tool_registry.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/tests/test_tree_ls.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/tests/test_voice.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/tests/test_webbridge_tabs.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/tests/test_wire_events.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/tmux_tools.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/tool_registry.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/tools.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/tools_background.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/tools_think.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/tools_todo.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/ui/__init__.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/ui/input.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/ui/render.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/updater.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/voice/__init__.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/voice/audio_utils.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/voice/keyterms.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/voice/recorder.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/voice/stt.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/voice/tts.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/voice/wake_word.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/webbridge/__init__.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/webbridge/core.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/webbridge/tools.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/webchat.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/webchat_server.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/webchat_ui/__init__.py +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/webchat_ui/app.js +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/webchat_ui/index.html +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/webchat_ui/style.css +0 -0
- {dulus-3.10.76 → dulus-3.10.78}/welcome.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: dulus
|
|
3
|
-
Version: 3.10.
|
|
3
|
+
Version: 3.10.78
|
|
4
4
|
Summary: Provider-independent agent runtime for the terminal — cloud and local models, real tools, MCP, plugins, memory, sub-agents, and Dulus OS.
|
|
5
5
|
Author: KevRojo
|
|
6
6
|
License: GPL-3.0-only
|
|
@@ -254,6 +254,14 @@ iwr -useb https://raw.githubusercontent.com/KevRojo/Dulus/main/install.ps1 | iex
|
|
|
254
254
|
|
|
255
255
|
Windows users who do not want Python or a terminal can download the self-contained MSI from [GitHub Releases](https://github.com/KevRojo/Dulus/releases).
|
|
256
256
|
|
|
257
|
+
### Dulus for Windows — desktop app (alpha/beta trial)
|
|
258
|
+
|
|
259
|
+
The full **Dulus desktop app** for Windows is now in open trial. No Python, no terminal, no API key — download, install, sign in, and go.
|
|
260
|
+
|
|
261
|
+
**[⬇️ Dulus Windows Trial setup (alpha/beta)](https://drive.google.com/file/d/1Nxz_coVpXRwKQB7baI_0nG12-p1cLznZ/view)**
|
|
262
|
+
|
|
263
|
+
It is early — expect rough edges, and tell us about them in [Issues](https://github.com/KevRojo/Dulus/issues). The CLI in this repository stays free and open source; the desktop app is the same engine with a face.
|
|
264
|
+
|
|
257
265
|
### Docker
|
|
258
266
|
|
|
259
267
|
```bash
|
|
@@ -176,6 +176,14 @@ iwr -useb https://raw.githubusercontent.com/KevRojo/Dulus/main/install.ps1 | iex
|
|
|
176
176
|
|
|
177
177
|
Windows users who do not want Python or a terminal can download the self-contained MSI from [GitHub Releases](https://github.com/KevRojo/Dulus/releases).
|
|
178
178
|
|
|
179
|
+
### Dulus for Windows — desktop app (alpha/beta trial)
|
|
180
|
+
|
|
181
|
+
The full **Dulus desktop app** for Windows is now in open trial. No Python, no terminal, no API key — download, install, sign in, and go.
|
|
182
|
+
|
|
183
|
+
**[⬇️ Dulus Windows Trial setup (alpha/beta)](https://drive.google.com/file/d/1Nxz_coVpXRwKQB7baI_0nG12-p1cLznZ/view)**
|
|
184
|
+
|
|
185
|
+
It is early — expect rough edges, and tell us about them in [Issues](https://github.com/KevRojo/Dulus/issues). The CLI in this repository stays free and open source; the desktop app is the same engine with a face.
|
|
186
|
+
|
|
179
187
|
### Docker
|
|
180
188
|
|
|
181
189
|
```bash
|
|
@@ -9,6 +9,7 @@ from .store import (
|
|
|
9
9
|
files_changed_since,
|
|
10
10
|
delete_session_checkpoints,
|
|
11
11
|
cleanup_old_sessions,
|
|
12
|
+
enforce_store_budget,
|
|
12
13
|
reset_file_versions,
|
|
13
14
|
)
|
|
14
15
|
from .hooks import (
|
|
@@ -22,6 +23,7 @@ __all__ = [
|
|
|
22
23
|
"FileBackup", "Snapshot", "MAX_SNAPSHOTS",
|
|
23
24
|
"track_file_edit", "make_snapshot", "list_snapshots", "get_snapshot",
|
|
24
25
|
"rewind_files", "files_changed_since",
|
|
25
|
-
"delete_session_checkpoints", "cleanup_old_sessions", "
|
|
26
|
+
"delete_session_checkpoints", "cleanup_old_sessions", "enforce_store_budget",
|
|
27
|
+
"reset_file_versions",
|
|
26
28
|
"set_session", "get_tracked_edits", "reset_tracked", "install_hooks",
|
|
27
29
|
]
|
|
@@ -22,6 +22,18 @@ from .types import FileBackup, Snapshot, MAX_SNAPSHOTS
|
|
|
22
22
|
# Max file size to back up (1 MB)
|
|
23
23
|
_MAX_FILE_SIZE = 1 * 1024 * 1024
|
|
24
24
|
|
|
25
|
+
# Hard ceiling for the whole checkpoint store. Age-based cleanup alone cannot
|
|
26
|
+
# bound this: MAX_SNAPSHOTS caps snapshots *per session*, but nothing capped
|
|
27
|
+
# the number of sessions, so a busy machine could accumulate gigabytes long
|
|
28
|
+
# before the 30-day cutoff ever applied. Sentry caught exactly that in the
|
|
29
|
+
# wild -- ENOSPC from mkdir under ~/.dulus/checkpoints on a user's server.
|
|
30
|
+
_MAX_STORE_BYTES = 512 * 1024 * 1024
|
|
31
|
+
|
|
32
|
+
# Free space below which we stop writing new backups entirely. Checkpoints are
|
|
33
|
+
# a convenience feature; they must never be the reason a user's disk fills up
|
|
34
|
+
# or their session dies.
|
|
35
|
+
_MIN_FREE_BYTES = 256 * 1024 * 1024
|
|
36
|
+
|
|
25
37
|
# Per-file version counters (reset per session)
|
|
26
38
|
_file_versions: dict[str, int] = {}
|
|
27
39
|
|
|
@@ -34,15 +46,29 @@ def _session_dir(session_id: str) -> Path:
|
|
|
34
46
|
return _checkpoints_root() / session_id
|
|
35
47
|
|
|
36
48
|
|
|
49
|
+
def _safe_mkdir(path: Path) -> bool:
|
|
50
|
+
"""mkdir -p that never raises.
|
|
51
|
+
|
|
52
|
+
A full disk raised OSError(ENOSPC) straight out of pathlib.mkdir and took
|
|
53
|
+
the whole session down with it. Checkpointing is optional; failing to
|
|
54
|
+
create its directory must degrade silently, not crash the caller.
|
|
55
|
+
"""
|
|
56
|
+
try:
|
|
57
|
+
path.mkdir(parents=True, exist_ok=True)
|
|
58
|
+
return True
|
|
59
|
+
except OSError:
|
|
60
|
+
return False
|
|
61
|
+
|
|
62
|
+
|
|
37
63
|
def _backups_dir(session_id: str) -> Path:
|
|
38
64
|
d = _session_dir(session_id) / "backups"
|
|
39
|
-
d
|
|
65
|
+
_safe_mkdir(d)
|
|
40
66
|
return d
|
|
41
67
|
|
|
42
68
|
|
|
43
69
|
def _snapshots_file(session_id: str) -> Path:
|
|
44
70
|
d = _session_dir(session_id)
|
|
45
|
-
d
|
|
71
|
+
_safe_mkdir(d)
|
|
46
72
|
return d / "snapshots.json"
|
|
47
73
|
|
|
48
74
|
|
|
@@ -51,6 +77,90 @@ def _path_hash(file_path: str) -> str:
|
|
|
51
77
|
return hashlib.sha256(file_path.encode()).hexdigest()[:16]
|
|
52
78
|
|
|
53
79
|
|
|
80
|
+
def _dir_size(path: Path) -> int:
|
|
81
|
+
"""Total bytes under path. Best-effort: unreadable entries are skipped."""
|
|
82
|
+
total = 0
|
|
83
|
+
try:
|
|
84
|
+
for entry in path.rglob("*"):
|
|
85
|
+
try:
|
|
86
|
+
if entry.is_file():
|
|
87
|
+
total += entry.stat().st_size
|
|
88
|
+
except OSError:
|
|
89
|
+
continue
|
|
90
|
+
except OSError:
|
|
91
|
+
pass
|
|
92
|
+
return total
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _free_bytes(path: Path) -> int | None:
|
|
96
|
+
"""Free space on the filesystem holding path, or None if undeterminable."""
|
|
97
|
+
try:
|
|
98
|
+
return shutil.disk_usage(str(path)).free
|
|
99
|
+
except OSError:
|
|
100
|
+
return None
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _has_room_for_backups() -> bool:
|
|
104
|
+
"""Whether it is safe to write another backup.
|
|
105
|
+
|
|
106
|
+
Returns False when the store has grown past its ceiling or the disk is
|
|
107
|
+
nearly full. Callers must degrade gracefully: losing a checkpoint is
|
|
108
|
+
acceptable, killing the user's session with ENOSPC is not.
|
|
109
|
+
"""
|
|
110
|
+
root = _checkpoints_root()
|
|
111
|
+
free = _free_bytes(root if root.exists() else root.parent)
|
|
112
|
+
if free is not None and free < _MIN_FREE_BYTES:
|
|
113
|
+
return False
|
|
114
|
+
return _dir_size(root) < _MAX_STORE_BYTES
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def enforce_store_budget() -> int:
|
|
118
|
+
"""Evict oldest sessions until the store fits in _MAX_STORE_BYTES.
|
|
119
|
+
|
|
120
|
+
Age-based cleanup runs on a 30-day cutoff, which is far too slow for a
|
|
121
|
+
machine that opens many sessions a day. This is the size-based backstop.
|
|
122
|
+
Returns the number of sessions removed.
|
|
123
|
+
"""
|
|
124
|
+
root = _checkpoints_root()
|
|
125
|
+
if not root.exists():
|
|
126
|
+
return 0
|
|
127
|
+
|
|
128
|
+
size = _dir_size(root)
|
|
129
|
+
free = _free_bytes(root)
|
|
130
|
+
over_budget = size > _MAX_STORE_BYTES
|
|
131
|
+
low_disk = free is not None and free < _MIN_FREE_BYTES
|
|
132
|
+
if not (over_budget or low_disk):
|
|
133
|
+
return 0
|
|
134
|
+
|
|
135
|
+
try:
|
|
136
|
+
sessions = [d for d in root.iterdir() if d.is_dir()]
|
|
137
|
+
except OSError:
|
|
138
|
+
return 0
|
|
139
|
+
|
|
140
|
+
# Oldest first, so recent (likely resumable) sessions survive.
|
|
141
|
+
def _mtime(d: Path) -> float:
|
|
142
|
+
try:
|
|
143
|
+
return d.stat().st_mtime
|
|
144
|
+
except OSError:
|
|
145
|
+
return 0.0
|
|
146
|
+
|
|
147
|
+
sessions.sort(key=_mtime)
|
|
148
|
+
|
|
149
|
+
# When the disk itself is tight, free space aggressively rather than
|
|
150
|
+
# stopping at the store ceiling.
|
|
151
|
+
target = _MAX_STORE_BYTES // 2 if low_disk else _MAX_STORE_BYTES
|
|
152
|
+
|
|
153
|
+
removed = 0
|
|
154
|
+
for d in sessions:
|
|
155
|
+
if size <= target:
|
|
156
|
+
break
|
|
157
|
+
freed = _dir_size(d)
|
|
158
|
+
shutil.rmtree(str(d), ignore_errors=True)
|
|
159
|
+
size -= freed
|
|
160
|
+
removed += 1
|
|
161
|
+
return removed
|
|
162
|
+
|
|
163
|
+
|
|
54
164
|
def _next_version(file_path: str) -> int:
|
|
55
165
|
v = _file_versions.get(file_path, 0) + 1
|
|
56
166
|
_file_versions[file_path] = v
|
|
@@ -72,9 +182,14 @@ def _load_snapshots(session_id: str) -> list[Snapshot]:
|
|
|
72
182
|
|
|
73
183
|
def _save_snapshots(session_id: str, snapshots: list[Snapshot]) -> None:
|
|
74
184
|
f = _snapshots_file(session_id)
|
|
75
|
-
f.parent
|
|
185
|
+
if not _safe_mkdir(f.parent):
|
|
186
|
+
return
|
|
76
187
|
data = [s.to_dict() for s in snapshots]
|
|
77
|
-
|
|
188
|
+
try:
|
|
189
|
+
f.write_text(json.dumps(data, indent=2, ensure_ascii=False), encoding="utf-8")
|
|
190
|
+
except OSError as e:
|
|
191
|
+
# ENOSPC and friends: drop the checkpoint rather than the session.
|
|
192
|
+
print(f"[checkpoint] could not persist snapshots: {e}")
|
|
78
193
|
|
|
79
194
|
|
|
80
195
|
# ── Public API ───────────────────────────────────────────────────────────────
|
|
@@ -100,6 +215,12 @@ def track_file_edit(session_id: str, file_path: str) -> str | None:
|
|
|
100
215
|
print(f"[checkpoint] skipping large file ({size} bytes): {file_path}")
|
|
101
216
|
return None
|
|
102
217
|
|
|
218
|
+
# Disk guard: never let a convenience feature fill the user's disk.
|
|
219
|
+
if not _has_room_for_backups():
|
|
220
|
+
enforce_store_budget()
|
|
221
|
+
if not _has_room_for_backups():
|
|
222
|
+
return None
|
|
223
|
+
|
|
103
224
|
# Copy file to backups/
|
|
104
225
|
version = _next_version(file_path)
|
|
105
226
|
backup_name = f"{_path_hash(file_path)}@v{version}"
|
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
## 🔥🔥🔥 News (Pacific Time)
|
|
4
4
|
|
|
5
|
+
- August 14, 2026 (**v3.10.78**): **🩹 Checkpoints can no longer fill your disk.** The checkpoint store only pruned sessions older than 30 days, so a machine that opens many sessions a day could run out of space long before that cutoff applied — and when the disk filled, the resulting `ENOSPC` escaped and took the session down with it. There is now a hard 512 MB ceiling on the store plus a free-space floor: oldest sessions are evicted first, backups are skipped when space is tight, and a full disk degrades to "no checkpoint" instead of an error. Reported from the wild via crash telemetry. 🦅🇩🇴
|
|
6
|
+
|
|
7
|
+
- August 14, 2026 (**v3.10.77**): **🖥️ The Dulus desktop app for Windows is out in open trial (alpha/beta).** Same engine you've been running in the terminal — now with a face. No Python, no terminal, no API key: download, install, sign in, go. **[⬇️ Dulus Windows Trial setup (alpha/beta)](https://drive.google.com/file/d/1Nxz_coVpXRwKQB7baI_0nG12-p1cLznZ/view)**. It is early and it will have rough edges — that's the point of a trial, so [file what breaks](https://github.com/KevRojo/Dulus/issues). The CLI in this repo stays free and open source, forever. 🦅🇩🇴
|
|
8
|
+
|
|
5
9
|
- August 13, 2026 (**v3.10.76**): **⌨️ Quick menu moved to `↓↓`.** It used to open on double-tap `←←` — which happens to be the exact key you press to go back and fix one word, so editing a typo kept ambushing you with a menu. Now it's **double-tap Down (`↓↓`)**; a single `↓` still just moves down / walks history. Fewer accidental menus, same fast effort/thinking/model switcher. `pip install --upgrade dulus`. 🦅🇩🇴
|
|
6
10
|
|
|
7
11
|
- August 9, 2026 (**v3.10.75**): **⚡ `/mcp search` got instant too + smarter tool use.** MCP server search now hits our own **Algolia index** (~3k servers) before the slow live registry crawl — **~1ms, typo-tolerant** instead of 10s+. And Dulus now chains tools far more aggressively: independent calls fire in parallel, multi-step file hunts run in **one Python-kernel sweep** (`os.walk` + regex) instead of a dozen Grep/Read round-trips — fewer tokens, faster answers. `DULUS_ALGOLIA=0` opts out of Algolia. `pip install --upgrade dulus`. 🦅🇩🇴
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: dulus
|
|
3
|
-
Version: 3.10.
|
|
3
|
+
Version: 3.10.78
|
|
4
4
|
Summary: Provider-independent agent runtime for the terminal — cloud and local models, real tools, MCP, plugins, memory, sub-agents, and Dulus OS.
|
|
5
5
|
Author: KevRojo
|
|
6
6
|
License: GPL-3.0-only
|
|
@@ -254,6 +254,14 @@ iwr -useb https://raw.githubusercontent.com/KevRojo/Dulus/main/install.ps1 | iex
|
|
|
254
254
|
|
|
255
255
|
Windows users who do not want Python or a terminal can download the self-contained MSI from [GitHub Releases](https://github.com/KevRojo/Dulus/releases).
|
|
256
256
|
|
|
257
|
+
### Dulus for Windows — desktop app (alpha/beta trial)
|
|
258
|
+
|
|
259
|
+
The full **Dulus desktop app** for Windows is now in open trial. No Python, no terminal, no API key — download, install, sign in, and go.
|
|
260
|
+
|
|
261
|
+
**[⬇️ Dulus Windows Trial setup (alpha/beta)](https://drive.google.com/file/d/1Nxz_coVpXRwKQB7baI_0nG12-p1cLznZ/view)**
|
|
262
|
+
|
|
263
|
+
It is early — expect rough edges, and tell us about them in [Issues](https://github.com/KevRojo/Dulus/issues). The CLI in this repository stays free and open source; the desktop app is the same engine with a face.
|
|
264
|
+
|
|
257
265
|
### Docker
|
|
258
266
|
|
|
259
267
|
```bash
|
|
@@ -380,7 +380,7 @@ try:
|
|
|
380
380
|
from importlib.metadata import version as _pkg_version
|
|
381
381
|
VERSION = _pkg_version("dulus")
|
|
382
382
|
except Exception:
|
|
383
|
-
VERSION = "3.10.
|
|
383
|
+
VERSION = "3.10.78" # dev fallback — keep in sync with pyproject.toml
|
|
384
384
|
|
|
385
385
|
# ── ANSI helpers (used even with rich for non-markdown output) ─────────────
|
|
386
386
|
from common import C, clr, info, ok, warn, err, stream_thinking, sanitize_text
|
|
@@ -10669,15 +10669,21 @@ def cmd_claude_batch(args: str, _state, config) -> bool:
|
|
|
10669
10669
|
def cmd_buy_dulus(_args: str, _state, _config) -> bool:
|
|
10670
10670
|
"""Open the $DULUS DexScreener page. Temporary / undocumented — not in /help."""
|
|
10671
10671
|
url = "https://tinyurl.com/DulusDexScreener"
|
|
10672
|
+
trial_url = "https://drive.google.com/file/d/1Nxz_coVpXRwKQB7baI_0nG12-p1cLznZ/view"
|
|
10672
10673
|
try:
|
|
10673
10674
|
from cli_animations.ansi import gradient_text, ORANGE, CYAN
|
|
10674
10675
|
styled = gradient_text(url, ORANGE, CYAN)
|
|
10676
|
+
styled_trial = gradient_text(trial_url, ORANGE, CYAN)
|
|
10675
10677
|
except Exception:
|
|
10676
10678
|
styled = url
|
|
10679
|
+
styled_trial = trial_url
|
|
10677
10680
|
print()
|
|
10678
10681
|
print(clr(" buy $dulus", "bold"))
|
|
10679
10682
|
print(f" {styled}")
|
|
10680
10683
|
print()
|
|
10684
|
+
print(clr(" Dulus Windows Trial setup (alpha/beta)", "bold"))
|
|
10685
|
+
print(f" {styled_trial}")
|
|
10686
|
+
print()
|
|
10681
10687
|
try:
|
|
10682
10688
|
import webbrowser
|
|
10683
10689
|
webbrowser.open(url)
|
|
@@ -11558,6 +11564,14 @@ def repl(config: dict, initial_prompt: str | None = None):
|
|
|
11558
11564
|
|
|
11559
11565
|
ckpt.set_session(session_id)
|
|
11560
11566
|
ckpt.cleanup_old_sessions()
|
|
11567
|
+
# Age-based cleanup only evicts sessions older than 30 days, which is far
|
|
11568
|
+
# too slow for a machine that opens many sessions a day. Enforce the size
|
|
11569
|
+
# ceiling too, otherwise the store grows until the disk runs out (observed
|
|
11570
|
+
# in the wild: ENOSPC under ~/.dulus/checkpoints on a user's server).
|
|
11571
|
+
try:
|
|
11572
|
+
ckpt.enforce_store_budget()
|
|
11573
|
+
except Exception:
|
|
11574
|
+
pass
|
|
11561
11575
|
# Initial snapshot: capture the "blank slate" before any prompts
|
|
11562
11576
|
ckpt.make_snapshot(session_id, state, config, "(initial state)", tracked_edits=None)
|
|
11563
11577
|
|
|
@@ -10,7 +10,7 @@ build-backend = "setuptools.build_meta"
|
|
|
10
10
|
|
|
11
11
|
[project]
|
|
12
12
|
name = "dulus"
|
|
13
|
-
version = "3.10.
|
|
13
|
+
version = "3.10.78"
|
|
14
14
|
description = "Provider-independent agent runtime for the terminal — cloud and local models, real tools, MCP, plugins, memory, sub-agents, and Dulus OS."
|
|
15
15
|
readme = "README.md"
|
|
16
16
|
requires-python = ">=3.10"
|
|
@@ -456,3 +456,78 @@ class TestIntegration:
|
|
|
456
456
|
state.turn_count = snap1.turn_count
|
|
457
457
|
assert len(state.messages) == 0
|
|
458
458
|
assert state.turn_count == 0
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
class TestDiskBudget:
|
|
462
|
+
"""Regression tests for the ENOSPC incident.
|
|
463
|
+
|
|
464
|
+
A user's server filled its disk and pathlib.mkdir raised OSError straight
|
|
465
|
+
out of the checkpoint store, killing the session. Age-based cleanup could
|
|
466
|
+
not have prevented it: MAX_SNAPSHOTS bounds snapshots per session, but
|
|
467
|
+
nothing bounded the number of sessions before the 30-day cutoff applied.
|
|
468
|
+
"""
|
|
469
|
+
|
|
470
|
+
def test_enforce_store_budget_evicts_oldest_first(self, tmp_home):
|
|
471
|
+
import time as _time
|
|
472
|
+
from checkpoint import store
|
|
473
|
+
|
|
474
|
+
_tmp, ckpt_root = tmp_home
|
|
475
|
+
for name in ("old", "mid", "new"):
|
|
476
|
+
d = ckpt_root / name
|
|
477
|
+
(d / "backups").mkdir(parents=True)
|
|
478
|
+
(d / "backups" / "blob").write_bytes(b"x" * 4096)
|
|
479
|
+
|
|
480
|
+
now = _time.time()
|
|
481
|
+
os.utime(ckpt_root / "old", (now - 9000, now - 9000))
|
|
482
|
+
os.utime(ckpt_root / "mid", (now - 6000, now - 6000))
|
|
483
|
+
os.utime(ckpt_root / "new", (now - 10, now - 10))
|
|
484
|
+
|
|
485
|
+
# Force the store over budget so eviction has to run.
|
|
486
|
+
with patch.object(store, "_MAX_STORE_BYTES", 6000):
|
|
487
|
+
removed = store.enforce_store_budget()
|
|
488
|
+
|
|
489
|
+
assert removed >= 1
|
|
490
|
+
# Newest session must survive; oldest must be the first to go.
|
|
491
|
+
assert (ckpt_root / "new").exists()
|
|
492
|
+
assert not (ckpt_root / "old").exists()
|
|
493
|
+
|
|
494
|
+
def test_enforce_store_budget_noop_when_under_budget(self, tmp_home):
|
|
495
|
+
from checkpoint import store
|
|
496
|
+
|
|
497
|
+
_tmp, ckpt_root = tmp_home
|
|
498
|
+
d = ckpt_root / "session-a"
|
|
499
|
+
(d / "backups").mkdir(parents=True)
|
|
500
|
+
(d / "backups" / "blob").write_bytes(b"x" * 128)
|
|
501
|
+
|
|
502
|
+
assert store.enforce_store_budget() == 0
|
|
503
|
+
assert d.exists()
|
|
504
|
+
|
|
505
|
+
def test_track_file_edit_skips_when_disk_is_full(self, tmp_home, tmp_path):
|
|
506
|
+
"""No backup should be written when the disk is nearly full."""
|
|
507
|
+
from checkpoint import store
|
|
508
|
+
|
|
509
|
+
target = tmp_path / "code.py"
|
|
510
|
+
target.write_text("print('hi')", encoding="utf-8")
|
|
511
|
+
|
|
512
|
+
with patch.object(store, "_has_room_for_backups", return_value=False):
|
|
513
|
+
result = store.track_file_edit("session-full", str(target))
|
|
514
|
+
|
|
515
|
+
assert result is None
|
|
516
|
+
|
|
517
|
+
def test_safe_mkdir_swallows_enospc(self, tmp_home):
|
|
518
|
+
"""A full disk must not raise out of the checkpoint store."""
|
|
519
|
+
from checkpoint import store
|
|
520
|
+
|
|
521
|
+
with patch.object(
|
|
522
|
+
Path, "mkdir", side_effect=OSError(28, "No space left on device")
|
|
523
|
+
):
|
|
524
|
+
assert store._safe_mkdir(Path("/whatever/nested")) is False
|
|
525
|
+
|
|
526
|
+
def test_save_snapshots_survives_enospc(self, tmp_home):
|
|
527
|
+
"""Persisting snapshots must degrade, not crash, on a full disk."""
|
|
528
|
+
from checkpoint import store
|
|
529
|
+
|
|
530
|
+
with patch.object(
|
|
531
|
+
Path, "write_text", side_effect=OSError(28, "No space left on device")
|
|
532
|
+
):
|
|
533
|
+
store._save_snapshots("session-x", []) # must not raise
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|