dreadnode 2.0.4__tar.gz → 2.0.5__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.
- {dreadnode-2.0.4 → dreadnode-2.0.5}/PKG-INFO +1 -1
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/agents/events.py +14 -6
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/app/main.py +2 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/app/server/app.py +52 -2
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/app/tui/app.py +82 -21
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/app/tui/client.py +9 -1
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/app/tui/dreadnode.tcss +4 -10
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/app/tui/event_contract.py +6 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/app/tui/model_variants.py +10 -6
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/app/tui/turn_reducer.py +2 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/app/tui/widgets/conversation.py +3 -4
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/app/tui/widgets/skills_dialog.py +3 -3
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/app/tui/widgets/slash_overlay.py +15 -6
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/app/tui/widgets/tool_progress.py +7 -7
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/app/tui/widgets/welcome.py +1 -1
- {dreadnode-2.0.4 → dreadnode-2.0.5}/pyproject.toml +1 -1
- {dreadnode-2.0.4 → dreadnode-2.0.5}/.gitignore +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/LICENSE +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/README.md +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/__init__.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/__main__.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/agents/__init__.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/agents/agent.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/agents/exceptions.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/agents/format.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/agents/hooks.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/agents/mcp/__init__.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/agents/mcp/auth.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/agents/mcp/client.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/agents/mcp/config.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/agents/mcp/server.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/agents/reactions.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/agents/skills.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/agents/stopping.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/agents/subagent.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/agents/tools.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/agents/trajectory.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/airt/__init__.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/airt/analytics/__init__.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/airt/analytics/aggregator.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/airt/analytics/classifier.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/airt/analytics/compliance.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/airt/analytics/engine.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/airt/analytics/recommendations.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/airt/analytics/types.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/airt/assessment.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/airt/autodan_turbo.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/airt/beast.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/airt/compliance/__init__.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/airt/compliance/atlas.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/airt/compliance/nist.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/airt/compliance/owasp.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/airt/compliance/owasp_agentic.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/airt/compliance/saif.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/airt/constants.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/airt/crescendo.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/airt/data/__init__.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/airt/data/assets/audio/adversarial_query.mp3 +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/airt/data/assets/image/bomb.jpg +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/airt/data/assets/image/meth.png +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/airt/data/prompts/adversarial_benchmark_subset.csv +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/airt/data/prompts/ai_safety.csv +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/airt/data/rubrics/data_exfiltration.yaml +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/airt/data/rubrics/goal_hijacking.yaml +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/airt/data/rubrics/memory_poisoning.yaml +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/airt/data/rubrics/privilege_escalation.yaml +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/airt/data/rubrics/rce.yaml +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/airt/data/rubrics/scope_creep.yaml +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/airt/data/rubrics/tool_chaining.yaml +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/airt/data/rubrics/tool_selection_safety.yaml +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/airt/data/rubrics/unbounded_agency.yaml +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/airt/data/rubrics/web_chatbot_security.yaml +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/airt/data/templates/crescendo/variant_1.yaml +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/airt/data/templates/crescendo/variant_2.yaml +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/airt/data/templates/crescendo/variant_3.yaml +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/airt/data/templates/crescendo/variant_4.yaml +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/airt/data/templates/crescendo/variant_5.yaml +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/airt/deep_inception.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/airt/drattack.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/airt/events.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/airt/goat.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/airt/gptfuzzer.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/airt/image.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/airt/multimodal.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/airt/pair.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/airt/prompt.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/airt/rainbow.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/airt/renellm.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/airt/reporting/__init__.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/airt/reporting/json_report.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/airt/reporting/llm_summary.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/airt/reporting/markdown.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/airt/tap.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/app/__init__.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/app/api/__init__.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/app/api/client.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/app/api/models.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/app/cli/__init__.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/app/cli/airt.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/app/cli/capability.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/app/cli/dataset.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/app/cli/evaluation.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/app/cli/main.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/app/cli/model.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/app/cli/optimize.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/app/cli/runtime.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/app/cli/shared.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/app/cli/task.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/app/cli/train.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/app/cli/worlds.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/app/print_mode.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/app/server/__init__.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/app/server/auth.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/app/server/default-agent/tools/coding.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/app/server/default-agent/tools/subagent.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/app/server/session.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/app/server/system-prompt.md +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/app/server/utils.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/app/tui/__init__.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/app/tui/commands.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/app/tui/connection.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/app/tui/runtime_cache.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/app/tui/screens/__init__.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/app/tui/screens/auth.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/app/tui/screens/base.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/app/tui/screens/capabilities.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/app/tui/screens/console.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/app/tui/screens/environments.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/app/tui/screens/evaluations.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/app/tui/screens/model_picker.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/app/tui/screens/runtimes.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/app/tui/screens/sandboxes.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/app/tui/screens/secrets.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/app/tui/screens/sessions.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/app/tui/screens/traces.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/app/tui/screens/workspaces.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/app/tui/theme.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/app/tui/update_check.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/app/tui/widgets/__init__.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/app/tui/widgets/agent_dialog.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/app/tui/widgets/agent_suggester.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/app/tui/widgets/composer.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/app/tui/widgets/context_bar.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/app/tui/widgets/flash.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/app/tui/widgets/header_bar.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/app/tui/widgets/help_panel.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/app/tui/widgets/mcp_dialog.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/app/tui/widgets/mention_overlay.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/app/tui/widgets/message_queue.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/app/tui/widgets/overlay_mixin.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/app/tui/widgets/permission_prompt.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/app/tui/widgets/prompt_info.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/app/tui/widgets/session_sidebar.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/app/tui/widgets/status_bar.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/app/tui/widgets/throbber.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/app/tui/widgets/tool.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/app/tui/widgets/tools_dialog.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/capabilities/__init__.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/capabilities/capability.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/capabilities/loader.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/capabilities/sync.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/capabilities/tool_rules.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/capabilities/types.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/core/__init__.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/core/conditions.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/core/discovery.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/core/environment.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/core/exceptions.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/core/execution.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/core/hook.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/core/judge.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/core/load.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/core/log.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/core/meta/__init__.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/core/meta/config.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/core/meta/context.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/core/meta/hydrate.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/core/meta/introspect.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/core/metric.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/core/object.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/core/scorer.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/core/serialization.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/core/stopping.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/core/task.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/core/transforms.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/core/types/__init__.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/core/types/audio.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/core/types/base.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/core/types/common.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/core/types/image.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/core/types/object_3d.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/core/types/table.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/core/types/text.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/core/types/video.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/core/util.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/datasets/__init__.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/datasets/dataset.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/datasets/hf.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/datasets/local.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/evaluations/__init__.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/evaluations/console.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/evaluations/evaluation.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/evaluations/events.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/evaluations/format.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/evaluations/result.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/evaluations/sample.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/generators/__init__.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/generators/caching.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/generators/chat.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/generators/data.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/generators/exceptions.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/generators/generator/__init__.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/generators/generator/base.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/generators/generator/http.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/generators/generator/litellm_.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/generators/generator/transformers_.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/generators/generator/vllm_.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/generators/message.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/generators/models.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/generators/parsing.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/generators/tokenizer/__init__.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/generators/tokenizer/base.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/generators/tokenizer/transformers_.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/generators/utils.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/models/__init__.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/models/hf.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/models/local.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/models/model.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/optimization/__init__.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/optimization/adapters/__init__.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/optimization/adapters/agent.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/optimization/api.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/optimization/backends/__init__.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/optimization/backends/base.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/optimization/backends/gepa.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/optimization/collectors.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/optimization/config.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/optimization/console.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/optimization/events.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/optimization/format.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/optimization/jobs.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/optimization/result.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/optimization/sampler.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/optimization/sampling.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/optimization/search.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/optimization/stopping.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/optimization/study.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/optimization/trial.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/packaging/__init__.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/packaging/loader.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/packaging/manifest.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/packaging/oci.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/packaging/package.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/packaging/task_validation.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/py.typed +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/samplers/__init__.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/samplers/boundary.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/samplers/fuzzing.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/samplers/graph.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/samplers/grid.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/samplers/image.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/samplers/mapelites.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/samplers/optuna.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/samplers/random.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/samplers/registry.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/samplers/strategy.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/scorers/__init__.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/scorers/advanced_jailbreak_detection.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/scorers/agent_security.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/scorers/agentic.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/scorers/agentic_workflow.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/scorers/classification.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/scorers/consistency.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/scorers/contains.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/scorers/cosine_sim.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/scorers/credentials.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/scorers/crucible.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/scorers/documentation_security.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/scorers/exfiltration_detection.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/scorers/format.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/scorers/harm.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/scorers/ide_security.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/scorers/image.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/scorers/json.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/scorers/judge.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/scorers/length.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/scorers/lexical.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/scorers/mcp_security.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/scorers/memorization.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/scorers/multi_agent_security.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/scorers/pii.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/scorers/prompt_leak.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/scorers/readability.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/scorers/reasoning_security.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/scorers/sentiment.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/scorers/similarity.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/storage/__init__.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/storage/providers.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/storage/session_store.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/storage/storage.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/tools/__init__.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/tools/_ripgrep.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/tools/apply_patch.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/tools/editing.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/tools/execute.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/tools/fetch.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/tools/glob.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/tools/grep.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/tools/interaction.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/tools/ls.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/tools/memory.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/tools/read.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/tools/task.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/tools/think.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/tools/todo.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/tools/web_search.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/tools/write.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/tracing/__init__.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/tracing/constants.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/tracing/convert.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/tracing/exporter.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/tracing/exporters.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/tracing/span.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/tracing/spans.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/tracing/trace_converter.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/training/__init__.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/training/base.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/training/dpo.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/training/etl/__init__.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/training/etl/_common.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/training/etl/rl.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/training/etl/sft.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/training/etl/worlds.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/training/events.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/training/grpo.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/training/jobs.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/training/ppo.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/training/prime.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/training/ray/__init__.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/training/ray/async_trainer.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/training/ray/config.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/training/ray/coordinator.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/training/ray/distributed.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/training/ray/dpo.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/training/ray/experience.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/training/ray/fsdp2_learner.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/training/ray/inference.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/training/ray/learner.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/training/ray/multi_turn.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/training/ray/ppo.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/training/ray/reward_model.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/training/ray/rollout_env.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/training/ray/rollout_worker.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/training/ray/sft.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/training/ray/trainer.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/training/recipes.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/training/rewards/__init__.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/training/rewards/aggregator.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/training/rewards/functions.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/training/rewards/scorer_bridge.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/training/rewards/shaping.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/training/rewards/types.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/training/rollouts/__init__.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/training/rollouts/adapters.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/training/rollouts/orchestrator.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/training/rollouts/types.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/training/rollouts/worlds.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/training/serving/__init__.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/training/serving/vllm_client.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/training/sft.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/training/tinker/__init__.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/training/tinker/config.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/training/tinker/data.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/training/tinker/renderer.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/training/tinker/rl.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/training/tinker/trainer.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/training/tinker_sft.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/training/utils.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/transforms/__init__.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/transforms/advanced_jailbreak.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/transforms/adversarial_suffix.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/transforms/agent_skill.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/transforms/agentic_workflow.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/transforms/audio.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/transforms/browser_agent_attacks.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/transforms/cipher.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/transforms/constitutional.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/transforms/document.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/transforms/documentation_poison.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/transforms/encoding.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/transforms/exfiltration.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/transforms/flip_attack.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/transforms/guardrail_bypass.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/transforms/ide_injection.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/transforms/image.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/transforms/injection.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/transforms/json_tools.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/transforms/language.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/transforms/logic_bomb.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/transforms/mcp_attacks.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/transforms/multi_agent_attacks.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/transforms/persuasion.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/transforms/perturbation.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/transforms/pii_extraction.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/transforms/pythonic_tools.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/transforms/rag_poisoning.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/transforms/reasoning_attacks.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/transforms/refine.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/transforms/response_steering.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/transforms/stylistic.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/transforms/substitution.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/transforms/swap.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/transforms/system_prompt_extraction.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/transforms/text.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/transforms/video.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/transforms/xml_tools.py +0 -0
- {dreadnode-2.0.4 → dreadnode-2.0.5}/dreadnode/version.py +0 -0
|
@@ -4,7 +4,7 @@ from datetime import datetime, timezone
|
|
|
4
4
|
from uuid import UUID, uuid4
|
|
5
5
|
|
|
6
6
|
import typing_extensions as te
|
|
7
|
-
from pydantic import BaseModel, ConfigDict, Field
|
|
7
|
+
from pydantic import BaseModel, ConfigDict, Field, PlainSerializer, WithJsonSchema
|
|
8
8
|
from rich.console import Console, ConsoleOptions, RenderableType, RenderResult
|
|
9
9
|
from rich.panel import Panel
|
|
10
10
|
from rich.rule import Rule
|
|
@@ -55,6 +55,14 @@ if t.TYPE_CHECKING:
|
|
|
55
55
|
AgentEventT = te.TypeVar("AgentEventT", bound="AgentEvent", default="AgentEvent")
|
|
56
56
|
AgentStepT = te.TypeVar("AgentStepT", bound="AgentStep", default="AgentStep")
|
|
57
57
|
AgentStopReason = t.Literal["finished", "max_steps_reached", "error", "stalled"]
|
|
58
|
+
|
|
59
|
+
# Reusable annotation for error fields that store raw exception objects.
|
|
60
|
+
# Pydantic can't serialize exceptions to JSON, so we coerce to str on serialization.
|
|
61
|
+
# Same pattern as dreadnode.generators.chat.Chat.error.
|
|
62
|
+
_ErrorSerializer = PlainSerializer(lambda x: str(x), return_type=str, when_used="json-unless-none")
|
|
63
|
+
_ErrorJsonSchema = WithJsonSchema({"type": "string", "description": "Error message"})
|
|
64
|
+
SerializableError = t.Annotated[BaseException, _ErrorSerializer, _ErrorJsonSchema]
|
|
65
|
+
SerializableException = t.Annotated[Exception, _ErrorSerializer, _ErrorJsonSchema]
|
|
58
66
|
AgentStatus = t.Literal["running", "stalled", "errored", "finished"]
|
|
59
67
|
|
|
60
68
|
|
|
@@ -139,7 +147,7 @@ class AgentStep(AgentEvent):
|
|
|
139
147
|
step: int = 0
|
|
140
148
|
messages: list[Message] = Field(default_factory=list)
|
|
141
149
|
usage: Usage = Usage(input_tokens=0, output_tokens=0, total_tokens=0)
|
|
142
|
-
error:
|
|
150
|
+
error: SerializableException | None = None
|
|
143
151
|
stop: bool | None = None
|
|
144
152
|
|
|
145
153
|
@property
|
|
@@ -231,7 +239,7 @@ class AgentEnd(AgentEvent):
|
|
|
231
239
|
"""
|
|
232
240
|
|
|
233
241
|
stop_reason: AgentStopReason
|
|
234
|
-
error:
|
|
242
|
+
error: SerializableException | str | None = None
|
|
235
243
|
|
|
236
244
|
def _get_data(self) -> dict[str, t.Any]:
|
|
237
245
|
error = self.error
|
|
@@ -336,7 +344,7 @@ class AgentError(AgentEvent):
|
|
|
336
344
|
error: The error that occurred during the agent's execution.
|
|
337
345
|
"""
|
|
338
346
|
|
|
339
|
-
error:
|
|
347
|
+
error: SerializableError
|
|
340
348
|
|
|
341
349
|
def _get_data(self) -> dict[str, t.Any]:
|
|
342
350
|
return {
|
|
@@ -543,7 +551,7 @@ class ToolError(AgentEvent):
|
|
|
543
551
|
"""
|
|
544
552
|
|
|
545
553
|
tool_call: ToolCall
|
|
546
|
-
error:
|
|
554
|
+
error: SerializableError
|
|
547
555
|
|
|
548
556
|
def _get_data(self) -> dict[str, t.Any]:
|
|
549
557
|
return {
|
|
@@ -817,7 +825,7 @@ class GenerationError(AgentEvent):
|
|
|
817
825
|
"""
|
|
818
826
|
|
|
819
827
|
generator: Generator | None = None
|
|
820
|
-
error:
|
|
828
|
+
error: SerializableError
|
|
821
829
|
step: int = 0
|
|
822
830
|
messages: list["Message"] = Field(default_factory=list)
|
|
823
831
|
|
|
@@ -1926,6 +1926,8 @@ class Dreadnode:
|
|
|
1926
1926
|
|
|
1927
1927
|
remote_sha = remote_digests.get(env_name)
|
|
1928
1928
|
if not force and remote_sha and remote_sha == local_sha:
|
|
1929
|
+
if public:
|
|
1930
|
+
self.api.update_task_visibility(org, env_name, is_public=True)
|
|
1929
1931
|
with lock:
|
|
1930
1932
|
result.skipped.append(env_name)
|
|
1931
1933
|
if on_progress:
|
|
@@ -60,7 +60,7 @@ if t.TYPE_CHECKING:
|
|
|
60
60
|
from dreadnode.storage import SessionRecord, SessionStore
|
|
61
61
|
|
|
62
62
|
EventPayload = dict[str, t.Any]
|
|
63
|
-
_TERMINAL_CHAT_EVENT_TYPES = frozenset({"agentend", "error"})
|
|
63
|
+
_TERMINAL_CHAT_EVENT_TYPES = frozenset({"agentend", "error", "cancelled"})
|
|
64
64
|
DEFAULT_MODEL = "anthropic/claude-sonnet-4-20250514"
|
|
65
65
|
_DREADNODE_LLM_BASE_ENV = "DREADNODE_LLM_BASE"
|
|
66
66
|
_DREADNODE_LLM_API_KEY_ENV = "DREADNODE_LLM_API_KEY"
|
|
@@ -1827,6 +1827,28 @@ class SessionRuntime:
|
|
|
1827
1827
|
if self._processing_task is not None and not self._processing_task.done():
|
|
1828
1828
|
self._processing_task.cancel()
|
|
1829
1829
|
|
|
1830
|
+
async def cancel(self) -> bool:
|
|
1831
|
+
"""Cancel the active turn and drain queued requests.
|
|
1832
|
+
|
|
1833
|
+
Returns True if a running turn was cancelled, False if already idle.
|
|
1834
|
+
"""
|
|
1835
|
+
was_busy = False
|
|
1836
|
+
if self._processing_task is not None and not self._processing_task.done():
|
|
1837
|
+
self._processing_task.cancel()
|
|
1838
|
+
was_busy = True
|
|
1839
|
+
# Wait for the task to finish its cancellation cleanup
|
|
1840
|
+
with suppress(asyncio.CancelledError):
|
|
1841
|
+
await self._processing_task
|
|
1842
|
+
|
|
1843
|
+
# Drain any queued requests so their SSE connections terminate
|
|
1844
|
+
async with self._queue_lock:
|
|
1845
|
+
while self._queue:
|
|
1846
|
+
queued = self._queue.popleft()
|
|
1847
|
+
await queued.event_queue.put(None)
|
|
1848
|
+
self._processing_task = None
|
|
1849
|
+
|
|
1850
|
+
return was_busy
|
|
1851
|
+
|
|
1830
1852
|
def persist_manifest(self) -> None:
|
|
1831
1853
|
"""Persist minimal runtime metadata needed to rebind the session."""
|
|
1832
1854
|
self._ensure_session_dir()
|
|
@@ -1906,6 +1928,7 @@ class SessionRuntime:
|
|
|
1906
1928
|
)
|
|
1907
1929
|
_log_chat_timing(self.session_id, "processor_start", request.queued_at_monotonic)
|
|
1908
1930
|
|
|
1931
|
+
cancelled = False
|
|
1909
1932
|
try:
|
|
1910
1933
|
if request.model is not None and request.model != self.model:
|
|
1911
1934
|
self.model = request.model
|
|
@@ -2005,13 +2028,25 @@ class SessionRuntime:
|
|
|
2005
2028
|
# Clear cached agent so restore paths create fresh
|
|
2006
2029
|
self._agent = None
|
|
2007
2030
|
self._persist_state()
|
|
2031
|
+
except asyncio.CancelledError:
|
|
2032
|
+
cancelled = True
|
|
2033
|
+
logger.info("Session {}: turn cancelled by user", self.session_id[:8])
|
|
2034
|
+
# Persist whatever trajectory we have so far
|
|
2035
|
+
self._agent = None
|
|
2036
|
+
self._persist_state()
|
|
2037
|
+
await request.event_queue.put(
|
|
2038
|
+
{"type": "cancelled", "data": {"reason": "user_interrupt"}}
|
|
2039
|
+
)
|
|
2040
|
+
# Don't re-raise — let the method exit cleanly so cancel() can drain remaining queue
|
|
2041
|
+
return
|
|
2008
2042
|
except Exception as exc:
|
|
2009
2043
|
logger.exception("Error during chat for session {}", self.session_id)
|
|
2010
2044
|
await request.event_queue.put({"type": "error", "error": str(exc)})
|
|
2011
2045
|
finally:
|
|
2012
2046
|
logger.debug("Session {}: chat turn complete", self.session_id[:8])
|
|
2013
2047
|
_log_chat_timing(self.session_id, "processor_complete", request.queued_at_monotonic)
|
|
2014
|
-
|
|
2048
|
+
if not cancelled:
|
|
2049
|
+
await request.event_queue.put(None)
|
|
2015
2050
|
|
|
2016
2051
|
def _first_user_preview(self, max_len: int = 200) -> str | None:
|
|
2017
2052
|
"""Extract preview text from the first user message in the trajectory."""
|
|
@@ -2229,6 +2264,21 @@ async def reset_session(session_id: str) -> JSONResponse:
|
|
|
2229
2264
|
return JSONResponse(content={"status": "reset", "session_id": session_id})
|
|
2230
2265
|
|
|
2231
2266
|
|
|
2267
|
+
@app.post("/api/sessions/{session_id}/cancel")
|
|
2268
|
+
async def cancel_session(session_id: str) -> JSONResponse:
|
|
2269
|
+
"""Cancel the active turn and drain queued requests for a session."""
|
|
2270
|
+
session = get_state().get_session(session_id)
|
|
2271
|
+
if session is None:
|
|
2272
|
+
raise HTTPException(status_code=404, detail=f"Session not found: {session_id}")
|
|
2273
|
+
was_busy = await session.cancel()
|
|
2274
|
+
return JSONResponse(
|
|
2275
|
+
content={
|
|
2276
|
+
"status": "cancelled" if was_busy else "idle",
|
|
2277
|
+
"session_id": session_id,
|
|
2278
|
+
}
|
|
2279
|
+
)
|
|
2280
|
+
|
|
2281
|
+
|
|
2232
2282
|
@app.post("/api/sessions/{session_id}/compact")
|
|
2233
2283
|
async def compact_session(session_id: str, body: dict[str, t.Any] | None = None) -> JSONResponse:
|
|
2234
2284
|
"""Compact a session's conversation history (CMP-API-001)."""
|
|
@@ -404,6 +404,7 @@ class DreadnodeTextualApp(App[None]):
|
|
|
404
404
|
yield ConversationView(
|
|
405
405
|
id="conversation",
|
|
406
406
|
)
|
|
407
|
+
yield ToolProgress(id="tool-progress")
|
|
407
408
|
yield PermissionPrompt(id="human-prompt")
|
|
408
409
|
yield SlashOverlay(id="slash-overlay")
|
|
409
410
|
yield MentionOverlay(id="mention-overlay")
|
|
@@ -430,7 +431,7 @@ class DreadnodeTextualApp(App[None]):
|
|
|
430
431
|
logger.opt(exception=True).debug("Could not import VERSION")
|
|
431
432
|
version = ""
|
|
432
433
|
else:
|
|
433
|
-
version = VERSION
|
|
434
|
+
version = f"v{VERSION}"
|
|
434
435
|
welcome = Welcome(id="welcome")
|
|
435
436
|
welcome.version = version
|
|
436
437
|
welcome.working_dir = str(Path.cwd())
|
|
@@ -617,7 +618,7 @@ class DreadnodeTextualApp(App[None]):
|
|
|
617
618
|
record.transcript.append(entry)
|
|
618
619
|
conv.append_entry(entry)
|
|
619
620
|
self._set_status("Thinking", busy=True)
|
|
620
|
-
|
|
621
|
+
self.query_one("#tool-progress", ToolProgress).show_activity("thinking")
|
|
621
622
|
self._send_chat(value, _user_entry_shown=session is not None)
|
|
622
623
|
|
|
623
624
|
def _submit_human_prompt_response(self, prompt: HumanPrompt, raw_value: str) -> None:
|
|
@@ -810,11 +811,14 @@ class DreadnodeTextualApp(App[None]):
|
|
|
810
811
|
@on(SlashOverlay.SlashSelected)
|
|
811
812
|
def _on_slash_selected(self, event: SlashOverlay.SlashSelected) -> None:
|
|
812
813
|
composer = self.query_one("#composer", ComposerInput)
|
|
813
|
-
# Commands without a hint take no args — execute immediately
|
|
814
814
|
from dreadnode.app.tui.commands import SLASH_COMMANDS
|
|
815
815
|
|
|
816
816
|
cmd_def = next((c for c in SLASH_COMMANDS if c.name == event.command), None)
|
|
817
|
-
|
|
817
|
+
# Builtins without a hint and all skills take no args — execute immediately
|
|
818
|
+
is_skill = cmd_def is None and any(
|
|
819
|
+
name == event.command.lstrip("/") for name, _ in self._skill_names
|
|
820
|
+
)
|
|
821
|
+
if is_skill or (cmd_def and not cmd_def.hint):
|
|
818
822
|
composer.load_text("")
|
|
819
823
|
self._handle_command(event.command)
|
|
820
824
|
return
|
|
@@ -1145,6 +1149,7 @@ class DreadnodeTextualApp(App[None]):
|
|
|
1145
1149
|
self._last_quit_time = now
|
|
1146
1150
|
return
|
|
1147
1151
|
self.workers.cancel_group(self, "session")
|
|
1152
|
+
self._cancel_server_turn()
|
|
1148
1153
|
self._commit_draft_to_transcript(self.active_session_id or "")
|
|
1149
1154
|
self._set_status("Interrupted", busy=False)
|
|
1150
1155
|
self._set_composer_enabled(True)
|
|
@@ -1196,6 +1201,7 @@ class DreadnodeTextualApp(App[None]):
|
|
|
1196
1201
|
self._flash("Prompt cancelled", severity="warning")
|
|
1197
1202
|
return
|
|
1198
1203
|
self.workers.cancel_group(self, "session")
|
|
1204
|
+
self._cancel_server_turn()
|
|
1199
1205
|
self._commit_draft_to_transcript(self.active_session_id or "")
|
|
1200
1206
|
self._set_status("Interrupted", busy=False)
|
|
1201
1207
|
self._set_composer_enabled(True)
|
|
@@ -1206,6 +1212,20 @@ class DreadnodeTextualApp(App[None]):
|
|
|
1206
1212
|
# 5. Nothing to do — just ensure composer has focus
|
|
1207
1213
|
composer.focus()
|
|
1208
1214
|
|
|
1215
|
+
def _cancel_server_turn(self) -> None:
|
|
1216
|
+
"""Fire-and-forget: tell the server to cancel the active turn."""
|
|
1217
|
+
sid = self.active_session_id
|
|
1218
|
+
if not sid:
|
|
1219
|
+
return
|
|
1220
|
+
|
|
1221
|
+
async def _do_cancel() -> None:
|
|
1222
|
+
try:
|
|
1223
|
+
await self.server_client.cancel_session(sid)
|
|
1224
|
+
except Exception:
|
|
1225
|
+
logger.debug("Server cancel request failed (session may already be idle)")
|
|
1226
|
+
|
|
1227
|
+
asyncio.get_running_loop().create_task(_do_cancel())
|
|
1228
|
+
|
|
1209
1229
|
def _require_authenticated(self) -> bool:
|
|
1210
1230
|
if not self.authenticated:
|
|
1211
1231
|
self._flash("Not authenticated", severity="warning")
|
|
@@ -1269,7 +1289,7 @@ class DreadnodeTextualApp(App[None]):
|
|
|
1269
1289
|
|
|
1270
1290
|
resolved_url = server_url or self._resolved_server_url()
|
|
1271
1291
|
update_banner = (
|
|
1272
|
-
f"Update available: {VERSION} \u2192 {self.update_available} \u2014 press F9 to update now, or /update after login"
|
|
1292
|
+
f"Update available: v{VERSION} \u2192 v{self.update_available} \u2014 press F9 to update now, or /update after login"
|
|
1273
1293
|
if self.update_available
|
|
1274
1294
|
else None
|
|
1275
1295
|
)
|
|
@@ -1354,7 +1374,7 @@ class DreadnodeTextualApp(App[None]):
|
|
|
1354
1374
|
|
|
1355
1375
|
screen = self.screen
|
|
1356
1376
|
if isinstance(screen, AuthModal):
|
|
1357
|
-
banner = f"Update available: {VERSION} \u2192 {info.latest} \u2014 press F9 to update now, or /update after login"
|
|
1377
|
+
banner = f"Update available: v{VERSION} \u2192 v{info.latest} \u2014 press F9 to update now, or /update after login"
|
|
1358
1378
|
screen.query_one("#auth-update-banner", Static).update(f"[bold yellow]{banner}[/]")
|
|
1359
1379
|
except Exception:
|
|
1360
1380
|
logger.opt(exception=True).debug("Failed to update auth modal banner")
|
|
@@ -1959,7 +1979,7 @@ class DreadnodeTextualApp(App[None]):
|
|
|
1959
1979
|
self._flash("Failed to create a session", severity="error")
|
|
1960
1980
|
return
|
|
1961
1981
|
|
|
1962
|
-
tp = self.query_one(
|
|
1982
|
+
tp = self.query_one("#tool-progress", ToolProgress)
|
|
1963
1983
|
if not _user_entry_shown:
|
|
1964
1984
|
self._set_status("Thinking", busy=True)
|
|
1965
1985
|
tp.show_activity("thinking")
|
|
@@ -2033,7 +2053,7 @@ class DreadnodeTextualApp(App[None]):
|
|
|
2033
2053
|
TranscriptEntry(kind="user", title="shell", body=f"$ {command}"), sid
|
|
2034
2054
|
)
|
|
2035
2055
|
self._set_status("Running", busy=True)
|
|
2036
|
-
tp = self.query_one(
|
|
2056
|
+
tp = self.query_one("#tool-progress", ToolProgress)
|
|
2037
2057
|
tp.show_activity("running")
|
|
2038
2058
|
try:
|
|
2039
2059
|
result = await self.server_client.execute_shell(command)
|
|
@@ -2073,7 +2093,7 @@ class DreadnodeTextualApp(App[None]):
|
|
|
2073
2093
|
return
|
|
2074
2094
|
# Inline the chat logic to avoid nesting @work calls
|
|
2075
2095
|
self._set_status("Thinking", busy=True)
|
|
2076
|
-
tp = self.query_one(
|
|
2096
|
+
tp = self.query_one("#tool-progress", ToolProgress)
|
|
2077
2097
|
tp.show_activity("thinking")
|
|
2078
2098
|
|
|
2079
2099
|
logger.info(
|
|
@@ -2211,6 +2231,30 @@ class DreadnodeTextualApp(App[None]):
|
|
|
2211
2231
|
# Event handling
|
|
2212
2232
|
# ==================================================================
|
|
2213
2233
|
|
|
2234
|
+
def _sync_progress_indicator(self, state: TurnState) -> None:
|
|
2235
|
+
"""Update the progress indicator based on current turn state."""
|
|
2236
|
+
tp = self.query_one("#tool-progress", ToolProgress)
|
|
2237
|
+
running = [r for r in state.tool_runs.values() if r.status == "running"]
|
|
2238
|
+
|
|
2239
|
+
if state.phase == "running_tools" and running:
|
|
2240
|
+
if len(running) == 1:
|
|
2241
|
+
tp.show_tool(running[0].tool_name)
|
|
2242
|
+
else:
|
|
2243
|
+
tp.show_activity(f"running {len(running)} tools")
|
|
2244
|
+
elif state.phase == "generating":
|
|
2245
|
+
if self._draft_active:
|
|
2246
|
+
tp.show_activity("streaming")
|
|
2247
|
+
else:
|
|
2248
|
+
tp.show_activity("thinking")
|
|
2249
|
+
elif state.phase in ("completed", "failed", "idle"):
|
|
2250
|
+
# Idle between tool_end and next generation — show thinking
|
|
2251
|
+
# unless the turn is actually done
|
|
2252
|
+
if state.phase == "idle" and state.tool_runs:
|
|
2253
|
+
tp.show_activity("thinking")
|
|
2254
|
+
else:
|
|
2255
|
+
tp.hide_tool()
|
|
2256
|
+
# awaiting_permission / awaiting_input — keep current state
|
|
2257
|
+
|
|
2214
2258
|
def _handle_event(self, event: dict[str, t.Any], session_id: str) -> None:
|
|
2215
2259
|
normalized = normalize_event(event, session_id)
|
|
2216
2260
|
prev_state = self._turn_state.get(session_id, TurnState.empty(session_id))
|
|
@@ -2223,6 +2267,10 @@ class DreadnodeTextualApp(App[None]):
|
|
|
2223
2267
|
if normalized.usage_total_tokens is not None:
|
|
2224
2268
|
self.total_tokens = max(self.total_tokens, normalized.usage_total_tokens)
|
|
2225
2269
|
|
|
2270
|
+
if event_type == "cancelled":
|
|
2271
|
+
logger.debug("Server confirmed turn cancellation for session {}", session_id[:8])
|
|
2272
|
+
return
|
|
2273
|
+
|
|
2226
2274
|
if event_type == "generation_step":
|
|
2227
2275
|
# Render reasoning/thinking content (expanded, muted, left-border)
|
|
2228
2276
|
if normalized.reasoning_content and self._show_thinking:
|
|
@@ -2242,16 +2290,21 @@ class DreadnodeTextualApp(App[None]):
|
|
|
2242
2290
|
draft.start_stream()
|
|
2243
2291
|
self._draft_active = True
|
|
2244
2292
|
draft.append_token(text)
|
|
2245
|
-
|
|
2293
|
+
self._sync_progress_indicator(next_state)
|
|
2246
2294
|
return
|
|
2247
2295
|
|
|
2248
2296
|
if event_type == "generation_end":
|
|
2249
2297
|
return
|
|
2250
2298
|
|
|
2251
2299
|
if event_type == "tool_start":
|
|
2252
|
-
self.
|
|
2253
|
-
|
|
2254
|
-
|
|
2300
|
+
if self._draft_active:
|
|
2301
|
+
draft = self.query_one("#draft", StreamingDraft)
|
|
2302
|
+
if draft._buffer.strip():
|
|
2303
|
+
self._commit_draft_to_transcript(session_id)
|
|
2304
|
+
else:
|
|
2305
|
+
draft.clear_stream()
|
|
2306
|
+
self._draft_active = False
|
|
2307
|
+
self._sync_progress_indicator(next_state)
|
|
2255
2308
|
return
|
|
2256
2309
|
|
|
2257
2310
|
if event_type == "tool_end":
|
|
@@ -2270,11 +2323,14 @@ class DreadnodeTextualApp(App[None]):
|
|
|
2270
2323
|
if self._tool_details_mode == "expanded"
|
|
2271
2324
|
else ""
|
|
2272
2325
|
)
|
|
2326
|
+
conv = self.query_one(ConversationView)
|
|
2273
2327
|
self._append_transcript(
|
|
2274
2328
|
TranscriptEntry(kind="tool", title=label, body=details, meta=summary),
|
|
2275
2329
|
session_id,
|
|
2330
|
+
scroll=False,
|
|
2276
2331
|
)
|
|
2277
|
-
self.
|
|
2332
|
+
self._sync_progress_indicator(next_state)
|
|
2333
|
+
self.call_after_refresh(conv.scroll_end, animate=False)
|
|
2278
2334
|
return
|
|
2279
2335
|
|
|
2280
2336
|
if event_type == "tool_step":
|
|
@@ -3490,8 +3546,12 @@ class DreadnodeTextualApp(App[None]):
|
|
|
3490
3546
|
import subprocess
|
|
3491
3547
|
import sys
|
|
3492
3548
|
|
|
3549
|
+
# Sanitize: replace unrepresentable characters so encode() never raises
|
|
3550
|
+
safe_text = text.encode("utf-8", errors="replace").decode("utf-8")
|
|
3551
|
+
|
|
3493
3552
|
# Try Textual's OSC 52 first (works in iTerm2, WezTerm, kitty, etc.)
|
|
3494
|
-
|
|
3553
|
+
with contextlib.suppress(Exception):
|
|
3554
|
+
self.copy_to_clipboard(safe_text)
|
|
3495
3555
|
|
|
3496
3556
|
# Also try native clipboard commands as fallback
|
|
3497
3557
|
if sys.platform == "darwin":
|
|
@@ -3504,10 +3564,10 @@ class DreadnodeTextualApp(App[None]):
|
|
|
3504
3564
|
cmd = "wl-copy"
|
|
3505
3565
|
else:
|
|
3506
3566
|
return
|
|
3507
|
-
with contextlib.suppress(subprocess.SubprocessError, FileNotFoundError):
|
|
3567
|
+
with contextlib.suppress(subprocess.SubprocessError, FileNotFoundError, OSError):
|
|
3508
3568
|
subprocess.run( # noqa: S603 - command is selected from a fixed local allowlist
|
|
3509
3569
|
cmd.split(),
|
|
3510
|
-
input=
|
|
3570
|
+
input=safe_text.encode("utf-8"),
|
|
3511
3571
|
check=True,
|
|
3512
3572
|
stdout=subprocess.DEVNULL,
|
|
3513
3573
|
stderr=subprocess.DEVNULL,
|
|
@@ -3601,15 +3661,16 @@ class DreadnodeTextualApp(App[None]):
|
|
|
3601
3661
|
self._sync_sessions()
|
|
3602
3662
|
self._update_context()
|
|
3603
3663
|
|
|
3604
|
-
def _append_transcript(
|
|
3664
|
+
def _append_transcript(
|
|
3665
|
+
self, entry: TranscriptEntry, session_id: str, *, scroll: bool = True
|
|
3666
|
+
) -> None:
|
|
3605
3667
|
record = self.sessions.get(session_id)
|
|
3606
3668
|
if record is None:
|
|
3607
3669
|
return
|
|
3608
3670
|
record.transcript.append(entry)
|
|
3609
3671
|
if self.active_session_id == session_id:
|
|
3610
|
-
self.
|
|
3611
|
-
|
|
3612
|
-
)
|
|
3672
|
+
conv = self.query_one("#conversation", ConversationView)
|
|
3673
|
+
self.call_after_refresh(conv.append_entry, entry, scroll=scroll)
|
|
3613
3674
|
|
|
3614
3675
|
def _commit_draft_to_transcript(self, session_id: str) -> None:
|
|
3615
3676
|
"""Flush live draft text into transcript and reset draft widget state."""
|
|
@@ -20,7 +20,7 @@ DEFAULT_SERVER_PORT = int(os.environ.get("DREADNODE_SERVER_PORT", "8787"))
|
|
|
20
20
|
DEFAULT_SERVER_URL = f"http://{DEFAULT_SERVER_HOST}:{DEFAULT_SERVER_PORT}"
|
|
21
21
|
DEFAULT_MODEL = "anthropic/claude-sonnet-4-20250514"
|
|
22
22
|
DEFAULT_START_TIMEOUT_S = 20.0
|
|
23
|
-
_TERMINAL_CHAT_EVENT_TYPES = frozenset({"agentend", "error"})
|
|
23
|
+
_TERMINAL_CHAT_EVENT_TYPES = frozenset({"agentend", "error", "cancelled"})
|
|
24
24
|
|
|
25
25
|
_SENTINEL = object()
|
|
26
26
|
|
|
@@ -623,6 +623,14 @@ class RuntimeServerClient:
|
|
|
623
623
|
)
|
|
624
624
|
response.raise_for_status()
|
|
625
625
|
|
|
626
|
+
async def cancel_session(self, session_id: str) -> None:
|
|
627
|
+
"""Cancel the active turn for a session."""
|
|
628
|
+
await self.start()
|
|
629
|
+
response = await self._client.post(
|
|
630
|
+
f"/api/sessions/{session_id}/cancel",
|
|
631
|
+
)
|
|
632
|
+
response.raise_for_status()
|
|
633
|
+
|
|
626
634
|
async def stream_chat(
|
|
627
635
|
self,
|
|
628
636
|
*,
|
|
@@ -90,22 +90,16 @@ Screen {
|
|
|
90
90
|
|
|
91
91
|
ToolProgress {
|
|
92
92
|
display: none;
|
|
93
|
-
height:
|
|
94
|
-
padding
|
|
95
|
-
margin-top: 1;
|
|
93
|
+
height: 1;
|
|
94
|
+
padding: 0 2;
|
|
96
95
|
color: $fg-faintest;
|
|
97
96
|
background: $bg;
|
|
98
97
|
}
|
|
99
98
|
|
|
100
99
|
ToolProgress.-active {
|
|
101
100
|
display: block;
|
|
102
|
-
|
|
103
|
-
color: $fg-
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
ToolProgress.-tool-mode {
|
|
107
|
-
padding-left: 2;
|
|
108
|
-
margin-top: 0;
|
|
101
|
+
margin-top: 1;
|
|
102
|
+
color: $fg-muted;
|
|
109
103
|
}
|
|
110
104
|
|
|
111
105
|
/* -- Message Queue ----------------------------------------- */
|
|
@@ -236,6 +236,12 @@ def normalize_event(raw: dict[str, t.Any], session_id: str) -> NormalizedEvent:
|
|
|
236
236
|
error_text=_as_str(payload.get("reason")) or "Agent stalled",
|
|
237
237
|
**base,
|
|
238
238
|
)
|
|
239
|
+
elif event_type == "cancelled":
|
|
240
|
+
normalized = NormalizedEvent(
|
|
241
|
+
type="cancelled",
|
|
242
|
+
error_text=_as_str(payload.get("reason")) or "Turn cancelled",
|
|
243
|
+
**base,
|
|
244
|
+
)
|
|
239
245
|
elif event_type == "error":
|
|
240
246
|
normalized = NormalizedEvent(
|
|
241
247
|
type="runtime_error",
|
|
@@ -18,10 +18,10 @@ import typing as t
|
|
|
18
18
|
# ---------------------------------------------------------------------------
|
|
19
19
|
|
|
20
20
|
_ANTHROPIC_VARIANTS: dict[str, dict[str, t.Any]] = {
|
|
21
|
-
"low": {"
|
|
22
|
-
"medium": {"
|
|
23
|
-
"high": {"
|
|
24
|
-
"max": {"
|
|
21
|
+
"low": {"reasoning_effort": "low"},
|
|
22
|
+
"medium": {"reasoning_effort": "medium"},
|
|
23
|
+
"high": {"reasoning_effort": "high"},
|
|
24
|
+
"max": {"reasoning_effort": "max"},
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
_OPENAI_VARIANTS: dict[str, dict[str, t.Any]] = {
|
|
@@ -158,6 +158,7 @@ def cycle_variant(
|
|
|
158
158
|
_MODEL_DISPLAY_NAMES: dict[str, str] = {
|
|
159
159
|
# Anthropic — versioned (e.g. claude-opus-4-6) and base (e.g. claude-sonnet-4-20250514)
|
|
160
160
|
"claude-opus-4-6": "Opus 4.6",
|
|
161
|
+
"claude-opus-4-5": "Opus 4.5",
|
|
161
162
|
"claude-sonnet-4-5": "Sonnet 4.5",
|
|
162
163
|
"claude-haiku-4-5": "Haiku 4.5",
|
|
163
164
|
"claude-haiku-3-5": "Haiku 3.5",
|
|
@@ -212,10 +213,13 @@ def display_name(model: str) -> str:
|
|
|
212
213
|
"""
|
|
213
214
|
bare = _strip_provider(model).lower()
|
|
214
215
|
|
|
216
|
+
# Normalize both sides: dots ↔ dashes so "claude-opus-4.5" matches "claude-opus-4-5"
|
|
217
|
+
bare_norm = bare.replace(".", "-")
|
|
215
218
|
best_key: str | None = None
|
|
216
219
|
for key in _MODEL_DISPLAY_NAMES:
|
|
217
|
-
|
|
218
|
-
|
|
220
|
+
key_norm = key.replace(".", "-")
|
|
221
|
+
if key_norm in bare_norm:
|
|
222
|
+
if best_key is None or len(key_norm) > len(best_key.replace(".", "-")):
|
|
219
223
|
best_key = key
|
|
220
224
|
|
|
221
225
|
if best_key is not None:
|
|
@@ -128,6 +128,8 @@ def reduce_event(
|
|
|
128
128
|
pass # Lifecycle signal — no phase change
|
|
129
129
|
elif event.type == "tool_step":
|
|
130
130
|
pass # Intermediate tool progress — no phase change
|
|
131
|
+
elif event.type == "agent_start":
|
|
132
|
+
pass # Agent lifecycle start — no phase change
|
|
131
133
|
elif event.type == "heartbeat":
|
|
132
134
|
next_state.last_heartbeat_at = event.timestamp
|
|
133
135
|
elif event.type == "agent_end":
|
|
@@ -20,7 +20,6 @@ from textual.widgets import Markdown, Static
|
|
|
20
20
|
|
|
21
21
|
from dreadnode.app.tui.theme import ACCENT, ERROR, FG, FG_FAINTEST, FG_MUTED, FG_SUBTLE
|
|
22
22
|
from dreadnode.app.tui.widgets.tool import ToolCall
|
|
23
|
-
from dreadnode.app.tui.widgets.tool_progress import ToolProgress
|
|
24
23
|
|
|
25
24
|
if t.TYPE_CHECKING:
|
|
26
25
|
from textual.app import ComposeResult
|
|
@@ -105,7 +104,6 @@ class ConversationView(VerticalScroll):
|
|
|
105
104
|
def compose(self) -> ComposeResult:
|
|
106
105
|
"""Compose the content of the conversation view."""
|
|
107
106
|
yield StreamingDraft(id="draft", classes="-empty")
|
|
108
|
-
yield ToolProgress(id="tool-progress")
|
|
109
107
|
|
|
110
108
|
def show_empty(self) -> None:
|
|
111
109
|
"""Show an empty-state hint."""
|
|
@@ -140,7 +138,7 @@ class ConversationView(VerticalScroll):
|
|
|
140
138
|
self.mount_all(widgets)
|
|
141
139
|
self.call_after_refresh(self.scroll_end, animate=False)
|
|
142
140
|
|
|
143
|
-
def append_entry(self, entry: TranscriptEntry) -> None:
|
|
141
|
+
def append_entry(self, entry: TranscriptEntry, *, scroll: bool = True) -> None:
|
|
144
142
|
"""Add a single message to the stream."""
|
|
145
143
|
# Remove empty-state hint if present
|
|
146
144
|
for w in self.query("#empty-hint"):
|
|
@@ -156,7 +154,8 @@ class ConversationView(VerticalScroll):
|
|
|
156
154
|
self.mount_all(widgets, before=draft)
|
|
157
155
|
except NoMatches:
|
|
158
156
|
self.mount_all(widgets)
|
|
159
|
-
|
|
157
|
+
if scroll:
|
|
158
|
+
self.call_after_refresh(self.scroll_end, animate=False)
|
|
160
159
|
|
|
161
160
|
def write(self, renderable: t.Any) -> None:
|
|
162
161
|
"""Write a renderable to the stream."""
|
|
@@ -8,7 +8,7 @@ from rich.text import Text
|
|
|
8
8
|
from textual.message import Message
|
|
9
9
|
from textual.widgets.option_list import Option
|
|
10
10
|
|
|
11
|
-
from dreadnode.app.tui.theme import
|
|
11
|
+
from dreadnode.app.tui.theme import ACCENT, FG, FG_FAINTEST, FG_MUTED
|
|
12
12
|
from dreadnode.app.tui.widgets.overlay_mixin import OverlayMixin
|
|
13
13
|
|
|
14
14
|
|
|
@@ -51,8 +51,8 @@ class SkillsDialog(OverlayMixin):
|
|
|
51
51
|
|
|
52
52
|
for name, desc in skills:
|
|
53
53
|
label = Text()
|
|
54
|
-
label.append(" /", style=
|
|
55
|
-
label.append(name, style=
|
|
54
|
+
label.append(" /", style=ACCENT)
|
|
55
|
+
label.append(name, style=FG)
|
|
56
56
|
if desc:
|
|
57
57
|
truncated = desc[:60] + "\u2026" if len(desc) > 60 else desc
|
|
58
58
|
label.append(f" {truncated}", style=FG_FAINTEST)
|
|
@@ -7,7 +7,7 @@ from textual.message import Message
|
|
|
7
7
|
from textual.widgets.option_list import Option
|
|
8
8
|
|
|
9
9
|
from dreadnode.app.tui.commands import SLASH_COMMANDS, SlashCommand
|
|
10
|
-
from dreadnode.app.tui.theme import
|
|
10
|
+
from dreadnode.app.tui.theme import ACCENT, FG, FG_MUTED, FG_SUBTLE
|
|
11
11
|
from dreadnode.app.tui.widgets.overlay_mixin import OverlayMixin
|
|
12
12
|
|
|
13
13
|
|
|
@@ -33,6 +33,9 @@ class SlashOverlay(OverlayMixin):
|
|
|
33
33
|
if extra_commands:
|
|
34
34
|
all_commands.extend(c for c in extra_commands if c.name not in builtin_names)
|
|
35
35
|
|
|
36
|
+
# Sort all commands alphabetically so skills interleave with builtins
|
|
37
|
+
all_commands.sort(key=lambda c: c.name.lstrip("/").lower())
|
|
38
|
+
|
|
36
39
|
query = prefix.lstrip("/").lower()
|
|
37
40
|
matches: list[SlashCommand] = []
|
|
38
41
|
for cmd in all_commands:
|
|
@@ -46,17 +49,23 @@ class SlashOverlay(OverlayMixin):
|
|
|
46
49
|
self.add_option(Option("No matching commands", disabled=True))
|
|
47
50
|
return
|
|
48
51
|
|
|
52
|
+
# Truncate labels to overlay width; fall back to container width when
|
|
53
|
+
# the overlay hasn't been laid out yet (display: none → width 0).
|
|
54
|
+
width = self.size.width or (self.container_size.width or 0) or 120
|
|
55
|
+
max_width = width - 4 # borders + scrollbar
|
|
56
|
+
|
|
49
57
|
for cmd in matches:
|
|
50
58
|
is_skill = cmd.name not in builtin_names
|
|
51
59
|
label = Text()
|
|
52
60
|
if is_skill:
|
|
53
|
-
label.append(" /", style=
|
|
54
|
-
label.append(cmd.name.lstrip("/"), style=
|
|
61
|
+
label.append(" /", style=ACCENT)
|
|
62
|
+
label.append(cmd.name.lstrip("/"), style=FG)
|
|
55
63
|
else:
|
|
56
|
-
label.append(f" {cmd.name}", style=f"bold {
|
|
64
|
+
label.append(f" {cmd.name}", style=f"bold {ACCENT}")
|
|
57
65
|
if cmd.hint:
|
|
58
|
-
label.append(f" {cmd.hint}", style=
|
|
59
|
-
label.append(f" {cmd.description}", style=
|
|
66
|
+
label.append(f" {cmd.hint}", style=FG_SUBTLE)
|
|
67
|
+
label.append(f" {cmd.description}", style=FG_MUTED)
|
|
68
|
+
label.truncate(max_width, overflow="ellipsis")
|
|
60
69
|
self.add_option(Option(label, id=cmd.name))
|
|
61
70
|
|
|
62
71
|
self.highlighted = 0
|