dulus 3.12.0__tar.gz → 3.13.0__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.12.0/dulus.egg-info → dulus-3.13.0}/PKG-INFO +28 -3
- {dulus-3.12.0 → dulus-3.13.0}/README.md +27 -2
- {dulus-3.12.0 → dulus-3.13.0}/agent.py +27 -1
- {dulus-3.12.0 → dulus-3.13.0}/common.py +23 -0
- {dulus-3.12.0 → dulus-3.13.0}/docs/CHANGELOG.md +33 -0
- {dulus-3.12.0 → dulus-3.13.0}/docs/news.md +2 -0
- {dulus-3.12.0 → dulus-3.13.0/dulus.egg-info}/PKG-INFO +28 -3
- {dulus-3.12.0 → dulus-3.13.0}/dulus.egg-info/SOURCES.txt +0 -11
- {dulus-3.12.0 → dulus-3.13.0}/dulus.egg-info/top_level.txt +0 -1
- {dulus-3.12.0 → dulus-3.13.0}/dulus.py +100 -24
- {dulus-3.12.0 → dulus-3.13.0}/pyproject.toml +1 -1
- dulus-3.13.0/tests/test_json_protocol.py +456 -0
- {dulus-3.12.0 → dulus-3.13.0}/webchat_ui/index.html +6 -0
- dulus-3.12.0/sandbox/components.json +0 -22
- dulus-3.12.0/sandbox/eslint.config.js +0 -23
- dulus-3.12.0/sandbox/index.html +0 -16
- dulus-3.12.0/sandbox/package-lock.json +0 -8274
- dulus-3.12.0/sandbox/package.json +0 -80
- dulus-3.12.0/sandbox/postcss.config.js +0 -6
- dulus-3.12.0/sandbox/tailwind.config.js +0 -84
- dulus-3.12.0/sandbox/tsconfig.app.json +0 -34
- dulus-3.12.0/sandbox/tsconfig.json +0 -17
- dulus-3.12.0/sandbox/tsconfig.node.json +0 -26
- dulus-3.12.0/sandbox/vite.config.ts +0 -28
- dulus-3.12.0/tests/test_json_protocol.py +0 -203
- {dulus-3.12.0 → dulus-3.13.0}/LICENSE +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/MANIFEST.in +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/algolia_search.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/analytics.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/backend/__init__.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/backend/agents_bridge.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/backend/compressor.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/backend/context.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/backend/githook.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/backend/marketplace.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/backend/mempalace_bridge.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/backend/paths.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/backend/personas.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/backend/plugins.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/backend/server.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/backend/tasks.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/banners/__init__.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/banners/banner_rotator.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/banners/banners_dulus_ai.txt +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/batch_api.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/checkpoint/__init__.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/checkpoint/hooks.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/checkpoint/store.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/checkpoint/types.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/cli_animations/__init__.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/cli_animations/__main__.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/cli_animations/ansi.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/cli_animations/assets/ascii/dulus_logo.txt +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/cli_animations/assets/ascii/eagle.txt +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/cli_animations/assets/ascii/terminal_art.txt +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/cli_animations/banners.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/cli_animations/demo.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/cli_animations/effects.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/cli_animations/effort_slider.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/cli_animations/progress.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/cli_animations/py.typed +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/cli_animations/spinners.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/cli_animations/status.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/clipboard_utils.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/cloudsave.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/compaction.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/config.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/context.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/data/__init__.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/data/active_persona.json +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/data/marketplace.json +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/data/personas.json +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/data/plugins/__init__.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/data/plugins/composio/__init__.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/data/plugins/composio/composio_plugin/__init__.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/data/plugins/composio/composio_plugin/session_manager.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/data/plugins/composio/composio_plugin/tool_generator.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/data/plugins/composio/plugin.json +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/data/plugins/composio/plugin_tool.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/data/tasks.json +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/display_blocks.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/docs/API.md +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/docs/BRAND_GUIDE.md +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/docs/CONTRIBUTING.md +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/docs/DEPLOYMENT.md +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/docs/Dulus_AI_Whitepaper_(v2.0).pdf +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/docs/Dulus_YC_Company_Overview_Aug_2026.pdf +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/docs/FAQ.md +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/docs/GETTING_STARTED.md +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/docs/README.md +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/docs/README_AR.md +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/docs/README_EN.md +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/docs/README_ES.md +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/docs/README_FR.md +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/docs/README_JA.md +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/docs/README_KO.md +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/docs/README_PT.md +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/docs/README_RU.md +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/docs/README_ZH.md +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/docs/SECURITY.md +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/docs/__init__.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/docs/api.html +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/docs/architecture.md +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/docs/azure-speech-template.json +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/docs/dashboard/index.html +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/docs/divider.svg +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/docs/dulus-bird.png +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/docs/generate.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/docs/index.html +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/docs/nvidia-models.svg +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/docs/particle-playground.html +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/docs/personas/index.html +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/docs/poetry-banner.png +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/docs/preview.html +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/docs/python-console.md +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/docs/readme/auto-adapter.png +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/docs/readme/dulus-os.png +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/docs/readme/hero.png +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/docs/readme/lookback_two_windows.png +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/docs/readme/pyc_heap.svg +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/docs/readme/pyc_receipts.svg +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/docs/readme/pyc_trap.svg +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/docs/readme/roundtable.png +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/docs/readme/task-board.png +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/docs/readme/webchat.png +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/docs/sec-agents.svg +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/docs/sec-brainstorm.svg +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/docs/sec-bridges.svg +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/docs/sec-features.svg +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/docs/sec-freetier.svg +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/docs/sec-memory.svg +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/docs/sec-models.svg +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/docs/sec-perms.svg +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/docs/sec-plugins.svg +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/docs/sec-quickstart.svg +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/docs/sec-ssj.svg +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/docs/spinners.svg +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/docs/split-pane.svg +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/docs/terminal-boot.svg +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/docs/termux-edge.md +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/dulus.egg-info/dependency_links.txt +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/dulus.egg-info/entry_points.txt +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/dulus.egg-info/requires.txt +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/dulus_account.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/dulus_bar_client.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/dulus_gui.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/dulus_mcp/__init__.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/dulus_mcp/client.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/dulus_mcp/commands.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/dulus_mcp/config.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/dulus_mcp/hub.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/dulus_mcp/tools.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/dulus_mcp/types.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/dulus_quick_menu.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/dulus_tools/__init__.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/dulus_tools/add_dir_manager.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/dulus_tools/afk_mode.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/dulus_tools/approval_runtime.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/dulus_tools/background_tasks.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/dulus_tools/clipboard_utils.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/dulus_tools/diff_visualizer.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/dulus_tools/export_import.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/dulus_tools/hook_engine.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/dulus_tools/notification_manager.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/dulus_tools/session_fork.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/dulus_tools/shell_mode.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/dulus_tools/todo_visualizer.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/dulus_tools/tree_ls.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/dulus_tools/wire_events.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/dulus_tools/yolo_mode.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/file_filter.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/fs_compass.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/governance.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/gui/__init__.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/gui/agent_bridge.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/gui/chat_widget.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/gui/main_window.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/gui/personas.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/gui/sandbox_server.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/gui/session_utils.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/gui/settings_dialog.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/gui/sidebar.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/gui/tasks_view.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/gui/themes.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/gui/tool_panel.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/input.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/license_manager.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/lookback.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/memory/__init__.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/memory/audit.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/memory/consolidator.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/memory/context.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/memory/mempalace_bridge.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/memory/offload.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/memory/palace.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/memory/scan.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/memory/seeds/insights/adaptation_guides.md +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/memory/seeds/insights/askuserquestion_after_numbered_list.md +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/memory/seeds/insights/background_jobs_no_extra_timers.md +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/memory/seeds/insights/cleverness_over_resources.md +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/memory/seeds/insights/corporate_tls_breaks_curl_cffi.md +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/memory/seeds/insights/hardened_rules.md +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/memory/seeds/insights/investigator_workflow_heuristic.md +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/memory/seeds/insights/native_code_over_sketchy_plugins.md +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/memory/seeds/insights/no_personal_info_in_code.md +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/memory/seeds/insights/searches_include_modified_date.md +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/memory/seeds/insights/tmux_send_keys_power_patterns.md +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/memory/seeds/insights/token_optimization_technique.md +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/memory/seeds/insights/tool_chaining_capability.md +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/memory/seeds/insights/webbridge_evaluate_first_pattern.md +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/memory/seeds/short_memory.md +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/memory/sessions.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/memory/store.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/memory/tools.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/memory/types.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/memory/vector_search.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/model_params.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/multi_agent/__init__.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/multi_agent/subagent.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/multi_agent/tools.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/paste_placeholders.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/personality_config.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/plugin/__init__.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/plugin/autoadapter.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/plugin/loader.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/plugin/recommend.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/plugin/store.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/plugin/types.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/profiles.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/providers.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/pyconsole.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/sandbox/README.md +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/sandbox/__init__.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/sandbox/dist/assets/index-DE51D6wI.css +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/sandbox/dist/assets/index-DMCCNE9Y.js +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/sandbox/dist/index.html +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/sandbox/dist/wallpaper-default.jpg +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/sandbox/dist/wallpapers/default.jpeg +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/sandbox/dist/wallpapers/light.jpeg +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/sandbox/dist/wallpapers/nature.jpeg +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/sandbox/dist/wallpapers/tech.jpeg +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/sandbox/info.md +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/sandbox_bootstrap.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/setup.cfg +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/skill/__init__.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/skill/builtin.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/LICENSE-obsidian-skills +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/README.md +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/defuddle/SKILL.md +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/json-canvas/SKILL.md +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/json-canvas/references/EXAMPLES.md +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/obsidian-bases/SKILL.md +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/obsidian-bases/references/FUNCTIONS_REFERENCE.md +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/obsidian-cli/SKILL.md +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/obsidian-markdown/SKILL.md +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/obsidian-markdown/references/CALLOUTS.md +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/obsidian-markdown/references/EMBEDS.md +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/obsidian-markdown/references/PROPERTIES.md +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/README.md +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/SKILL.md +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/references/brokers/addresses.json +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/references/brokers/advancedbackgroundchecks.json +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/references/brokers/beenverified.json +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/references/brokers/clustal.json +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/references/brokers/clustrmaps.json +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/references/brokers/cyberbackgroundchecks.json +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/references/brokers/familytreenow.json +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/references/brokers/fastpeoplesearch.json +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/references/brokers/intelius.json +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/references/brokers/mylife.json +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/references/brokers/nuwber.json +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/references/brokers/peekyou.json +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/references/brokers/peoplefinders.json +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/references/brokers/radaris.json +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/references/brokers/rehold.json +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/references/brokers/searchpeoplefree.json +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/references/brokers/socialcatfish.json +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/references/brokers/spokeo.json +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/references/brokers/thatsthem.json +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/references/brokers/truepeoplesearch.json +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/references/brokers/usphonebook.json +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/references/brokers/whitepages.json +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/references/legal/ccpa.md +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/references/legal/drop.md +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/references/legal/gdpr.md +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/references/methods.md +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/references/site-playbooks.md +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/references/state-machine.md +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/scripts/autopilot.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/scripts/badbool.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/scripts/brokers.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/scripts/cdp.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/scripts/config.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/scripts/crypto.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/scripts/dossier.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/scripts/email_modes.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/scripts/emailer.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/scripts/ledger.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/scripts/legal.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/scripts/paths.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/scripts/pdd.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/scripts/registry.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/scripts/report.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/scripts/scan.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/scripts/storage.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/scripts/tiers.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/scripts/vectors.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/templates/consent/authorization.md +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/templates/emails/ccpa-authorized-agent.txt +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/templates/emails/ccpa-deletion.txt +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/templates/emails/ccpa-indirect-deletion.txt +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/templates/emails/gdpr-erasure.txt +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/templates/emails/generic-optout.txt +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/skill/clawhub.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/skill/executor.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/skill/loader.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/skill/tools.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/skills.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/soul.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/spinner.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/steer_input.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/subagent.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/task/__init__.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/task/store.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/task/tools.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/task/types.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/tests/test_afk_yolo.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/tests/test_approval_runtime.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/tests/test_background_task_tools.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/tests/test_background_tasks.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/tests/test_chatgpt_oauth.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/tests/test_checkpoint.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/tests/test_cli_animations_integration.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/tests/test_clipboard_utils.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/tests/test_compaction.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/tests/test_composio_tool_generator.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/tests/test_diff_view.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/tests/test_diff_visualization.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/tests/test_display_blocks.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/tests/test_dulus_account.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/tests/test_export_import.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/tests/test_grok_usage.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/tests/test_hook_engine.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/tests/test_injection_fix.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/tests/test_license.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/tests/test_lookback_cache.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/tests/test_mcp.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/tests/test_mcp_algolia.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/tests/test_memory.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/tests/test_memory_home_resolution.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/tests/test_notification_manager.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/tests/test_plugin.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/tests/test_profile_isolation.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/tests/test_profile_plugin_scope.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/tests/test_provider_errors.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/tests/test_session_fork.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/tests/test_shell_detection.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/tests/test_shell_mode.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/tests/test_skills.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/tests/test_skillssh_algolia.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/tests/test_steer_input.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/tests/test_subagent.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/tests/test_task.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/tests/test_telegram_buffer.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/tests/test_think_tool.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/tests/test_todo_tool.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/tests/test_todo_visualization.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/tests/test_tool_registry.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/tests/test_tree_ls.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/tests/test_voice.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/tests/test_webbridge_tabs.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/tests/test_wire_events.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/tmux_tools.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/tool_registry.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/tools.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/tools_background.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/tools_think.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/tools_todo.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/ui/__init__.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/ui/input.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/ui/render.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/updater.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/voice/__init__.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/voice/audio_utils.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/voice/keyterms.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/voice/recorder.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/voice/stt.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/voice/tts.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/voice/wake_word.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/webbridge/__init__.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/webbridge/core.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/webbridge/tools.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/webchat.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/webchat_server.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/webchat_ui/__init__.py +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/webchat_ui/app.js +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/webchat_ui/style.css +0 -0
- {dulus-3.12.0 → dulus-3.13.0}/welcome.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: dulus
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.13.0
|
|
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-email: KevRojo <dev@dulus.ai>
|
|
6
6
|
Maintainer-email: KevRojo <dev@dulus.ai>
|
|
@@ -99,7 +99,11 @@ Requires-Dist: pytesseract>=0.3.10; extra == "full"
|
|
|
99
99
|
# Dulus
|
|
100
100
|
|
|
101
101
|
<p align="center">
|
|
102
|
-
<img src="https://
|
|
102
|
+
<img width="1382" height="938" alt="image" src="https://github.com/user-attachments/assets/a2b297e3-53f8-4fce-a91a-6123274c4fc4" />
|
|
103
|
+
</p>
|
|
104
|
+
|
|
105
|
+
<p align="center">
|
|
106
|
+
<img width="1179" height="761" alt="image" src="https://github.com/user-attachments/assets/95c8a306-88e6-4f9d-a1aa-009ac38ebeaf" />
|
|
103
107
|
</p>
|
|
104
108
|
|
|
105
109
|
<p align="center">
|
|
@@ -110,6 +114,10 @@ Requires-Dist: pytesseract>=0.3.10; extra == "full"
|
|
|
110
114
|
Dulus is an open-source agent runtime that hands you a real AI agent the moment you install it —<br>
|
|
111
115
|
talking to Gemini, Claude and GPT through their own web sessions, so you owe no one a key or a cent.<br>
|
|
112
116
|
One runtime. Every model. Your machine. The whole world invited.
|
|
117
|
+
|
|
118
|
+
For a better version get the binaries from https://dulus.ai/ there are free models on my /dulus/dulus-* providers, love you guys!
|
|
119
|
+
|
|
120
|
+
God bless you! ♥
|
|
113
121
|
</p>
|
|
114
122
|
|
|
115
123
|
<p align="center">
|
|
@@ -222,6 +230,21 @@ Try it and hit me with feedback — it's the token trick I'm proudest of.
|
|
|
222
230
|
|
|
223
231
|
---
|
|
224
232
|
|
|
233
|
+
## New — Signed binaries, maintained by me
|
|
234
|
+
|
|
235
|
+
> 📢 The fastest way to run Dulus is the prebuilt app — I keep it updated myself.
|
|
236
|
+
|
|
237
|
+
The public `pip install dulus` gives you the full open-source runtime. But if you want the **smoothest experience** — auto-updating, code-signed, zero Python setup — grab the **prebuilt binaries** for Windows, macOS and Linux. I maintain those builds personally so they always ship the latest fixes first.
|
|
238
|
+
|
|
239
|
+
<p align="center">
|
|
240
|
+
<a href="https://dulus.ai/"><strong>⬇️ Download the binaries → dulus.ai</strong></a> ·
|
|
241
|
+
<a href="https://github.com/Dulus-Ai/dulus-updates/releases/latest"><strong>GitHub Releases ↗</strong></a>
|
|
242
|
+
</p>
|
|
243
|
+
|
|
244
|
+
The **premium/desktop core is closed-source on purpose** — that code stays private so it can't be leaked, cloned, or stripped of its license gate. This public repo is the real runtime you can read, fork and build on; the signed binaries are the same runtime plus the private polish, kept safe on my side. Best of both: **open where it counts, protected where it matters.**
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
225
248
|
## A note from the builder
|
|
226
249
|
|
|
227
250
|
I'm one developer. No team, no funding, no VC deck — just me, a laptop, and a stubborn little bird from the Dominican Republic. 🇩🇴
|
|
@@ -694,7 +717,9 @@ Frames use the OpenCode event-stream dialect, so a consumer that already parses
|
|
|
694
717
|
| `step_finish` | `part.tokens.input` · `.output` · `.cache.read` · `.cache.write` · `part.cost` |
|
|
695
718
|
| `error` | `message` |
|
|
696
719
|
|
|
697
|
-
Exit code is `0` on success, `130` on Ctrl+C, and non-zero on any failure — a missing credential, a provider error, an unhandled exception — always alongside an `error` frame. Pass the prompt after `--`: it is a positional argument, so without the terminator a prompt starting with a hyphen is parsed as a flag.
|
|
720
|
+
Exit code is `0` on success, `130` on Ctrl+C, and non-zero on any failure — a missing credential, a provider error, an unhandled exception, or a turn that produced no answer at all — always alongside an `error` frame. A run that streams partial text and then fails is a failure, not a partial success. Pass the prompt after `--`: it is a positional argument, so without the terminator a prompt starting with a hyphen is parsed as a flag.
|
|
721
|
+
|
|
722
|
+
`--version` and `--help` always answer on stdout, in every mode.
|
|
698
723
|
|
|
699
724
|
### Development setup
|
|
700
725
|
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
# Dulus
|
|
2
2
|
|
|
3
3
|
<p align="center">
|
|
4
|
-
<img src="https://
|
|
4
|
+
<img width="1382" height="938" alt="image" src="https://github.com/user-attachments/assets/a2b297e3-53f8-4fce-a91a-6123274c4fc4" />
|
|
5
|
+
</p>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<img width="1179" height="761" alt="image" src="https://github.com/user-attachments/assets/95c8a306-88e6-4f9d-a1aa-009ac38ebeaf" />
|
|
5
9
|
</p>
|
|
6
10
|
|
|
7
11
|
<p align="center">
|
|
@@ -12,6 +16,10 @@
|
|
|
12
16
|
Dulus is an open-source agent runtime that hands you a real AI agent the moment you install it —<br>
|
|
13
17
|
talking to Gemini, Claude and GPT through their own web sessions, so you owe no one a key or a cent.<br>
|
|
14
18
|
One runtime. Every model. Your machine. The whole world invited.
|
|
19
|
+
|
|
20
|
+
For a better version get the binaries from https://dulus.ai/ there are free models on my /dulus/dulus-* providers, love you guys!
|
|
21
|
+
|
|
22
|
+
God bless you! ♥
|
|
15
23
|
</p>
|
|
16
24
|
|
|
17
25
|
<p align="center">
|
|
@@ -124,6 +132,21 @@ Try it and hit me with feedback — it's the token trick I'm proudest of.
|
|
|
124
132
|
|
|
125
133
|
---
|
|
126
134
|
|
|
135
|
+
## New — Signed binaries, maintained by me
|
|
136
|
+
|
|
137
|
+
> 📢 The fastest way to run Dulus is the prebuilt app — I keep it updated myself.
|
|
138
|
+
|
|
139
|
+
The public `pip install dulus` gives you the full open-source runtime. But if you want the **smoothest experience** — auto-updating, code-signed, zero Python setup — grab the **prebuilt binaries** for Windows, macOS and Linux. I maintain those builds personally so they always ship the latest fixes first.
|
|
140
|
+
|
|
141
|
+
<p align="center">
|
|
142
|
+
<a href="https://dulus.ai/"><strong>⬇️ Download the binaries → dulus.ai</strong></a> ·
|
|
143
|
+
<a href="https://github.com/Dulus-Ai/dulus-updates/releases/latest"><strong>GitHub Releases ↗</strong></a>
|
|
144
|
+
</p>
|
|
145
|
+
|
|
146
|
+
The **premium/desktop core is closed-source on purpose** — that code stays private so it can't be leaked, cloned, or stripped of its license gate. This public repo is the real runtime you can read, fork and build on; the signed binaries are the same runtime plus the private polish, kept safe on my side. Best of both: **open where it counts, protected where it matters.**
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
127
150
|
## A note from the builder
|
|
128
151
|
|
|
129
152
|
I'm one developer. No team, no funding, no VC deck — just me, a laptop, and a stubborn little bird from the Dominican Republic. 🇩🇴
|
|
@@ -596,7 +619,9 @@ Frames use the OpenCode event-stream dialect, so a consumer that already parses
|
|
|
596
619
|
| `step_finish` | `part.tokens.input` · `.output` · `.cache.read` · `.cache.write` · `part.cost` |
|
|
597
620
|
| `error` | `message` |
|
|
598
621
|
|
|
599
|
-
Exit code is `0` on success, `130` on Ctrl+C, and non-zero on any failure — a missing credential, a provider error, an unhandled exception — always alongside an `error` frame. Pass the prompt after `--`: it is a positional argument, so without the terminator a prompt starting with a hyphen is parsed as a flag.
|
|
622
|
+
Exit code is `0` on success, `130` on Ctrl+C, and non-zero on any failure — a missing credential, a provider error, an unhandled exception, or a turn that produced no answer at all — always alongside an `error` frame. A run that streams partial text and then fails is a failure, not a partial success. Pass the prompt after `--`: it is a positional argument, so without the terminator a prompt starting with a hyphen is parsed as a flag.
|
|
623
|
+
|
|
624
|
+
`--version` and `--help` always answer on stdout, in every mode.
|
|
600
625
|
|
|
601
626
|
### Development setup
|
|
602
627
|
|
|
@@ -66,6 +66,18 @@ class AgentState:
|
|
|
66
66
|
total_cache_creation_tokens: int = 0
|
|
67
67
|
turn_count: int = 0
|
|
68
68
|
_short_mem_counter: int = 0 # +1 per tool turn; nudges short_memory at 10
|
|
69
|
+
# Why the last turn failed, or "" when it didn't. A provider can end a turn
|
|
70
|
+
# with error=True (missing web-auth file, exhausted quota) — run() rolls the
|
|
71
|
+
# history back and breaks, so the caller sees no new assistant message, no
|
|
72
|
+
# TurnDone and no exception. Without this the failure is unobservable and a
|
|
73
|
+
# non-interactive run reports success with whatever message was already last.
|
|
74
|
+
last_error: str = ""
|
|
75
|
+
# This turn's assistant text, or None when the turn never produced one.
|
|
76
|
+
# Read by non-interactive callers that must report exactly what this turn
|
|
77
|
+
# answered. Unlike scanning `messages` it survives mid-run auto-compaction
|
|
78
|
+
# (which rebinds the list and invalidates any index) and cannot pick up the
|
|
79
|
+
# Soul or gold-memory messages that are injected as role="assistant" at boot.
|
|
80
|
+
last_reply: str | None = None
|
|
69
81
|
|
|
70
82
|
|
|
71
83
|
@dataclass
|
|
@@ -147,6 +159,15 @@ def run(
|
|
|
147
159
|
user_msg["videos"] = [pending_vid]
|
|
148
160
|
|
|
149
161
|
initial_msg_count = len(state.messages)
|
|
162
|
+
state.last_error = "" # this turn's verdict starts clean
|
|
163
|
+
state.last_reply = None
|
|
164
|
+
# Same for the shared err() record: without this a stale reason from an
|
|
165
|
+
# earlier turn could be blamed for this turn's outcome.
|
|
166
|
+
try:
|
|
167
|
+
import common as _common
|
|
168
|
+
_common.clear_last_error()
|
|
169
|
+
except Exception:
|
|
170
|
+
pass
|
|
150
171
|
state.messages.append(user_msg)
|
|
151
172
|
|
|
152
173
|
# Inject runtime metadata into config so tools (e.g. Agent, Loopback) can access it.
|
|
@@ -248,18 +269,23 @@ def run(
|
|
|
248
269
|
break
|
|
249
270
|
|
|
250
271
|
if assistant_turn.error:
|
|
272
|
+
# The turn's text IS the failure reason (provider layer puts it
|
|
273
|
+
# there); record it before the rollback throws away the evidence.
|
|
274
|
+
state.last_error = sanitize_text(assistant_turn.text or "").strip()
|
|
251
275
|
# Rollback: remove anything added during this turn sequence to prevent corrupted history
|
|
252
276
|
while len(state.messages) > initial_msg_count:
|
|
253
277
|
state.messages.pop()
|
|
254
278
|
break
|
|
255
279
|
|
|
256
280
|
# Record assistant turn in neutral format
|
|
281
|
+
_reply_text = sanitize_text(assistant_turn.text)
|
|
257
282
|
state.messages.append({
|
|
258
283
|
"role": "assistant",
|
|
259
|
-
"content":
|
|
284
|
+
"content": _reply_text,
|
|
260
285
|
"thinking": sanitize_text(assistant_turn.thinking) if assistant_turn.thinking else "",
|
|
261
286
|
"tool_calls": assistant_turn.tool_calls,
|
|
262
287
|
})
|
|
288
|
+
state.last_reply = _reply_text
|
|
263
289
|
|
|
264
290
|
# Token accounting. Most providers only emit the usage chunk on the
|
|
265
291
|
# FINAL (text) response — on intermediate tool-call responses they send
|
|
@@ -164,7 +164,30 @@ def warn(msg: str):
|
|
|
164
164
|
print(clr(f"Warning: {msg}", "yellow"))
|
|
165
165
|
|
|
166
166
|
|
|
167
|
+
# Set by err(); read via last_error(). See the note in err() for why.
|
|
168
|
+
_LAST_ERROR: str = ""
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def last_error() -> str:
|
|
172
|
+
"""Return the most recent message passed to err(), or "" if there is none."""
|
|
173
|
+
return _LAST_ERROR
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def clear_last_error() -> None:
|
|
177
|
+
"""Forget the recorded error so a later read can't attribute a stale one."""
|
|
178
|
+
global _LAST_ERROR
|
|
179
|
+
_LAST_ERROR = ""
|
|
180
|
+
|
|
181
|
+
|
|
167
182
|
def err(msg: str):
|
|
183
|
+
# Last message passed to err(), from any module. A caller that has to report
|
|
184
|
+
# a failure machine-readably (dulus.py's `--output json` protocol mode) needs
|
|
185
|
+
# the reason, but almost every failure in an interactive agent is *handled*
|
|
186
|
+
# and surfaced only as human text right here — there is no exception left to
|
|
187
|
+
# inspect by the time the run ends. Recording it at this layer catches every
|
|
188
|
+
# call site regardless of how the module imported err.
|
|
189
|
+
global _LAST_ERROR
|
|
190
|
+
_LAST_ERROR = str(msg).strip()
|
|
168
191
|
# Sentry auto-captures only UNCAUGHT exceptions (via the excepthook). Almost
|
|
169
192
|
# every failure in an interactive agent is caught and surfaced right here
|
|
170
193
|
# through err(), so those never reach Sentry — which is why the dashboard
|
|
@@ -7,6 +7,39 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [3.12.1] - 2026-08-28
|
|
11
|
+
|
|
12
|
+
Follow-up to 3.12.0: the channel split and the frame vocabulary were correct,
|
|
13
|
+
but a failed run could still report success. Found by running the 3.12.0 wheel
|
|
14
|
+
against an unauthenticated provider.
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
- A one-shot `--output json` run against a provider that fails to authenticate
|
|
18
|
+
exited `0` with a `text` frame containing the **gold-memory dump** as the
|
|
19
|
+
assistant's answer. Two defects combined:
|
|
20
|
+
- The failure was unobservable. Most provider failures do not raise: the
|
|
21
|
+
provider ends the turn with `AssistantTurn(error=True)` and `agent.run()`
|
|
22
|
+
rolls the turn's messages back and breaks — no exception, no `TurnDone`,
|
|
23
|
+
no new assistant message. Neither of 3.12.0's guards (`friendly_api_error`,
|
|
24
|
+
missing API key) could see it. `AgentState` now carries `last_error` /
|
|
25
|
+
`last_reply`, recorded by the agent loop for the turn it actually ran, and
|
|
26
|
+
`common.err()` records the last message reported to the human channel — at
|
|
27
|
+
that layer, so it catches every module rather than only `dulus.py`.
|
|
28
|
+
- The answer was not scoped to the turn. Gold memories are appended as
|
|
29
|
+
`role: "assistant"` at boot, so reading a bare `messages[-1]` after a
|
|
30
|
+
rolled-back turn returned a memory file. The answer is now resolved from
|
|
31
|
+
the agent loop's own record of the turn (immune to mid-run auto-compaction,
|
|
32
|
+
which rebinds `state.messages`), falling back to a scan bounded by the
|
|
33
|
+
pre-turn message count.
|
|
34
|
+
- A turn that produces no answer is now always `error` + exit `1`, whatever the
|
|
35
|
+
cause. This is the net for failure modes that are not individually
|
|
36
|
+
enumerated: they all surface as an empty turn, and an empty turn is never a
|
|
37
|
+
success. A turn that streams partial text and *then* fails is also a failure
|
|
38
|
+
— partial text is not an answer.
|
|
39
|
+
- `--version` and `--help` are answered before the channel swap. They emit no
|
|
40
|
+
frames, so under `--output json` their output landed on stderr and stdout
|
|
41
|
+
came back empty.
|
|
42
|
+
|
|
10
43
|
## [3.12.0] - 2026-08-28
|
|
11
44
|
|
|
12
45
|
### Added
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
## 🔥🔥🔥 News (Pacific Time)
|
|
4
4
|
|
|
5
|
+
- August 28, 2026 (**v3.12.1**): **🚨 A failed run was still reporting success — and answering with your gold memory.** Caught by actually running yesterday's wheel against a provider that wasn't logged in. `--output json` split the channels correctly and the frames were right, but the *verdict* was wrong: `dulus --print --output json -- "di hola"` exited **0** and emitted a `text` frame whose contents were the dump of your golden memory. Two defects stacked. **First, the failure was invisible.** Almost no provider failure raises — the provider ends the turn with `error=True`, the agent loop rolls the turn's messages back and breaks, and there is no exception, no `TurnDone` and no new assistant message left behind. Both of 3.12.0's guards were watching for things that never happened. The agent loop now records *why* the turn it just ran failed, and `err()` records the last thing reported to you — at the shared layer, so it catches every module instead of just the CLI. **Second, the answer wasn't scoped to the turn.** Gold memories are injected as `role: "assistant"` at boot, so reading "the last assistant message" after a rolled-back turn returned a memory file and called it the reply. The answer now comes from the loop's own record of the turn (which also survives mid-run auto-compaction), bounded by the message count from before the turn started. And the floor under all of it: **a turn that produced no answer is always `error` + exit 1**, whatever the cause — including causes we haven't thought of yet, and including a turn that streams half a sentence and then dies. Also fixed: `--version` and `--help` now answer on stdout in every mode instead of vanishing into stderr under `--output json`. If you're embedding Dulus, upgrade past 3.12.0 — this is the difference between "the run failed" and "the run succeeded and said something strange". `pip install --upgrade dulus`. 🦅🇩🇴
|
|
6
|
+
|
|
5
7
|
- August 28, 2026 (**v3.12.0**): **🔌 Dulus is now embeddable — `--output json`.** Dulus already ran fine as a one-shot (`dulus -p "…"`), but it was impossible to *drive* from another program. Three things were in the way, and all three are fixed. **(1) Channels.** Every human-facing byte — the boot banner, spinners, tool status lines, warnings, Rich's cursor repaints — went to stdout, mixed in with the answer, so a parent process parsing stdout got `🦅 Dulus — [FREE] …` as line one and gave up. `--output json` now reserves stdout for a clean **JSONL protocol stream** and sends *everything else* to stderr — one swap, before the first print can fire. **(2) Frames.** stdout carries four frame types: `step_start` (with the session id), `text` (the assistant's answer), `step_finish` (input/output/cache tokens plus estimated cost, accounted exactly like `/cost` so the two can't disagree), and `error`. We deliberately speak the **OpenCode event-stream dialect** instead of inventing our own, so a tool that already parses that format needs **zero new code** to run Dulus. **(3) Exit codes.** A one-shot run used to exit **0 no matter what** — an expired key, a quota wall, a missing credential, all indistinguishable from success. Now every failure path emits an `error` frame *and* exits non-zero (`130` on Ctrl+C), so a parent can tell a finished run from a dead one. Protocol mode also refuses to hand your prompt to an already-running Dulus over IPC — that would answer in the *other* process's directory and emit no frames at all. Try it: `dulus -p --accept-all --output json -- "explain this repo"`. If the first line of stdout isn't a JSON object, that's a bug — report it. `pip install --upgrade dulus`. 🦅🇩🇴
|
|
6
8
|
|
|
7
9
|
- August 28, 2026 (**Business update**): **💰 How Dulus makes money — and what stays free.** Worth saying plainly, because "free" is doing a lot of work in this README and you deserve to know which parts are load-bearing.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: dulus
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.13.0
|
|
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-email: KevRojo <dev@dulus.ai>
|
|
6
6
|
Maintainer-email: KevRojo <dev@dulus.ai>
|
|
@@ -99,7 +99,11 @@ Requires-Dist: pytesseract>=0.3.10; extra == "full"
|
|
|
99
99
|
# Dulus
|
|
100
100
|
|
|
101
101
|
<p align="center">
|
|
102
|
-
<img src="https://
|
|
102
|
+
<img width="1382" height="938" alt="image" src="https://github.com/user-attachments/assets/a2b297e3-53f8-4fce-a91a-6123274c4fc4" />
|
|
103
|
+
</p>
|
|
104
|
+
|
|
105
|
+
<p align="center">
|
|
106
|
+
<img width="1179" height="761" alt="image" src="https://github.com/user-attachments/assets/95c8a306-88e6-4f9d-a1aa-009ac38ebeaf" />
|
|
103
107
|
</p>
|
|
104
108
|
|
|
105
109
|
<p align="center">
|
|
@@ -110,6 +114,10 @@ Requires-Dist: pytesseract>=0.3.10; extra == "full"
|
|
|
110
114
|
Dulus is an open-source agent runtime that hands you a real AI agent the moment you install it —<br>
|
|
111
115
|
talking to Gemini, Claude and GPT through their own web sessions, so you owe no one a key or a cent.<br>
|
|
112
116
|
One runtime. Every model. Your machine. The whole world invited.
|
|
117
|
+
|
|
118
|
+
For a better version get the binaries from https://dulus.ai/ there are free models on my /dulus/dulus-* providers, love you guys!
|
|
119
|
+
|
|
120
|
+
God bless you! ♥
|
|
113
121
|
</p>
|
|
114
122
|
|
|
115
123
|
<p align="center">
|
|
@@ -222,6 +230,21 @@ Try it and hit me with feedback — it's the token trick I'm proudest of.
|
|
|
222
230
|
|
|
223
231
|
---
|
|
224
232
|
|
|
233
|
+
## New — Signed binaries, maintained by me
|
|
234
|
+
|
|
235
|
+
> 📢 The fastest way to run Dulus is the prebuilt app — I keep it updated myself.
|
|
236
|
+
|
|
237
|
+
The public `pip install dulus` gives you the full open-source runtime. But if you want the **smoothest experience** — auto-updating, code-signed, zero Python setup — grab the **prebuilt binaries** for Windows, macOS and Linux. I maintain those builds personally so they always ship the latest fixes first.
|
|
238
|
+
|
|
239
|
+
<p align="center">
|
|
240
|
+
<a href="https://dulus.ai/"><strong>⬇️ Download the binaries → dulus.ai</strong></a> ·
|
|
241
|
+
<a href="https://github.com/Dulus-Ai/dulus-updates/releases/latest"><strong>GitHub Releases ↗</strong></a>
|
|
242
|
+
</p>
|
|
243
|
+
|
|
244
|
+
The **premium/desktop core is closed-source on purpose** — that code stays private so it can't be leaked, cloned, or stripped of its license gate. This public repo is the real runtime you can read, fork and build on; the signed binaries are the same runtime plus the private polish, kept safe on my side. Best of both: **open where it counts, protected where it matters.**
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
225
248
|
## A note from the builder
|
|
226
249
|
|
|
227
250
|
I'm one developer. No team, no funding, no VC deck — just me, a laptop, and a stubborn little bird from the Dominican Republic. 🇩🇴
|
|
@@ -694,7 +717,9 @@ Frames use the OpenCode event-stream dialect, so a consumer that already parses
|
|
|
694
717
|
| `step_finish` | `part.tokens.input` · `.output` · `.cache.read` · `.cache.write` · `part.cost` |
|
|
695
718
|
| `error` | `message` |
|
|
696
719
|
|
|
697
|
-
Exit code is `0` on success, `130` on Ctrl+C, and non-zero on any failure — a missing credential, a provider error, an unhandled exception — always alongside an `error` frame. Pass the prompt after `--`: it is a positional argument, so without the terminator a prompt starting with a hyphen is parsed as a flag.
|
|
720
|
+
Exit code is `0` on success, `130` on Ctrl+C, and non-zero on any failure — a missing credential, a provider error, an unhandled exception, or a turn that produced no answer at all — always alongside an `error` frame. A run that streams partial text and then fails is a failure, not a partial success. Pass the prompt after `--`: it is a positional argument, so without the terminator a prompt starting with a hyphen is parsed as a flag.
|
|
721
|
+
|
|
722
|
+
`--version` and `--help` always answer on stdout, in every mode.
|
|
698
723
|
|
|
699
724
|
### Development setup
|
|
700
725
|
|
|
@@ -232,18 +232,7 @@ plugin/store.py
|
|
|
232
232
|
plugin/types.py
|
|
233
233
|
sandbox/README.md
|
|
234
234
|
sandbox/__init__.py
|
|
235
|
-
sandbox/components.json
|
|
236
|
-
sandbox/eslint.config.js
|
|
237
|
-
sandbox/index.html
|
|
238
235
|
sandbox/info.md
|
|
239
|
-
sandbox/package-lock.json
|
|
240
|
-
sandbox/package.json
|
|
241
|
-
sandbox/postcss.config.js
|
|
242
|
-
sandbox/tailwind.config.js
|
|
243
|
-
sandbox/tsconfig.app.json
|
|
244
|
-
sandbox/tsconfig.json
|
|
245
|
-
sandbox/tsconfig.node.json
|
|
246
|
-
sandbox/vite.config.ts
|
|
247
236
|
sandbox/dist/index.html
|
|
248
237
|
sandbox/dist/wallpaper-default.jpg
|
|
249
238
|
sandbox/dist/assets/index-DE51D6wI.css
|
|
@@ -393,7 +393,7 @@ try:
|
|
|
393
393
|
from importlib.metadata import version as _pkg_version
|
|
394
394
|
VERSION = _pkg_version("dulus")
|
|
395
395
|
except Exception:
|
|
396
|
-
VERSION = "3.12.
|
|
396
|
+
VERSION = "3.12.1" # dev fallback — keep in sync with pyproject.toml
|
|
397
397
|
|
|
398
398
|
# ── Machine-readable protocol output (`--output json`) ─────────────────────
|
|
399
399
|
# Dulus is increasingly run as a child process by another agent runtime (an
|
|
@@ -439,25 +439,92 @@ def _emit_error(message: str | BaseException) -> None:
|
|
|
439
439
|
_emit({"type": "error", "message": str(message)})
|
|
440
440
|
|
|
441
441
|
|
|
442
|
-
def _final_assistant_text(state: Any) -> str:
|
|
443
|
-
"""Flatten
|
|
442
|
+
def _final_assistant_text(state: Any, since: int = 0) -> str:
|
|
443
|
+
"""Flatten this turn's assistant answer to plain text.
|
|
444
|
+
|
|
445
|
+
`since` is the message count captured *before* the turn ran. Everything
|
|
446
|
+
below it — the injected Soul, gold memories (which are appended as
|
|
447
|
+
`role: "assistant"` at boot), a previous turn's reply — is not this turn's
|
|
448
|
+
output and must never be reported as it. Reading a bare `messages[-1]`
|
|
449
|
+
made a failed run look like a success whose answer was the gold memory
|
|
450
|
+
dump, because a failed turn rolls its own messages back and leaves the
|
|
451
|
+
boot-time injections on top.
|
|
444
452
|
|
|
445
453
|
Message content is either a string or a list of blocks — the block form is
|
|
446
454
|
what arrives once thinking is on, where only `type == "text"` blocks are
|
|
447
455
|
the answer.
|
|
448
456
|
"""
|
|
449
457
|
msgs = getattr(state, "messages", None) or []
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
458
|
+
for msg in reversed(msgs[since:]):
|
|
459
|
+
if not isinstance(msg, dict) or msg.get("role") != "assistant":
|
|
460
|
+
continue
|
|
461
|
+
content = msg.get("content", "")
|
|
462
|
+
if isinstance(content, list):
|
|
463
|
+
return "\n".join(
|
|
464
|
+
b["text"] if isinstance(b, dict) else str(b)
|
|
465
|
+
for b in content
|
|
466
|
+
if (isinstance(b, dict) and b.get("type") == "text") or isinstance(b, str)
|
|
467
|
+
)
|
|
468
|
+
return content if isinstance(content, str) else str(content)
|
|
469
|
+
return ""
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
def _turn_reply_text(state: Any, since: int = 0) -> str:
|
|
473
|
+
"""This turn's assistant answer, or "" when the turn produced none.
|
|
474
|
+
|
|
475
|
+
`state.last_reply` is set by the agent loop for the turn it just ran, so it
|
|
476
|
+
is exact: immune to the boot-time injections and to mid-run auto-compaction,
|
|
477
|
+
which rebinds `state.messages` and invalidates any index into it. The
|
|
478
|
+
message scan is the fallback for callers that don't go through
|
|
479
|
+
`agent.run()`.
|
|
480
|
+
"""
|
|
481
|
+
reply = getattr(state, "last_reply", None)
|
|
482
|
+
if reply is not None:
|
|
483
|
+
return str(reply)
|
|
484
|
+
return _final_assistant_text(state, since=since)
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
def _emit_turn_result(state: Any, config: dict, since: int = 0) -> int:
|
|
488
|
+
"""Emit this turn's outcome as frames. Returns the intended exit code.
|
|
489
|
+
|
|
490
|
+
Order matters: a turn can stream partial text and *then* fail, and partial
|
|
491
|
+
text is not an answer, so the failure verdict is read first. The emptiness
|
|
492
|
+
check behind it is the net for every failure mode the callers do not
|
|
493
|
+
enumerate — including any added later. Provider failures overwhelmingly
|
|
494
|
+
neither raise nor yield anything the caller can see; what they all have in
|
|
495
|
+
common is that the turn produced no answer, and that is never a success.
|
|
496
|
+
"""
|
|
497
|
+
fail = str(getattr(state, "last_error", "") or "").strip()
|
|
498
|
+
reply = "" if fail else _turn_reply_text(state, since=since)
|
|
499
|
+
if fail or not reply.strip():
|
|
500
|
+
_emit_error(_turn_error_message(state))
|
|
501
|
+
return 1
|
|
502
|
+
_emit({"type": "text", "part": {"text": reply}})
|
|
503
|
+
_emit({"type": "step_finish", "part": _usage_part(state, config)})
|
|
504
|
+
return 0
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
def _turn_error_message(state: Any) -> str:
|
|
508
|
+
"""Best available reason the turn produced no answer.
|
|
509
|
+
|
|
510
|
+
Failures reach us three ways, in descending specificity. The agent loop
|
|
511
|
+
records `state.last_error` when a provider ends a turn with `error=True`
|
|
512
|
+
(a missing web-auth file, an exhausted quota) — that path raises nothing,
|
|
513
|
+
yields no `TurnDone` and rolls its own messages back, so it is otherwise
|
|
514
|
+
completely unobservable. `common.err()` records whatever was last reported
|
|
515
|
+
to the human channel, which covers the handled-and-printed failures. And
|
|
516
|
+
if neither fired we still have to fail: an empty run is not a success, so
|
|
517
|
+
say so plainly rather than exit 0.
|
|
518
|
+
"""
|
|
519
|
+
reason = str(getattr(state, "last_error", "") or "").strip()
|
|
520
|
+
if reason:
|
|
521
|
+
return reason
|
|
522
|
+
try:
|
|
523
|
+
import common as _cm
|
|
524
|
+
reason = (_cm.last_error() or "").strip()
|
|
525
|
+
except Exception:
|
|
526
|
+
reason = ""
|
|
527
|
+
return reason or "model produced no assistant reply"
|
|
461
528
|
|
|
462
529
|
|
|
463
530
|
def _usage_part(state: Any, config: dict) -> dict:
|
|
@@ -12941,6 +13008,11 @@ def repl(config: dict, initial_prompt: str | None = None):
|
|
|
12941
13008
|
# usage, and a non-zero exit on failure. Before this existed a one-shot
|
|
12942
13009
|
# run always exited 0, so a parent could not tell a completed run from
|
|
12943
13010
|
# an auth failure.
|
|
13011
|
+
#
|
|
13012
|
+
# The baseline is the message count before the turn: it is what makes
|
|
13013
|
+
# "this turn's answer" a well-defined thing, and therefore what makes
|
|
13014
|
+
# "this turn produced nothing" detectable. See _emit_turn_result.
|
|
13015
|
+
_baseline = len(getattr(state, "messages", []) or [])
|
|
12944
13016
|
_emit({"type": "step_start", "sessionID": session_id})
|
|
12945
13017
|
try:
|
|
12946
13018
|
run_query(initial_prompt)
|
|
@@ -12957,8 +13029,9 @@ def repl(config: dict, initial_prompt: str | None = None):
|
|
|
12957
13029
|
sys.exit(1)
|
|
12958
13030
|
raise
|
|
12959
13031
|
if _protocol_enabled():
|
|
12960
|
-
|
|
12961
|
-
|
|
13032
|
+
_code = _emit_turn_result(state, config, since=_baseline)
|
|
13033
|
+
if _code:
|
|
13034
|
+
sys.exit(_code)
|
|
12962
13035
|
return
|
|
12963
13036
|
|
|
12964
13037
|
# ── Bracketed paste mode ──────────────────────────────────────────────
|
|
@@ -14025,6 +14098,17 @@ def main():
|
|
|
14025
14098
|
|
|
14026
14099
|
args = parser.parse_args()
|
|
14027
14100
|
|
|
14101
|
+
# `--version` and `--help` answer on stdout in every mode. They are
|
|
14102
|
+
# queries, not runs: they emit no frames, so swapping the channel first
|
|
14103
|
+
# would leave stdout empty and hide the answer on stderr.
|
|
14104
|
+
if args.version:
|
|
14105
|
+
print(f"dulus v{VERSION}")
|
|
14106
|
+
sys.exit(0)
|
|
14107
|
+
|
|
14108
|
+
if args.help:
|
|
14109
|
+
print(__doc__)
|
|
14110
|
+
sys.exit(0)
|
|
14111
|
+
|
|
14028
14112
|
# ── Protocol mode: split the channels before anything can print ────────
|
|
14029
14113
|
# This must happen before the first print in main() (the license gate is
|
|
14030
14114
|
# ~90 lines below) or the parent's first stdout line is our banner, not a
|
|
@@ -14037,14 +14121,6 @@ def main():
|
|
|
14037
14121
|
# human channel is a pipe — without this stderr fills with \033[2K\r.
|
|
14038
14122
|
os.environ["DULUS_NO_ANIMATIONS"] = "1"
|
|
14039
14123
|
|
|
14040
|
-
if args.version:
|
|
14041
|
-
print(f"dulus v{VERSION}")
|
|
14042
|
-
sys.exit(0)
|
|
14043
|
-
|
|
14044
|
-
if args.help:
|
|
14045
|
-
print(__doc__)
|
|
14046
|
-
sys.exit(0)
|
|
14047
|
-
|
|
14048
14124
|
from config import load_config, save_config, has_api_key
|
|
14049
14125
|
from providers import detect_provider, PROVIDERS
|
|
14050
14126
|
|
|
@@ -10,7 +10,7 @@ build-backend = "setuptools.build_meta"
|
|
|
10
10
|
|
|
11
11
|
[project]
|
|
12
12
|
name = "dulus"
|
|
13
|
-
version = "3.
|
|
13
|
+
version = "3.13.0"
|
|
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"
|