dreadnode 2.0.4__tar.gz → 2.0.5__tar.gz

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