deepagents-cli 0.0.33__tar.gz → 0.0.34__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 (168) hide show
  1. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/CHANGELOG.md +11 -0
  2. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/PKG-INFO +1 -1
  3. deepagents_cli-0.0.34/deepagents_cli/_version.py +3 -0
  4. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/deepagents_cli/agent.py +101 -10
  5. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/deepagents_cli/app.py +181 -8
  6. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/deepagents_cli/app.tcss +10 -0
  7. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/deepagents_cli/built_in_skills/skill-creator/SKILL.md +0 -16
  8. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/deepagents_cli/configurable_model.py +23 -9
  9. deepagents_cli-0.0.34/deepagents_cli/editor.py +142 -0
  10. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/deepagents_cli/main.py +4 -1
  11. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/deepagents_cli/server_graph.py +4 -1
  12. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/deepagents_cli/widgets/autocomplete.py +1 -0
  13. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/deepagents_cli/widgets/chat_input.py +29 -0
  14. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/deepagents_cli/widgets/welcome.py +1 -0
  15. deepagents_cli-0.0.34/examples/skills/arxiv-search/SKILL.md +33 -0
  16. deepagents_cli-0.0.34/examples/skills/langgraph-docs/SKILL.md +28 -0
  17. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/examples/skills/skill-creator/SKILL.md +0 -16
  18. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/examples/skills/web-research/SKILL.md +1 -26
  19. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/pyproject.toml +1 -1
  20. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/tests/unit_tests/test_agent.py +131 -0
  21. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/tests/unit_tests/test_app.py +458 -0
  22. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/tests/unit_tests/test_chat_input.py +87 -0
  23. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/tests/unit_tests/test_configurable_model.py +63 -31
  24. deepagents_cli-0.0.34/tests/unit_tests/test_editor.py +274 -0
  25. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/tests/unit_tests/test_end_to_end.py +11 -1
  26. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/tests/unit_tests/test_server_graph.py +2 -0
  27. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/uv.lock +1 -1
  28. deepagents_cli-0.0.33/deepagents_cli/_version.py +0 -3
  29. deepagents_cli-0.0.33/examples/skills/arxiv-search/SKILL.md +0 -102
  30. deepagents_cli-0.0.33/examples/skills/langgraph-docs/SKILL.md +0 -35
  31. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/.gitignore +0 -0
  32. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/Makefile +0 -0
  33. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/README.md +0 -0
  34. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/deepagents_cli/__init__.py +0 -0
  35. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/deepagents_cli/__main__.py +0 -0
  36. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/deepagents_cli/_debug.py +0 -0
  37. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/deepagents_cli/_server_config.py +0 -0
  38. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/deepagents_cli/_server_constants.py +0 -0
  39. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/deepagents_cli/_testing_models.py +0 -0
  40. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/deepagents_cli/ask_user.py +0 -0
  41. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/deepagents_cli/built_in_skills/__init__.py +0 -0
  42. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/deepagents_cli/built_in_skills/skill-creator/scripts/init_skill.py +0 -0
  43. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/deepagents_cli/built_in_skills/skill-creator/scripts/quick_validate.py +0 -0
  44. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/deepagents_cli/clipboard.py +0 -0
  45. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/deepagents_cli/config.py +0 -0
  46. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/deepagents_cli/default_agent_prompt.md +0 -0
  47. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/deepagents_cli/file_ops.py +0 -0
  48. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/deepagents_cli/hooks.py +0 -0
  49. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/deepagents_cli/input.py +0 -0
  50. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/deepagents_cli/integrations/__init__.py +0 -0
  51. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/deepagents_cli/integrations/daytona.py +0 -0
  52. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/deepagents_cli/integrations/langsmith.py +0 -0
  53. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/deepagents_cli/integrations/modal.py +0 -0
  54. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/deepagents_cli/integrations/runloop.py +0 -0
  55. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/deepagents_cli/integrations/sandbox_factory.py +0 -0
  56. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/deepagents_cli/integrations/sandbox_provider.py +0 -0
  57. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/deepagents_cli/local_context.py +0 -0
  58. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/deepagents_cli/mcp_tools.py +0 -0
  59. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/deepagents_cli/mcp_trust.py +0 -0
  60. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/deepagents_cli/media_utils.py +0 -0
  61. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/deepagents_cli/model_config.py +0 -0
  62. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/deepagents_cli/non_interactive.py +0 -0
  63. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/deepagents_cli/offload.py +0 -0
  64. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/deepagents_cli/output.py +0 -0
  65. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/deepagents_cli/project_utils.py +0 -0
  66. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/deepagents_cli/py.typed +0 -0
  67. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/deepagents_cli/remote_client.py +0 -0
  68. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/deepagents_cli/server.py +0 -0
  69. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/deepagents_cli/server_manager.py +0 -0
  70. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/deepagents_cli/sessions.py +0 -0
  71. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/deepagents_cli/skills/__init__.py +0 -0
  72. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/deepagents_cli/skills/commands.py +0 -0
  73. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/deepagents_cli/skills/load.py +0 -0
  74. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/deepagents_cli/subagents.py +0 -0
  75. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/deepagents_cli/system_prompt.md +0 -0
  76. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/deepagents_cli/textual_adapter.py +0 -0
  77. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/deepagents_cli/tool_display.py +0 -0
  78. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/deepagents_cli/tools.py +0 -0
  79. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/deepagents_cli/ui.py +0 -0
  80. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/deepagents_cli/unicode_security.py +0 -0
  81. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/deepagents_cli/update_check.py +0 -0
  82. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/deepagents_cli/widgets/__init__.py +0 -0
  83. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/deepagents_cli/widgets/_links.py +0 -0
  84. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/deepagents_cli/widgets/approval.py +0 -0
  85. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/deepagents_cli/widgets/ask_user.py +0 -0
  86. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/deepagents_cli/widgets/diff.py +0 -0
  87. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/deepagents_cli/widgets/history.py +0 -0
  88. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/deepagents_cli/widgets/loading.py +0 -0
  89. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/deepagents_cli/widgets/mcp_viewer.py +0 -0
  90. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/deepagents_cli/widgets/message_store.py +0 -0
  91. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/deepagents_cli/widgets/messages.py +0 -0
  92. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/deepagents_cli/widgets/model_selector.py +0 -0
  93. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/deepagents_cli/widgets/status.py +0 -0
  94. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/deepagents_cli/widgets/thread_selector.py +0 -0
  95. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/deepagents_cli/widgets/tool_renderers.py +0 -0
  96. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/deepagents_cli/widgets/tool_widgets.py +0 -0
  97. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/examples/skills/arxiv-search/arxiv_search.py +0 -0
  98. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/examples/skills/skill-creator/scripts/init_skill.py +0 -0
  99. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/examples/skills/skill-creator/scripts/quick_validate.py +0 -0
  100. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/images/cli.png +0 -0
  101. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/scripts/check_imports.py +0 -0
  102. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/scripts/install.sh +0 -0
  103. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/tests/README.md +0 -0
  104. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/tests/integration_tests/__init__.py +0 -0
  105. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/tests/integration_tests/benchmarks/__init__.py +0 -0
  106. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/tests/integration_tests/benchmarks/test_startup_benchmarks.py +0 -0
  107. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/tests/integration_tests/conftest.py +0 -0
  108. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/tests/integration_tests/test_acp_mode.py +0 -0
  109. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/tests/integration_tests/test_compact_resume.py +0 -0
  110. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/tests/integration_tests/test_sandbox_factory.py +0 -0
  111. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/tests/integration_tests/test_sandbox_operations.py +0 -0
  112. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/tests/unit_tests/__init__.py +0 -0
  113. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/tests/unit_tests/conftest.py +0 -0
  114. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/tests/unit_tests/skills/__init__.py +0 -0
  115. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/tests/unit_tests/skills/test_commands.py +0 -0
  116. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/tests/unit_tests/skills/test_load.py +0 -0
  117. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/tests/unit_tests/skills/test_skills_json.py +0 -0
  118. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/tests/unit_tests/test_approval.py +0 -0
  119. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/tests/unit_tests/test_args.py +0 -0
  120. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/tests/unit_tests/test_ask_user.py +0 -0
  121. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/tests/unit_tests/test_ask_user_middleware.py +0 -0
  122. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/tests/unit_tests/test_autocomplete.py +0 -0
  123. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/tests/unit_tests/test_charset.py +0 -0
  124. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/tests/unit_tests/test_compact_tool.py +0 -0
  125. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/tests/unit_tests/test_config.py +0 -0
  126. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/tests/unit_tests/test_debug.py +0 -0
  127. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/tests/unit_tests/test_exception_handling.py +0 -0
  128. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/tests/unit_tests/test_file_ops.py +0 -0
  129. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/tests/unit_tests/test_history.py +0 -0
  130. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/tests/unit_tests/test_hooks.py +0 -0
  131. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/tests/unit_tests/test_imports.py +0 -0
  132. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/tests/unit_tests/test_input_parsing.py +0 -0
  133. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/tests/unit_tests/test_local_context.py +0 -0
  134. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/tests/unit_tests/test_main.py +0 -0
  135. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/tests/unit_tests/test_main_acp_mode.py +0 -0
  136. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/tests/unit_tests/test_main_args.py +0 -0
  137. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/tests/unit_tests/test_mcp_tools.py +0 -0
  138. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/tests/unit_tests/test_mcp_trust.py +0 -0
  139. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/tests/unit_tests/test_mcp_viewer.py +0 -0
  140. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/tests/unit_tests/test_media_utils.py +0 -0
  141. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/tests/unit_tests/test_message_store.py +0 -0
  142. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/tests/unit_tests/test_messages.py +0 -0
  143. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/tests/unit_tests/test_model_config.py +0 -0
  144. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/tests/unit_tests/test_model_selector.py +0 -0
  145. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/tests/unit_tests/test_model_switch.py +0 -0
  146. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/tests/unit_tests/test_non_interactive.py +0 -0
  147. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/tests/unit_tests/test_offload.py +0 -0
  148. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/tests/unit_tests/test_output.py +0 -0
  149. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/tests/unit_tests/test_reload.py +0 -0
  150. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/tests/unit_tests/test_remote_client.py +0 -0
  151. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/tests/unit_tests/test_server.py +0 -0
  152. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/tests/unit_tests/test_server_config.py +0 -0
  153. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/tests/unit_tests/test_server_helpers.py +0 -0
  154. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/tests/unit_tests/test_server_manager.py +0 -0
  155. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/tests/unit_tests/test_sessions.py +0 -0
  156. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/tests/unit_tests/test_shell_allow_list.py +0 -0
  157. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/tests/unit_tests/test_status.py +0 -0
  158. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/tests/unit_tests/test_subagents.py +0 -0
  159. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/tests/unit_tests/test_textual_adapter.py +0 -0
  160. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/tests/unit_tests/test_thread_selector.py +0 -0
  161. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/tests/unit_tests/test_token_tracker.py +0 -0
  162. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/tests/unit_tests/test_ui.py +0 -0
  163. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/tests/unit_tests/test_unicode_security.py +0 -0
  164. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/tests/unit_tests/test_update_check.py +0 -0
  165. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/tests/unit_tests/test_version.py +0 -0
  166. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/tests/unit_tests/test_welcome.py +0 -0
  167. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/tests/unit_tests/tools/__init__.py +0 -0
  168. {deepagents_cli-0.0.33 → deepagents_cli-0.0.34}/tests/unit_tests/tools/test_fetch_url.py +0 -0
@@ -1,5 +1,16 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.0.34](https://github.com/langchain-ai/deepagents/compare/deepagents-cli==0.0.33...deepagents-cli==0.0.34) (2026-03-17)
4
+
5
+ ### Features
6
+
7
+ * External editor support via `ctrl+x` and `/editor` ([#1861](https://github.com/langchain-ai/deepagents/issues/1861)) ([bf5d088](https://github.com/langchain-ai/deepagents/commit/bf5d088d4b3cee6c7e44c3abe3736f9972897896))
8
+ * Defer HITL approval menu while user is typing ([#1833](https://github.com/langchain-ai/deepagents/issues/1833)) ([1d1572e](https://github.com/langchain-ai/deepagents/commit/1d1572e40cc9f87b97832cbe2b9152c281f8ec92))
9
+
10
+ ### Bug Fixes
11
+
12
+ * Resolve config-defined providers during runtime model swaps ([#1941](https://github.com/langchain-ai/deepagents/issues/1941)) ([aebc660](https://github.com/langchain-ai/deepagents/commit/aebc660321895909f6b6eb71e72a99ca7754bcf1))
13
+
3
14
  ## [0.0.33](https://github.com/langchain-ai/deepagents/compare/deepagents-cli==0.0.32...deepagents-cli==0.0.33) (2026-03-16)
4
15
 
5
16
  ### Highlights
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: deepagents-cli
3
- Version: 0.0.33
3
+ Version: 0.0.34
4
4
  Summary: Terminal interface for Deep Agents - interactive AI agent with file operations, shell access, and sub-agent capabilities.
5
5
  Project-URL: Homepage, https://docs.langchain.com/oss/python/deepagents/overview
6
6
  Project-URL: Documentation, https://reference.langchain.com/python/deepagents/
@@ -0,0 +1,3 @@
1
+ """Version information for `deepagents-cli`."""
2
+
3
+ __version__ = "0.0.34" # x-release-please-version
@@ -7,6 +7,7 @@ import os
7
7
  import re
8
8
  import shutil
9
9
  import tempfile
10
+ import tomllib
10
11
  from pathlib import Path
11
12
  from typing import TYPE_CHECKING, Any
12
13
 
@@ -19,6 +20,7 @@ if TYPE_CHECKING:
19
20
  from collections.abc import Callable, Sequence
20
21
 
21
22
  from deepagents.backends.sandbox import SandboxBackendProtocol
23
+ from deepagents.middleware.async_subagents import AsyncSubAgent
22
24
  from deepagents.middleware.subagents import CompiledSubAgent, SubAgent
23
25
  from langchain.agents.middleware import InterruptOnConfig
24
26
  from langchain.agents.middleware.types import AgentState
@@ -63,6 +65,74 @@ REQUIRE_COMPACT_TOOL_APPROVAL: bool = True
63
65
  """When `True`, `compact_conversation` requires HITL approval like other gated tools."""
64
66
 
65
67
 
68
+ def load_async_subagents(config_path: Path | None = None) -> list[AsyncSubAgent]:
69
+ """Load async subagent definitions from `config.toml`.
70
+
71
+ Reads the `[async_subagents]` section where each sub-table defines a remote
72
+ LangGraph deployment:
73
+
74
+ ```toml
75
+ [async_subagents.researcher]
76
+ description = "Research agent"
77
+ url = "https://my-deployment.langsmith.dev"
78
+ graph_id = "agent"
79
+ ```
80
+
81
+ Args:
82
+ config_path: Path to config file.
83
+
84
+ Defaults to `~/.deepagents/config.toml`.
85
+
86
+ Returns:
87
+ List of `AsyncSubAgent` specs (empty if section is absent or invalid).
88
+ """
89
+ if config_path is None:
90
+ config_path = Path.home() / ".deepagents" / "config.toml"
91
+
92
+ if not config_path.exists():
93
+ return []
94
+
95
+ try:
96
+ with config_path.open("rb") as f:
97
+ data = tomllib.load(f)
98
+ except (tomllib.TOMLDecodeError, PermissionError, OSError) as e:
99
+ logger.warning("Could not read async subagents from %s: %s", config_path, e)
100
+ console.print(
101
+ f"[bold yellow]Warning:[/bold yellow] Could not read async subagents "
102
+ f"from {config_path}: {e}",
103
+ )
104
+ return []
105
+
106
+ section = data.get("async_subagents")
107
+ if not isinstance(section, dict):
108
+ return []
109
+
110
+ required = {"description", "graph_id"}
111
+ agents: list[AsyncSubAgent] = []
112
+ for name, spec in section.items():
113
+ if not isinstance(spec, dict):
114
+ logger.warning("Skipping async subagent '%s': expected a table", name)
115
+ continue
116
+ missing = required - spec.keys()
117
+ if missing:
118
+ logger.warning(
119
+ "Skipping async subagent '%s': missing fields %s", name, missing
120
+ )
121
+ continue
122
+ agent: AsyncSubAgent = {
123
+ "name": name,
124
+ "description": spec["description"],
125
+ "graph_id": spec["graph_id"],
126
+ }
127
+ if "url" in spec and isinstance(spec["url"], str):
128
+ agent["url"] = spec["url"]
129
+ if "headers" in spec and isinstance(spec["headers"], dict):
130
+ agent["headers"] = spec["headers"]
131
+ agents.append(agent)
132
+
133
+ return agents
134
+
135
+
66
136
  def list_agents(*, output_format: OutputFormat = "text") -> None:
67
137
  """List all available agents.
68
138
 
@@ -537,6 +607,11 @@ def _add_interrupt_on() -> dict[str, InterruptOnConfig]:
537
607
  "description": _format_task_description, # type: ignore[typeddict-item] # Callable description narrower than TypedDict expects
538
608
  }
539
609
 
610
+ async_subagent_interrupt_config: InterruptOnConfig = {
611
+ "allowed_decisions": ["approve", "reject"],
612
+ "description": "Launch, update, or cancel a remote async subagent.",
613
+ }
614
+
540
615
  interrupt_map: dict[str, InterruptOnConfig] = {
541
616
  "execute": execute_interrupt_config,
542
617
  "write_file": write_file_interrupt_config,
@@ -544,6 +619,9 @@ def _add_interrupt_on() -> dict[str, InterruptOnConfig]:
544
619
  "web_search": web_search_interrupt_config,
545
620
  "fetch_url": fetch_url_interrupt_config,
546
621
  "task": task_interrupt_config,
622
+ "launch_async_subagent": async_subagent_interrupt_config,
623
+ "update_async_subagent": async_subagent_interrupt_config,
624
+ "cancel_async_subagent": async_subagent_interrupt_config,
547
625
  }
548
626
 
549
627
  if REQUIRE_COMPACT_TOOL_APPROVAL:
@@ -577,6 +655,7 @@ def create_cli_agent(
577
655
  mcp_server_info: list[MCPServerInfo] | None = None,
578
656
  cwd: str | Path | None = None,
579
657
  project_context: ProjectContext | None = None,
658
+ async_subagents: list[AsyncSubAgent] | None = None,
580
659
  ) -> tuple[Pregel, CompositeBackend]:
581
660
  """Create a CLI-configured agent with flexible options.
582
661
 
@@ -619,6 +698,9 @@ def create_cli_agent(
619
698
  project_context: Explicit project path context for project-sensitive
620
699
  behavior such as project `AGENTS.md` files, skills, subagents, and
621
700
  MCP trust.
701
+ async_subagents: Remote LangGraph deployments to expose as async subagent tools.
702
+
703
+ Loaded from `[async_subagents]` in `config.toml` or passed directly.
622
704
 
623
705
  Returns:
624
706
  2-tuple of `(agent_graph, backend)`
@@ -817,14 +899,23 @@ def create_cli_agent(
817
899
  )
818
900
 
819
901
  # Create the agent
820
- agent = create_deep_agent(
821
- model=model,
822
- system_prompt=system_prompt,
823
- tools=tools,
824
- backend=composite_backend,
825
- middleware=agent_middleware,
826
- interrupt_on=interrupt_on,
827
- checkpointer=checkpointer,
828
- subagents=custom_subagents or None,
829
- ).with_config(config)
902
+ #
903
+ # TODO: revert to direct keyword arguments once the CLI pins SDK >=0.5.0.
904
+ # We use **kwargs here because `async_subagents` was added in SDK 0.5.0 but
905
+ # the CLI still pins 0.4.x. Passing an unknown kwarg — even as None — raises
906
+ # TypeError, so we must omit it from the dict entirely when unused.
907
+ agent_kwargs: dict[str, Any] = {
908
+ "model": model,
909
+ "system_prompt": system_prompt,
910
+ "tools": tools,
911
+ "backend": composite_backend,
912
+ "middleware": agent_middleware,
913
+ "interrupt_on": interrupt_on,
914
+ "checkpointer": checkpointer,
915
+ "subagents": custom_subagents or None,
916
+ }
917
+ if async_subagents:
918
+ agent_kwargs["async_subagents"] = async_subagents
919
+
920
+ agent = create_deep_agent(**agent_kwargs).with_config(config)
830
921
  return agent, composite_backend
@@ -26,6 +26,7 @@ from textual.css.query import NoMatches
26
26
  from textual.message import Message
27
27
  from textual.screen import ModalScreen
28
28
  from textual.style import Style as TStyle
29
+ from textual.widgets import Static
29
30
 
30
31
  from deepagents_cli.clipboard import copy_selection_to_clipboard
31
32
  from deepagents_cli.config import (
@@ -90,7 +91,6 @@ if TYPE_CHECKING:
90
91
  from textual.events import Click, MouseUp, Paste
91
92
  from textual.scrollbar import ScrollUp
92
93
  from textual.widget import Widget
93
- from textual.widgets import Static
94
94
  from textual.worker import Worker
95
95
 
96
96
  from deepagents_cli.ask_user import AskUserWidgetResult, Question
@@ -255,6 +255,18 @@ def _extract_model_params_flag(raw_arg: str) -> tuple[str, dict[str, Any] | None
255
255
 
256
256
  InputMode = Literal["normal", "shell", "command"]
257
257
 
258
+ _TYPING_IDLE_THRESHOLD_SECONDS: float = 2.0
259
+ """Seconds since the last keystroke after which the user is considered idle and
260
+ a pending approval widget can be shown.
261
+
262
+ Two seconds balances responsiveness with avoiding accidental approval
263
+ key presses.
264
+ """
265
+
266
+ _DEFERRED_APPROVAL_TIMEOUT_SECONDS: float = 30.0
267
+ """Maximum seconds the deferred-approval worker will wait for the user to stop
268
+ typing before showing the approval widget regardless."""
269
+
258
270
 
259
271
  @dataclass(frozen=True, slots=True)
260
272
  class QueuedMessage:
@@ -503,6 +515,13 @@ class DeepAgentsApp(App):
503
515
  show=False,
504
516
  priority=True,
505
517
  ),
518
+ Binding(
519
+ "ctrl+x",
520
+ "open_editor",
521
+ "Open Editor",
522
+ show=False,
523
+ priority=True,
524
+ ),
506
525
  # Approval menu keys (handled at App level for reliability)
507
526
  Binding("up", "approval_up", "Up", show=False),
508
527
  Binding("k", "approval_up", "Up", show=False),
@@ -621,6 +640,9 @@ class DeepAgentsApp(App):
621
640
  self._shell_running = False
622
641
  self._loading_widget: LoadingWidget | None = None
623
642
  self._token_tracker: TextualTokenTracker | None = None
643
+ # Typing-aware approval deferral state
644
+ self._last_typed_at: float | None = None
645
+ self._approval_placeholder: Static | None = None
624
646
  # Cumulative usage stats across all turns in this session
625
647
  self._session_stats: SessionStats = SessionStats()
626
648
  # User message queue for sequential processing
@@ -1220,27 +1242,114 @@ class DeepAgentsApp(App):
1220
1242
  menu = ApprovalMenu(action_requests, assistant_id, id=unique_id)
1221
1243
  menu.set_future(result_future)
1222
1244
 
1223
- # Store reference
1224
1245
  self._pending_approval_widget = menu
1225
1246
 
1226
- # Mount approval inline in messages area (not replacing ChatInput)
1247
+ if self._is_user_typing():
1248
+ # Show a placeholder until the user stops typing, then swap in the
1249
+ # real ApprovalMenu. This prevents accidental key presses (e.g.
1250
+ # 'y', 'n') from triggering approval decisions mid-sentence.
1251
+ placeholder = Static(
1252
+ "Waiting for typing to finish...",
1253
+ classes="approval-placeholder",
1254
+ )
1255
+ self._approval_placeholder = placeholder
1256
+ try:
1257
+ messages = self.query_one("#messages", Container)
1258
+ await self._mount_before_queued(messages, placeholder)
1259
+ self.call_after_refresh(placeholder.scroll_visible)
1260
+ except Exception:
1261
+ logger.exception("Failed to mount approval placeholder")
1262
+ # Placeholder failed — fall back to showing the menu directly
1263
+ # so the future is always resolvable.
1264
+ self._approval_placeholder = None
1265
+ await self._mount_approval_widget(menu, result_future)
1266
+ return result_future
1267
+
1268
+ self.run_worker(
1269
+ self._deferred_show_approval(placeholder, menu, result_future),
1270
+ exclusive=False,
1271
+ )
1272
+ else:
1273
+ await self._mount_approval_widget(menu, result_future)
1274
+
1275
+ return result_future
1276
+
1277
+ async def _mount_approval_widget(
1278
+ self,
1279
+ menu: ApprovalMenu,
1280
+ result_future: asyncio.Future[dict[str, str]],
1281
+ ) -> None:
1282
+ """Mount the approval menu widget inline in the messages area.
1283
+
1284
+ If mounting fails, clears `_pending_approval_widget` and propagates
1285
+ the exception via `result_future`.
1286
+
1287
+ Args:
1288
+ menu: The `ApprovalMenu` instance to mount.
1289
+ result_future: The future to resolve/reject for the caller.
1290
+ """
1227
1291
  try:
1228
1292
  messages = self.query_one("#messages", Container)
1229
1293
  await self._mount_before_queued(messages, menu)
1230
- # Scroll to make approval visible (but don't re-anchor)
1231
1294
  self.call_after_refresh(menu.scroll_visible)
1232
- # Focus approval menu
1233
1295
  self.call_after_refresh(menu.focus)
1234
1296
  except Exception as e:
1235
1297
  logger.exception(
1236
1298
  "Failed to mount approval menu (id=%s) in messages container",
1237
- unique_id,
1299
+ menu.id,
1238
1300
  )
1239
1301
  self._pending_approval_widget = None
1240
1302
  if not result_future.done():
1241
1303
  result_future.set_exception(e)
1242
1304
 
1243
- return result_future
1305
+ async def _deferred_show_approval(
1306
+ self,
1307
+ placeholder: Static,
1308
+ menu: ApprovalMenu,
1309
+ result_future: asyncio.Future[dict[str, str]],
1310
+ ) -> None:
1311
+ """Wait until the user is idle, then swap the placeholder for the real menu.
1312
+
1313
+ Exits early if the placeholder has already been detached (e.g. the
1314
+ approval was cancelled while waiting). In that case the future is
1315
+ cancelled so the caller is not left hanging.
1316
+
1317
+ Args:
1318
+ placeholder: The temporary placeholder widget currently mounted.
1319
+ menu: The `ApprovalMenu` to show once the user stops typing.
1320
+ result_future: The future backing this approval flow.
1321
+ """
1322
+ deadline = _monotonic() + _DEFERRED_APPROVAL_TIMEOUT_SECONDS
1323
+ while self._is_user_typing(): # Simple polling
1324
+ if _monotonic() > deadline:
1325
+ logger.warning(
1326
+ "Timed out waiting for user to stop typing; showing approval now"
1327
+ )
1328
+ break
1329
+ await asyncio.sleep(0.2)
1330
+
1331
+ # Guard: if the placeholder was already removed (e.g. agent cancelled
1332
+ # the approval while we were waiting), clean up and cancel the future.
1333
+ if not placeholder.is_attached:
1334
+ logger.warning(
1335
+ "Approval placeholder detached before menu shown (id=%s)",
1336
+ menu.id,
1337
+ )
1338
+ self._approval_placeholder = None
1339
+ self._pending_approval_widget = None
1340
+ if not result_future.done():
1341
+ result_future.cancel()
1342
+ return
1343
+
1344
+ self._approval_placeholder = None
1345
+ try:
1346
+ await placeholder.remove()
1347
+ except Exception:
1348
+ logger.warning(
1349
+ "Failed to remove approval placeholder during swap",
1350
+ exc_info=True,
1351
+ )
1352
+ await self._mount_approval_widget(menu, result_future)
1244
1353
 
1245
1354
  def _on_auto_approve_enabled(self) -> None:
1246
1355
  """Handle auto-approve being enabled via the HITL approval menu.
@@ -1414,11 +1523,42 @@ class DeepAgentsApp(App):
1414
1523
  if self._status_bar:
1415
1524
  self._status_bar.set_mode(event.mode)
1416
1525
 
1526
+ def on_chat_input_typing(
1527
+ self,
1528
+ event: ChatInput.Typing, # noqa: ARG002 # Textual event handler signature
1529
+ ) -> None:
1530
+ """Record the most recent keystroke time for typing-aware approval deferral."""
1531
+ self._last_typed_at = _monotonic()
1532
+
1533
+ def _is_user_typing(self) -> bool:
1534
+ """Return whether the user typed recently (within the idle threshold).
1535
+
1536
+ Returns:
1537
+ `True` if the last recorded typing event occurred within the last
1538
+ `_TYPING_IDLE_THRESHOLD_SECONDS` seconds, `False` otherwise.
1539
+ """
1540
+ if self._last_typed_at is None:
1541
+ return False
1542
+ return (_monotonic() - self._last_typed_at) < _TYPING_IDLE_THRESHOLD_SECONDS
1543
+
1417
1544
  async def on_approval_menu_decided(
1418
1545
  self,
1419
1546
  event: Any, # noqa: ARG002, ANN401 # Textual event handler signature
1420
1547
  ) -> None:
1421
1548
  """Handle approval menu decision - remove from messages and refocus input."""
1549
+ # Defensively remove any lingering placeholder (should already be gone
1550
+ # once the deferred worker swaps it, but guard against edge cases).
1551
+ if self._approval_placeholder is not None:
1552
+ if self._approval_placeholder.is_attached:
1553
+ try:
1554
+ await self._approval_placeholder.remove()
1555
+ except Exception:
1556
+ logger.warning(
1557
+ "Failed to remove approval placeholder during cleanup",
1558
+ exc_info=True,
1559
+ )
1560
+ self._approval_placeholder = None
1561
+
1422
1562
  # Remove ApprovalMenu using stored reference
1423
1563
  if self._pending_approval_widget:
1424
1564
  await self._pending_approval_widget.remove()
@@ -1659,12 +1799,13 @@ class DeepAgentsApp(App):
1659
1799
  elif cmd == "/help":
1660
1800
  await self._mount_message(UserMessage(command))
1661
1801
  help_body = (
1662
- "Commands: /quit, /clear, /offload, /mcp, "
1802
+ "Commands: /quit, /clear, /offload, /editor, /mcp, "
1663
1803
  "/model [--model-params JSON] [--default], /reload, /remember, "
1664
1804
  "/tokens, /threads, /trace, /changelog, /docs, /feedback, /help\n\n"
1665
1805
  "Interactive Features:\n"
1666
1806
  " Enter Submit your message\n"
1667
1807
  f" {newline_shortcut():<15} Insert newline\n"
1808
+ " Ctrl+X Open prompt in external editor\n"
1668
1809
  " Shift+Tab Toggle auto-approve mode\n"
1669
1810
  " @filename Auto-complete files and inject content\n"
1670
1811
  " /command Slash commands (/help, /clear, /quit)\n"
@@ -1728,6 +1869,8 @@ class DeepAgentsApp(App):
1728
1869
  await self._mount_message(
1729
1870
  AppMessage(f"Started new thread: {new_thread_id}")
1730
1871
  )
1872
+ elif cmd == "/editor":
1873
+ await self.action_open_editor()
1731
1874
  elif cmd in {"/offload", "/compact"}:
1732
1875
  await self._mount_message(UserMessage(command))
1733
1876
  await self._handle_offload()
@@ -2950,6 +3093,36 @@ class DeepAgentsApp(App):
2950
3093
  if self._pending_approval_widget:
2951
3094
  self._pending_approval_widget.action_select_reject()
2952
3095
 
3096
+ async def action_open_editor(self) -> None:
3097
+ """Open the current prompt text in an external editor ($VISUAL/$EDITOR)."""
3098
+ from deepagents_cli.editor import open_in_editor
3099
+
3100
+ chat_input = self._chat_input
3101
+ if not chat_input or not chat_input._text_area:
3102
+ return
3103
+
3104
+ current_text = chat_input._text_area.text or ""
3105
+
3106
+ edited: str | None = None
3107
+ try:
3108
+ with self.suspend():
3109
+ edited = open_in_editor(current_text)
3110
+ except Exception:
3111
+ logger.warning("External editor failed", exc_info=True)
3112
+ self.notify(
3113
+ "External editor failed. Check $VISUAL/$EDITOR.",
3114
+ severity="error",
3115
+ timeout=5,
3116
+ )
3117
+ chat_input.focus_input()
3118
+ return
3119
+
3120
+ if edited is not None:
3121
+ chat_input._text_area.text = edited
3122
+ lines = edited.split("\n")
3123
+ chat_input._text_area.move_cursor((len(lines) - 1, len(lines[-1])))
3124
+ chat_input.focus_input()
3125
+
2953
3126
  def on_paste(self, event: Paste) -> None:
2954
3127
  """Route unfocused paste events to chat input for drag/drop reliability."""
2955
3128
  if not self._chat_input:
@@ -51,6 +51,16 @@ Screen {
51
51
  border: solid $warning;
52
52
  }
53
53
 
54
+ /* Placeholder shown while the user is actively typing (approval deferred) */
55
+ .approval-placeholder {
56
+ height: auto;
57
+ margin: 1 0;
58
+ padding: 0 1;
59
+ border: solid $panel;
60
+ color: $text-muted;
61
+ text-style: italic;
62
+ }
63
+
54
64
  .approval-menu .approval-title {
55
65
  text-style: bold;
56
66
  color: $warning;
@@ -7,15 +7,6 @@ compatibility: designed for deepagents-cli
7
7
 
8
8
  # Skill Creator
9
9
 
10
- This skill provides guidance for creating effective skills.
11
-
12
- ## About Skills
13
-
14
- Skills are modular, self-contained packages that extend agent capabilities by providing
15
- specialized knowledge, workflows, and tools. Think of them as "onboarding guides" for specific
16
- domains or tasks—they transform a general-purpose agent into a specialized agent
17
- equipped with procedural knowledge and domain expertise.
18
-
19
10
  ### Skill Location for Deepagents
20
11
 
21
12
  The deepagents CLI loads skills from five sources, listed here from lowest to highest precedence:
@@ -44,13 +35,6 @@ Example directory layout:
44
35
  └── ...
45
36
  ```
46
37
 
47
- ### What Skills Provide
48
-
49
- 1. Specialized workflows - Multi-step procedures for specific domains
50
- 2. Tool integrations - Instructions for working with specific file formats or APIs
51
- 3. Domain expertise - Company-specific knowledge, schemas, business logic
52
- 4. Bundled resources - Scripts, references, and assets for complex and repetitive tasks
53
-
54
38
  ## Core Principles
55
39
 
56
40
  ### Concise is Key
@@ -10,7 +10,7 @@ from __future__ import annotations
10
10
  import logging
11
11
  from typing import TYPE_CHECKING, Any
12
12
 
13
- from deepagents._models import model_matches_spec, resolve_model # noqa: PLC2701
13
+ from deepagents._models import model_matches_spec # noqa: PLC2701
14
14
  from langchain.agents.middleware.types import (
15
15
  AgentMiddleware,
16
16
  ModelRequest,
@@ -41,19 +41,22 @@ class CLIContext(TypedDict, total=False):
41
41
 
42
42
 
43
43
  def _is_anthropic_model(model: object) -> bool:
44
- """Check whether a resolved model is an Anthropic `ChatAnthropic` instance.
44
+ """Check whether a resolved model reports `'anthropic'` as its provider.
45
45
 
46
- Returns `False` if `langchain-anthropic` is not installed.
46
+ Uses `_get_ls_params` from `BaseChatModel` to read the provider name.
47
47
 
48
48
  Returns:
49
- `True` if the model is a `ChatAnthropic` instance.
49
+ `True` if the model's `ls_provider` is `'anthropic'`.
50
50
  """
51
51
  try:
52
- from langchain_anthropic import ChatAnthropic
53
- except ImportError:
54
- logger.debug("langchain_anthropic not installed; assuming non-Anthropic model")
52
+ ls_params = model._get_ls_params() # type: ignore[attr-defined]
53
+ except (AttributeError, TypeError, RuntimeError):
54
+ logger.debug(
55
+ "_get_ls_params raised for %s; assuming non-Anthropic",
56
+ type(model).__name__,
57
+ )
55
58
  return False
56
- return isinstance(model, ChatAnthropic)
59
+ return isinstance(ls_params, dict) and ls_params.get("ls_provider") == "anthropic"
57
60
 
58
61
 
59
62
  _ANTHROPIC_ONLY_SETTINGS: set[str] = {"cache_control"}
@@ -83,8 +86,19 @@ def _apply_overrides(request: ModelRequest) -> ModelRequest:
83
86
  new_model = None
84
87
  model = ctx.get("model")
85
88
  if model and not model_matches_spec(request.model, model):
89
+ from deepagents_cli.config import create_model
90
+ from deepagents_cli.model_config import ModelConfigError
91
+
86
92
  logger.debug("Overriding model to %s", model)
87
- new_model = resolve_model(model)
93
+ try:
94
+ new_model = create_model(model).model
95
+ except ModelConfigError:
96
+ logger.exception(
97
+ "Failed to resolve runtime model override '%s'; "
98
+ "continuing with current model",
99
+ model,
100
+ )
101
+ return request
88
102
  overrides["model"] = new_model
89
103
 
90
104
  # Param merge