dreadnode 2.0.10__tar.gz → 2.0.11__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 (454) hide show
  1. {dreadnode-2.0.10 → dreadnode-2.0.11}/PKG-INFO +1 -1
  2. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/agents/agent.py +131 -0
  3. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/agents/events.py +27 -0
  4. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/agents/hooks.py +4 -75
  5. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/cli/evaluation.py +24 -0
  6. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/cli/shared.py +1 -1
  7. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/tui/app.py +26 -3
  8. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/tui/event_contract.py +9 -0
  9. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/tui/screens/auth.py +6 -0
  10. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/tui/screens/runtimes.py +89 -18
  11. {dreadnode-2.0.10 → dreadnode-2.0.11}/pyproject.toml +1 -1
  12. {dreadnode-2.0.10 → dreadnode-2.0.11}/.gitignore +0 -0
  13. {dreadnode-2.0.10 → dreadnode-2.0.11}/LICENSE +0 -0
  14. {dreadnode-2.0.10 → dreadnode-2.0.11}/README.md +0 -0
  15. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/__init__.py +0 -0
  16. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/__main__.py +0 -0
  17. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/agents/__init__.py +0 -0
  18. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/agents/exceptions.py +0 -0
  19. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/agents/format.py +0 -0
  20. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/agents/mcp/__init__.py +0 -0
  21. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/agents/mcp/auth.py +0 -0
  22. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/agents/mcp/client.py +0 -0
  23. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/agents/mcp/config.py +0 -0
  24. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/agents/mcp/server.py +0 -0
  25. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/agents/reactions.py +0 -0
  26. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/agents/skills.py +0 -0
  27. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/agents/stopping.py +0 -0
  28. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/agents/subagent.py +0 -0
  29. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/agents/tools.py +0 -0
  30. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/agents/trajectory.py +0 -0
  31. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/airt/__init__.py +0 -0
  32. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/airt/analytics/__init__.py +0 -0
  33. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/airt/analytics/aggregator.py +0 -0
  34. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/airt/analytics/classifier.py +0 -0
  35. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/airt/analytics/compliance.py +0 -0
  36. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/airt/analytics/engine.py +0 -0
  37. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/airt/analytics/recommendations.py +0 -0
  38. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/airt/analytics/types.py +0 -0
  39. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/airt/assessment.py +0 -0
  40. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/airt/autodan_turbo.py +0 -0
  41. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/airt/beast.py +0 -0
  42. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/airt/compliance/__init__.py +0 -0
  43. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/airt/compliance/atlas.py +0 -0
  44. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/airt/compliance/nist.py +0 -0
  45. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/airt/compliance/owasp.py +0 -0
  46. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/airt/compliance/owasp_agentic.py +0 -0
  47. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/airt/compliance/saif.py +0 -0
  48. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/airt/constants.py +0 -0
  49. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/airt/crescendo.py +0 -0
  50. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/airt/data/__init__.py +0 -0
  51. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/airt/data/assets/audio/adversarial_query.mp3 +0 -0
  52. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/airt/data/assets/image/bomb.jpg +0 -0
  53. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/airt/data/assets/image/meth.png +0 -0
  54. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/airt/data/prompts/adversarial_benchmark_subset.csv +0 -0
  55. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/airt/data/prompts/ai_safety.csv +0 -0
  56. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/airt/data/rubrics/data_exfiltration.yaml +0 -0
  57. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/airt/data/rubrics/goal_hijacking.yaml +0 -0
  58. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/airt/data/rubrics/memory_poisoning.yaml +0 -0
  59. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/airt/data/rubrics/privilege_escalation.yaml +0 -0
  60. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/airt/data/rubrics/rce.yaml +0 -0
  61. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/airt/data/rubrics/scope_creep.yaml +0 -0
  62. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/airt/data/rubrics/tool_chaining.yaml +0 -0
  63. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/airt/data/rubrics/tool_selection_safety.yaml +0 -0
  64. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/airt/data/rubrics/unbounded_agency.yaml +0 -0
  65. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/airt/data/rubrics/web_chatbot_security.yaml +0 -0
  66. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/airt/data/templates/crescendo/variant_1.yaml +0 -0
  67. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/airt/data/templates/crescendo/variant_2.yaml +0 -0
  68. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/airt/data/templates/crescendo/variant_3.yaml +0 -0
  69. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/airt/data/templates/crescendo/variant_4.yaml +0 -0
  70. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/airt/data/templates/crescendo/variant_5.yaml +0 -0
  71. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/airt/deep_inception.py +0 -0
  72. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/airt/drattack.py +0 -0
  73. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/airt/events.py +0 -0
  74. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/airt/goat.py +0 -0
  75. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/airt/gptfuzzer.py +0 -0
  76. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/airt/image.py +0 -0
  77. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/airt/multimodal.py +0 -0
  78. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/airt/pair.py +0 -0
  79. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/airt/prompt.py +0 -0
  80. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/airt/rainbow.py +0 -0
  81. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/airt/renellm.py +0 -0
  82. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/airt/reporting/__init__.py +0 -0
  83. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/airt/reporting/json_report.py +0 -0
  84. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/airt/reporting/llm_summary.py +0 -0
  85. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/airt/reporting/markdown.py +0 -0
  86. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/airt/tap.py +0 -0
  87. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/__init__.py +0 -0
  88. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/api/__init__.py +0 -0
  89. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/api/client.py +0 -0
  90. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/api/models.py +0 -0
  91. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/cli/__init__.py +0 -0
  92. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/cli/airt.py +0 -0
  93. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/cli/args.py +0 -0
  94. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/cli/capability.py +0 -0
  95. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/cli/dataset.py +0 -0
  96. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/cli/main.py +0 -0
  97. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/cli/model.py +0 -0
  98. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/cli/optimize.py +0 -0
  99. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/cli/runtime.py +0 -0
  100. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/cli/sandbox.py +0 -0
  101. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/cli/task.py +0 -0
  102. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/cli/templates/__init__.py +0 -0
  103. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/cli/templates/init/__init__.py +0 -0
  104. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/cli/templates/init/challenge/Dockerfile +0 -0
  105. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/cli/templates/init/docker-compose.yaml +0 -0
  106. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/cli/templates/init/provision.sh +0 -0
  107. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/cli/templates/init/solution.sh +0 -0
  108. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/cli/templates/init/task-remote.yaml.tmpl +0 -0
  109. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/cli/templates/init/task.yaml.tmpl +0 -0
  110. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/cli/templates/init/teardown.sh +0 -0
  111. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/cli/templates/init/verify.sh +0 -0
  112. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/cli/train.py +0 -0
  113. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/cli/worlds.py +0 -0
  114. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/config.py +0 -0
  115. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/main.py +0 -0
  116. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/model_catalog.py +0 -0
  117. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/print_mode.py +0 -0
  118. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/server/__init__.py +0 -0
  119. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/server/app.py +0 -0
  120. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/server/auth.py +0 -0
  121. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/server/prompt.py +0 -0
  122. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/server/utils.py +0 -0
  123. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/tui/__init__.py +0 -0
  124. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/tui/client.py +0 -0
  125. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/tui/commands.py +0 -0
  126. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/tui/connection.py +0 -0
  127. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/tui/dreadnode.tcss +0 -0
  128. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/tui/model_variants.py +0 -0
  129. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/tui/runtime_cache.py +0 -0
  130. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/tui/screens/__init__.py +0 -0
  131. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/tui/screens/base.py +0 -0
  132. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/tui/screens/capabilities.py +0 -0
  133. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/tui/screens/capability_docs.py +0 -0
  134. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/tui/screens/connection_error.py +0 -0
  135. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/tui/screens/console.py +0 -0
  136. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/tui/screens/environments.py +0 -0
  137. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/tui/screens/evaluations.py +0 -0
  138. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/tui/screens/mcp.py +0 -0
  139. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/tui/screens/model_picker.py +0 -0
  140. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/tui/screens/models.py +0 -0
  141. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/tui/screens/raw_spans.py +0 -0
  142. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/tui/screens/sandboxes.py +0 -0
  143. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/tui/screens/secrets.py +0 -0
  144. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/tui/screens/sessions.py +0 -0
  145. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/tui/screens/theme_showcase.py +0 -0
  146. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/tui/screens/traces.py +0 -0
  147. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/tui/screens/workspaces.py +0 -0
  148. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/tui/spans_reader.py +0 -0
  149. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/tui/theme.py +0 -0
  150. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/tui/turn_lifecycle.py +0 -0
  151. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/tui/turn_reducer.py +0 -0
  152. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/tui/update_check.py +0 -0
  153. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/tui/widgets/__init__.py +0 -0
  154. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/tui/widgets/agent_dialog.py +0 -0
  155. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/tui/widgets/agent_suggester.py +0 -0
  156. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/tui/widgets/composer.py +0 -0
  157. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/tui/widgets/context_bar.py +0 -0
  158. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/tui/widgets/conversation.py +0 -0
  159. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/tui/widgets/flash.py +0 -0
  160. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/tui/widgets/header_bar.py +0 -0
  161. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/tui/widgets/help_panel.py +0 -0
  162. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/tui/widgets/mention_overlay.py +0 -0
  163. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/tui/widgets/message_queue.py +0 -0
  164. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/tui/widgets/overlay_mixin.py +0 -0
  165. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/tui/widgets/permission_prompt.py +0 -0
  166. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/tui/widgets/profile_dialog.py +0 -0
  167. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/tui/widgets/prompt_info.py +0 -0
  168. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/tui/widgets/session_sidebar.py +0 -0
  169. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/tui/widgets/skills_dialog.py +0 -0
  170. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/tui/widgets/slash_overlay.py +0 -0
  171. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/tui/widgets/status_bar.py +0 -0
  172. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/tui/widgets/throbber.py +0 -0
  173. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/tui/widgets/tool.py +0 -0
  174. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/tui/widgets/tool_progress.py +0 -0
  175. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/tui/widgets/tools_dialog.py +0 -0
  176. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/tui/widgets/welcome.py +0 -0
  177. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/app/tui/widgets/whoami.py +0 -0
  178. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/builtin_capabilities/__init__.py +0 -0
  179. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/builtin_capabilities/dreadnode/agents/dreadnode.md +0 -0
  180. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/builtin_capabilities/dreadnode/capability.yaml +0 -0
  181. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/builtin_capabilities/dreadnode/skills/dreadnode-runtime-reference/SKILL.md +0 -0
  182. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/builtin_capabilities/dreadnode/system-prompt.md +0 -0
  183. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/capabilities/__init__.py +0 -0
  184. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/capabilities/capability.py +0 -0
  185. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/capabilities/loader.py +0 -0
  186. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/capabilities/sync.py +0 -0
  187. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/capabilities/tool_rules.py +0 -0
  188. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/capabilities/types.py +0 -0
  189. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/core/__init__.py +0 -0
  190. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/core/conditions.py +0 -0
  191. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/core/discovery.py +0 -0
  192. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/core/environment.py +0 -0
  193. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/core/exceptions.py +0 -0
  194. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/core/execution.py +0 -0
  195. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/core/hook.py +0 -0
  196. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/core/judge.py +0 -0
  197. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/core/load.py +0 -0
  198. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/core/log.py +0 -0
  199. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/core/meta/__init__.py +0 -0
  200. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/core/meta/config.py +0 -0
  201. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/core/meta/context.py +0 -0
  202. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/core/meta/hydrate.py +0 -0
  203. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/core/meta/introspect.py +0 -0
  204. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/core/metric.py +0 -0
  205. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/core/object.py +0 -0
  206. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/core/scorer.py +0 -0
  207. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/core/serialization.py +0 -0
  208. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/core/stopping.py +0 -0
  209. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/core/task.py +0 -0
  210. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/core/transforms.py +0 -0
  211. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/core/types/__init__.py +0 -0
  212. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/core/types/audio.py +0 -0
  213. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/core/types/base.py +0 -0
  214. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/core/types/common.py +0 -0
  215. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/core/types/image.py +0 -0
  216. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/core/types/object_3d.py +0 -0
  217. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/core/types/table.py +0 -0
  218. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/core/types/text.py +0 -0
  219. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/core/types/video.py +0 -0
  220. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/core/util.py +0 -0
  221. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/datasets/__init__.py +0 -0
  222. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/datasets/dataset.py +0 -0
  223. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/datasets/hf.py +0 -0
  224. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/datasets/local.py +0 -0
  225. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/evaluations/__init__.py +0 -0
  226. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/evaluations/console.py +0 -0
  227. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/evaluations/evaluation.py +0 -0
  228. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/evaluations/events.py +0 -0
  229. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/evaluations/format.py +0 -0
  230. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/evaluations/result.py +0 -0
  231. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/evaluations/sample.py +0 -0
  232. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/generators/__init__.py +0 -0
  233. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/generators/caching.py +0 -0
  234. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/generators/chat.py +0 -0
  235. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/generators/data.py +0 -0
  236. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/generators/exceptions.py +0 -0
  237. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/generators/generator/__init__.py +0 -0
  238. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/generators/generator/base.py +0 -0
  239. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/generators/generator/http.py +0 -0
  240. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/generators/generator/litellm_.py +0 -0
  241. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/generators/generator/transformers_.py +0 -0
  242. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/generators/generator/vllm_.py +0 -0
  243. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/generators/message.py +0 -0
  244. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/generators/models.py +0 -0
  245. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/generators/parsing.py +0 -0
  246. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/generators/tokenizer/__init__.py +0 -0
  247. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/generators/tokenizer/base.py +0 -0
  248. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/generators/tokenizer/transformers_.py +0 -0
  249. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/generators/utils.py +0 -0
  250. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/models/__init__.py +0 -0
  251. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/models/hf.py +0 -0
  252. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/models/local.py +0 -0
  253. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/models/model.py +0 -0
  254. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/optimization/__init__.py +0 -0
  255. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/optimization/adapters/__init__.py +0 -0
  256. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/optimization/adapters/agent.py +0 -0
  257. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/optimization/adapters/stack.py +0 -0
  258. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/optimization/api.py +0 -0
  259. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/optimization/backends/__init__.py +0 -0
  260. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/optimization/backends/base.py +0 -0
  261. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/optimization/backends/gepa.py +0 -0
  262. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/optimization/collectors.py +0 -0
  263. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/optimization/config.py +0 -0
  264. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/optimization/console.py +0 -0
  265. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/optimization/events.py +0 -0
  266. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/optimization/format.py +0 -0
  267. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/optimization/jobs.py +0 -0
  268. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/optimization/result.py +0 -0
  269. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/optimization/sampler.py +0 -0
  270. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/optimization/sampling.py +0 -0
  271. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/optimization/search.py +0 -0
  272. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/optimization/stopping.py +0 -0
  273. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/optimization/study.py +0 -0
  274. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/optimization/trial.py +0 -0
  275. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/packaging/__init__.py +0 -0
  276. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/packaging/loader.py +0 -0
  277. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/packaging/manifest.py +0 -0
  278. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/packaging/oci.py +0 -0
  279. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/packaging/package.py +0 -0
  280. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/packaging/task_validation.py +0 -0
  281. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/py.typed +0 -0
  282. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/samplers/__init__.py +0 -0
  283. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/samplers/boundary.py +0 -0
  284. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/samplers/fuzzing.py +0 -0
  285. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/samplers/graph.py +0 -0
  286. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/samplers/grid.py +0 -0
  287. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/samplers/image.py +0 -0
  288. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/samplers/mapelites.py +0 -0
  289. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/samplers/optuna.py +0 -0
  290. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/samplers/random.py +0 -0
  291. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/samplers/registry.py +0 -0
  292. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/samplers/strategy.py +0 -0
  293. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/scorers/__init__.py +0 -0
  294. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/scorers/advanced_jailbreak_detection.py +0 -0
  295. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/scorers/agent_security.py +0 -0
  296. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/scorers/agentic.py +0 -0
  297. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/scorers/agentic_workflow.py +0 -0
  298. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/scorers/classification.py +0 -0
  299. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/scorers/consistency.py +0 -0
  300. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/scorers/contains.py +0 -0
  301. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/scorers/cosine_sim.py +0 -0
  302. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/scorers/credentials.py +0 -0
  303. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/scorers/crucible.py +0 -0
  304. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/scorers/documentation_security.py +0 -0
  305. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/scorers/exfiltration_detection.py +0 -0
  306. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/scorers/format.py +0 -0
  307. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/scorers/harm.py +0 -0
  308. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/scorers/ide_security.py +0 -0
  309. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/scorers/image.py +0 -0
  310. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/scorers/json.py +0 -0
  311. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/scorers/judge.py +0 -0
  312. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/scorers/length.py +0 -0
  313. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/scorers/lexical.py +0 -0
  314. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/scorers/mcp_security.py +0 -0
  315. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/scorers/memorization.py +0 -0
  316. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/scorers/multi_agent_security.py +0 -0
  317. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/scorers/pii.py +0 -0
  318. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/scorers/prompt_leak.py +0 -0
  319. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/scorers/readability.py +0 -0
  320. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/scorers/reasoning_security.py +0 -0
  321. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/scorers/sentiment.py +0 -0
  322. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/scorers/similarity.py +0 -0
  323. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/skills/__init__.py +0 -0
  324. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/skills/creating-capabilities/SKILL.md +0 -0
  325. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/skills/creating-capabilities/capability-components.md +0 -0
  326. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/skills/creating-capabilities/capability-improvement.md +0 -0
  327. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/skills/creating-capabilities/runtime-default-capability.md +0 -0
  328. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/skills/dreadnode-cli/SKILL.md +0 -0
  329. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/skills/dreadnode-cli/references/command-groups.md +0 -0
  330. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/skills/dreadnode-cli/references/tui-crosswalk.md +0 -0
  331. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/storage/__init__.py +0 -0
  332. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/storage/providers.py +0 -0
  333. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/storage/session_store.py +0 -0
  334. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/storage/storage.py +0 -0
  335. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/tools/__init__.py +0 -0
  336. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/tools/_ripgrep.py +0 -0
  337. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/tools/apply_patch.py +0 -0
  338. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/tools/dreadnode_cli.py +0 -0
  339. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/tools/editing.py +0 -0
  340. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/tools/execute.py +0 -0
  341. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/tools/fetch.py +0 -0
  342. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/tools/glob.py +0 -0
  343. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/tools/grep.py +0 -0
  344. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/tools/interaction.py +0 -0
  345. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/tools/ls.py +0 -0
  346. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/tools/memory.py +0 -0
  347. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/tools/read.py +0 -0
  348. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/tools/report.py +0 -0
  349. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/tools/task.py +0 -0
  350. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/tools/think.py +0 -0
  351. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/tools/todo.py +0 -0
  352. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/tools/trajectory_search.py +0 -0
  353. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/tools/web_search.py +0 -0
  354. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/tools/write.py +0 -0
  355. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/tracing/__init__.py +0 -0
  356. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/tracing/constants.py +0 -0
  357. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/tracing/convert.py +0 -0
  358. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/tracing/exporter.py +0 -0
  359. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/tracing/exporters.py +0 -0
  360. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/tracing/span.py +0 -0
  361. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/tracing/spans.py +0 -0
  362. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/tracing/trace_converter.py +0 -0
  363. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/training/__init__.py +0 -0
  364. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/training/base.py +0 -0
  365. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/training/dpo.py +0 -0
  366. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/training/etl/__init__.py +0 -0
  367. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/training/etl/_common.py +0 -0
  368. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/training/etl/rl.py +0 -0
  369. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/training/etl/sft.py +0 -0
  370. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/training/etl/worlds.py +0 -0
  371. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/training/events.py +0 -0
  372. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/training/grpo.py +0 -0
  373. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/training/jobs.py +0 -0
  374. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/training/ppo.py +0 -0
  375. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/training/prime.py +0 -0
  376. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/training/ray/__init__.py +0 -0
  377. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/training/ray/async_trainer.py +0 -0
  378. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/training/ray/config.py +0 -0
  379. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/training/ray/coordinator.py +0 -0
  380. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/training/ray/distributed.py +0 -0
  381. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/training/ray/dpo.py +0 -0
  382. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/training/ray/experience.py +0 -0
  383. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/training/ray/fsdp2_learner.py +0 -0
  384. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/training/ray/inference.py +0 -0
  385. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/training/ray/learner.py +0 -0
  386. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/training/ray/multi_turn.py +0 -0
  387. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/training/ray/ppo.py +0 -0
  388. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/training/ray/reward_model.py +0 -0
  389. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/training/ray/rollout_env.py +0 -0
  390. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/training/ray/rollout_worker.py +0 -0
  391. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/training/ray/sft.py +0 -0
  392. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/training/ray/trainer.py +0 -0
  393. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/training/recipes.py +0 -0
  394. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/training/rewards/__init__.py +0 -0
  395. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/training/rewards/aggregator.py +0 -0
  396. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/training/rewards/functions.py +0 -0
  397. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/training/rewards/scorer_bridge.py +0 -0
  398. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/training/rewards/shaping.py +0 -0
  399. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/training/rewards/types.py +0 -0
  400. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/training/rollouts/__init__.py +0 -0
  401. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/training/rollouts/adapters.py +0 -0
  402. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/training/rollouts/orchestrator.py +0 -0
  403. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/training/rollouts/types.py +0 -0
  404. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/training/rollouts/worlds.py +0 -0
  405. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/training/serving/__init__.py +0 -0
  406. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/training/serving/vllm_client.py +0 -0
  407. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/training/sft.py +0 -0
  408. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/training/tinker/__init__.py +0 -0
  409. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/training/tinker/config.py +0 -0
  410. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/training/tinker/data.py +0 -0
  411. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/training/tinker/renderer.py +0 -0
  412. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/training/tinker/rl.py +0 -0
  413. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/training/tinker/trainer.py +0 -0
  414. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/training/tinker_sft.py +0 -0
  415. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/training/utils.py +0 -0
  416. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/transforms/__init__.py +0 -0
  417. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/transforms/advanced_jailbreak.py +0 -0
  418. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/transforms/adversarial_suffix.py +0 -0
  419. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/transforms/agent_skill.py +0 -0
  420. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/transforms/agentic_workflow.py +0 -0
  421. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/transforms/audio.py +0 -0
  422. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/transforms/browser_agent_attacks.py +0 -0
  423. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/transforms/cipher.py +0 -0
  424. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/transforms/constitutional.py +0 -0
  425. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/transforms/document.py +0 -0
  426. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/transforms/documentation_poison.py +0 -0
  427. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/transforms/encoding.py +0 -0
  428. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/transforms/exfiltration.py +0 -0
  429. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/transforms/flip_attack.py +0 -0
  430. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/transforms/guardrail_bypass.py +0 -0
  431. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/transforms/ide_injection.py +0 -0
  432. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/transforms/image.py +0 -0
  433. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/transforms/injection.py +0 -0
  434. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/transforms/json_tools.py +0 -0
  435. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/transforms/language.py +0 -0
  436. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/transforms/logic_bomb.py +0 -0
  437. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/transforms/mcp_attacks.py +0 -0
  438. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/transforms/multi_agent_attacks.py +0 -0
  439. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/transforms/persuasion.py +0 -0
  440. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/transforms/perturbation.py +0 -0
  441. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/transforms/pii_extraction.py +0 -0
  442. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/transforms/pythonic_tools.py +0 -0
  443. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/transforms/rag_poisoning.py +0 -0
  444. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/transforms/reasoning_attacks.py +0 -0
  445. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/transforms/refine.py +0 -0
  446. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/transforms/response_steering.py +0 -0
  447. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/transforms/stylistic.py +0 -0
  448. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/transforms/substitution.py +0 -0
  449. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/transforms/swap.py +0 -0
  450. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/transforms/system_prompt_extraction.py +0 -0
  451. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/transforms/text.py +0 -0
  452. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/transforms/video.py +0 -0
  453. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/transforms/xml_tools.py +0 -0
  454. {dreadnode-2.0.10 → dreadnode-2.0.11}/dreadnode/version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dreadnode
3
- Version: 2.0.10
3
+ Version: 2.0.11
4
4
  Summary: Dreadnode SDK
5
5
  Project-URL: Homepage, https://dreadnode.io
6
6
  Project-URL: Documentation, https://docs.dreadnode.io
@@ -1,5 +1,8 @@
1
1
  import asyncio
2
+ import contextlib
2
3
  import inspect
4
+ import random
5
+ import time
3
6
  import typing as t
4
7
  from contextlib import AsyncExitStack, asynccontextmanager, suppress
5
8
  from copy import deepcopy
@@ -30,6 +33,7 @@ from dreadnode.agents.events import (
30
33
  GenerationContent,
31
34
  GenerationEnd,
32
35
  GenerationError,
36
+ GenerationRetry,
33
37
  GenerationStart,
34
38
  GenerationStep,
35
39
  ReactStep,
@@ -80,6 +84,45 @@ def _raise_exception(error: BaseException) -> t.NoReturn:
80
84
  raise error
81
85
 
82
86
 
87
+ _TRANSIENT_LITELLM_EXCEPTION_NAMES: tuple[str, ...] = (
88
+ "RateLimitError",
89
+ "Timeout",
90
+ "APIConnectionError",
91
+ "APIConnectionTimeoutError",
92
+ "ServiceUnavailableError",
93
+ "InternalServerError",
94
+ "BadGatewayError",
95
+ "APIError",
96
+ )
97
+
98
+
99
+ def _is_transient_api_error(error: BaseException) -> bool:
100
+ """Classify an error as a transient LLM API failure worth retrying.
101
+
102
+ Matches an explicit allow-list of ``litellm.exceptions`` classes that
103
+ represent recoverable conditions: rate limits, timeouts, connection
104
+ failures, and 5xx responses. Notably **does not** match
105
+ ``BadRequestError``, ``AuthenticationError``,
106
+ ``ContextWindowExceededError`` (handled by overflow recovery), or other
107
+ deterministic client errors.
108
+
109
+ The allow-list is walked dynamically because ``litellm.exceptions`` does
110
+ not expose a single common ancestor for its transient exceptions.
111
+ Returns ``False`` if ``litellm`` is not importable.
112
+ """
113
+ with contextlib.suppress(ImportError):
114
+ import litellm.exceptions as _litellm_exc
115
+
116
+ classes = tuple(
117
+ cls
118
+ for name in _TRANSIENT_LITELLM_EXCEPTION_NAMES
119
+ if (cls := getattr(_litellm_exc, name, None)) is not None
120
+ )
121
+ if classes and isinstance(error, classes):
122
+ return True
123
+ return False
124
+
125
+
83
126
  class Agent(Executor[AgentEvent, Trajectory]):
84
127
  """
85
128
  Agent abstraction for applying tools, event logic, and message state to LLM generation.
@@ -128,6 +171,14 @@ class Agent(Executor[AgentEvent, Trajectory]):
128
171
  """Extra parameters merged into GenerateParams for every generation (e.g. thinking config)."""
129
172
  working_dir: Path | None = Config(default=None)
130
173
  """Working directory used for tool output offloading and other IO."""
174
+ backoff_max_tries: int = Config(default=8, ge=0)
175
+ """Maximum retries on transient LLM API errors per step. ``0`` disables retry."""
176
+ backoff_max_time: float = Config(default=300.0, ge=0)
177
+ """Maximum total seconds to spend retrying transient LLM API errors per step."""
178
+ backoff_base_factor: float = Config(default=1.0, ge=0)
179
+ """Base factor for exponential backoff: wait = base_factor * 2 ** (attempt - 1)."""
180
+ backoff_jitter: bool = Config(default=True)
181
+ """Whether to add up to ``backoff_base_factor`` seconds of random jitter to each wait."""
131
182
 
132
183
  # Private state
133
184
  _generator: Generator | None = PrivateAttr(None, init=False)
@@ -381,6 +432,49 @@ class Agent(Executor[AgentEvent, Trajectory]):
381
432
 
382
433
  raise winning_reaction
383
434
 
435
+ def _try_backoff(
436
+ self,
437
+ error: BaseException,
438
+ *,
439
+ tries: int,
440
+ start_time: float,
441
+ ) -> float | None:
442
+ """Compute a retry sleep for a transient LLM API error.
443
+
444
+ Returns the sleep duration in seconds if the caller should retry, or
445
+ ``None`` if the error is not transient, tries are exhausted, or the
446
+ per-step time budget has been spent. Caller owns sleeping and emitting
447
+ the ``GenerationRetry`` event. Backoff is agent-owned at the error
448
+ site, mirroring ``_try_overflow_recovery`` — no step is consumed and
449
+ no hook indirection is involved.
450
+ """
451
+ if self.backoff_max_tries <= 0:
452
+ return None
453
+
454
+ if not _is_transient_api_error(error):
455
+ return None
456
+
457
+ if tries >= self.backoff_max_tries:
458
+ logger.warning("Backoff aborted: max tries ({}) exceeded.", self.backoff_max_tries)
459
+ return None
460
+
461
+ remaining = self.backoff_max_time - (time.monotonic() - start_time)
462
+ if remaining <= 0:
463
+ logger.warning("Backoff aborted: max time ({:.2f}s) exceeded.", self.backoff_max_time)
464
+ return None
465
+
466
+ seconds = self.backoff_base_factor * (2**tries)
467
+ if self.backoff_jitter:
468
+ seconds += random.uniform(0, self.backoff_base_factor)
469
+ if seconds > remaining:
470
+ logger.warning(
471
+ "Backoff aborted: next sleep ({:.2f}s) would exceed remaining budget ({:.2f}s).",
472
+ seconds,
473
+ remaining,
474
+ )
475
+ return None
476
+ return seconds
477
+
384
478
  async def _try_overflow_recovery(
385
479
  self,
386
480
  error: BaseException,
@@ -628,6 +722,43 @@ class Agent(Executor[AgentEvent, Trajectory]):
628
722
 
629
723
  step_chat = await self._generate(messages)
630
724
 
725
+ # In-place transient-error backoff — rate limits and
726
+ # other litellm.APIError failures get retried at the
727
+ # error site with exponential backoff. No step budget
728
+ # consumed; clients observe GenerationRetry events
729
+ # rather than a spurious terminal GenerationError.
730
+ backoff_tries = 0
731
+ backoff_started = time.monotonic()
732
+ while step_chat.failed and step_chat.error:
733
+ wait = self._try_backoff(
734
+ step_chat.error,
735
+ tries=backoff_tries,
736
+ start_time=backoff_started,
737
+ )
738
+ if wait is None:
739
+ break
740
+ backoff_tries += 1
741
+ logger.warning(
742
+ "Backing off {:.2f}s (try {}/{})",
743
+ wait,
744
+ backoff_tries,
745
+ self.backoff_max_tries,
746
+ )
747
+ retry_event = GenerationRetry(
748
+ agent_id=self.agent_id,
749
+ agent_name=self.name,
750
+ step=step_count,
751
+ attempt=backoff_tries,
752
+ max_attempts=self.backoff_max_tries,
753
+ wait_seconds=wait,
754
+ error_type=type(step_chat.error).__name__,
755
+ error_message=str(step_chat.error),
756
+ )
757
+ async for event in self._dispatch(retry_event):
758
+ yield event
759
+ await asyncio.sleep(wait)
760
+ step_chat = await self._generate(messages)
761
+
631
762
  # In-place overflow recovery — at most once per step,
632
763
  # no step budget consumed, preserves step_count for
633
764
  # user_message inclusion in trajectory.
@@ -311,6 +311,32 @@ class CompactionEvent(AgentEvent):
311
311
  }
312
312
 
313
313
 
314
+ class GenerationRetry(AgentEvent):
315
+ """Lifecycle event: the agent is about to sleep and retry a failed generation.
316
+
317
+ Emitted by the agent loop when a transient LLM API error (rate limit, etc.)
318
+ is recovered in place via ``Agent._try_backoff``. This is a lifecycle signal
319
+ only — it does not consume a step or land in the trajectory.
320
+ """
321
+
322
+ step: int
323
+ attempt: int
324
+ max_attempts: int
325
+ wait_seconds: float
326
+ error_type: str
327
+ error_message: str
328
+
329
+ def _get_data(self) -> dict[str, t.Any]:
330
+ return {
331
+ "step": self.step,
332
+ "attempt": self.attempt,
333
+ "max_attempts": self.max_attempts,
334
+ "wait_seconds": self.wait_seconds,
335
+ "error_type": self.error_type,
336
+ "error_message": self.error_message,
337
+ }
338
+
339
+
314
340
  class AgentStalled(AgentEvent):
315
341
  """Event: The agent is stalled and there are no tool calls, or stop condition).
316
342
 
@@ -1017,6 +1043,7 @@ EVENT_TYPES: dict[str, type[AgentEvent]] = {
1017
1043
  "GenerationStep": GenerationStep,
1018
1044
  "GenerationContent": GenerationContent,
1019
1045
  "GenerationError": GenerationError,
1046
+ "GenerationRetry": GenerationRetry,
1020
1047
  "ReactStep": ReactStep,
1021
1048
  "UserInputRequired": UserInputRequired,
1022
1049
  "Heartbeat": Heartbeat,
@@ -1,19 +1,15 @@
1
- """Core agent hooks: backoff, metrics, and summarization.
1
+ """Optional agent hooks: tool metrics and conversation summarization.
2
2
 
3
- These hooks are included by default in every agent created by the server.
3
+ These hooks are opt-in users register them explicitly on an ``Agent`` via
4
+ the ``hooks=`` constructor argument. Transient-error backoff is handled inline
5
+ by the agent loop (see ``Agent._try_backoff``) and is not a hook.
4
6
  """
5
7
 
6
- import asyncio
7
8
  import contextlib
8
- import random
9
- import time
10
9
  import typing as t
11
10
  from dataclasses import dataclass
12
11
 
13
- from loguru import logger
14
-
15
12
  from dreadnode.agents.events import (
16
- AgentError,
17
13
  AgentEvent,
18
14
  AgentStep,
19
15
  ToolEnd,
@@ -28,73 +24,6 @@ if t.TYPE_CHECKING:
28
24
  from datetime import datetime
29
25
 
30
26
 
31
- # =============================================================================
32
- # Backoff
33
- # =============================================================================
34
-
35
-
36
- def _make_backoff_hook(
37
- exception_types: tuple[type[Exception], ...],
38
- *,
39
- max_tries: int = 8,
40
- max_time: float = 300.0,
41
- base_factor: float = 1.0,
42
- jitter: bool = True,
43
- ):
44
- """Create a backoff hook for specific exception types."""
45
- tries = 0
46
- start_time: float | None = None
47
- last_step_seen = -1
48
-
49
- @hook(AgentEvent)
50
- async def backoff_hook(event: AgentEvent) -> Reaction | None:
51
- nonlocal tries, start_time, last_step_seen
52
-
53
- if isinstance(event, AgentStep):
54
- if event.step > last_step_seen:
55
- tries = 0
56
- start_time = None
57
- last_step_seen = event.step
58
- return None
59
-
60
- if not isinstance(event, AgentError) or not isinstance(event.error, exception_types):
61
- return None
62
-
63
- if start_time is None:
64
- start_time = time.monotonic()
65
-
66
- if tries >= max_tries:
67
- logger.warning(f"Backoff aborted: max tries ({max_tries}) exceeded.")
68
- return None
69
-
70
- if (time.monotonic() - start_time) >= max_time:
71
- logger.warning(f"Backoff aborted: max time ({max_time:.2f}s) exceeded.")
72
- return None
73
-
74
- tries += 1
75
- seconds = base_factor * (2 ** (tries - 1))
76
- if jitter:
77
- seconds += random.uniform(0, base_factor)
78
-
79
- logger.warning(f"Backing off {seconds:.2f}s (try {tries}/{max_tries})")
80
- await asyncio.sleep(seconds)
81
- return Retry()
82
-
83
- return backoff_hook
84
-
85
-
86
- # Pre-instantiated for common LLM rate limit errors
87
- backoff_on_ratelimit: Hook | None = None
88
- try:
89
- import litellm.exceptions
90
-
91
- backoff_on_ratelimit = _make_backoff_hook(
92
- (litellm.exceptions.RateLimitError, litellm.exceptions.APIError),
93
- )
94
- except ImportError:
95
- pass
96
-
97
-
98
27
  # =============================================================================
99
28
  # Tool Metrics
100
29
  # =============================================================================
@@ -1181,6 +1181,30 @@ def get_sample(
1181
1181
  console.print()
1182
1182
  console.print(f"{_label('Error')}[red]{error}[/red]")
1183
1183
 
1184
+ # Phase-aware provisioning failure — written by the eval worker when the
1185
+ # env-sandbox / runtime-sandbox phases fail outside of a task-authored
1186
+ # provision script. Surfaces which phase stalled + elapsed, so operators
1187
+ # don't have to open Logfire for every timeout (ENG-6249).
1188
+ provision_failure = dreadnode_meta.get("provision_failure") or {}
1189
+ if provision_failure:
1190
+ console.print()
1191
+ phase_raw = str(provision_failure.get("phase") or "unknown")
1192
+ phase_label = {
1193
+ "env_sandbox": "env sandbox",
1194
+ "provision_script": "provision script",
1195
+ "runtime_sandbox": "runtime sandbox",
1196
+ }.get(phase_raw, phase_raw)
1197
+ elapsed_ms = provision_failure.get("elapsed_ms") or 0
1198
+ elapsed_s = int(elapsed_ms) // 1000
1199
+ timed_out = bool(provision_failure.get("timed_out"))
1200
+ verb = "timed out provisioning" if timed_out else "failed to provision"
1201
+ console.print(f"{_label('Provisioning')}[red]{verb} {phase_label} after {elapsed_s}s[/red]")
1202
+ if not timed_out:
1203
+ cause_type = provision_failure.get("cause_type") or ""
1204
+ cause_message = provision_failure.get("cause_message") or ""
1205
+ if cause_type or cause_message:
1206
+ console.print(f"{_label('Cause')}[dim]{cause_type}: {cause_message}[/dim]")
1207
+
1184
1208
  # Verification result
1185
1209
  result = payload.get("result_json")
1186
1210
  if result:
@@ -54,7 +54,7 @@ def print_link(profile_url: str, path: str, **query: str) -> None:
54
54
  qs = f"?{urlencode(query)}" if query else ""
55
55
  url = f"{root}/{path.lstrip('/')}{qs}"
56
56
  console.print()
57
- console.print(f" [dim]→[/dim] [u][link={url}]View on web[/link][/u]")
57
+ console.print(f" [dim]→[/dim] View on web: [u][link={url}]{url}[/link][/u]")
58
58
 
59
59
 
60
60
  # ---------------------------------------------------------------------------
@@ -279,7 +279,6 @@ class DreadnodeTextualApp(App[None]):
279
279
  Binding("ctrl+e", "open_evaluations", "Evals", show=False),
280
280
  Binding("f5", "open_console", "Console", show=False),
281
281
  Binding("ctrl+a", "select_agent", "Agent", show=False),
282
- Binding("f9", "run_update", "Update", show=False),
283
282
  Binding("ctrl+k", "select_model", "Model", show=False),
284
283
  Binding("ctrl+shift+k", "cycle_effort", "Effort", show=False),
285
284
  Binding("ctrl+n", "new_session", "New", show=False),
@@ -1647,7 +1646,9 @@ class DreadnodeTextualApp(App[None]):
1647
1646
 
1648
1647
  resolved_url = server_url or self._server_url
1649
1648
  update_banner = (
1650
- f"Update available: v{VERSION} \u2192 v{self.update_available} \u2014 press F9 to update now, or /update after login"
1649
+ "Update available: "
1650
+ f"v{VERSION} \u2192 v{self.update_available} \u2014 "
1651
+ "press U to update now, or /update after login"
1651
1652
  if self.update_available
1652
1653
  else None
1653
1654
  )
@@ -1755,7 +1756,11 @@ class DreadnodeTextualApp(App[None]):
1755
1756
 
1756
1757
  screen = self.screen
1757
1758
  if isinstance(screen, AuthModal):
1758
- banner = f"Update available: v{VERSION} \u2192 v{info.latest} \u2014 press F9 to update now, or /update after login"
1759
+ banner = (
1760
+ "Update available: "
1761
+ f"v{VERSION} \u2192 v{info.latest} \u2014 "
1762
+ "press U to update now, or /update after login"
1763
+ )
1759
1764
  screen.query_one("#auth-update-banner", Static).update(f"[bold yellow]{banner}[/]")
1760
1765
  except Exception:
1761
1766
  logger.opt(exception=True).debug("Failed to update auth modal banner")
@@ -2880,6 +2885,24 @@ class DreadnodeTextualApp(App[None]):
2880
2885
  )
2881
2886
  return
2882
2887
 
2888
+ if event_type == "generation_retry":
2889
+ data = (normalized.raw or {}).get("data", {})
2890
+ attempt = data.get("attempt", 0)
2891
+ max_attempts = data.get("max_attempts", 0)
2892
+ wait_seconds = float(data.get("wait_seconds", 0.0) or 0.0)
2893
+ err_type = normalized.error_type or "error"
2894
+ err_msg = normalized.error_text or ""
2895
+ detail = f": {err_msg}" if err_msg else ""
2896
+ label = (
2897
+ f"{err_type} — retrying in {wait_seconds:.1f}s "
2898
+ f"(attempt {attempt}/{max_attempts}){detail}"
2899
+ )
2900
+ self._append_transcript(
2901
+ TranscriptEntry(kind="system", title="retry", body=label),
2902
+ session_id,
2903
+ )
2904
+ return
2905
+
2883
2906
  if event_type == "agent_stalled":
2884
2907
  logger.warning("Agent stalled: {}", normalized.error_text)
2885
2908
  self._append_transcript(
@@ -241,6 +241,15 @@ def normalize_event(raw: dict[str, t.Any], session_id: str) -> NormalizedEvent:
241
241
  prompt_text=_as_str(payload.get("compaction_status")),
242
242
  **base,
243
243
  )
244
+ elif event_type == "generationretry":
245
+ error_type = _as_str(payload.get("error_type"))
246
+ error_message = _as_str(payload.get("error_message"))
247
+ normalized = NormalizedEvent(
248
+ type="generation_retry",
249
+ error_type=error_type,
250
+ error_text=error_message,
251
+ **base,
252
+ )
244
253
  elif event_type == "agentstalled":
245
254
  normalized = NormalizedEvent(
246
255
  type="agent_stalled",
@@ -68,6 +68,7 @@ class AuthModal(ModalScreen[Profile | None]):
68
68
  BINDINGS: t.ClassVar[list[Binding]] = [
69
69
  Binding("escape", "escape", "Back / Cancel", show=False, priority=True),
70
70
  Binding("ctrl+c", "cancel", "Cancel", show=False),
71
+ Binding("u", "run_update", "Update", show=False),
71
72
  Binding("up", "move_selection(-1)", "Up", show=False),
72
73
  Binding("down", "move_selection(1)", "Down", show=False),
73
74
  Binding("enter", "confirm", "Confirm", show=False),
@@ -165,6 +166,11 @@ class AuthModal(ModalScreen[Profile | None]):
165
166
  self._cancel_auth_workers()
166
167
  self.dismiss(None)
167
168
 
169
+ def action_run_update(self) -> None:
170
+ handler = getattr(self.app, "action_run_update", None)
171
+ if callable(handler):
172
+ handler()
173
+
168
174
  def action_escape(self) -> None:
169
175
  if self._active_view == "method":
170
176
  self.action_cancel()
@@ -292,6 +292,12 @@ class RuntimeScreen(DreadnodeScreen):
292
292
  self._current_actions: list[tuple[str, str]] = []
293
293
  self._logs_content: str | None = None
294
294
 
295
+ # In-flight action tracking — blocks double-clicks and drives the
296
+ # "Starting..." active state in the detail view. Mirrors the
297
+ # pending-action pattern in capabilities.py.
298
+ self._pending_action_runtime_id: str | None = None
299
+ self._pending_action_label: str | None = None
300
+
295
301
  # ── Connected runtime helpers ────────────────────────────────────────
296
302
 
297
303
  @property
@@ -484,7 +490,7 @@ class RuntimeScreen(DreadnodeScreen):
484
490
 
485
491
  def _quick_start(self) -> None:
486
492
  """Start directly from list — skip detail view for idle runtimes."""
487
- if self._is_app_busy():
493
+ if self._is_app_busy() or self._is_action_pending():
488
494
  return
489
495
  selected = self._selected_list_runtime()
490
496
  if not selected:
@@ -694,6 +700,10 @@ class RuntimeScreen(DreadnodeScreen):
694
700
  is_connected=is_connected,
695
701
  is_any_remote=self._is_any_remote,
696
702
  )
703
+ # While an action is in flight for this runtime, replace the action
704
+ # list with a single disabled noop showing progress ("Starting...").
705
+ if runtime_id == self._pending_action_runtime_id and self._pending_action_label:
706
+ actions = [("noop", self._pending_action_label)]
697
707
  self._current_actions = actions
698
708
 
699
709
  for i, (action_id, label) in enumerate(actions):
@@ -701,7 +711,9 @@ class RuntimeScreen(DreadnodeScreen):
701
711
  text.append(" ❯ ", style=f"bold {ACCENT}")
702
712
  else:
703
713
  text.append(" ")
704
- if action_id in ("reset",):
714
+ if action_id == "noop":
715
+ text.append(f"{label}\n", style=FG_FAINTEST)
716
+ elif action_id in ("reset",):
705
717
  text.append(f"{label}\n", style=ERROR)
706
718
  elif action_id in ("connect", "start", "resume"):
707
719
  text.append(f"{label}\n", style=INFO)
@@ -889,7 +901,9 @@ class RuntimeScreen(DreadnodeScreen):
889
901
  # ── Action execution ─────────────────────────────────────────────────
890
902
 
891
903
  def _execute_action(self, action_id: str) -> None:
892
- if self._is_app_busy():
904
+ if action_id == "noop":
905
+ return
906
+ if self._is_app_busy() or self._is_action_pending():
893
907
  return
894
908
  runtime = self._selected_runtime
895
909
  if not runtime:
@@ -922,20 +936,55 @@ class RuntimeScreen(DreadnodeScreen):
922
936
  return True
923
937
  return False
924
938
 
939
+ def _is_action_pending(self) -> bool:
940
+ """True if a runtime action is in flight — blocks new actions."""
941
+ if self._pending_action_runtime_id is not None:
942
+ self.notify("Action already in progress", title="Runtimes", severity="warning")
943
+ return True
944
+ return False
945
+
946
+ def _show_action_notice(self, runtime_id: str, verb: str) -> None:
947
+ """Mark a runtime action as in flight. Re-renders the detail view
948
+ so the action list shows a disabled `{verb}...` entry."""
949
+ self._pending_action_runtime_id = runtime_id
950
+ self._pending_action_label = f"{verb}..."
951
+ if self.is_mounted:
952
+ self._render_current_view()
953
+
954
+ def _clear_pending_action(self, runtime_id: str | None = None) -> None:
955
+ """Clear the pending action marker. When ``runtime_id`` is provided
956
+ and doesn't match the tracked id, this is a no-op — avoids one
957
+ worker clearing another's pending state."""
958
+ if (
959
+ runtime_id is not None
960
+ and self._pending_action_runtime_id is not None
961
+ and self._pending_action_runtime_id != runtime_id
962
+ ):
963
+ return
964
+ had_pending = self._pending_action_runtime_id is not None
965
+ self._pending_action_runtime_id = None
966
+ self._pending_action_label = None
967
+ if had_pending and self.is_mounted:
968
+ self._render_current_view()
969
+
925
970
  # ── Runtime actions ──────────────────────────────────────────────────
926
971
 
927
- @work(exclusive=True, group="runtime-action")
972
+ @work(group="runtime-action")
928
973
  async def _do_pause(self, runtime_id: str) -> None:
974
+ self._show_action_notice(runtime_id, "Pausing")
929
975
  self.notify(f"Pausing {runtime_id[:8]}…", title="Runtimes", severity="warning")
930
976
  try:
931
977
  await asyncio.to_thread(self._api.pause_runtime, self._org, self._workspace, runtime_id)
932
978
  self.notify(f"Paused {runtime_id[:8]}", title="Runtimes", severity="information")
933
979
  except Exception as exc:
934
980
  self.notify(f"Pause failed: {exc}", title="Runtimes", severity="error")
981
+ finally:
982
+ self._clear_pending_action(runtime_id)
935
983
  self._load_data()
936
984
 
937
- @work(exclusive=True, group="runtime-action")
985
+ @work(group="runtime-action")
938
986
  async def _do_resume(self, runtime_id: str) -> None:
987
+ self._show_action_notice(runtime_id, "Resuming")
939
988
  self.notify(f"Resuming {runtime_id[:8]}…", title="Runtimes", severity="information")
940
989
  try:
941
990
  await asyncio.to_thread(
@@ -944,10 +993,13 @@ class RuntimeScreen(DreadnodeScreen):
944
993
  self.notify(f"Resumed {runtime_id[:8]}", title="Runtimes", severity="information")
945
994
  except Exception as exc:
946
995
  self.notify(f"Resume failed: {exc}", title="Runtimes", severity="error")
996
+ finally:
997
+ self._clear_pending_action(runtime_id)
947
998
  self._load_data()
948
999
 
949
- @work(exclusive=True, group="runtime-action")
1000
+ @work(group="runtime-action")
950
1001
  async def _do_reset(self, runtime_id: str) -> None:
1002
+ self._show_action_notice(runtime_id, "Resetting")
951
1003
  self.notify(f"Resetting {runtime_id[:8]}…", title="Runtimes", severity="warning")
952
1004
  try:
953
1005
  if (
@@ -964,10 +1016,13 @@ class RuntimeScreen(DreadnodeScreen):
964
1016
  self.notify(f"Reset {runtime_id[:8]}", title="Runtimes", severity="information")
965
1017
  except Exception as exc:
966
1018
  self.notify(f"Reset failed: {exc}", title="Runtimes", severity="error")
1019
+ finally:
1020
+ self._clear_pending_action(runtime_id)
967
1021
  self._load_data()
968
1022
 
969
- @work(exclusive=True, group="runtime-action")
1023
+ @work(group="runtime-action")
970
1024
  async def _do_keepalive(self, runtime_id: str) -> None:
1025
+ self._show_action_notice(runtime_id, "Extending")
971
1026
  self.notify(f"Extending {runtime_id[:8]}…", title="Runtimes", severity="information")
972
1027
  try:
973
1028
  await asyncio.to_thread(
@@ -984,10 +1039,22 @@ class RuntimeScreen(DreadnodeScreen):
984
1039
  )
985
1040
  except Exception as exc:
986
1041
  self.notify(f"Keepalive failed: {exc}", title="Runtimes", severity="error")
1042
+ finally:
1043
+ self._clear_pending_action(runtime_id)
987
1044
  self._load_data()
988
1045
 
989
1046
  def _cache_start_result(self, runtime_id: str, result: dict[str, t.Any]) -> bool:
990
- """Cache connection info from a start_runtime response. Returns True if cached."""
1047
+ """Cache connection info from a start_runtime response. Returns True if cached.
1048
+
1049
+ The server only returns ``sandbox_token`` on the request that actually
1050
+ provisions the sandbox. Subsequent /start calls for a still-live
1051
+ sandbox return ``sandbox_token=None`` (the plaintext token is never
1052
+ persisted). We must NOT overwrite the cache in that case — doing so
1053
+ blows away a valid token from our original successful start and
1054
+ leaves us unable to reconnect. If no token is returned, leave the
1055
+ cache alone; ``_do_connect`` handles staleness separately by
1056
+ comparing ``provider_sandbox_id``.
1057
+ """
991
1058
  from dreadnode.app.tui.runtime_cache import RuntimeTokenCache
992
1059
 
993
1060
  sandbox_token = result.get("sandbox_token")
@@ -1003,18 +1070,20 @@ class RuntimeScreen(DreadnodeScreen):
1003
1070
  provider_sandbox_id or "null",
1004
1071
  )
1005
1072
 
1006
- if sandbox_url and provider_sandbox_id:
1007
- RuntimeTokenCache().put(
1008
- runtime_id,
1009
- sandbox_url=sandbox_url,
1010
- token=sandbox_token or "",
1011
- provider_sandbox_id=provider_sandbox_id,
1012
- )
1013
- return True
1014
- return False
1073
+ if not sandbox_token or not sandbox_url or not provider_sandbox_id:
1074
+ return False
1015
1075
 
1016
- @work(exclusive=True, group="runtime-action")
1076
+ RuntimeTokenCache().put(
1077
+ runtime_id,
1078
+ sandbox_url=sandbox_url,
1079
+ token=sandbox_token,
1080
+ provider_sandbox_id=provider_sandbox_id,
1081
+ )
1082
+ return True
1083
+
1084
+ @work(group="runtime-action")
1017
1085
  async def _do_start(self, runtime_id: str) -> None:
1086
+ self._show_action_notice(runtime_id, "Starting")
1018
1087
  self.notify(f"Starting {runtime_id[:8]}…", title="Runtimes", severity="information")
1019
1088
  try:
1020
1089
  result = await asyncio.to_thread(
@@ -1040,6 +1109,8 @@ class RuntimeScreen(DreadnodeScreen):
1040
1109
  except Exception as exc:
1041
1110
  logger.warning("start_runtime failed for {}: {}", runtime_id[:8], exc)
1042
1111
  self.notify(f"Start failed: {exc}", title="Runtimes", severity="error")
1112
+ finally:
1113
+ self._clear_pending_action(runtime_id)
1043
1114
  self._load_data()
1044
1115
 
1045
1116
  @work(exclusive=True, group="runtime-action")
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "dreadnode"
3
- version = "2.0.10"
3
+ version = "2.0.11"
4
4
  description = "Dreadnode SDK"
5
5
  authors = [{ name = "Dreadnode Team", email = "support@dreadnode.io" }]
6
6
  readme = "README.md"
File without changes
File without changes
File without changes