dreadnode 2.0.3__tar.gz → 2.0.4__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 (417) hide show
  1. {dreadnode-2.0.3 → dreadnode-2.0.4}/PKG-INFO +2 -2
  2. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/app/cli/task.py +3 -0
  3. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/app/main.py +59 -9
  4. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/app/server/app.py +1 -0
  5. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/app/tui/app.py +92 -6
  6. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/app/tui/client.py +13 -6
  7. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/app/tui/dreadnode.tcss +31 -5
  8. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/app/tui/screens/auth.py +38 -33
  9. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/app/tui/screens/capabilities.py +22 -2
  10. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/app/tui/theme.py +21 -20
  11. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/app/tui/turn_reducer.py +2 -0
  12. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/app/tui/widgets/conversation.py +3 -1
  13. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/app/tui/widgets/tool_progress.py +26 -4
  14. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/app/tui/widgets/welcome.py +5 -1
  15. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/packaging/task_validation.py +30 -27
  16. {dreadnode-2.0.3 → dreadnode-2.0.4}/pyproject.toml +2 -2
  17. {dreadnode-2.0.3 → dreadnode-2.0.4}/.gitignore +0 -0
  18. {dreadnode-2.0.3 → dreadnode-2.0.4}/LICENSE +0 -0
  19. {dreadnode-2.0.3 → dreadnode-2.0.4}/README.md +0 -0
  20. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/__init__.py +0 -0
  21. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/__main__.py +0 -0
  22. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/agents/__init__.py +0 -0
  23. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/agents/agent.py +0 -0
  24. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/agents/events.py +0 -0
  25. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/agents/exceptions.py +0 -0
  26. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/agents/format.py +0 -0
  27. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/agents/hooks.py +0 -0
  28. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/agents/mcp/__init__.py +0 -0
  29. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/agents/mcp/auth.py +0 -0
  30. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/agents/mcp/client.py +0 -0
  31. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/agents/mcp/config.py +0 -0
  32. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/agents/mcp/server.py +0 -0
  33. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/agents/reactions.py +0 -0
  34. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/agents/skills.py +0 -0
  35. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/agents/stopping.py +0 -0
  36. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/agents/subagent.py +0 -0
  37. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/agents/tools.py +0 -0
  38. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/agents/trajectory.py +0 -0
  39. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/airt/__init__.py +0 -0
  40. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/airt/analytics/__init__.py +0 -0
  41. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/airt/analytics/aggregator.py +0 -0
  42. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/airt/analytics/classifier.py +0 -0
  43. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/airt/analytics/compliance.py +0 -0
  44. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/airt/analytics/engine.py +0 -0
  45. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/airt/analytics/recommendations.py +0 -0
  46. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/airt/analytics/types.py +0 -0
  47. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/airt/assessment.py +0 -0
  48. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/airt/autodan_turbo.py +0 -0
  49. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/airt/beast.py +0 -0
  50. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/airt/compliance/__init__.py +0 -0
  51. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/airt/compliance/atlas.py +0 -0
  52. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/airt/compliance/nist.py +0 -0
  53. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/airt/compliance/owasp.py +0 -0
  54. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/airt/compliance/owasp_agentic.py +0 -0
  55. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/airt/compliance/saif.py +0 -0
  56. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/airt/constants.py +0 -0
  57. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/airt/crescendo.py +0 -0
  58. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/airt/data/__init__.py +0 -0
  59. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/airt/data/assets/audio/adversarial_query.mp3 +0 -0
  60. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/airt/data/assets/image/bomb.jpg +0 -0
  61. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/airt/data/assets/image/meth.png +0 -0
  62. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/airt/data/prompts/adversarial_benchmark_subset.csv +0 -0
  63. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/airt/data/prompts/ai_safety.csv +0 -0
  64. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/airt/data/rubrics/data_exfiltration.yaml +0 -0
  65. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/airt/data/rubrics/goal_hijacking.yaml +0 -0
  66. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/airt/data/rubrics/memory_poisoning.yaml +0 -0
  67. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/airt/data/rubrics/privilege_escalation.yaml +0 -0
  68. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/airt/data/rubrics/rce.yaml +0 -0
  69. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/airt/data/rubrics/scope_creep.yaml +0 -0
  70. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/airt/data/rubrics/tool_chaining.yaml +0 -0
  71. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/airt/data/rubrics/tool_selection_safety.yaml +0 -0
  72. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/airt/data/rubrics/unbounded_agency.yaml +0 -0
  73. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/airt/data/rubrics/web_chatbot_security.yaml +0 -0
  74. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/airt/data/templates/crescendo/variant_1.yaml +0 -0
  75. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/airt/data/templates/crescendo/variant_2.yaml +0 -0
  76. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/airt/data/templates/crescendo/variant_3.yaml +0 -0
  77. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/airt/data/templates/crescendo/variant_4.yaml +0 -0
  78. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/airt/data/templates/crescendo/variant_5.yaml +0 -0
  79. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/airt/deep_inception.py +0 -0
  80. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/airt/drattack.py +0 -0
  81. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/airt/events.py +0 -0
  82. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/airt/goat.py +0 -0
  83. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/airt/gptfuzzer.py +0 -0
  84. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/airt/image.py +0 -0
  85. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/airt/multimodal.py +0 -0
  86. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/airt/pair.py +0 -0
  87. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/airt/prompt.py +0 -0
  88. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/airt/rainbow.py +0 -0
  89. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/airt/renellm.py +0 -0
  90. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/airt/reporting/__init__.py +0 -0
  91. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/airt/reporting/json_report.py +0 -0
  92. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/airt/reporting/llm_summary.py +0 -0
  93. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/airt/reporting/markdown.py +0 -0
  94. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/airt/tap.py +0 -0
  95. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/app/__init__.py +0 -0
  96. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/app/api/__init__.py +0 -0
  97. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/app/api/client.py +0 -0
  98. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/app/api/models.py +0 -0
  99. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/app/cli/__init__.py +0 -0
  100. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/app/cli/airt.py +0 -0
  101. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/app/cli/capability.py +0 -0
  102. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/app/cli/dataset.py +0 -0
  103. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/app/cli/evaluation.py +0 -0
  104. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/app/cli/main.py +0 -0
  105. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/app/cli/model.py +0 -0
  106. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/app/cli/optimize.py +0 -0
  107. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/app/cli/runtime.py +0 -0
  108. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/app/cli/shared.py +0 -0
  109. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/app/cli/train.py +0 -0
  110. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/app/cli/worlds.py +0 -0
  111. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/app/print_mode.py +0 -0
  112. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/app/server/__init__.py +0 -0
  113. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/app/server/auth.py +0 -0
  114. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/app/server/default-agent/tools/coding.py +0 -0
  115. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/app/server/default-agent/tools/subagent.py +0 -0
  116. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/app/server/session.py +0 -0
  117. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/app/server/system-prompt.md +0 -0
  118. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/app/server/utils.py +0 -0
  119. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/app/tui/__init__.py +0 -0
  120. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/app/tui/commands.py +0 -0
  121. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/app/tui/connection.py +0 -0
  122. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/app/tui/event_contract.py +0 -0
  123. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/app/tui/model_variants.py +0 -0
  124. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/app/tui/runtime_cache.py +0 -0
  125. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/app/tui/screens/__init__.py +0 -0
  126. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/app/tui/screens/base.py +0 -0
  127. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/app/tui/screens/console.py +0 -0
  128. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/app/tui/screens/environments.py +0 -0
  129. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/app/tui/screens/evaluations.py +0 -0
  130. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/app/tui/screens/model_picker.py +0 -0
  131. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/app/tui/screens/runtimes.py +0 -0
  132. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/app/tui/screens/sandboxes.py +0 -0
  133. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/app/tui/screens/secrets.py +0 -0
  134. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/app/tui/screens/sessions.py +0 -0
  135. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/app/tui/screens/traces.py +0 -0
  136. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/app/tui/screens/workspaces.py +0 -0
  137. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/app/tui/update_check.py +0 -0
  138. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/app/tui/widgets/__init__.py +0 -0
  139. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/app/tui/widgets/agent_dialog.py +0 -0
  140. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/app/tui/widgets/agent_suggester.py +0 -0
  141. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/app/tui/widgets/composer.py +0 -0
  142. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/app/tui/widgets/context_bar.py +0 -0
  143. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/app/tui/widgets/flash.py +0 -0
  144. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/app/tui/widgets/header_bar.py +0 -0
  145. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/app/tui/widgets/help_panel.py +0 -0
  146. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/app/tui/widgets/mcp_dialog.py +0 -0
  147. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/app/tui/widgets/mention_overlay.py +0 -0
  148. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/app/tui/widgets/message_queue.py +0 -0
  149. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/app/tui/widgets/overlay_mixin.py +0 -0
  150. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/app/tui/widgets/permission_prompt.py +0 -0
  151. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/app/tui/widgets/prompt_info.py +0 -0
  152. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/app/tui/widgets/session_sidebar.py +0 -0
  153. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/app/tui/widgets/skills_dialog.py +0 -0
  154. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/app/tui/widgets/slash_overlay.py +0 -0
  155. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/app/tui/widgets/status_bar.py +0 -0
  156. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/app/tui/widgets/throbber.py +0 -0
  157. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/app/tui/widgets/tool.py +0 -0
  158. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/app/tui/widgets/tools_dialog.py +0 -0
  159. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/capabilities/__init__.py +0 -0
  160. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/capabilities/capability.py +0 -0
  161. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/capabilities/loader.py +0 -0
  162. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/capabilities/sync.py +0 -0
  163. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/capabilities/tool_rules.py +0 -0
  164. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/capabilities/types.py +0 -0
  165. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/core/__init__.py +0 -0
  166. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/core/conditions.py +0 -0
  167. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/core/discovery.py +0 -0
  168. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/core/environment.py +0 -0
  169. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/core/exceptions.py +0 -0
  170. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/core/execution.py +0 -0
  171. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/core/hook.py +0 -0
  172. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/core/judge.py +0 -0
  173. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/core/load.py +0 -0
  174. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/core/log.py +0 -0
  175. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/core/meta/__init__.py +0 -0
  176. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/core/meta/config.py +0 -0
  177. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/core/meta/context.py +0 -0
  178. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/core/meta/hydrate.py +0 -0
  179. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/core/meta/introspect.py +0 -0
  180. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/core/metric.py +0 -0
  181. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/core/object.py +0 -0
  182. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/core/scorer.py +0 -0
  183. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/core/serialization.py +0 -0
  184. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/core/stopping.py +0 -0
  185. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/core/task.py +0 -0
  186. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/core/transforms.py +0 -0
  187. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/core/types/__init__.py +0 -0
  188. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/core/types/audio.py +0 -0
  189. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/core/types/base.py +0 -0
  190. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/core/types/common.py +0 -0
  191. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/core/types/image.py +0 -0
  192. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/core/types/object_3d.py +0 -0
  193. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/core/types/table.py +0 -0
  194. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/core/types/text.py +0 -0
  195. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/core/types/video.py +0 -0
  196. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/core/util.py +0 -0
  197. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/datasets/__init__.py +0 -0
  198. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/datasets/dataset.py +0 -0
  199. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/datasets/hf.py +0 -0
  200. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/datasets/local.py +0 -0
  201. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/evaluations/__init__.py +0 -0
  202. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/evaluations/console.py +0 -0
  203. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/evaluations/evaluation.py +0 -0
  204. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/evaluations/events.py +0 -0
  205. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/evaluations/format.py +0 -0
  206. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/evaluations/result.py +0 -0
  207. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/evaluations/sample.py +0 -0
  208. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/generators/__init__.py +0 -0
  209. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/generators/caching.py +0 -0
  210. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/generators/chat.py +0 -0
  211. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/generators/data.py +0 -0
  212. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/generators/exceptions.py +0 -0
  213. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/generators/generator/__init__.py +0 -0
  214. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/generators/generator/base.py +0 -0
  215. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/generators/generator/http.py +0 -0
  216. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/generators/generator/litellm_.py +0 -0
  217. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/generators/generator/transformers_.py +0 -0
  218. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/generators/generator/vllm_.py +0 -0
  219. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/generators/message.py +0 -0
  220. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/generators/models.py +0 -0
  221. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/generators/parsing.py +0 -0
  222. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/generators/tokenizer/__init__.py +0 -0
  223. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/generators/tokenizer/base.py +0 -0
  224. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/generators/tokenizer/transformers_.py +0 -0
  225. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/generators/utils.py +0 -0
  226. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/models/__init__.py +0 -0
  227. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/models/hf.py +0 -0
  228. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/models/local.py +0 -0
  229. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/models/model.py +0 -0
  230. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/optimization/__init__.py +0 -0
  231. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/optimization/adapters/__init__.py +0 -0
  232. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/optimization/adapters/agent.py +0 -0
  233. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/optimization/api.py +0 -0
  234. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/optimization/backends/__init__.py +0 -0
  235. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/optimization/backends/base.py +0 -0
  236. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/optimization/backends/gepa.py +0 -0
  237. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/optimization/collectors.py +0 -0
  238. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/optimization/config.py +0 -0
  239. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/optimization/console.py +0 -0
  240. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/optimization/events.py +0 -0
  241. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/optimization/format.py +0 -0
  242. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/optimization/jobs.py +0 -0
  243. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/optimization/result.py +0 -0
  244. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/optimization/sampler.py +0 -0
  245. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/optimization/sampling.py +0 -0
  246. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/optimization/search.py +0 -0
  247. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/optimization/stopping.py +0 -0
  248. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/optimization/study.py +0 -0
  249. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/optimization/trial.py +0 -0
  250. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/packaging/__init__.py +0 -0
  251. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/packaging/loader.py +0 -0
  252. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/packaging/manifest.py +0 -0
  253. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/packaging/oci.py +0 -0
  254. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/packaging/package.py +0 -0
  255. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/py.typed +0 -0
  256. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/samplers/__init__.py +0 -0
  257. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/samplers/boundary.py +0 -0
  258. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/samplers/fuzzing.py +0 -0
  259. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/samplers/graph.py +0 -0
  260. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/samplers/grid.py +0 -0
  261. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/samplers/image.py +0 -0
  262. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/samplers/mapelites.py +0 -0
  263. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/samplers/optuna.py +0 -0
  264. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/samplers/random.py +0 -0
  265. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/samplers/registry.py +0 -0
  266. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/samplers/strategy.py +0 -0
  267. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/scorers/__init__.py +0 -0
  268. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/scorers/advanced_jailbreak_detection.py +0 -0
  269. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/scorers/agent_security.py +0 -0
  270. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/scorers/agentic.py +0 -0
  271. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/scorers/agentic_workflow.py +0 -0
  272. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/scorers/classification.py +0 -0
  273. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/scorers/consistency.py +0 -0
  274. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/scorers/contains.py +0 -0
  275. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/scorers/cosine_sim.py +0 -0
  276. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/scorers/credentials.py +0 -0
  277. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/scorers/crucible.py +0 -0
  278. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/scorers/documentation_security.py +0 -0
  279. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/scorers/exfiltration_detection.py +0 -0
  280. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/scorers/format.py +0 -0
  281. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/scorers/harm.py +0 -0
  282. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/scorers/ide_security.py +0 -0
  283. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/scorers/image.py +0 -0
  284. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/scorers/json.py +0 -0
  285. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/scorers/judge.py +0 -0
  286. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/scorers/length.py +0 -0
  287. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/scorers/lexical.py +0 -0
  288. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/scorers/mcp_security.py +0 -0
  289. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/scorers/memorization.py +0 -0
  290. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/scorers/multi_agent_security.py +0 -0
  291. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/scorers/pii.py +0 -0
  292. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/scorers/prompt_leak.py +0 -0
  293. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/scorers/readability.py +0 -0
  294. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/scorers/reasoning_security.py +0 -0
  295. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/scorers/sentiment.py +0 -0
  296. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/scorers/similarity.py +0 -0
  297. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/storage/__init__.py +0 -0
  298. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/storage/providers.py +0 -0
  299. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/storage/session_store.py +0 -0
  300. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/storage/storage.py +0 -0
  301. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/tools/__init__.py +0 -0
  302. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/tools/_ripgrep.py +0 -0
  303. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/tools/apply_patch.py +0 -0
  304. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/tools/editing.py +0 -0
  305. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/tools/execute.py +0 -0
  306. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/tools/fetch.py +0 -0
  307. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/tools/glob.py +0 -0
  308. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/tools/grep.py +0 -0
  309. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/tools/interaction.py +0 -0
  310. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/tools/ls.py +0 -0
  311. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/tools/memory.py +0 -0
  312. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/tools/read.py +0 -0
  313. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/tools/task.py +0 -0
  314. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/tools/think.py +0 -0
  315. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/tools/todo.py +0 -0
  316. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/tools/web_search.py +0 -0
  317. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/tools/write.py +0 -0
  318. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/tracing/__init__.py +0 -0
  319. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/tracing/constants.py +0 -0
  320. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/tracing/convert.py +0 -0
  321. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/tracing/exporter.py +0 -0
  322. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/tracing/exporters.py +0 -0
  323. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/tracing/span.py +0 -0
  324. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/tracing/spans.py +0 -0
  325. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/tracing/trace_converter.py +0 -0
  326. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/training/__init__.py +0 -0
  327. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/training/base.py +0 -0
  328. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/training/dpo.py +0 -0
  329. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/training/etl/__init__.py +0 -0
  330. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/training/etl/_common.py +0 -0
  331. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/training/etl/rl.py +0 -0
  332. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/training/etl/sft.py +0 -0
  333. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/training/etl/worlds.py +0 -0
  334. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/training/events.py +0 -0
  335. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/training/grpo.py +0 -0
  336. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/training/jobs.py +0 -0
  337. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/training/ppo.py +0 -0
  338. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/training/prime.py +0 -0
  339. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/training/ray/__init__.py +0 -0
  340. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/training/ray/async_trainer.py +0 -0
  341. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/training/ray/config.py +0 -0
  342. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/training/ray/coordinator.py +0 -0
  343. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/training/ray/distributed.py +0 -0
  344. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/training/ray/dpo.py +0 -0
  345. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/training/ray/experience.py +0 -0
  346. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/training/ray/fsdp2_learner.py +0 -0
  347. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/training/ray/inference.py +0 -0
  348. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/training/ray/learner.py +0 -0
  349. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/training/ray/multi_turn.py +0 -0
  350. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/training/ray/ppo.py +0 -0
  351. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/training/ray/reward_model.py +0 -0
  352. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/training/ray/rollout_env.py +0 -0
  353. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/training/ray/rollout_worker.py +0 -0
  354. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/training/ray/sft.py +0 -0
  355. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/training/ray/trainer.py +0 -0
  356. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/training/recipes.py +0 -0
  357. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/training/rewards/__init__.py +0 -0
  358. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/training/rewards/aggregator.py +0 -0
  359. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/training/rewards/functions.py +0 -0
  360. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/training/rewards/scorer_bridge.py +0 -0
  361. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/training/rewards/shaping.py +0 -0
  362. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/training/rewards/types.py +0 -0
  363. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/training/rollouts/__init__.py +0 -0
  364. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/training/rollouts/adapters.py +0 -0
  365. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/training/rollouts/orchestrator.py +0 -0
  366. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/training/rollouts/types.py +0 -0
  367. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/training/rollouts/worlds.py +0 -0
  368. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/training/serving/__init__.py +0 -0
  369. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/training/serving/vllm_client.py +0 -0
  370. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/training/sft.py +0 -0
  371. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/training/tinker/__init__.py +0 -0
  372. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/training/tinker/config.py +0 -0
  373. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/training/tinker/data.py +0 -0
  374. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/training/tinker/renderer.py +0 -0
  375. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/training/tinker/rl.py +0 -0
  376. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/training/tinker/trainer.py +0 -0
  377. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/training/tinker_sft.py +0 -0
  378. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/training/utils.py +0 -0
  379. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/transforms/__init__.py +0 -0
  380. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/transforms/advanced_jailbreak.py +0 -0
  381. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/transforms/adversarial_suffix.py +0 -0
  382. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/transforms/agent_skill.py +0 -0
  383. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/transforms/agentic_workflow.py +0 -0
  384. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/transforms/audio.py +0 -0
  385. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/transforms/browser_agent_attacks.py +0 -0
  386. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/transforms/cipher.py +0 -0
  387. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/transforms/constitutional.py +0 -0
  388. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/transforms/document.py +0 -0
  389. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/transforms/documentation_poison.py +0 -0
  390. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/transforms/encoding.py +0 -0
  391. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/transforms/exfiltration.py +0 -0
  392. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/transforms/flip_attack.py +0 -0
  393. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/transforms/guardrail_bypass.py +0 -0
  394. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/transforms/ide_injection.py +0 -0
  395. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/transforms/image.py +0 -0
  396. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/transforms/injection.py +0 -0
  397. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/transforms/json_tools.py +0 -0
  398. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/transforms/language.py +0 -0
  399. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/transforms/logic_bomb.py +0 -0
  400. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/transforms/mcp_attacks.py +0 -0
  401. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/transforms/multi_agent_attacks.py +0 -0
  402. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/transforms/persuasion.py +0 -0
  403. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/transforms/perturbation.py +0 -0
  404. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/transforms/pii_extraction.py +0 -0
  405. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/transforms/pythonic_tools.py +0 -0
  406. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/transforms/rag_poisoning.py +0 -0
  407. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/transforms/reasoning_attacks.py +0 -0
  408. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/transforms/refine.py +0 -0
  409. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/transforms/response_steering.py +0 -0
  410. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/transforms/stylistic.py +0 -0
  411. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/transforms/substitution.py +0 -0
  412. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/transforms/swap.py +0 -0
  413. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/transforms/system_prompt_extraction.py +0 -0
  414. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/transforms/text.py +0 -0
  415. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/transforms/video.py +0 -0
  416. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/transforms/xml_tools.py +0 -0
  417. {dreadnode-2.0.3 → dreadnode-2.0.4}/dreadnode/version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dreadnode
3
- Version: 2.0.3
3
+ Version: 2.0.4
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.80.11
22
+ Requires-Dist: litellm<=1.82.2,>=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
@@ -158,6 +158,7 @@ def sync(
158
158
  *,
159
159
  force: t.Annotated[bool, cyclopts.Parameter(negative=())] = False,
160
160
  public: t.Annotated[bool, cyclopts.Parameter(negative=())] = False,
161
+ workers: t.Annotated[int, cyclopts.Parameter(help="Parallel upload workers.")] = 8,
161
162
  registry: RegistryConfig = RegistryConfig(),
162
163
  ) -> None:
163
164
  """Sync task environments from a directory to the platform."""
@@ -166,7 +167,9 @@ def sync(
166
167
  directory,
167
168
  force=force,
168
169
  public=public,
170
+ max_workers=workers,
169
171
  on_progress=_sync_progress,
172
+ on_status=lambda msg: print(msg),
170
173
  )
171
174
 
172
175
  total = len(result.uploaded) + len(result.skipped) + len(result.failed)
@@ -1819,13 +1819,37 @@ class Dreadnode:
1819
1819
  result.blobs_skipped += oci_result.blobs_existed
1820
1820
  return result
1821
1821
 
1822
+ def _fetch_remote_task_digests(self, org: str) -> dict[str, str]:
1823
+ """Fetch all remote task OCI digests in bulk via paginated list.
1824
+
1825
+ Returns a mapping of ``{task_name: oci_layer_sha}`` for every task
1826
+ that has a recorded OCI digest.
1827
+ """
1828
+ digests: dict[str, str] = {}
1829
+ page = 1
1830
+ while True:
1831
+ resp = self.api.list_tasks(org, page=page, limit=100)
1832
+ for item in resp.get("tasks", []):
1833
+ name = item.get("name")
1834
+ raw = item.get("oci_digest")
1835
+ if name and isinstance(raw, str) and raw:
1836
+ sha = raw.split(":", 1)[-1] if ":" in raw else raw
1837
+ digests[name] = sha
1838
+ total = resp.get("total", 0)
1839
+ if page * 100 >= total:
1840
+ break
1841
+ page += 1
1842
+ return digests
1843
+
1822
1844
  def sync_environments(
1823
1845
  self,
1824
1846
  directory: str | Path,
1825
1847
  *,
1826
1848
  force: bool = False,
1827
1849
  public: bool = False,
1850
+ max_workers: int = 8,
1828
1851
  on_progress: t.Callable[[str, str, str | None], None] | None = None,
1852
+ on_status: t.Callable[[str], None] | None = None,
1829
1853
  ) -> EnvironmentSyncResult:
1830
1854
  """Sync task environments from a directory to the platform.
1831
1855
 
@@ -1837,11 +1861,15 @@ class Dreadnode:
1837
1861
  directory: Root directory containing task subdirectories.
1838
1862
  force: Upload even when the remote SHA matches.
1839
1863
  public: Set ``is_public=True`` after upload.
1864
+ max_workers: Maximum parallel build/upload threads.
1840
1865
  on_progress: Optional callback ``(name, status, error)`` for each task.
1841
1866
 
1842
1867
  Returns:
1843
1868
  :class:`EnvironmentSyncResult` with uploaded/skipped/failed details.
1844
1869
  """
1870
+ import concurrent.futures
1871
+ import threading
1872
+
1845
1873
  from dreadnode.packaging.oci import build_environment
1846
1874
 
1847
1875
  if not self.can_sync:
@@ -1851,13 +1879,18 @@ class Dreadnode:
1851
1879
 
1852
1880
  from dreadnode.packaging.task_validation import discover_task_directories
1853
1881
 
1882
+ _status = on_status or (lambda _msg: None)
1883
+
1854
1884
  root = Path(directory).resolve()
1855
1885
  if not root.is_dir():
1856
1886
  raise FileNotFoundError(f"Task directory not found: {root}")
1857
1887
 
1858
1888
  org = self.session.org_key
1859
1889
 
1890
+ _status("Discovering tasks...")
1860
1891
  task_dirs, conflicts = discover_task_directories(root)
1892
+ _status(f"Found {len(task_dirs)} task(s)")
1893
+
1861
1894
  if conflicts and on_progress:
1862
1895
  for parent, nested in conflicts:
1863
1896
  on_progress(
@@ -1869,9 +1902,17 @@ class Dreadnode:
1869
1902
  result = EnvironmentSyncResult(
1870
1903
  failed=[(nested.name, f"nested inside {parent.name}") for parent, nested in conflicts],
1871
1904
  )
1872
- oci_client = self.storage.oci_client()
1873
1905
 
1874
- for task_dir in task_dirs:
1906
+ # Batch-fetch all remote digests (one paginated call vs N individual GETs)
1907
+ _status("Fetching remote digests...")
1908
+ remote_digests = self._fetch_remote_task_digests(org) if not force else {}
1909
+ _status(
1910
+ f"Fetched {len(remote_digests)} remote digest(s), syncing with {max_workers} workers..."
1911
+ )
1912
+
1913
+ lock = threading.Lock()
1914
+
1915
+ def _sync_one(task_dir: Path) -> None:
1875
1916
  dir_name = task_dir.name
1876
1917
  try:
1877
1918
  env_name, env_version = _load_environment_metadata(task_dir)
@@ -1883,33 +1924,42 @@ class Dreadnode:
1883
1924
  raise ValueError("Environment OCI image has no layers") # noqa: TRY301
1884
1925
  local_sha = image.layers[0].digest.split(":", 1)[-1]
1885
1926
 
1886
- remote_sha = self._get_remote_task_sha(org, env_name)
1927
+ remote_sha = remote_digests.get(env_name)
1887
1928
  if not force and remote_sha and remote_sha == local_sha:
1888
- result.skipped.append(env_name)
1929
+ with lock:
1930
+ result.skipped.append(env_name)
1889
1931
  if on_progress:
1890
1932
  on_progress(env_name, "skipped", None)
1891
- continue
1933
+ return
1892
1934
 
1935
+ # Each thread gets its own OCI client (own httpx.Client)
1936
+ oci_client = self.storage.oci_client()
1893
1937
  push_result = oci_client.push(resolved_name, env_version, image)
1894
1938
  if not push_result.success:
1895
1939
  msg = "; ".join(push_result.errors) or "OCI push failed"
1896
- result.failed.append((env_name, msg))
1940
+ with lock:
1941
+ result.failed.append((env_name, msg))
1897
1942
  if on_progress:
1898
1943
  on_progress(env_name, "failed", msg)
1899
- continue
1944
+ return
1900
1945
 
1901
1946
  if public:
1902
1947
  self.api.update_task_visibility(org, env_name, is_public=True)
1903
1948
 
1904
- result.uploaded.append(env_name)
1949
+ with lock:
1950
+ result.uploaded.append(env_name)
1905
1951
  if on_progress:
1906
1952
  on_progress(env_name, "uploaded", None)
1907
1953
 
1908
1954
  except Exception as exc:
1909
- result.failed.append((dir_name, str(exc)))
1955
+ with lock:
1956
+ result.failed.append((dir_name, str(exc)))
1910
1957
  if on_progress:
1911
1958
  on_progress(dir_name, "failed", str(exc))
1912
1959
 
1960
+ with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as pool:
1961
+ list(pool.map(_sync_one, task_dirs))
1962
+
1913
1963
  return result
1914
1964
 
1915
1965
  def pull_package(
@@ -812,6 +812,7 @@ def create_agent(
812
812
  instructions=instructions,
813
813
  tools=tools,
814
814
  hooks=hooks,
815
+ max_steps=500,
815
816
  )
816
817
 
817
818
 
@@ -549,6 +549,11 @@ class DreadnodeTextualApp(App[None]):
549
549
  @on(ComposerInput.Submitted)
550
550
  def _on_composer_submitted(self, event: ComposerInput.Submitted) -> None:
551
551
  value = event.value
552
+ logger.debug(
553
+ "Composer submitted | length={} | has_session={}",
554
+ len(value),
555
+ self.active_session_id is not None,
556
+ )
552
557
  self.query_one("#slash-overlay", SlashOverlay).hide()
553
558
  self.query_one("#mention-overlay", MentionOverlay).hide()
554
559
  self._dismiss_welcome()
@@ -596,7 +601,10 @@ class DreadnodeTextualApp(App[None]):
596
601
  else:
597
602
  # Show user message + thinking indicator immediately (before @work schedules)
598
603
  # so the first render frame already shows the conversation with user input.
604
+ # For new sessions (no active session yet), we still show thinking state
605
+ # immediately so the UI feels responsive during session creation.
599
606
  session = self._active_session()
607
+ conv = self.query_one("#conversation", ConversationView)
600
608
  if session is not None:
601
609
  sid = session.info.session_id
602
610
  self._turn_counts.setdefault(sid, 0)
@@ -607,13 +615,10 @@ class DreadnodeTextualApp(App[None]):
607
615
  record = self.sessions.get(sid)
608
616
  if record is not None:
609
617
  record.transcript.append(entry)
610
- conv = self.query_one("#conversation", ConversationView)
611
618
  conv.append_entry(entry)
612
- self._set_status("Thinking", busy=True)
613
- conv.query_one(ToolProgress).show_activity("thinking")
614
- self._send_chat(value, _user_entry_shown=True)
615
- else:
616
- self._send_chat(value)
619
+ self._set_status("Thinking", busy=True)
620
+ conv.query_one(ToolProgress).show_activity("thinking")
621
+ self._send_chat(value, _user_entry_shown=session is not None)
617
622
 
618
623
  def _submit_human_prompt_response(self, prompt: HumanPrompt, raw_value: str) -> None:
619
624
  """Handle composer submission while a human prompt is active."""
@@ -1140,6 +1145,7 @@ class DreadnodeTextualApp(App[None]):
1140
1145
  self._last_quit_time = now
1141
1146
  return
1142
1147
  self.workers.cancel_group(self, "session")
1148
+ self._commit_draft_to_transcript(self.active_session_id or "")
1143
1149
  self._set_status("Interrupted", busy=False)
1144
1150
  self._set_composer_enabled(True)
1145
1151
  self.query_one("#composer", ComposerInput).focus()
@@ -1190,6 +1196,7 @@ class DreadnodeTextualApp(App[None]):
1190
1196
  self._flash("Prompt cancelled", severity="warning")
1191
1197
  return
1192
1198
  self.workers.cancel_group(self, "session")
1199
+ self._commit_draft_to_transcript(self.active_session_id or "")
1193
1200
  self._set_status("Interrupted", busy=False)
1194
1201
  self._set_composer_enabled(True)
1195
1202
  composer.focus()
@@ -1354,6 +1361,7 @@ class DreadnodeTextualApp(App[None]):
1354
1361
 
1355
1362
  @work(exit_on_error=False)
1356
1363
  async def _boot(self) -> None:
1364
+ boot_t0 = time.monotonic()
1357
1365
  self._clear_platform_proxy_state()
1358
1366
  self._set_status("Starting runtime", busy=True)
1359
1367
  override_url = self._platform_override_url()
@@ -1401,6 +1409,7 @@ class DreadnodeTextualApp(App[None]):
1401
1409
  api = ApiClient(platform_profile.url, api_key=platform_profile.api_key)
1402
1410
  backoffs = [1.0, 2.0, 4.0]
1403
1411
  response: httpx.Response | None = None
1412
+ t0 = time.monotonic()
1404
1413
  for attempt in range(len(backoffs) + 1):
1405
1414
  try:
1406
1415
  response = await asyncio.to_thread(api._request, "GET", "/user")
@@ -1423,6 +1432,7 @@ class DreadnodeTextualApp(App[None]):
1423
1432
  return
1424
1433
 
1425
1434
  if response.is_success:
1435
+ logger.debug("Boot timing | validate_key={:.0f}ms", (time.monotonic() - t0) * 1000)
1426
1436
  break
1427
1437
 
1428
1438
  # Server error (5xx) — retry with backoff
@@ -1445,8 +1455,12 @@ class DreadnodeTextualApp(App[None]):
1445
1455
  return
1446
1456
 
1447
1457
  try:
1458
+ t0 = time.monotonic()
1448
1459
  await asyncio.to_thread(api.get_organization, org)
1449
1460
  workspaces = await asyncio.to_thread(api.list_workspaces, org)
1461
+ logger.debug(
1462
+ "Boot timing | validate_org_workspace={:.0f}ms", (time.monotonic() - t0) * 1000
1463
+ )
1450
1464
  except Exception as exc:
1451
1465
  self._set_status("Authentication required", busy=False)
1452
1466
  logger.warning("Boot: failed to validate org/workspace: {}", exc)
@@ -1462,11 +1476,21 @@ class DreadnodeTextualApp(App[None]):
1462
1476
  try:
1463
1477
  self._connection_manager.set_api_context(api, org, workspace)
1464
1478
  self._connection_manager.local_client.set_platform_profile(platform_profile)
1479
+ t0 = time.monotonic()
1465
1480
  await self._connection_manager.local_client.start()
1481
+ logger.debug("Boot timing | runtime_start={:.0f}ms", (time.monotonic() - t0) * 1000)
1482
+ t0 = time.monotonic()
1466
1483
  await self._refresh_runtime()
1484
+ logger.debug("Boot timing | refresh_runtime={:.0f}ms", (time.monotonic() - t0) * 1000)
1485
+ t0 = time.monotonic()
1467
1486
  await self._refresh_server_sessions()
1487
+ logger.debug("Boot timing | refresh_sessions={:.0f}ms", (time.monotonic() - t0) * 1000)
1468
1488
  try:
1489
+ t0 = time.monotonic()
1469
1490
  await self._refresh_platform_models_and_key(api)
1491
+ logger.debug(
1492
+ "Boot timing | platform_models_and_key={:.0f}ms", (time.monotonic() - t0) * 1000
1493
+ )
1470
1494
  except AuthenticationError:
1471
1495
  self._handle_authentication_error("Session expired — please sign in again")
1472
1496
  # Resume a specific session if requested, otherwise start fresh.
@@ -1477,6 +1501,7 @@ class DreadnodeTextualApp(App[None]):
1477
1501
  self._sync_conversation()
1478
1502
  self.authenticated = True
1479
1503
  self._refresh_skill_names()
1504
+ logger.info("Boot timing | total={:.0f}ms", (time.monotonic() - boot_t0) * 1000)
1480
1505
  self._flash("Connected to Dreadnode runtime", severity="success")
1481
1506
  self._write_activity("Connected to Dreadnode runtime", style="success")
1482
1507
  self._set_status("Ready", busy=False)
@@ -1853,6 +1878,7 @@ class DreadnodeTextualApp(App[None]):
1853
1878
  @work(exclusive=True, group="session")
1854
1879
  async def _switch_session(self, session_id: str) -> None:
1855
1880
  logger.info("Session switch | session={}", session_id[:8])
1881
+ self._dismiss_welcome()
1856
1882
  if self.active_session_id is not None:
1857
1883
  self._inline_activity_sessions.discard(self.active_session_id)
1858
1884
  self.active_session_id = session_id
@@ -1922,9 +1948,12 @@ class DreadnodeTextualApp(App[None]):
1922
1948
 
1923
1949
  @work(exit_on_error=False, exclusive=True, group="session")
1924
1950
  async def _send_chat(self, message: str, _user_entry_shown: bool = False) -> None:
1951
+ send_t0 = time.monotonic()
1925
1952
  session = self._active_session()
1926
1953
  if session is None:
1954
+ t0 = time.monotonic()
1927
1955
  await self.__create_new_session()
1956
+ logger.debug("Send timing | create_session={:.0f}ms", (time.monotonic() - t0) * 1000)
1928
1957
  session = self._active_session()
1929
1958
  if session is None:
1930
1959
  self._flash("Failed to create a session", severity="error")
@@ -1953,7 +1982,15 @@ class DreadnodeTextualApp(App[None]):
1953
1982
  len(message),
1954
1983
  )
1955
1984
  try:
1985
+ t0 = time.monotonic()
1956
1986
  await self._ensure_litellm_key_fresh()
1987
+ logger.debug(
1988
+ "Send timing | ensure_litellm_key={:.0f}ms", (time.monotonic() - t0) * 1000
1989
+ )
1990
+ logger.debug(
1991
+ "Send timing | pre_stream_total={:.0f}ms", (time.monotonic() - send_t0) * 1000
1992
+ )
1993
+ first_event = True
1957
1994
  async for event in self.server_client.stream_chat(
1958
1995
  session_id=session.info.session_id,
1959
1996
  message=message,
@@ -1961,6 +1998,12 @@ class DreadnodeTextualApp(App[None]):
1961
1998
  agent=None if session.info.agent == "default" else session.info.agent,
1962
1999
  generate_params_extra=self.generate_params_extra or None,
1963
2000
  ):
2001
+ if first_event:
2002
+ logger.debug(
2003
+ "Send timing | first_sse_event={:.0f}ms",
2004
+ (time.monotonic() - send_t0) * 1000,
2005
+ )
2006
+ first_event = False
1964
2007
  self._handle_event(event, session.info.session_id)
1965
2008
  except AuthenticationError:
1966
2009
  self._handle_authentication_error("Session expired — please sign in again")
@@ -2338,6 +2381,13 @@ class DreadnodeTextualApp(App[None]):
2338
2381
 
2339
2382
  if event_type == "agent_end":
2340
2383
  stop_reason = str(payload.get("stop_reason", "")).lower()
2384
+ logger.debug(
2385
+ "Agent end | session={} | stop_reason={} | status={} | error={}",
2386
+ session_id[:8],
2387
+ stop_reason or "finished",
2388
+ normalized.status or "ok",
2389
+ normalized.error_text or "none",
2390
+ )
2341
2391
  if normalized.error_text:
2342
2392
  logger.error(
2343
2393
  "Agent ended with error: {} (type={}, payload={})",
@@ -2380,6 +2430,34 @@ class DreadnodeTextualApp(App[None]):
2380
2430
  TranscriptEntry(kind="error", title="agent", body="run ended with an error"),
2381
2431
  session_id,
2382
2432
  )
2433
+ elif stop_reason == "max_steps_reached":
2434
+ logger.warning(
2435
+ "Agent hit max steps | session={} | payload={}",
2436
+ session_id[:8],
2437
+ payload,
2438
+ )
2439
+ self._append_transcript(
2440
+ TranscriptEntry(
2441
+ kind="error",
2442
+ title="agent",
2443
+ body="stopped — reached the maximum number of steps. Send a follow-up message to continue.",
2444
+ ),
2445
+ session_id,
2446
+ )
2447
+ elif stop_reason == "stalled":
2448
+ logger.warning(
2449
+ "Agent stalled | session={} | payload={}",
2450
+ session_id[:8],
2451
+ payload,
2452
+ )
2453
+ self._append_transcript(
2454
+ TranscriptEntry(
2455
+ kind="error",
2456
+ title="agent",
2457
+ body="stopped — agent appears stalled. Send a follow-up message to continue.",
2458
+ ),
2459
+ session_id,
2460
+ )
2383
2461
  return
2384
2462
 
2385
2463
  if event_type == "human_prompt":
@@ -3650,7 +3728,11 @@ class DreadnodeTextualApp(App[None]):
3650
3728
  async def _refresh_platform_models_and_key(self, api: ApiClient) -> None:
3651
3729
  self._clear_platform_proxy_state()
3652
3730
  try:
3731
+ t0 = time.monotonic()
3653
3732
  preferences = await asyncio.to_thread(api.get_user_preferences)
3733
+ logger.debug(
3734
+ "Boot timing | get_user_preferences={:.0f}ms", (time.monotonic() - t0) * 1000
3735
+ )
3654
3736
  except Exception as exc:
3655
3737
  logger.warning("Failed to fetch user preferences: {}", exc)
3656
3738
  return
@@ -3676,7 +3758,11 @@ class DreadnodeTextualApp(App[None]):
3676
3758
  logger.debug("Cannot provision LiteLLM key: no org context available")
3677
3759
  return
3678
3760
  try:
3761
+ t0 = time.monotonic()
3679
3762
  result = await asyncio.to_thread(api.provision_inference_key, org)
3763
+ logger.debug(
3764
+ "LiteLLM key provisioned | elapsed={:.0f}ms", (time.monotonic() - t0) * 1000
3765
+ )
3680
3766
  except Exception as exc:
3681
3767
  if self._is_litellm_service_unavailable(exc):
3682
3768
  self._flash(_PLATFORM_MODELS_UNAVAILABLE_MESSAGE, severity="info")
@@ -4,6 +4,7 @@ import asyncio
4
4
  import contextlib
5
5
  import json
6
6
  import os
7
+ import shlex
7
8
  import subprocess
8
9
  import tempfile
9
10
  import time
@@ -363,8 +364,14 @@ class RuntimeServerClient:
363
364
  async def _start_in_process(self) -> None:
364
365
  """Initialize the FastAPI app in-process and connect via ASGI transport."""
365
366
  logger.info("Starting in-process runtime server")
367
+ from dreadnode.app.server.app import _warm_litellm, initialize_app
366
368
  from dreadnode.app.server.app import app as server_app
367
- from dreadnode.app.server.app import initialize_app
369
+
370
+ # Warm litellm in background — the lifespan does this for uvicorn,
371
+ # but in-process mode skips lifespan events. Fire before initialize_app
372
+ # so the ~2s import overlaps with app init and boot network calls.
373
+ loop = asyncio.get_running_loop()
374
+ loop.run_in_executor(None, _warm_litellm)
368
375
 
369
376
  await asyncio.to_thread(
370
377
  initialize_app,
@@ -825,15 +832,15 @@ class RuntimeServerClient:
825
832
  str(DEFAULT_SERVER_PORT),
826
833
  ]
827
834
  if self._platform_server:
828
- command.extend(["--platform-server", self._platform_server])
835
+ command.extend(["--platform-server", shlex.quote(self._platform_server)])
829
836
  if self._platform_api_key:
830
- command.extend(["--api-key", self._platform_api_key])
837
+ command.extend(["--api-key", shlex.quote(self._platform_api_key)])
831
838
  if self._platform_organization:
832
- command.extend(["--organization", self._platform_organization])
839
+ command.extend(["--organization", shlex.quote(self._platform_organization)])
833
840
  if self._platform_workspace:
834
- command.extend(["--workspace", self._platform_workspace])
841
+ command.extend(["--workspace", shlex.quote(self._platform_workspace)])
835
842
  if self._platform_project:
836
- command.extend(["--project", self._platform_project])
843
+ command.extend(["--project", shlex.quote(self._platform_project)])
837
844
 
838
845
  self._owned_log_file = tempfile.NamedTemporaryFile( # noqa: SIM115 - manually owned across subprocess lifecycle
839
846
  mode="w+",
@@ -285,9 +285,16 @@ PermissionPrompt.-active {
285
285
  padding: 1 2;
286
286
  }
287
287
 
288
+ #auth-form-wrapper {
289
+ width: 1fr;
290
+ height: auto;
291
+ align: center top;
292
+ }
293
+
288
294
  #auth-content {
289
295
  width: 1fr;
290
- height: 1fr;
296
+ max-width: 80;
297
+ height: auto;
291
298
  }
292
299
 
293
300
  #auth-methods,
@@ -307,13 +314,13 @@ PermissionPrompt.-active {
307
314
  #auth-intro {
308
315
  color: $fg-subtle;
309
316
  margin-bottom: 1;
310
- text-align: center;
311
317
  }
312
318
 
313
319
  #auth-banner {
314
320
  color: $brand;
315
321
  text-align: center;
316
- margin-bottom: 1;
322
+ margin-top: 3;
323
+ margin-bottom: 0;
317
324
  }
318
325
 
319
326
  #auth-method-prompt {
@@ -374,10 +381,29 @@ PermissionPrompt.-active {
374
381
  margin-bottom: 1;
375
382
  }
376
383
 
377
- #auth-api-key {
384
+ #auth-key-bar {
385
+ height: auto;
378
386
  width: 1fr;
379
- max-width: 72;
380
387
  margin-bottom: 1;
388
+ padding: 0;
389
+ }
390
+
391
+ #auth-key-chevron {
392
+ width: 2;
393
+ height: 1;
394
+ color: $accent;
395
+ background: transparent;
396
+ padding: 0;
397
+ }
398
+
399
+ #auth-api-key {
400
+ width: 1fr;
401
+ border: none;
402
+ background: transparent;
403
+ color: $fg;
404
+ height: auto;
405
+ min-height: 1;
406
+ padding: 0;
381
407
  }
382
408
 
383
409
  .-hidden {
@@ -12,7 +12,7 @@ from datetime import datetime, timezone
12
12
  from rich.text import Text
13
13
  from textual import on, work
14
14
  from textual.binding import Binding
15
- from textual.containers import Container, Vertical
15
+ from textual.containers import Container, Horizontal, Vertical
16
16
  from textual.screen import ModalScreen
17
17
  from textual.widgets import Input, OptionList, Static
18
18
  from textual.widgets.option_list import Option
@@ -62,7 +62,7 @@ class AuthModal(ModalScreen[ServerConfig | None]):
62
62
  """Full-screen auth modal — blocks all TUI interaction until authenticated."""
63
63
 
64
64
  BINDINGS: t.ClassVar[list[Binding]] = [
65
- Binding("escape", "cancel", "Cancel", show=False),
65
+ Binding("escape", "escape", "Back / Cancel", show=False, priority=True),
66
66
  Binding("ctrl+c", "cancel", "Cancel", show=False),
67
67
  Binding("up", "move_selection(-1)", "Up", show=False),
68
68
  Binding("down", "move_selection(1)", "Down", show=False),
@@ -70,7 +70,6 @@ class AuthModal(ModalScreen[ServerConfig | None]):
70
70
  Binding("1", "select_method(0)", "Browser", show=False),
71
71
  Binding("2", "select_method(1)", "API key", show=False),
72
72
  Binding("r", "retry", "Retry", show=False),
73
- Binding("b", "back", "Back", show=False),
74
73
  ]
75
74
 
76
75
  def __init__(
@@ -110,31 +109,38 @@ class AuthModal(ModalScreen[ServerConfig | None]):
110
109
  self._open_browser = webbrowser.open
111
110
 
112
111
  def compose(self) -> ComposeResult:
113
- with Container(id="auth-modal"), Vertical(id="auth-content"):
114
- with Vertical(id="auth-methods"):
115
- yield _AuthBanner(id="auth-banner")
116
- yield Static("", id="auth-reason")
117
- yield Static("", id="auth-update-banner")
112
+ with Vertical(id="auth-modal"):
113
+ yield _AuthBanner(id="auth-banner")
114
+ yield Static("", id="auth-reason")
115
+ yield Static("", id="auth-update-banner")
116
+ with Container(id="auth-form-wrapper"), Vertical(id="auth-content"):
118
117
  yield Static(
119
118
  "Dreadnode can be used with your browser or by entering an API key directly.",
120
119
  id="auth-intro",
121
120
  )
122
- yield Static("Select login method:", id="auth-method-prompt")
123
- yield OptionList(id="auth-method-list")
124
- yield Static(
125
- "Use Up/Down to move, Enter/click/1/2 to select", id="auth-method-help"
126
- )
127
- with Vertical(id="auth-device", classes="-hidden"):
128
- yield Static("Waiting for browser authorization...", id="auth-status")
129
- yield Static("", id="auth-user-code")
130
- yield Static("", id="auth-verification-url")
131
- yield Static("", id="auth-device-help")
132
- yield Static("", id="auth-error")
133
- with Vertical(id="auth-key", classes="-hidden"):
134
- yield Static("Paste your API key:", id="auth-key-title")
135
- yield Input(placeholder="dn_...", password=True, id="auth-api-key")
136
- yield Static("Enter to submit, b to go back, Esc to cancel", id="auth-key-help")
137
- yield Static("", id="auth-key-error")
121
+ with Vertical(id="auth-methods"):
122
+ yield Static("Select login method:", id="auth-method-prompt")
123
+ yield OptionList(id="auth-method-list")
124
+ yield Static(
125
+ "Use Up/Down to move, Enter/click/1/2 to select",
126
+ id="auth-method-help",
127
+ )
128
+ with Vertical(id="auth-device", classes="-hidden"):
129
+ yield Static("Waiting for browser authorization...", id="auth-status")
130
+ yield Static("", id="auth-user-code")
131
+ yield Static("", id="auth-verification-url")
132
+ yield Static("", id="auth-device-help")
133
+ yield Static("", id="auth-error")
134
+ with Vertical(id="auth-key", classes="-hidden"):
135
+ yield Static("Paste your API key:", id="auth-key-title")
136
+ with Horizontal(id="auth-key-bar"):
137
+ yield Static(">", id="auth-key-chevron")
138
+ yield Input(placeholder="dn_...", id="auth-api-key")
139
+ yield Static(
140
+ "Enter to submit, Esc to go back",
141
+ id="auth-key-help",
142
+ )
143
+ yield Static("", id="auth-key-error")
138
144
 
139
145
  def on_mount(self) -> None:
140
146
  if self._reason:
@@ -150,6 +156,13 @@ class AuthModal(ModalScreen[ServerConfig | None]):
150
156
  self._cancel_auth_workers()
151
157
  self.dismiss(None)
152
158
 
159
+ def action_escape(self) -> None:
160
+ if self._active_view == "method":
161
+ self.action_cancel()
162
+ else:
163
+ self._cancel_auth_workers()
164
+ self._show_method_view()
165
+
153
166
  def action_move_selection(self, direction: int) -> None:
154
167
  if self._active_view != "method":
155
168
  return
@@ -180,12 +193,6 @@ class AuthModal(ModalScreen[ServerConfig | None]):
180
193
  return
181
194
  self._begin_device_code_flow()
182
195
 
183
- def action_back(self) -> None:
184
- if self._active_view == "method":
185
- return
186
- self._cancel_auth_workers()
187
- self._show_method_view()
188
-
189
196
  @on(Input.Submitted, "#auth-api-key")
190
197
  def _on_api_key_submitted(self) -> None:
191
198
  self._submit_api_key_from_input()
@@ -249,9 +256,7 @@ class AuthModal(ModalScreen[ServerConfig | None]):
249
256
  self.query_one("#auth-methods", Vertical).add_class("-hidden")
250
257
  self.query_one("#auth-key", Vertical).add_class("-hidden")
251
258
  self.query_one("#auth-device", Vertical).remove_class("-hidden")
252
- self.query_one("#auth-device-help", Static).update(
253
- "r to retry, b to go back, Esc to cancel"
254
- )
259
+ self.query_one("#auth-device-help", Static).update("r to retry, Esc to go back")
255
260
  self._set_device_error("")
256
261
 
257
262
  def _begin_device_code_flow(self) -> None: