dreadnode 2.0.7__tar.gz → 2.0.9__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.7 → dreadnode-2.0.9}/PKG-INFO +2 -2
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/agents/__init__.py +0 -1
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/agents/agent.py +3 -2
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/agents/events.py +18 -1
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/agents/hooks.py +81 -103
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/agents/mcp/auth.py +17 -8
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/agents/mcp/client.py +144 -61
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/agents/subagent.py +8 -4
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/agents/tools.py +9 -5
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/agents/trajectory.py +12 -4
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/assessment.py +197 -67
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/autodan_turbo.py +1 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/beast.py +1 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/crescendo.py +2 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/deep_inception.py +1 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/drattack.py +1 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/goat.py +2 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/gptfuzzer.py +1 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/image.py +4 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/multimodal.py +2 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/pair.py +2 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/prompt.py +104 -9
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/rainbow.py +2 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/renellm.py +1 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/tap.py +1 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/api/client.py +607 -81
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/api/models.py +10 -3
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/cli/airt.py +127 -81
- dreadnode-2.0.9/dreadnode/app/cli/args.py +224 -0
- dreadnode-2.0.9/dreadnode/app/cli/capability.py +610 -0
- dreadnode-2.0.9/dreadnode/app/cli/dataset.py +340 -0
- dreadnode-2.0.9/dreadnode/app/cli/evaluation.py +1539 -0
- dreadnode-2.0.9/dreadnode/app/cli/main.py +505 -0
- dreadnode-2.0.9/dreadnode/app/cli/model.py +510 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/cli/optimize.py +119 -85
- dreadnode-2.0.9/dreadnode/app/cli/runtime.py +55 -0
- dreadnode-2.0.9/dreadnode/app/cli/sandbox.py +127 -0
- dreadnode-2.0.9/dreadnode/app/cli/shared.py +427 -0
- dreadnode-2.0.9/dreadnode/app/cli/task.py +868 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/cli/train.py +145 -107
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/cli/worlds.py +146 -134
- dreadnode-2.0.9/dreadnode/app/config.py +503 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/main.py +200 -191
- dreadnode-2.0.9/dreadnode/app/model_catalog.py +205 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/print_mode.py +9 -24
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/server/app.py +570 -203
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/server/prompt.py +4 -4
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/app.py +514 -260
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/client.py +162 -79
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/commands.py +92 -59
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/dreadnode.tcss +49 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/model_variants.py +2 -163
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/screens/auth.py +46 -17
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/screens/base.py +4 -3
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/screens/capabilities.py +485 -147
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/screens/connection_error.py +15 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/screens/console.py +2 -2
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/screens/evaluations.py +10 -6
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/screens/mcp.py +27 -27
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/screens/model_picker.py +1 -1
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/screens/runtimes.py +43 -47
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/screens/sandboxes.py +16 -13
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/screens/sessions.py +5 -5
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/screens/theme_showcase.py +1 -1
- dreadnode-2.0.9/dreadnode/app/tui/turn_lifecycle.py +193 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/turn_reducer.py +12 -1
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/update_check.py +3 -1
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/widgets/__init__.py +2 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/widgets/composer.py +8 -2
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/widgets/context_bar.py +2 -1
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/widgets/conversation.py +47 -5
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/widgets/help_panel.py +4 -10
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/widgets/overlay_mixin.py +13 -0
- dreadnode-2.0.9/dreadnode/app/tui/widgets/profile_dialog.py +128 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/widgets/status_bar.py +28 -10
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/widgets/throbber.py +2 -2
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/widgets/tool.py +2 -2
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/widgets/tool_progress.py +3 -2
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/widgets/whoami.py +3 -1
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/capabilities/capability.py +29 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/capabilities/loader.py +228 -14
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/capabilities/sync.py +254 -29
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/capabilities/types.py +80 -8
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/core/load.py +13 -13
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/core/log.py +2 -1
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/core/util.py +11 -11
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/datasets/local.py +5 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/generators/generator/base.py +32 -2
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/generators/generator/litellm_.py +98 -2
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/models/local.py +37 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/optimization/backends/gepa.py +1 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/optimization/jobs.py +47 -10
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/optimization/stopping.py +0 -1
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/optimization/study.py +159 -19
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/packaging/loader.py +2 -2
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/packaging/manifest.py +14 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/packaging/oci.py +49 -8
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/packaging/package.py +3 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/packaging/task_validation.py +41 -7
- dreadnode-2.0.9/dreadnode/skills/__init__.py +27 -0
- dreadnode-2.0.9/dreadnode/skills/creating-capabilities/SKILL.md +154 -0
- dreadnode-2.0.9/dreadnode/skills/creating-capabilities/capability-components.md +111 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/storage/storage.py +15 -15
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/tools/ls.py +9 -3
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/tracing/constants.py +3 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/tracing/spans.py +12 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/jobs.py +13 -4
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/transforms/refine.py +31 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/transforms/text.py +4 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/pyproject.toml +55 -4
- dreadnode-2.0.7/dreadnode/app/cli/capability.py +0 -260
- dreadnode-2.0.7/dreadnode/app/cli/dataset.py +0 -139
- dreadnode-2.0.7/dreadnode/app/cli/evaluation.py +0 -204
- dreadnode-2.0.7/dreadnode/app/cli/main.py +0 -346
- dreadnode-2.0.7/dreadnode/app/cli/model.py +0 -104
- dreadnode-2.0.7/dreadnode/app/cli/runtime.py +0 -52
- dreadnode-2.0.7/dreadnode/app/cli/shared.py +0 -771
- dreadnode-2.0.7/dreadnode/app/cli/task.py +0 -284
- dreadnode-2.0.7/dreadnode/app/server/default-agent/tools/coding.py +0 -968
- dreadnode-2.0.7/dreadnode/app/server/default-agent/tools/subagent.py +0 -214
- dreadnode-2.0.7/dreadnode/app/server/session.py +0 -284
- {dreadnode-2.0.7 → dreadnode-2.0.9}/.gitignore +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/LICENSE +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/README.md +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/__init__.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/__main__.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/agents/exceptions.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/agents/format.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/agents/mcp/__init__.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/agents/mcp/config.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/agents/mcp/server.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/agents/reactions.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/agents/skills.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/agents/stopping.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/__init__.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/analytics/__init__.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/analytics/aggregator.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/analytics/classifier.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/analytics/compliance.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/analytics/engine.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/analytics/recommendations.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/analytics/types.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/compliance/__init__.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/compliance/atlas.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/compliance/nist.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/compliance/owasp.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/compliance/owasp_agentic.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/compliance/saif.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/constants.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/data/__init__.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/data/assets/audio/adversarial_query.mp3 +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/data/assets/image/bomb.jpg +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/data/assets/image/meth.png +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/data/prompts/adversarial_benchmark_subset.csv +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/data/prompts/ai_safety.csv +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/data/rubrics/data_exfiltration.yaml +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/data/rubrics/goal_hijacking.yaml +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/data/rubrics/memory_poisoning.yaml +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/data/rubrics/privilege_escalation.yaml +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/data/rubrics/rce.yaml +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/data/rubrics/scope_creep.yaml +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/data/rubrics/tool_chaining.yaml +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/data/rubrics/tool_selection_safety.yaml +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/data/rubrics/unbounded_agency.yaml +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/data/rubrics/web_chatbot_security.yaml +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/data/templates/crescendo/variant_1.yaml +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/data/templates/crescendo/variant_2.yaml +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/data/templates/crescendo/variant_3.yaml +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/data/templates/crescendo/variant_4.yaml +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/data/templates/crescendo/variant_5.yaml +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/events.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/reporting/__init__.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/reporting/json_report.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/reporting/llm_summary.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/reporting/markdown.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/__init__.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/api/__init__.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/cli/__init__.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/server/__init__.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/server/auth.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/server/utils.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/__init__.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/connection.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/event_contract.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/runtime_cache.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/screens/__init__.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/screens/environments.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/screens/secrets.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/screens/traces.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/screens/workspaces.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/theme.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/widgets/agent_dialog.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/widgets/agent_suggester.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/widgets/flash.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/widgets/header_bar.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/widgets/mention_overlay.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/widgets/message_queue.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/widgets/permission_prompt.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/widgets/prompt_info.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/widgets/session_sidebar.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/widgets/skills_dialog.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/widgets/slash_overlay.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/widgets/tools_dialog.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/widgets/welcome.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/capabilities/__init__.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/capabilities/tool_rules.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/core/__init__.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/core/conditions.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/core/discovery.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/core/environment.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/core/exceptions.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/core/execution.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/core/hook.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/core/judge.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/core/meta/__init__.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/core/meta/config.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/core/meta/context.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/core/meta/hydrate.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/core/meta/introspect.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/core/metric.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/core/object.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/core/scorer.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/core/serialization.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/core/stopping.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/core/task.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/core/transforms.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/core/types/__init__.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/core/types/audio.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/core/types/base.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/core/types/common.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/core/types/image.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/core/types/object_3d.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/core/types/table.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/core/types/text.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/core/types/video.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/datasets/__init__.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/datasets/dataset.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/datasets/hf.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/evaluations/__init__.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/evaluations/console.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/evaluations/evaluation.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/evaluations/events.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/evaluations/format.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/evaluations/result.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/evaluations/sample.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/generators/__init__.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/generators/caching.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/generators/chat.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/generators/data.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/generators/exceptions.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/generators/generator/__init__.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/generators/generator/http.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/generators/generator/transformers_.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/generators/generator/vllm_.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/generators/message.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/generators/models.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/generators/parsing.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/generators/tokenizer/__init__.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/generators/tokenizer/base.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/generators/tokenizer/transformers_.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/generators/utils.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/models/__init__.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/models/hf.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/models/model.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/optimization/__init__.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/optimization/adapters/__init__.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/optimization/adapters/agent.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/optimization/api.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/optimization/backends/__init__.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/optimization/backends/base.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/optimization/collectors.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/optimization/config.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/optimization/console.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/optimization/events.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/optimization/format.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/optimization/result.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/optimization/sampler.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/optimization/sampling.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/optimization/search.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/optimization/trial.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/packaging/__init__.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/py.typed +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/samplers/__init__.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/samplers/boundary.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/samplers/fuzzing.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/samplers/graph.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/samplers/grid.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/samplers/image.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/samplers/mapelites.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/samplers/optuna.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/samplers/random.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/samplers/registry.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/samplers/strategy.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/scorers/__init__.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/scorers/advanced_jailbreak_detection.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/scorers/agent_security.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/scorers/agentic.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/scorers/agentic_workflow.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/scorers/classification.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/scorers/consistency.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/scorers/contains.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/scorers/cosine_sim.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/scorers/credentials.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/scorers/crucible.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/scorers/documentation_security.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/scorers/exfiltration_detection.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/scorers/format.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/scorers/harm.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/scorers/ide_security.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/scorers/image.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/scorers/json.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/scorers/judge.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/scorers/length.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/scorers/lexical.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/scorers/mcp_security.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/scorers/memorization.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/scorers/multi_agent_security.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/scorers/pii.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/scorers/prompt_leak.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/scorers/readability.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/scorers/reasoning_security.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/scorers/sentiment.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/scorers/similarity.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/storage/__init__.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/storage/providers.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/storage/session_store.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/tools/__init__.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/tools/_ripgrep.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/tools/apply_patch.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/tools/editing.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/tools/execute.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/tools/fetch.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/tools/glob.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/tools/grep.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/tools/interaction.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/tools/memory.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/tools/read.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/tools/task.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/tools/think.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/tools/todo.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/tools/web_search.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/tools/write.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/tracing/__init__.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/tracing/convert.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/tracing/exporter.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/tracing/exporters.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/tracing/span.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/tracing/trace_converter.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/__init__.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/base.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/dpo.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/etl/__init__.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/etl/_common.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/etl/rl.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/etl/sft.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/etl/worlds.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/events.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/grpo.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/ppo.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/prime.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/ray/__init__.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/ray/async_trainer.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/ray/config.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/ray/coordinator.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/ray/distributed.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/ray/dpo.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/ray/experience.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/ray/fsdp2_learner.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/ray/inference.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/ray/learner.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/ray/multi_turn.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/ray/ppo.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/ray/reward_model.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/ray/rollout_env.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/ray/rollout_worker.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/ray/sft.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/ray/trainer.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/recipes.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/rewards/__init__.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/rewards/aggregator.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/rewards/functions.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/rewards/scorer_bridge.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/rewards/shaping.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/rewards/types.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/rollouts/__init__.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/rollouts/adapters.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/rollouts/orchestrator.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/rollouts/types.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/rollouts/worlds.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/serving/__init__.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/serving/vllm_client.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/sft.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/tinker/__init__.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/tinker/config.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/tinker/data.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/tinker/renderer.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/tinker/rl.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/tinker/trainer.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/tinker_sft.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/utils.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/transforms/__init__.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/transforms/advanced_jailbreak.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/transforms/adversarial_suffix.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/transforms/agent_skill.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/transforms/agentic_workflow.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/transforms/audio.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/transforms/browser_agent_attacks.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/transforms/cipher.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/transforms/constitutional.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/transforms/document.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/transforms/documentation_poison.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/transforms/encoding.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/transforms/exfiltration.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/transforms/flip_attack.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/transforms/guardrail_bypass.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/transforms/ide_injection.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/transforms/image.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/transforms/injection.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/transforms/json_tools.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/transforms/language.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/transforms/logic_bomb.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/transforms/mcp_attacks.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/transforms/multi_agent_attacks.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/transforms/persuasion.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/transforms/perturbation.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/transforms/pii_extraction.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/transforms/pythonic_tools.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/transforms/rag_poisoning.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/transforms/reasoning_attacks.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/transforms/response_steering.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/transforms/stylistic.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/transforms/substitution.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/transforms/swap.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/transforms/system_prompt_extraction.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/transforms/video.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/transforms/xml_tools.py +0 -0
- {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/version.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dreadnode
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.9
|
|
4
4
|
Summary: Dreadnode SDK
|
|
5
5
|
Project-URL: Homepage, https://dreadnode.io
|
|
6
6
|
Project-URL: Documentation, https://docs.dreadnode.io
|
|
@@ -19,7 +19,7 @@ Requires-Dist: gepa>=0.1.1
|
|
|
19
19
|
Requires-Dist: httpx<1.0.0,>=0.28.0
|
|
20
20
|
Requires-Dist: jsonpath-ng>=1.7.0
|
|
21
21
|
Requires-Dist: jsonref>=1.1.0
|
|
22
|
-
Requires-Dist: litellm<=1.82.
|
|
22
|
+
Requires-Dist: litellm<=1.82.3,>=1.80.11
|
|
23
23
|
Requires-Dist: logfire<=3.20.0,>=3.5.3
|
|
24
24
|
Requires-Dist: loguru>=0.7.3
|
|
25
25
|
Requires-Dist: mcp<2.0.0,>=1.25.0
|
|
@@ -120,7 +120,7 @@ class Agent(Executor[AgentEvent, Trajectory]):
|
|
|
120
120
|
)
|
|
121
121
|
judge: Judge[Rubric] | None = Config(default=None, repr=False)
|
|
122
122
|
trajectory: Trajectory = Field(default_factory=Trajectory, exclude=True, repr=False)
|
|
123
|
-
max_steps: int = Config(default=
|
|
123
|
+
max_steps: int = Config(default=1000, ge=1)
|
|
124
124
|
"""Maximum number of generation/tool steps before the agent stops."""
|
|
125
125
|
generation_timeout: int | None = Config(default=None)
|
|
126
126
|
"""Timeout in seconds for each LLM generation call. None = no timeout."""
|
|
@@ -423,7 +423,8 @@ class Agent(Executor[AgentEvent, Trajectory]):
|
|
|
423
423
|
)
|
|
424
424
|
|
|
425
425
|
try:
|
|
426
|
-
summary = await summarize_conversation
|
|
426
|
+
summary = await summarize_conversation(
|
|
427
|
+
summarizer,
|
|
427
428
|
"\n".join(str(msg) for msg in to_summarize),
|
|
428
429
|
)
|
|
429
430
|
except Exception:
|
|
@@ -890,9 +890,26 @@ class GenerationError(AgentEvent):
|
|
|
890
890
|
messages: list["Message"] = Field(default_factory=list)
|
|
891
891
|
|
|
892
892
|
def _get_data(self) -> dict[str, t.Any]:
|
|
893
|
+
error_str = str(self.error)
|
|
894
|
+
# When litellm wraps provider exceptions with empty messages (e.g.
|
|
895
|
+
# "AnthropicException - ."), enrich with the underlying cause so the
|
|
896
|
+
# user gets something actionable instead of a cryptic dot.
|
|
897
|
+
if error_str.endswith(
|
|
898
|
+
("Exception - .", "Exception - . Handle with `litellm.InternalServerError`.")
|
|
899
|
+
):
|
|
900
|
+
cause = getattr(self.error, "__cause__", None) or getattr(
|
|
901
|
+
self.error, "__context__", None
|
|
902
|
+
)
|
|
903
|
+
if cause:
|
|
904
|
+
error_str = f"{error_str} (cause: {cause})"
|
|
905
|
+
else:
|
|
906
|
+
error_str = (
|
|
907
|
+
f"{type(self.error).__name__}: The API returned an error with no details. "
|
|
908
|
+
"This is usually a transient issue — try again."
|
|
909
|
+
)
|
|
893
910
|
return {
|
|
894
911
|
"model": self.generator.model if self.generator else None,
|
|
895
|
-
"error":
|
|
912
|
+
"error": error_str,
|
|
896
913
|
"error_type": type(self.error).__name__,
|
|
897
914
|
"step": self.step,
|
|
898
915
|
}
|
|
@@ -21,7 +21,6 @@ from dreadnode.agents.events import (
|
|
|
21
21
|
)
|
|
22
22
|
from dreadnode.agents.reactions import Reaction, Retry
|
|
23
23
|
from dreadnode.core.hook import Hook, hook
|
|
24
|
-
from dreadnode.generators.chat import Chat
|
|
25
24
|
from dreadnode.generators.generator import Generator
|
|
26
25
|
from dreadnode.generators.message import Message, make_compaction_message
|
|
27
26
|
|
|
@@ -159,98 +158,98 @@ CONTEXT_LENGTH_ERROR_PATTERNS = [
|
|
|
159
158
|
class Summary:
|
|
160
159
|
analysis: str
|
|
161
160
|
summary: str
|
|
162
|
-
chat: Chat
|
|
163
161
|
|
|
164
162
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
This summary should be thorough in capturing technical details, code patterns, and architectural decisions that would be essential for continuing development work without losing context.
|
|
169
|
-
|
|
170
|
-
Before providing your final summary, wrap your analysis in <analysis> tags to organize your thoughts and ensure you've covered all necessary points. In your analysis process:
|
|
171
|
-
|
|
172
|
-
1. Chronologically analyze each message and section of the conversation. For each section thoroughly identify:
|
|
173
|
-
- The user's explicit requests and intents
|
|
174
|
-
- Your approach to addressing the user's requests
|
|
175
|
-
- Key decisions, technical concepts and code patterns
|
|
176
|
-
- Specific technical details like paths, usernames, structured objects, and code
|
|
177
|
-
- Tool interactions performed with a specific focus on intent and outcome
|
|
178
|
-
- Errors that you ran into and how you fixed them
|
|
179
|
-
- Pay special attention to specific user feedback that you received, especially if the user told you to do something differently.
|
|
180
|
-
|
|
181
|
-
2. Double-check for technical accuracy and completeness, addressing each required element thoroughly.
|
|
182
|
-
|
|
183
|
-
Your summary should include the following sections:
|
|
184
|
-
|
|
185
|
-
1. Primary Request and Intent: Capture all of the user's explicit requests and intents in detail
|
|
186
|
-
2. Key Technical Concepts: List all important technical concepts, technologies, and frameworks discussed.
|
|
187
|
-
3. Files and Code Sections: Enumerate specific files and code sections examined, modified, or created. Pay special attention to the most recent messages and include full code snippets where applicable and include a summary of why this file read or edit is important.
|
|
188
|
-
4. Errors and fixes: List all errors that you ran into, and how you fixed them. Pay special attention to specific user feedback that you received, especially if the user told you to do something differently.
|
|
189
|
-
5. Problem Solving: Document problems solved and any ongoing troubleshooting efforts.
|
|
190
|
-
6. All user messages: List ALL user messages that are not tool results. These are critical for understanding the users' feedback and changing intent.
|
|
191
|
-
7. Pending Tasks: Outline any pending tasks that you have explicitly been asked to work on.
|
|
192
|
-
8. Current Work: Describe in detail precisely what was being worked on immediately before this summary request, paying special attention to the most recent messages from both user and assistant. Include file names and code snippets where applicable.
|
|
193
|
-
9. Optional Next Step: List the next step that you will take that is related to the most recent work you were doing. IMPORTANT: ensure that this step is DIRECTLY in line with the user's explicit requests, and the task you were working on immediately before this summary request. If your last task was concluded, then only list next steps if they are explicitly in line with the users request. Do not start on tangential requests without confirming with the user first.
|
|
194
|
-
If there is a next step, include direct quotes from the most recent conversation showing exactly what task you were working on and where you left off. This should be verbatim to ensure there's no drift in task interpretation.
|
|
195
|
-
|
|
196
|
-
Here's an example of how your output should be structured:
|
|
163
|
+
_SUMMARIZATION_SYSTEM_PROMPT = """\
|
|
164
|
+
Your task is to create a detailed summary of the conversation so far, paying close attention to the user's explicit requests and your previous actions.
|
|
165
|
+
This summary should be thorough in capturing technical details, code patterns, and architectural decisions that would be essential for continuing development work without losing context.
|
|
197
166
|
|
|
198
|
-
|
|
199
|
-
<analysis>
|
|
200
|
-
[Your thought process, ensuring all points are covered thoroughly and accurately]
|
|
201
|
-
</analysis>
|
|
167
|
+
Before providing your final summary, wrap your analysis in <analysis> tags to organize your thoughts and ensure you've covered all necessary points. In your analysis process:
|
|
202
168
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
169
|
+
1. Chronologically analyze each message and section of the conversation. For each section thoroughly identify:
|
|
170
|
+
- The user's explicit requests and intents
|
|
171
|
+
- Your approach to addressing the user's requests
|
|
172
|
+
- Key decisions, technical concepts and code patterns
|
|
173
|
+
- Specific technical details like paths, usernames, structured objects, and code
|
|
174
|
+
- Tool interactions performed with a specific focus on intent and outcome
|
|
175
|
+
- Errors that you ran into and how you fixed them
|
|
176
|
+
- Pay special attention to specific user feedback that you received, especially if the user told you to do something differently.
|
|
206
177
|
|
|
207
|
-
|
|
178
|
+
2. Double-check for technical accuracy and completeness, addressing each required element thoroughly.
|
|
208
179
|
|
|
209
|
-
|
|
210
|
-
- [Concept 2]
|
|
211
|
-
- [Object 1]
|
|
212
|
-
- [...]
|
|
180
|
+
Your summary should include the following sections:
|
|
213
181
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
- [...]
|
|
226
|
-
|
|
227
|
-
5. Problem Solving:
|
|
228
|
-
[Description of solved problems and ongoing troubleshooting]
|
|
182
|
+
1. Primary Request and Intent: Capture all of the user's explicit requests and intents in detail
|
|
183
|
+
2. Key Technical Concepts: List all important technical concepts, technologies, and frameworks discussed.
|
|
184
|
+
3. Files and Code Sections: Enumerate specific files and code sections examined, modified, or created. Pay special attention to the most recent messages and include full code snippets where applicable and include a summary of why this file read or edit is important.
|
|
185
|
+
4. Errors and fixes: List all errors that you ran into, and how you fixed them. Pay special attention to specific user feedback that you received, especially if the user told you to do something differently.
|
|
186
|
+
5. Problem Solving: Document problems solved and any ongoing troubleshooting efforts.
|
|
187
|
+
6. All user messages: List ALL user messages that are not tool results. These are critical for understanding the users' feedback and changing intent.
|
|
188
|
+
7. Pending Tasks: Outline any pending tasks that you have explicitly been asked to work on.
|
|
189
|
+
8. Current Work: Describe in detail precisely what was being worked on immediately before this summary request, paying special attention to the most recent messages from both user and assistant. Include file names and code snippets where applicable.
|
|
190
|
+
9. Optional Next Step: List the next step that you will take that is related to the most recent work you were doing. IMPORTANT: ensure that this step is DIRECTLY in line with the user's explicit requests, and the task you were working on immediately before this summary request. If your last task was concluded, then only list next steps if they are explicitly in line with the users request. Do not start on tangential requests without confirming with the user first.
|
|
191
|
+
If there is a next step, include direct quotes from the most recent conversation showing exactly what task you were working on and where you left off. This should be verbatim to ensure there's no drift in task interpretation.
|
|
192
|
+
"""
|
|
229
193
|
|
|
230
|
-
|
|
194
|
+
_SUMMARIZATION_USER_TEMPLATE = """\
|
|
195
|
+
Please provide your summary based on the conversation below, following the system instructions and ensuring precision and thoroughness in your response.
|
|
231
196
|
|
|
232
|
-
|
|
233
|
-
|
|
197
|
+
{guidance_section}<conversation>
|
|
198
|
+
{conversation}
|
|
199
|
+
</conversation>"""
|
|
234
200
|
|
|
235
|
-
7. Pending Tasks:
|
|
236
201
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
202
|
+
async def summarize_conversation(
|
|
203
|
+
generator: "str | Generator",
|
|
204
|
+
conversation: str,
|
|
205
|
+
*,
|
|
206
|
+
guidance: str = "",
|
|
207
|
+
) -> Summary:
|
|
208
|
+
"""Run the summarization prompt against the given generator and return a Summary."""
|
|
209
|
+
import re
|
|
210
|
+
|
|
211
|
+
from dreadnode.generators.generator import GenerateParams
|
|
212
|
+
from dreadnode.generators.generator import get_generator as _get_generator
|
|
213
|
+
|
|
214
|
+
if isinstance(generator, str):
|
|
215
|
+
generator = _get_generator(generator)
|
|
216
|
+
|
|
217
|
+
guidance_section = ""
|
|
218
|
+
if guidance:
|
|
219
|
+
guidance_section = f"Additional summarization guidance:\n{guidance}\n\n"
|
|
220
|
+
|
|
221
|
+
messages = [
|
|
222
|
+
Message(role="system", content=_SUMMARIZATION_SYSTEM_PROMPT),
|
|
223
|
+
Message(
|
|
224
|
+
role="user",
|
|
225
|
+
content=_SUMMARIZATION_USER_TEMPLATE.format(
|
|
226
|
+
guidance_section=guidance_section,
|
|
227
|
+
conversation=conversation,
|
|
228
|
+
),
|
|
229
|
+
),
|
|
230
|
+
]
|
|
240
231
|
|
|
241
|
-
|
|
242
|
-
|
|
232
|
+
results = await generator.generate_messages(
|
|
233
|
+
[messages],
|
|
234
|
+
[generator.params or GenerateParams()],
|
|
235
|
+
)
|
|
243
236
|
|
|
244
|
-
|
|
245
|
-
|
|
237
|
+
result = results[0]
|
|
238
|
+
if isinstance(result, BaseException):
|
|
239
|
+
raise result
|
|
246
240
|
|
|
247
|
-
|
|
248
|
-
</example>
|
|
241
|
+
response_text = result.message.content
|
|
249
242
|
|
|
250
|
-
|
|
243
|
+
analysis = ""
|
|
244
|
+
summary_text = response_text
|
|
245
|
+
analysis_match = re.search(r"<analysis>(.*?)</analysis>", response_text, re.DOTALL)
|
|
246
|
+
if analysis_match:
|
|
247
|
+
analysis = analysis_match.group(1).strip()
|
|
248
|
+
summary_match = re.search(r"<summary>(.*?)</summary>", response_text, re.DOTALL)
|
|
249
|
+
if summary_match:
|
|
250
|
+
summary_text = summary_match.group(1).strip()
|
|
251
251
|
|
|
252
|
-
|
|
253
|
-
"""
|
|
252
|
+
return Summary(analysis=analysis, summary=summary_text)
|
|
254
253
|
|
|
255
254
|
|
|
256
255
|
def _is_context_length_error(error: BaseException) -> bool:
|
|
@@ -273,8 +272,8 @@ def find_summarization_boundary(
|
|
|
273
272
|
|
|
274
273
|
Walks messages from the start and finds the latest safe split point that
|
|
275
274
|
leaves at least ``min_messages_to_keep`` messages in the "keep" portion.
|
|
276
|
-
|
|
277
|
-
|
|
275
|
+
A safe boundary is after a simple assistant message (no tool calls) —
|
|
276
|
+
this is the natural end of a complete conversational turn.
|
|
278
277
|
|
|
279
278
|
Returns:
|
|
280
279
|
Index splitting ``messages[:boundary]`` (to summarize) from
|
|
@@ -288,10 +287,7 @@ def find_summarization_boundary(
|
|
|
288
287
|
is_simple_assistant = message.role == "assistant" and not getattr(
|
|
289
288
|
message, "tool_calls", None
|
|
290
289
|
)
|
|
291
|
-
|
|
292
|
-
i + 1 == len(messages) or messages[i + 1].role != "tool"
|
|
293
|
-
)
|
|
294
|
-
if is_simple_assistant or is_last_tool:
|
|
290
|
+
if is_simple_assistant:
|
|
295
291
|
best_boundary = i + 1
|
|
296
292
|
return best_boundary
|
|
297
293
|
|
|
@@ -371,7 +367,8 @@ def _make_summarize_hook(
|
|
|
371
367
|
if not to_summarize:
|
|
372
368
|
return None
|
|
373
369
|
|
|
374
|
-
summary = await summarize_conversation
|
|
370
|
+
summary = await summarize_conversation(
|
|
371
|
+
summarizer_model,
|
|
375
372
|
"\n".join(str(msg) for msg in to_summarize),
|
|
376
373
|
guidance=guidance,
|
|
377
374
|
)
|
|
@@ -398,22 +395,3 @@ def _make_summarize_hook(
|
|
|
398
395
|
|
|
399
396
|
# Pre-instantiated with defaults -- this is what the wrapper discovers
|
|
400
397
|
summarize_when_long = _make_summarize_hook()
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
# =============================================================================
|
|
404
|
-
# Default Hooks
|
|
405
|
-
# =============================================================================
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
def default_hooks() -> list:
|
|
409
|
-
"""Core hooks every agent should have."""
|
|
410
|
-
from dreadnode.agents import stopping
|
|
411
|
-
|
|
412
|
-
hooks: list = [
|
|
413
|
-
tool_metrics(),
|
|
414
|
-
summarize_when_long,
|
|
415
|
-
stopping.step_count(50, name="server_step_limit"),
|
|
416
|
-
]
|
|
417
|
-
if backoff_on_ratelimit is not None:
|
|
418
|
-
hooks.insert(0, backoff_on_ratelimit)
|
|
419
|
-
return hooks
|
|
@@ -5,6 +5,7 @@ Provides file-based token storage and helpers for building OAuth providers
|
|
|
5
5
|
using the MCP SDK's built-in OAuthClientProvider.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
+
import asyncio
|
|
8
9
|
import json
|
|
9
10
|
import os
|
|
10
11
|
import typing as t
|
|
@@ -64,7 +65,8 @@ class FileTokenStorage:
|
|
|
64
65
|
async def get_tokens(self) -> "OAuthToken | None":
|
|
65
66
|
from mcp.shared.auth import OAuthToken
|
|
66
67
|
|
|
67
|
-
|
|
68
|
+
entry = await asyncio.to_thread(self._get_entry)
|
|
69
|
+
data = entry.get("tokens")
|
|
68
70
|
if data is None:
|
|
69
71
|
return None
|
|
70
72
|
try:
|
|
@@ -74,14 +76,18 @@ class FileTokenStorage:
|
|
|
74
76
|
return None
|
|
75
77
|
|
|
76
78
|
async def set_tokens(self, tokens: "OAuthToken") -> None:
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
79
|
+
def _update() -> None:
|
|
80
|
+
entry = self._get_entry()
|
|
81
|
+
entry["tokens"] = tokens.model_dump(mode="json", exclude_none=True)
|
|
82
|
+
self._set_entry(entry)
|
|
83
|
+
|
|
84
|
+
await asyncio.to_thread(_update)
|
|
80
85
|
|
|
81
86
|
async def get_client_info(self) -> "OAuthClientInformationFull | None":
|
|
82
87
|
from mcp.shared.auth import OAuthClientInformationFull
|
|
83
88
|
|
|
84
|
-
|
|
89
|
+
entry = await asyncio.to_thread(self._get_entry)
|
|
90
|
+
data = entry.get("client_info")
|
|
85
91
|
if data is None:
|
|
86
92
|
return None
|
|
87
93
|
try:
|
|
@@ -91,9 +97,12 @@ class FileTokenStorage:
|
|
|
91
97
|
return None
|
|
92
98
|
|
|
93
99
|
async def set_client_info(self, client_info: "OAuthClientInformationFull") -> None:
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
100
|
+
def _update() -> None:
|
|
101
|
+
entry = self._get_entry()
|
|
102
|
+
entry["client_info"] = client_info.model_dump(mode="json", exclude_none=True)
|
|
103
|
+
self._set_entry(entry)
|
|
104
|
+
|
|
105
|
+
await asyncio.to_thread(_update)
|
|
97
106
|
|
|
98
107
|
|
|
99
108
|
async def _default_redirect_handler(url: str) -> None:
|
|
@@ -144,6 +144,10 @@ class MCPClient:
|
|
|
144
144
|
_exit_stack: AsyncExitStack
|
|
145
145
|
_session: "ClientSession | None"
|
|
146
146
|
_oauth_config: t.Any # OAuthConfig | None
|
|
147
|
+
_owner_task: asyncio.Task[None] | None
|
|
148
|
+
_shutdown_event: asyncio.Event | None
|
|
149
|
+
_ready_future: asyncio.Future[None] | None
|
|
150
|
+
_lifecycle_lock: asyncio.Lock | None
|
|
147
151
|
|
|
148
152
|
def __init__(
|
|
149
153
|
self,
|
|
@@ -173,6 +177,10 @@ class MCPClient:
|
|
|
173
177
|
self._oauth_config = oauth
|
|
174
178
|
self._init_timeout = init_timeout
|
|
175
179
|
self._stderr_capture: _StderrCapture | None = None
|
|
180
|
+
self._owner_task = None
|
|
181
|
+
self._shutdown_event = None
|
|
182
|
+
self._ready_future = None
|
|
183
|
+
self._lifecycle_lock = None
|
|
176
184
|
|
|
177
185
|
@classmethod
|
|
178
186
|
def from_config(cls, config: ServerConfig) -> "MCPClient":
|
|
@@ -222,6 +230,83 @@ class MCPClient:
|
|
|
222
230
|
|
|
223
231
|
return execute_on_server
|
|
224
232
|
|
|
233
|
+
def _get_lifecycle_lock(self) -> asyncio.Lock:
|
|
234
|
+
if self._lifecycle_lock is None:
|
|
235
|
+
self._lifecycle_lock = asyncio.Lock()
|
|
236
|
+
return self._lifecycle_lock
|
|
237
|
+
|
|
238
|
+
@staticmethod
|
|
239
|
+
def _unwrap_exception(exc: BaseException) -> BaseException:
|
|
240
|
+
cause = exc
|
|
241
|
+
if isinstance(exc, BaseExceptionGroup):
|
|
242
|
+
leaf = exc.exceptions[0] if exc.exceptions else exc
|
|
243
|
+
while isinstance(leaf, BaseExceptionGroup) and leaf.exceptions:
|
|
244
|
+
leaf = leaf.exceptions[0]
|
|
245
|
+
cause = leaf
|
|
246
|
+
return cause
|
|
247
|
+
|
|
248
|
+
def _set_error_status(self, exc: BaseException) -> BaseException:
|
|
249
|
+
cause = self._unwrap_exception(exc)
|
|
250
|
+
error_msg = str(cause)
|
|
251
|
+
|
|
252
|
+
if self._stderr_capture and self._stderr_capture.last_lines:
|
|
253
|
+
stderr_tail = "\n".join(self._stderr_capture.last_lines[-10:])
|
|
254
|
+
error_msg = f"{error_msg}\n\nServer stderr:\n{stderr_tail}"
|
|
255
|
+
|
|
256
|
+
error_lower = error_msg.lower()
|
|
257
|
+
if any(kw in error_lower for kw in ("unauthorized", "forbidden", "401", "403", "oauth")):
|
|
258
|
+
self._status = MCPStatus.NEEDS_AUTH
|
|
259
|
+
else:
|
|
260
|
+
self._status = MCPStatus.FAILED
|
|
261
|
+
|
|
262
|
+
self._error = error_msg
|
|
263
|
+
return cause
|
|
264
|
+
|
|
265
|
+
def _reset_connection_state(self) -> None:
|
|
266
|
+
self._session = None
|
|
267
|
+
self.tools.clear()
|
|
268
|
+
if self._status == MCPStatus.CONNECTED:
|
|
269
|
+
self._status = MCPStatus.DISCONNECTED
|
|
270
|
+
self._exit_stack = AsyncExitStack()
|
|
271
|
+
self._stderr_capture = None
|
|
272
|
+
|
|
273
|
+
async def _run_connection(self, shutdown_event: asyncio.Event) -> None:
|
|
274
|
+
try:
|
|
275
|
+
if self.transport == "stdio":
|
|
276
|
+
self._session = await self._connect_via_stdio(
|
|
277
|
+
t.cast("StdioConnection", self.connection),
|
|
278
|
+
)
|
|
279
|
+
elif self.transport == "streamable-http":
|
|
280
|
+
self._session = await self._connect_via_streamable_http(
|
|
281
|
+
t.cast("dict[str, t.Any]", self.connection),
|
|
282
|
+
)
|
|
283
|
+
else:
|
|
284
|
+
msg = (
|
|
285
|
+
f"Unsupported transport: {self.transport}. Must be 'stdio' or 'streamable-http'"
|
|
286
|
+
)
|
|
287
|
+
raise TypeError(msg) # noqa: TRY301
|
|
288
|
+
|
|
289
|
+
await asyncio.wait_for(self.session.initialize(), timeout=self._init_timeout)
|
|
290
|
+
await asyncio.wait_for(self._load_tools(), timeout=self._init_timeout)
|
|
291
|
+
|
|
292
|
+
self._status = MCPStatus.CONNECTED
|
|
293
|
+
self._error = None
|
|
294
|
+
|
|
295
|
+
if self._ready_future is not None and not self._ready_future.done():
|
|
296
|
+
self._ready_future.set_result(None)
|
|
297
|
+
|
|
298
|
+
await shutdown_event.wait()
|
|
299
|
+
except BaseException as exc:
|
|
300
|
+
cause = self._set_error_status(exc)
|
|
301
|
+
if self._ready_future is not None and not self._ready_future.done():
|
|
302
|
+
if isinstance(cause, Exception):
|
|
303
|
+
self._ready_future.set_exception(cause)
|
|
304
|
+
else:
|
|
305
|
+
self._ready_future.set_exception(RuntimeError(str(cause)))
|
|
306
|
+
finally:
|
|
307
|
+
await self._exit_stack.aclose()
|
|
308
|
+
self._reset_connection_state()
|
|
309
|
+
|
|
225
310
|
async def _load_tools(self) -> None:
|
|
226
311
|
mcp_tool_result = await self.session.list_tools()
|
|
227
312
|
|
|
@@ -278,9 +363,9 @@ class MCPClient:
|
|
|
278
363
|
"""Probe whether a URL supports streamable HTTP (POST).
|
|
279
364
|
|
|
280
365
|
Returns True if the server likely supports streamable HTTP, False if it
|
|
281
|
-
clearly doesn't
|
|
282
|
-
|
|
283
|
-
|
|
366
|
+
clearly doesn't. Connectivity errors also return False (server
|
|
367
|
+
unreachable via POST, try SSE). Auth and SSL errors are allowed to
|
|
368
|
+
propagate — they would affect SSE equally.
|
|
284
369
|
"""
|
|
285
370
|
try:
|
|
286
371
|
probe_headers = dict(headers) if headers else {}
|
|
@@ -292,6 +377,16 @@ class MCPClient:
|
|
|
292
377
|
)
|
|
293
378
|
async with httpx.AsyncClient(timeout=min(http_timeout, 5), auth=auth) as probe:
|
|
294
379
|
r = await probe.post(url, content=b"{}", headers=probe_headers)
|
|
380
|
+
# Auth errors affect both transports equally — propagate so
|
|
381
|
+
# callers can classify as NEEDS_AUTH instead of falling through.
|
|
382
|
+
if r.status_code in (401, 403):
|
|
383
|
+
r.raise_for_status()
|
|
384
|
+
if r.status_code == 400 and self._looks_like_jsonrpc_probe_rejection(r):
|
|
385
|
+
logger.debug(
|
|
386
|
+
"Streamable HTTP probe got JSON-RPC validation error for {}, using streamable-http",
|
|
387
|
+
url,
|
|
388
|
+
)
|
|
389
|
+
return True
|
|
295
390
|
# 400 = bad request (SSE-only server doesn't understand POST body)
|
|
296
391
|
# 404 = endpoint not found for POST
|
|
297
392
|
# 405 = method not allowed (explicitly rejects POST)
|
|
@@ -309,6 +404,24 @@ class MCPClient:
|
|
|
309
404
|
|
|
310
405
|
return True
|
|
311
406
|
|
|
407
|
+
@staticmethod
|
|
408
|
+
def _looks_like_jsonrpc_probe_rejection(response: httpx.Response) -> bool:
|
|
409
|
+
"""Detect JSON-RPC validation errors from real MCP streamable-http servers."""
|
|
410
|
+
content_type = response.headers.get("content-type", "").lower()
|
|
411
|
+
if "application/json" not in content_type:
|
|
412
|
+
return False
|
|
413
|
+
|
|
414
|
+
with contextlib.suppress(ValueError):
|
|
415
|
+
payload = response.json()
|
|
416
|
+
if (
|
|
417
|
+
isinstance(payload, dict)
|
|
418
|
+
and payload.get("jsonrpc") == "2.0"
|
|
419
|
+
and isinstance(payload.get("error"), dict)
|
|
420
|
+
):
|
|
421
|
+
return True
|
|
422
|
+
|
|
423
|
+
return False
|
|
424
|
+
|
|
312
425
|
async def _connect_via_streamable_http(self, connection: dict[str, t.Any]) -> "ClientSession":
|
|
313
426
|
"""Connect via streamable HTTP, falling back to SSE on failure."""
|
|
314
427
|
from mcp import ClientSession
|
|
@@ -378,73 +491,43 @@ class MCPClient:
|
|
|
378
491
|
|
|
379
492
|
Sets status to CONNECTED on success, FAILED or NEEDS_AUTH on error.
|
|
380
493
|
"""
|
|
381
|
-
|
|
382
|
-
if self.
|
|
383
|
-
|
|
384
|
-
t.cast("StdioConnection", self.connection),
|
|
385
|
-
)
|
|
386
|
-
elif self.transport == "streamable-http":
|
|
387
|
-
self._session = await self._connect_via_streamable_http(
|
|
388
|
-
t.cast("dict[str, t.Any]", self.connection),
|
|
389
|
-
)
|
|
494
|
+
async with self._get_lifecycle_lock():
|
|
495
|
+
if self._owner_task is not None and not self._owner_task.done():
|
|
496
|
+
ready_future = self._ready_future
|
|
390
497
|
else:
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
498
|
+
self._reset_connection_state()
|
|
499
|
+
self._shutdown_event = asyncio.Event()
|
|
500
|
+
self._ready_future = asyncio.get_running_loop().create_future()
|
|
501
|
+
self._owner_task = asyncio.create_task(
|
|
502
|
+
self._run_connection(self._shutdown_event),
|
|
503
|
+
name=f"mcp-client:{self.transport}",
|
|
394
504
|
)
|
|
505
|
+
ready_future = self._ready_future
|
|
395
506
|
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
self._status = MCPStatus.CONNECTED
|
|
400
|
-
self._error = None
|
|
401
|
-
|
|
402
|
-
except BaseException as e:
|
|
403
|
-
await self._shutdown()
|
|
404
|
-
|
|
405
|
-
# anyio TaskGroups wrap failures in BaseExceptionGroup — unwrap
|
|
406
|
-
# to surface the actual root cause (e.g. ConnectionRefusedError)
|
|
407
|
-
# instead of the opaque "unhandled errors in a TaskGroup" message.
|
|
408
|
-
cause = e
|
|
409
|
-
if isinstance(e, BaseExceptionGroup):
|
|
410
|
-
leaf = e.exceptions[0] if e.exceptions else e
|
|
411
|
-
while isinstance(leaf, BaseExceptionGroup) and leaf.exceptions:
|
|
412
|
-
leaf = leaf.exceptions[0]
|
|
413
|
-
cause = leaf
|
|
414
|
-
|
|
415
|
-
error_msg = str(cause)
|
|
416
|
-
|
|
417
|
-
# Append captured subprocess stderr for stdio failures —
|
|
418
|
-
# this surfaces the actual crash output (tracebacks, missing
|
|
419
|
-
# modules, config errors) instead of just "Connection closed".
|
|
420
|
-
if self._stderr_capture and self._stderr_capture.last_lines:
|
|
421
|
-
stderr_tail = "\n".join(self._stderr_capture.last_lines[-10:])
|
|
422
|
-
error_msg = f"{error_msg}\n\nServer stderr:\n{stderr_tail}"
|
|
423
|
-
|
|
424
|
-
# Check for auth-related failures
|
|
425
|
-
error_lower = error_msg.lower()
|
|
426
|
-
if "unauthorized" in error_lower or "401" in error_lower or "oauth" in error_lower:
|
|
427
|
-
self._status = MCPStatus.NEEDS_AUTH
|
|
428
|
-
else:
|
|
429
|
-
self._status = MCPStatus.FAILED
|
|
430
|
-
|
|
431
|
-
self._error = error_msg
|
|
432
|
-
|
|
433
|
-
# Re-raise the unwrapped cause so callers can catch with `except Exception`
|
|
434
|
-
if cause is not e and isinstance(cause, Exception):
|
|
435
|
-
raise cause from e
|
|
436
|
-
raise
|
|
507
|
+
assert ready_future is not None
|
|
508
|
+
await ready_future
|
|
437
509
|
|
|
438
510
|
async def disconnect(self) -> None:
|
|
439
511
|
"""Disconnect from the MCP server."""
|
|
440
512
|
await self._shutdown()
|
|
441
513
|
|
|
442
514
|
async def _shutdown(self) -> None:
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
515
|
+
async with self._get_lifecycle_lock():
|
|
516
|
+
owner_task = self._owner_task
|
|
517
|
+
shutdown_event = self._shutdown_event
|
|
518
|
+
if owner_task is None:
|
|
519
|
+
self._reset_connection_state()
|
|
520
|
+
return
|
|
521
|
+
if shutdown_event is not None:
|
|
522
|
+
shutdown_event.set()
|
|
523
|
+
|
|
524
|
+
await owner_task
|
|
525
|
+
|
|
526
|
+
async with self._get_lifecycle_lock():
|
|
527
|
+
if self._owner_task is owner_task:
|
|
528
|
+
self._owner_task = None
|
|
529
|
+
self._shutdown_event = None
|
|
530
|
+
self._ready_future = None
|
|
448
531
|
|
|
449
532
|
async def __aenter__(self) -> "MCPClient":
|
|
450
533
|
await self.connect()
|
|
@@ -172,14 +172,18 @@ class SubAgentToolset(Toolset):
|
|
|
172
172
|
sub_agent.reset()
|
|
173
173
|
|
|
174
174
|
try:
|
|
175
|
-
|
|
175
|
+
trajectory = await sub_agent.run(task)
|
|
176
|
+
# Get the last assistant message content as the response
|
|
177
|
+
last_message = trajectory.messages[-1] if trajectory.messages else None
|
|
178
|
+
response = str(last_message.content) if last_message else ""
|
|
179
|
+
|
|
176
180
|
result = f"## Sub-agent: {config['name']}\n\n"
|
|
177
181
|
result += f"**Task:** {task}\n\n"
|
|
178
|
-
result += f"**Steps taken:** {len(
|
|
179
|
-
result += f"**Tokens used:** {
|
|
182
|
+
result += f"**Steps taken:** {len(trajectory.steps)}\n"
|
|
183
|
+
result += f"**Tokens used:** {trajectory.usage.total_tokens}\n\n"
|
|
180
184
|
result += f"**Result:**\n{response}"
|
|
181
185
|
|
|
182
|
-
logger.info(f"Sub-agent completed in {len(
|
|
186
|
+
logger.info(f"Sub-agent completed in {len(trajectory.steps)} steps")
|
|
183
187
|
except Exception as e:
|
|
184
188
|
logger.error(f"Sub-agent failed: {e}")
|
|
185
189
|
return f"Sub-agent failed: {e}"
|