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.
Files changed (437) hide show
  1. {dreadnode-2.0.7 → dreadnode-2.0.9}/PKG-INFO +2 -2
  2. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/agents/__init__.py +0 -1
  3. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/agents/agent.py +3 -2
  4. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/agents/events.py +18 -1
  5. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/agents/hooks.py +81 -103
  6. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/agents/mcp/auth.py +17 -8
  7. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/agents/mcp/client.py +144 -61
  8. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/agents/subagent.py +8 -4
  9. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/agents/tools.py +9 -5
  10. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/agents/trajectory.py +12 -4
  11. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/assessment.py +197 -67
  12. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/autodan_turbo.py +1 -0
  13. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/beast.py +1 -0
  14. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/crescendo.py +2 -0
  15. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/deep_inception.py +1 -0
  16. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/drattack.py +1 -0
  17. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/goat.py +2 -0
  18. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/gptfuzzer.py +1 -0
  19. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/image.py +4 -0
  20. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/multimodal.py +2 -0
  21. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/pair.py +2 -0
  22. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/prompt.py +104 -9
  23. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/rainbow.py +2 -0
  24. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/renellm.py +1 -0
  25. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/tap.py +1 -0
  26. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/api/client.py +607 -81
  27. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/api/models.py +10 -3
  28. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/cli/airt.py +127 -81
  29. dreadnode-2.0.9/dreadnode/app/cli/args.py +224 -0
  30. dreadnode-2.0.9/dreadnode/app/cli/capability.py +610 -0
  31. dreadnode-2.0.9/dreadnode/app/cli/dataset.py +340 -0
  32. dreadnode-2.0.9/dreadnode/app/cli/evaluation.py +1539 -0
  33. dreadnode-2.0.9/dreadnode/app/cli/main.py +505 -0
  34. dreadnode-2.0.9/dreadnode/app/cli/model.py +510 -0
  35. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/cli/optimize.py +119 -85
  36. dreadnode-2.0.9/dreadnode/app/cli/runtime.py +55 -0
  37. dreadnode-2.0.9/dreadnode/app/cli/sandbox.py +127 -0
  38. dreadnode-2.0.9/dreadnode/app/cli/shared.py +427 -0
  39. dreadnode-2.0.9/dreadnode/app/cli/task.py +868 -0
  40. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/cli/train.py +145 -107
  41. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/cli/worlds.py +146 -134
  42. dreadnode-2.0.9/dreadnode/app/config.py +503 -0
  43. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/main.py +200 -191
  44. dreadnode-2.0.9/dreadnode/app/model_catalog.py +205 -0
  45. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/print_mode.py +9 -24
  46. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/server/app.py +570 -203
  47. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/server/prompt.py +4 -4
  48. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/app.py +514 -260
  49. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/client.py +162 -79
  50. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/commands.py +92 -59
  51. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/dreadnode.tcss +49 -0
  52. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/model_variants.py +2 -163
  53. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/screens/auth.py +46 -17
  54. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/screens/base.py +4 -3
  55. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/screens/capabilities.py +485 -147
  56. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/screens/connection_error.py +15 -0
  57. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/screens/console.py +2 -2
  58. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/screens/evaluations.py +10 -6
  59. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/screens/mcp.py +27 -27
  60. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/screens/model_picker.py +1 -1
  61. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/screens/runtimes.py +43 -47
  62. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/screens/sandboxes.py +16 -13
  63. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/screens/sessions.py +5 -5
  64. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/screens/theme_showcase.py +1 -1
  65. dreadnode-2.0.9/dreadnode/app/tui/turn_lifecycle.py +193 -0
  66. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/turn_reducer.py +12 -1
  67. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/update_check.py +3 -1
  68. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/widgets/__init__.py +2 -0
  69. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/widgets/composer.py +8 -2
  70. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/widgets/context_bar.py +2 -1
  71. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/widgets/conversation.py +47 -5
  72. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/widgets/help_panel.py +4 -10
  73. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/widgets/overlay_mixin.py +13 -0
  74. dreadnode-2.0.9/dreadnode/app/tui/widgets/profile_dialog.py +128 -0
  75. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/widgets/status_bar.py +28 -10
  76. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/widgets/throbber.py +2 -2
  77. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/widgets/tool.py +2 -2
  78. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/widgets/tool_progress.py +3 -2
  79. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/widgets/whoami.py +3 -1
  80. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/capabilities/capability.py +29 -0
  81. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/capabilities/loader.py +228 -14
  82. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/capabilities/sync.py +254 -29
  83. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/capabilities/types.py +80 -8
  84. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/core/load.py +13 -13
  85. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/core/log.py +2 -1
  86. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/core/util.py +11 -11
  87. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/datasets/local.py +5 -0
  88. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/generators/generator/base.py +32 -2
  89. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/generators/generator/litellm_.py +98 -2
  90. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/models/local.py +37 -0
  91. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/optimization/backends/gepa.py +1 -0
  92. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/optimization/jobs.py +47 -10
  93. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/optimization/stopping.py +0 -1
  94. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/optimization/study.py +159 -19
  95. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/packaging/loader.py +2 -2
  96. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/packaging/manifest.py +14 -0
  97. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/packaging/oci.py +49 -8
  98. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/packaging/package.py +3 -0
  99. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/packaging/task_validation.py +41 -7
  100. dreadnode-2.0.9/dreadnode/skills/__init__.py +27 -0
  101. dreadnode-2.0.9/dreadnode/skills/creating-capabilities/SKILL.md +154 -0
  102. dreadnode-2.0.9/dreadnode/skills/creating-capabilities/capability-components.md +111 -0
  103. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/storage/storage.py +15 -15
  104. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/tools/ls.py +9 -3
  105. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/tracing/constants.py +3 -0
  106. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/tracing/spans.py +12 -0
  107. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/jobs.py +13 -4
  108. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/transforms/refine.py +31 -0
  109. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/transforms/text.py +4 -0
  110. {dreadnode-2.0.7 → dreadnode-2.0.9}/pyproject.toml +55 -4
  111. dreadnode-2.0.7/dreadnode/app/cli/capability.py +0 -260
  112. dreadnode-2.0.7/dreadnode/app/cli/dataset.py +0 -139
  113. dreadnode-2.0.7/dreadnode/app/cli/evaluation.py +0 -204
  114. dreadnode-2.0.7/dreadnode/app/cli/main.py +0 -346
  115. dreadnode-2.0.7/dreadnode/app/cli/model.py +0 -104
  116. dreadnode-2.0.7/dreadnode/app/cli/runtime.py +0 -52
  117. dreadnode-2.0.7/dreadnode/app/cli/shared.py +0 -771
  118. dreadnode-2.0.7/dreadnode/app/cli/task.py +0 -284
  119. dreadnode-2.0.7/dreadnode/app/server/default-agent/tools/coding.py +0 -968
  120. dreadnode-2.0.7/dreadnode/app/server/default-agent/tools/subagent.py +0 -214
  121. dreadnode-2.0.7/dreadnode/app/server/session.py +0 -284
  122. {dreadnode-2.0.7 → dreadnode-2.0.9}/.gitignore +0 -0
  123. {dreadnode-2.0.7 → dreadnode-2.0.9}/LICENSE +0 -0
  124. {dreadnode-2.0.7 → dreadnode-2.0.9}/README.md +0 -0
  125. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/__init__.py +0 -0
  126. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/__main__.py +0 -0
  127. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/agents/exceptions.py +0 -0
  128. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/agents/format.py +0 -0
  129. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/agents/mcp/__init__.py +0 -0
  130. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/agents/mcp/config.py +0 -0
  131. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/agents/mcp/server.py +0 -0
  132. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/agents/reactions.py +0 -0
  133. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/agents/skills.py +0 -0
  134. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/agents/stopping.py +0 -0
  135. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/__init__.py +0 -0
  136. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/analytics/__init__.py +0 -0
  137. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/analytics/aggregator.py +0 -0
  138. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/analytics/classifier.py +0 -0
  139. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/analytics/compliance.py +0 -0
  140. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/analytics/engine.py +0 -0
  141. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/analytics/recommendations.py +0 -0
  142. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/analytics/types.py +0 -0
  143. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/compliance/__init__.py +0 -0
  144. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/compliance/atlas.py +0 -0
  145. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/compliance/nist.py +0 -0
  146. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/compliance/owasp.py +0 -0
  147. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/compliance/owasp_agentic.py +0 -0
  148. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/compliance/saif.py +0 -0
  149. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/constants.py +0 -0
  150. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/data/__init__.py +0 -0
  151. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/data/assets/audio/adversarial_query.mp3 +0 -0
  152. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/data/assets/image/bomb.jpg +0 -0
  153. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/data/assets/image/meth.png +0 -0
  154. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/data/prompts/adversarial_benchmark_subset.csv +0 -0
  155. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/data/prompts/ai_safety.csv +0 -0
  156. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/data/rubrics/data_exfiltration.yaml +0 -0
  157. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/data/rubrics/goal_hijacking.yaml +0 -0
  158. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/data/rubrics/memory_poisoning.yaml +0 -0
  159. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/data/rubrics/privilege_escalation.yaml +0 -0
  160. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/data/rubrics/rce.yaml +0 -0
  161. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/data/rubrics/scope_creep.yaml +0 -0
  162. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/data/rubrics/tool_chaining.yaml +0 -0
  163. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/data/rubrics/tool_selection_safety.yaml +0 -0
  164. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/data/rubrics/unbounded_agency.yaml +0 -0
  165. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/data/rubrics/web_chatbot_security.yaml +0 -0
  166. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/data/templates/crescendo/variant_1.yaml +0 -0
  167. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/data/templates/crescendo/variant_2.yaml +0 -0
  168. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/data/templates/crescendo/variant_3.yaml +0 -0
  169. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/data/templates/crescendo/variant_4.yaml +0 -0
  170. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/data/templates/crescendo/variant_5.yaml +0 -0
  171. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/events.py +0 -0
  172. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/reporting/__init__.py +0 -0
  173. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/reporting/json_report.py +0 -0
  174. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/reporting/llm_summary.py +0 -0
  175. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/airt/reporting/markdown.py +0 -0
  176. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/__init__.py +0 -0
  177. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/api/__init__.py +0 -0
  178. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/cli/__init__.py +0 -0
  179. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/server/__init__.py +0 -0
  180. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/server/auth.py +0 -0
  181. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/server/utils.py +0 -0
  182. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/__init__.py +0 -0
  183. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/connection.py +0 -0
  184. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/event_contract.py +0 -0
  185. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/runtime_cache.py +0 -0
  186. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/screens/__init__.py +0 -0
  187. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/screens/environments.py +0 -0
  188. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/screens/secrets.py +0 -0
  189. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/screens/traces.py +0 -0
  190. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/screens/workspaces.py +0 -0
  191. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/theme.py +0 -0
  192. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/widgets/agent_dialog.py +0 -0
  193. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/widgets/agent_suggester.py +0 -0
  194. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/widgets/flash.py +0 -0
  195. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/widgets/header_bar.py +0 -0
  196. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/widgets/mention_overlay.py +0 -0
  197. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/widgets/message_queue.py +0 -0
  198. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/widgets/permission_prompt.py +0 -0
  199. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/widgets/prompt_info.py +0 -0
  200. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/widgets/session_sidebar.py +0 -0
  201. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/widgets/skills_dialog.py +0 -0
  202. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/widgets/slash_overlay.py +0 -0
  203. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/widgets/tools_dialog.py +0 -0
  204. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/app/tui/widgets/welcome.py +0 -0
  205. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/capabilities/__init__.py +0 -0
  206. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/capabilities/tool_rules.py +0 -0
  207. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/core/__init__.py +0 -0
  208. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/core/conditions.py +0 -0
  209. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/core/discovery.py +0 -0
  210. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/core/environment.py +0 -0
  211. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/core/exceptions.py +0 -0
  212. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/core/execution.py +0 -0
  213. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/core/hook.py +0 -0
  214. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/core/judge.py +0 -0
  215. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/core/meta/__init__.py +0 -0
  216. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/core/meta/config.py +0 -0
  217. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/core/meta/context.py +0 -0
  218. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/core/meta/hydrate.py +0 -0
  219. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/core/meta/introspect.py +0 -0
  220. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/core/metric.py +0 -0
  221. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/core/object.py +0 -0
  222. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/core/scorer.py +0 -0
  223. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/core/serialization.py +0 -0
  224. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/core/stopping.py +0 -0
  225. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/core/task.py +0 -0
  226. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/core/transforms.py +0 -0
  227. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/core/types/__init__.py +0 -0
  228. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/core/types/audio.py +0 -0
  229. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/core/types/base.py +0 -0
  230. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/core/types/common.py +0 -0
  231. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/core/types/image.py +0 -0
  232. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/core/types/object_3d.py +0 -0
  233. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/core/types/table.py +0 -0
  234. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/core/types/text.py +0 -0
  235. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/core/types/video.py +0 -0
  236. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/datasets/__init__.py +0 -0
  237. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/datasets/dataset.py +0 -0
  238. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/datasets/hf.py +0 -0
  239. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/evaluations/__init__.py +0 -0
  240. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/evaluations/console.py +0 -0
  241. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/evaluations/evaluation.py +0 -0
  242. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/evaluations/events.py +0 -0
  243. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/evaluations/format.py +0 -0
  244. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/evaluations/result.py +0 -0
  245. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/evaluations/sample.py +0 -0
  246. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/generators/__init__.py +0 -0
  247. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/generators/caching.py +0 -0
  248. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/generators/chat.py +0 -0
  249. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/generators/data.py +0 -0
  250. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/generators/exceptions.py +0 -0
  251. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/generators/generator/__init__.py +0 -0
  252. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/generators/generator/http.py +0 -0
  253. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/generators/generator/transformers_.py +0 -0
  254. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/generators/generator/vllm_.py +0 -0
  255. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/generators/message.py +0 -0
  256. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/generators/models.py +0 -0
  257. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/generators/parsing.py +0 -0
  258. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/generators/tokenizer/__init__.py +0 -0
  259. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/generators/tokenizer/base.py +0 -0
  260. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/generators/tokenizer/transformers_.py +0 -0
  261. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/generators/utils.py +0 -0
  262. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/models/__init__.py +0 -0
  263. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/models/hf.py +0 -0
  264. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/models/model.py +0 -0
  265. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/optimization/__init__.py +0 -0
  266. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/optimization/adapters/__init__.py +0 -0
  267. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/optimization/adapters/agent.py +0 -0
  268. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/optimization/api.py +0 -0
  269. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/optimization/backends/__init__.py +0 -0
  270. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/optimization/backends/base.py +0 -0
  271. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/optimization/collectors.py +0 -0
  272. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/optimization/config.py +0 -0
  273. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/optimization/console.py +0 -0
  274. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/optimization/events.py +0 -0
  275. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/optimization/format.py +0 -0
  276. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/optimization/result.py +0 -0
  277. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/optimization/sampler.py +0 -0
  278. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/optimization/sampling.py +0 -0
  279. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/optimization/search.py +0 -0
  280. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/optimization/trial.py +0 -0
  281. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/packaging/__init__.py +0 -0
  282. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/py.typed +0 -0
  283. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/samplers/__init__.py +0 -0
  284. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/samplers/boundary.py +0 -0
  285. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/samplers/fuzzing.py +0 -0
  286. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/samplers/graph.py +0 -0
  287. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/samplers/grid.py +0 -0
  288. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/samplers/image.py +0 -0
  289. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/samplers/mapelites.py +0 -0
  290. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/samplers/optuna.py +0 -0
  291. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/samplers/random.py +0 -0
  292. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/samplers/registry.py +0 -0
  293. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/samplers/strategy.py +0 -0
  294. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/scorers/__init__.py +0 -0
  295. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/scorers/advanced_jailbreak_detection.py +0 -0
  296. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/scorers/agent_security.py +0 -0
  297. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/scorers/agentic.py +0 -0
  298. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/scorers/agentic_workflow.py +0 -0
  299. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/scorers/classification.py +0 -0
  300. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/scorers/consistency.py +0 -0
  301. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/scorers/contains.py +0 -0
  302. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/scorers/cosine_sim.py +0 -0
  303. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/scorers/credentials.py +0 -0
  304. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/scorers/crucible.py +0 -0
  305. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/scorers/documentation_security.py +0 -0
  306. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/scorers/exfiltration_detection.py +0 -0
  307. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/scorers/format.py +0 -0
  308. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/scorers/harm.py +0 -0
  309. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/scorers/ide_security.py +0 -0
  310. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/scorers/image.py +0 -0
  311. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/scorers/json.py +0 -0
  312. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/scorers/judge.py +0 -0
  313. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/scorers/length.py +0 -0
  314. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/scorers/lexical.py +0 -0
  315. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/scorers/mcp_security.py +0 -0
  316. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/scorers/memorization.py +0 -0
  317. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/scorers/multi_agent_security.py +0 -0
  318. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/scorers/pii.py +0 -0
  319. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/scorers/prompt_leak.py +0 -0
  320. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/scorers/readability.py +0 -0
  321. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/scorers/reasoning_security.py +0 -0
  322. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/scorers/sentiment.py +0 -0
  323. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/scorers/similarity.py +0 -0
  324. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/storage/__init__.py +0 -0
  325. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/storage/providers.py +0 -0
  326. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/storage/session_store.py +0 -0
  327. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/tools/__init__.py +0 -0
  328. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/tools/_ripgrep.py +0 -0
  329. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/tools/apply_patch.py +0 -0
  330. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/tools/editing.py +0 -0
  331. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/tools/execute.py +0 -0
  332. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/tools/fetch.py +0 -0
  333. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/tools/glob.py +0 -0
  334. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/tools/grep.py +0 -0
  335. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/tools/interaction.py +0 -0
  336. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/tools/memory.py +0 -0
  337. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/tools/read.py +0 -0
  338. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/tools/task.py +0 -0
  339. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/tools/think.py +0 -0
  340. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/tools/todo.py +0 -0
  341. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/tools/web_search.py +0 -0
  342. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/tools/write.py +0 -0
  343. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/tracing/__init__.py +0 -0
  344. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/tracing/convert.py +0 -0
  345. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/tracing/exporter.py +0 -0
  346. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/tracing/exporters.py +0 -0
  347. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/tracing/span.py +0 -0
  348. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/tracing/trace_converter.py +0 -0
  349. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/__init__.py +0 -0
  350. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/base.py +0 -0
  351. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/dpo.py +0 -0
  352. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/etl/__init__.py +0 -0
  353. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/etl/_common.py +0 -0
  354. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/etl/rl.py +0 -0
  355. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/etl/sft.py +0 -0
  356. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/etl/worlds.py +0 -0
  357. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/events.py +0 -0
  358. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/grpo.py +0 -0
  359. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/ppo.py +0 -0
  360. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/prime.py +0 -0
  361. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/ray/__init__.py +0 -0
  362. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/ray/async_trainer.py +0 -0
  363. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/ray/config.py +0 -0
  364. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/ray/coordinator.py +0 -0
  365. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/ray/distributed.py +0 -0
  366. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/ray/dpo.py +0 -0
  367. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/ray/experience.py +0 -0
  368. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/ray/fsdp2_learner.py +0 -0
  369. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/ray/inference.py +0 -0
  370. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/ray/learner.py +0 -0
  371. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/ray/multi_turn.py +0 -0
  372. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/ray/ppo.py +0 -0
  373. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/ray/reward_model.py +0 -0
  374. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/ray/rollout_env.py +0 -0
  375. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/ray/rollout_worker.py +0 -0
  376. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/ray/sft.py +0 -0
  377. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/ray/trainer.py +0 -0
  378. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/recipes.py +0 -0
  379. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/rewards/__init__.py +0 -0
  380. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/rewards/aggregator.py +0 -0
  381. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/rewards/functions.py +0 -0
  382. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/rewards/scorer_bridge.py +0 -0
  383. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/rewards/shaping.py +0 -0
  384. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/rewards/types.py +0 -0
  385. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/rollouts/__init__.py +0 -0
  386. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/rollouts/adapters.py +0 -0
  387. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/rollouts/orchestrator.py +0 -0
  388. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/rollouts/types.py +0 -0
  389. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/rollouts/worlds.py +0 -0
  390. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/serving/__init__.py +0 -0
  391. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/serving/vllm_client.py +0 -0
  392. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/sft.py +0 -0
  393. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/tinker/__init__.py +0 -0
  394. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/tinker/config.py +0 -0
  395. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/tinker/data.py +0 -0
  396. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/tinker/renderer.py +0 -0
  397. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/tinker/rl.py +0 -0
  398. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/tinker/trainer.py +0 -0
  399. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/tinker_sft.py +0 -0
  400. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/training/utils.py +0 -0
  401. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/transforms/__init__.py +0 -0
  402. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/transforms/advanced_jailbreak.py +0 -0
  403. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/transforms/adversarial_suffix.py +0 -0
  404. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/transforms/agent_skill.py +0 -0
  405. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/transforms/agentic_workflow.py +0 -0
  406. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/transforms/audio.py +0 -0
  407. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/transforms/browser_agent_attacks.py +0 -0
  408. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/transforms/cipher.py +0 -0
  409. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/transforms/constitutional.py +0 -0
  410. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/transforms/document.py +0 -0
  411. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/transforms/documentation_poison.py +0 -0
  412. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/transforms/encoding.py +0 -0
  413. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/transforms/exfiltration.py +0 -0
  414. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/transforms/flip_attack.py +0 -0
  415. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/transforms/guardrail_bypass.py +0 -0
  416. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/transforms/ide_injection.py +0 -0
  417. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/transforms/image.py +0 -0
  418. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/transforms/injection.py +0 -0
  419. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/transforms/json_tools.py +0 -0
  420. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/transforms/language.py +0 -0
  421. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/transforms/logic_bomb.py +0 -0
  422. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/transforms/mcp_attacks.py +0 -0
  423. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/transforms/multi_agent_attacks.py +0 -0
  424. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/transforms/persuasion.py +0 -0
  425. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/transforms/perturbation.py +0 -0
  426. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/transforms/pii_extraction.py +0 -0
  427. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/transforms/pythonic_tools.py +0 -0
  428. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/transforms/rag_poisoning.py +0 -0
  429. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/transforms/reasoning_attacks.py +0 -0
  430. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/transforms/response_steering.py +0 -0
  431. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/transforms/stylistic.py +0 -0
  432. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/transforms/substitution.py +0 -0
  433. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/transforms/swap.py +0 -0
  434. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/transforms/system_prompt_extraction.py +0 -0
  435. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/transforms/video.py +0 -0
  436. {dreadnode-2.0.7 → dreadnode-2.0.9}/dreadnode/transforms/xml_tools.py +0 -0
  437. {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.7
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.2,>=1.80.11
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
@@ -2,7 +2,6 @@ import importlib
2
2
  import typing as t
3
3
 
4
4
  from dreadnode.agents import exceptions
5
- from dreadnode.agents.agent import Agent
6
5
  from dreadnode.agents.tools import (
7
6
  FunctionCall,
8
7
  FunctionDefinition,
@@ -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=10, ge=1)
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.bind(summarizer)( # ty: ignore[unresolved-attribute] # .bind() added by generator framework at runtime
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": str(self.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
- async def summarize_conversation(conversation: str, *, guidance: str = "") -> Summary: # type: ignore[empty-body]
166
- """
167
- 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.
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
- <example>
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
- <summary>
204
- 1. Primary Request and Intent:
205
- [Detailed description]
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
- 2. Key Technical Details:
178
+ 2. Double-check for technical accuracy and completeness, addressing each required element thoroughly.
208
179
 
209
- - [Concept 1]
210
- - [Concept 2]
211
- - [Object 1]
212
- - [...]
180
+ Your summary should include the following sections:
213
181
 
214
- 3. Tool Interactions:
215
-
216
- - [Tool Call 1]: [Description of what the tool did, inputs, outputs]
217
- - [Tool Call 2]: ...
218
- - [...]
219
-
220
- 4. Errors and fixes:
221
-
222
- - [Detailed description of error 1]:
223
- - [How you fixed the error]
224
- - [User feedback on the error if any]
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
- 6. All user messages:
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
- - [Detailed non tool use user message]
233
- - [...]
197
+ {guidance_section}<conversation>
198
+ {conversation}
199
+ </conversation>"""
234
200
 
235
- 7. Pending Tasks:
236
201
 
237
- - [Task 1]
238
- - [Task 2]
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
- 8. Current Work:
242
- [Precise description of current work]
232
+ results = await generator.generate_messages(
233
+ [messages],
234
+ [generator.params or GenerateParams()],
235
+ )
243
236
 
244
- 9. Optional Next Step:
245
- [Optional Next step to take]
237
+ result = results[0]
238
+ if isinstance(result, BaseException):
239
+ raise result
246
240
 
247
- </summary>
248
- </example>
241
+ response_text = result.message.content
249
242
 
250
- Please provide your summary based on the conversation so far, following this structure and ensuring precision and thoroughness in your response.
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
- There may be additional summarization guidance provided. If so, remember to follow these instructions when creating the above summary.
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
- Safe boundaries are after a simple assistant message (no tool calls) or
277
- after the last tool result in a consecutive tool-result sequence.
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
- is_last_tool = message.role == "tool" and (
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.bind(summarizer_model)( # ty: ignore[unresolved-attribute] # .bind() added by generator framework at runtime
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
- data = self._get_entry().get("tokens")
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
- entry = self._get_entry()
78
- entry["tokens"] = tokens.model_dump(mode="json", exclude_none=True)
79
- self._set_entry(entry)
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
- data = self._get_entry().get("client_info")
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
- entry = self._get_entry()
95
- entry["client_info"] = client_info.model_dump(mode="json", exclude_none=True)
96
- self._set_entry(entry)
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 (4xx excluding auth errors). Connectivity errors also
282
- return False (server unreachable via POST, try SSE). Auth and SSL errors
283
- are allowed to propagate — they would affect SSE equally.
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
- try:
382
- if self.transport == "stdio":
383
- self._session = await self._connect_via_stdio(
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
- raise TypeError( # noqa: TRY301 - intentional routing through shared connection failure handling
392
- f"Unsupported transport: {self.transport}. "
393
- "Must be 'stdio' or 'streamable-http'",
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
- await asyncio.wait_for(self.session.initialize(), timeout=self._init_timeout)
397
- await asyncio.wait_for(self._load_tools(), timeout=self._init_timeout)
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
- await self._exit_stack.aclose()
444
- self._session = None
445
- self.tools.clear()
446
- if self._status == MCPStatus.CONNECTED:
447
- self._status = MCPStatus.DISCONNECTED
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
- response = await sub_agent.chat(task)
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(sub_agent.trajectory.steps)}\n"
179
- result += f"**Tokens used:** {sub_agent.trajectory.usage.total_tokens}\n\n"
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(sub_agent.trajectory.steps)} steps")
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}"