deepagents-cli 0.0.29__tar.gz → 0.0.30__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 (135) hide show
  1. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/CHANGELOG.md +14 -0
  2. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/PKG-INFO +2 -1
  3. deepagents_cli-0.0.30/deepagents_cli/_version.py +3 -0
  4. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/deepagents_cli/app.py +29 -7
  5. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/deepagents_cli/main.py +159 -8
  6. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/deepagents_cli/model_config.py +122 -1
  7. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/deepagents_cli/ui.py +1 -0
  8. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/deepagents_cli/widgets/chat_input.py +0 -2
  9. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/deepagents_cli/widgets/messages.py +52 -8
  10. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/deepagents_cli/widgets/model_selector.py +137 -2
  11. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/deepagents_cli/widgets/status.py +22 -1
  12. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/pyproject.toml +4 -1
  13. deepagents_cli-0.0.30/tests/integration_tests/test_acp_mode.py +112 -0
  14. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/tests/unit_tests/test_autocomplete.py +4 -1
  15. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/tests/unit_tests/test_chat_input.py +42 -0
  16. deepagents_cli-0.0.30/tests/unit_tests/test_main_acp_mode.py +182 -0
  17. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/tests/unit_tests/test_messages.py +151 -1
  18. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/tests/unit_tests/test_model_config.py +226 -0
  19. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/tests/unit_tests/test_model_selector.py +163 -1
  20. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/tests/unit_tests/test_model_switch.py +2 -0
  21. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/uv.lock +137 -233
  22. deepagents_cli-0.0.29/deepagents_cli/_version.py +0 -3
  23. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/.gitignore +0 -0
  24. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/Makefile +0 -0
  25. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/README.md +0 -0
  26. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/deepagents_cli/__init__.py +0 -0
  27. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/deepagents_cli/__main__.py +0 -0
  28. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/deepagents_cli/agent.py +0 -0
  29. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/deepagents_cli/app.tcss +0 -0
  30. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/deepagents_cli/built_in_skills/__init__.py +0 -0
  31. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/deepagents_cli/built_in_skills/skill-creator/SKILL.md +0 -0
  32. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/deepagents_cli/built_in_skills/skill-creator/scripts/init_skill.py +0 -0
  33. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/deepagents_cli/built_in_skills/skill-creator/scripts/quick_validate.py +0 -0
  34. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/deepagents_cli/clipboard.py +0 -0
  35. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/deepagents_cli/config.py +0 -0
  36. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/deepagents_cli/default_agent_prompt.md +0 -0
  37. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/deepagents_cli/file_ops.py +0 -0
  38. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/deepagents_cli/hooks.py +0 -0
  39. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/deepagents_cli/input.py +0 -0
  40. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/deepagents_cli/integrations/__init__.py +0 -0
  41. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/deepagents_cli/integrations/daytona.py +0 -0
  42. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/deepagents_cli/integrations/langsmith.py +0 -0
  43. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/deepagents_cli/integrations/modal.py +0 -0
  44. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/deepagents_cli/integrations/runloop.py +0 -0
  45. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/deepagents_cli/integrations/sandbox_factory.py +0 -0
  46. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/deepagents_cli/integrations/sandbox_provider.py +0 -0
  47. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/deepagents_cli/local_context.py +0 -0
  48. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/deepagents_cli/mcp_tools.py +0 -0
  49. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/deepagents_cli/mcp_trust.py +0 -0
  50. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/deepagents_cli/media_utils.py +0 -0
  51. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/deepagents_cli/non_interactive.py +0 -0
  52. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/deepagents_cli/project_utils.py +0 -0
  53. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/deepagents_cli/py.typed +0 -0
  54. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/deepagents_cli/sessions.py +0 -0
  55. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/deepagents_cli/skills/__init__.py +0 -0
  56. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/deepagents_cli/skills/commands.py +0 -0
  57. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/deepagents_cli/skills/load.py +0 -0
  58. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/deepagents_cli/subagents.py +0 -0
  59. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/deepagents_cli/system_prompt.md +0 -0
  60. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/deepagents_cli/textual_adapter.py +0 -0
  61. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/deepagents_cli/tool_display.py +0 -0
  62. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/deepagents_cli/tools.py +0 -0
  63. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/deepagents_cli/unicode_security.py +0 -0
  64. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/deepagents_cli/update_check.py +0 -0
  65. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/deepagents_cli/widgets/__init__.py +0 -0
  66. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/deepagents_cli/widgets/_links.py +0 -0
  67. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/deepagents_cli/widgets/approval.py +0 -0
  68. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/deepagents_cli/widgets/autocomplete.py +0 -0
  69. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/deepagents_cli/widgets/diff.py +0 -0
  70. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/deepagents_cli/widgets/history.py +0 -0
  71. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/deepagents_cli/widgets/loading.py +0 -0
  72. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/deepagents_cli/widgets/mcp_viewer.py +0 -0
  73. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/deepagents_cli/widgets/message_store.py +0 -0
  74. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/deepagents_cli/widgets/thread_selector.py +0 -0
  75. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/deepagents_cli/widgets/tool_renderers.py +0 -0
  76. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/deepagents_cli/widgets/tool_widgets.py +0 -0
  77. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/deepagents_cli/widgets/welcome.py +0 -0
  78. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/examples/skills/arxiv-search/SKILL.md +0 -0
  79. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/examples/skills/arxiv-search/arxiv_search.py +0 -0
  80. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/examples/skills/langgraph-docs/SKILL.md +0 -0
  81. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/examples/skills/skill-creator/SKILL.md +0 -0
  82. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/examples/skills/skill-creator/scripts/init_skill.py +0 -0
  83. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/examples/skills/skill-creator/scripts/quick_validate.py +0 -0
  84. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/examples/skills/web-research/SKILL.md +0 -0
  85. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/images/cli.png +0 -0
  86. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/scripts/check_imports.py +0 -0
  87. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/tests/README.md +0 -0
  88. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/tests/integration_tests/__init__.py +0 -0
  89. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/tests/integration_tests/benchmarks/__init__.py +0 -0
  90. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/tests/integration_tests/benchmarks/test_startup_benchmarks.py +0 -0
  91. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/tests/integration_tests/conftest.py +0 -0
  92. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/tests/integration_tests/test_sandbox_factory.py +0 -0
  93. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/tests/integration_tests/test_sandbox_operations.py +0 -0
  94. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/tests/unit_tests/__init__.py +0 -0
  95. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/tests/unit_tests/conftest.py +0 -0
  96. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/tests/unit_tests/skills/__init__.py +0 -0
  97. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/tests/unit_tests/skills/test_commands.py +0 -0
  98. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/tests/unit_tests/skills/test_load.py +0 -0
  99. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/tests/unit_tests/test_agent.py +0 -0
  100. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/tests/unit_tests/test_app.py +0 -0
  101. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/tests/unit_tests/test_approval.py +0 -0
  102. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/tests/unit_tests/test_args.py +0 -0
  103. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/tests/unit_tests/test_charset.py +0 -0
  104. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/tests/unit_tests/test_compact.py +0 -0
  105. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/tests/unit_tests/test_compact_tool.py +0 -0
  106. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/tests/unit_tests/test_config.py +0 -0
  107. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/tests/unit_tests/test_end_to_end.py +0 -0
  108. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/tests/unit_tests/test_exception_handling.py +0 -0
  109. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/tests/unit_tests/test_file_ops.py +0 -0
  110. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/tests/unit_tests/test_history.py +0 -0
  111. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/tests/unit_tests/test_hooks.py +0 -0
  112. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/tests/unit_tests/test_imports.py +0 -0
  113. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/tests/unit_tests/test_input_parsing.py +0 -0
  114. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/tests/unit_tests/test_local_context.py +0 -0
  115. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/tests/unit_tests/test_main.py +0 -0
  116. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/tests/unit_tests/test_main_args.py +0 -0
  117. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/tests/unit_tests/test_mcp_tools.py +0 -0
  118. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/tests/unit_tests/test_mcp_trust.py +0 -0
  119. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/tests/unit_tests/test_mcp_viewer.py +0 -0
  120. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/tests/unit_tests/test_media_utils.py +0 -0
  121. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/tests/unit_tests/test_message_store.py +0 -0
  122. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/tests/unit_tests/test_non_interactive.py +0 -0
  123. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/tests/unit_tests/test_sessions.py +0 -0
  124. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/tests/unit_tests/test_shell_allow_list.py +0 -0
  125. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/tests/unit_tests/test_subagents.py +0 -0
  126. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/tests/unit_tests/test_textual_adapter.py +0 -0
  127. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/tests/unit_tests/test_thread_selector.py +0 -0
  128. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/tests/unit_tests/test_token_tracker.py +0 -0
  129. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/tests/unit_tests/test_ui.py +0 -0
  130. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/tests/unit_tests/test_unicode_security.py +0 -0
  131. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/tests/unit_tests/test_update_check.py +0 -0
  132. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/tests/unit_tests/test_version.py +0 -0
  133. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/tests/unit_tests/test_welcome.py +0 -0
  134. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/tests/unit_tests/tools/__init__.py +0 -0
  135. {deepagents_cli-0.0.29 → deepagents_cli-0.0.30}/tests/unit_tests/tools/test_fetch_url.py +0 -0
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.0.30](https://github.com/langchain-ai/deepagents/compare/deepagents-cli==0.0.29...deepagents-cli==0.0.30) (2026-03-07)
4
+
5
+ ### Features
6
+
7
+ * `--acp` mode to run CLI agent as ACP server ([#1297](https://github.com/langchain-ai/deepagents/issues/1297)) ([c9ba00a](https://github.com/langchain-ai/deepagents/commit/c9ba00a56b7ee5e48b56b13f9f093bb8bf639700))
8
+ * Model detail footer + persist `--profile-override` on hot-swap ([#1700](https://github.com/langchain-ai/deepagents/issues/1700)) ([f2c8b54](https://github.com/langchain-ai/deepagents/commit/f2c8b54e9b4c541bf6f91139bfb9b6a2f20c8de0))
9
+ * Show message timestamp toast on click ([#1702](https://github.com/langchain-ai/deepagents/issues/1702)) ([4f403ec](https://github.com/langchain-ai/deepagents/commit/4f403ecb3332010062158ec30fd55f349654a533))
10
+
11
+ ### Bug Fixes
12
+
13
+ * Expire `ctrl+c` quit window when toast disappears ([#1701](https://github.com/langchain-ai/deepagents/issues/1701)) ([38b5ea9](https://github.com/langchain-ai/deepagents/commit/38b5ea9484ab121c9b2919dd74469e82fce19b82))
14
+ * Preserve input text when escaping shell/command mode ([#1706](https://github.com/langchain-ai/deepagents/issues/1706)) ([3c00edb](https://github.com/langchain-ai/deepagents/commit/3c00edb93eddf74e87d58526a02be72577ed65b1))
15
+ * Right-align token count next to model name in status bar ([#1705](https://github.com/langchain-ai/deepagents/issues/1705)) ([311c919](https://github.com/langchain-ai/deepagents/commit/311c9191cf663540e1b62eb9452abecda5bc7b4f))
16
+
3
17
  ## [0.0.29](https://github.com/langchain-ai/deepagents/compare/deepagents-cli==0.0.28...deepagents-cli==0.0.29) (2026-03-06)
4
18
 
5
19
  ### Features
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: deepagents-cli
3
- Version: 0.0.29
3
+ Version: 0.0.30
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/
@@ -25,6 +25,7 @@ Classifier: Topic :: Terminals
25
25
  Requires-Python: <4.0,>=3.11
26
26
  Requires-Dist: aiosqlite<1.0.0,>=0.19.0
27
27
  Requires-Dist: daytona<1.0.0,>=0.113.0
28
+ Requires-Dist: deepagents-acp>=0.0.4
28
29
  Requires-Dist: deepagents==0.4.7
29
30
  Requires-Dist: langchain-mcp-adapters<1.0.0,>=0.2.0
30
31
  Requires-Dist: langchain-openai<2.0.0,>=1.1.8
@@ -0,0 +1,3 @@
1
+ """Version information for `deepagents-cli`."""
2
+
3
+ __version__ = "0.0.30" # x-release-please-version
@@ -539,6 +539,7 @@ class DeepAgentsApp(App):
539
539
  sandbox: SandboxBackendProtocol | None = None,
540
540
  sandbox_type: str | None = None,
541
541
  mcp_server_info: list[MCPServerInfo] | None = None,
542
+ profile_override: dict[str, Any] | None = None,
542
543
  **kwargs: Any,
543
544
  ) -> None:
544
545
  """Initialize the Deep Agents application.
@@ -556,6 +557,8 @@ class DeepAgentsApp(App):
556
557
  sandbox: Sandbox backend (for model hot-swap)
557
558
  sandbox_type: Type of sandbox provider (for model hot-swap)
558
559
  mcp_server_info: MCP server metadata for the `/mcp` viewer.
560
+ profile_override: Extra profile fields from `--profile-override`,
561
+ retained for model hot-swap and footer display.
559
562
  **kwargs: Additional arguments passed to parent
560
563
  """
561
564
  super().__init__(**kwargs)
@@ -573,6 +576,7 @@ class DeepAgentsApp(App):
573
576
  self._sandbox = sandbox
574
577
  self._sandbox_type = sandbox_type
575
578
  self._mcp_server_info = mcp_server_info
579
+ self._profile_override = profile_override
576
580
  self._mcp_tool_count = sum(len(s.tools) for s in (mcp_server_info or []))
577
581
  self._status_bar: StatusBar | None = None
578
582
  self._chat_input: ChatInput | None = None
@@ -1649,7 +1653,10 @@ class DeepAgentsApp(App):
1649
1653
 
1650
1654
  try:
1651
1655
  model_spec = f"{settings.model_provider}:{settings.model_name}"
1652
- result = create_model(model_spec)
1656
+ result = create_model(
1657
+ model_spec,
1658
+ profile_overrides=self._profile_override,
1659
+ )
1653
1660
  model = result.model
1654
1661
  except Exception as exc: # noqa: BLE001 # surface model config errors to user
1655
1662
  await self._mount_message(
@@ -1659,10 +1666,10 @@ class DeepAgentsApp(App):
1659
1666
  )
1660
1667
  return
1661
1668
 
1662
- # create_model() applies config.toml overrides but not CLI
1663
- # --profile-override (the raw CLI dict isn't retained after
1664
- # startup). Patch settings.model_context_limit which reflects
1665
- # both sources — into the fresh model
1669
+ # create_model() receives --profile-override via self._profile_override,
1670
+ # but settings.model_context_limit may have been set by additional
1671
+ # runtime logic. Patch it into the fresh model when it differs from
1672
+ # the profile value.
1666
1673
  ctx = settings.model_context_limit
1667
1674
  if ctx is not None:
1668
1675
  # Guard against models that lack a profile dict
@@ -2340,6 +2347,10 @@ class DeepAgentsApp(App):
2340
2347
 
2341
2348
  # Store message data for virtualization
2342
2349
  message_data = MessageData.from_widget(widget)
2350
+ # Ensure the widget's DOM id matches the store id so that
2351
+ # features like click-to-show-timestamp can look it up.
2352
+ if not widget.id:
2353
+ widget.id = message_data.id
2343
2354
  self._message_store.append(message_data)
2344
2355
 
2345
2356
  # Queued-message widgets must always stay at the bottom so they
@@ -2485,7 +2496,9 @@ class DeepAgentsApp(App):
2485
2496
  self.exit()
2486
2497
  else:
2487
2498
  self._quit_pending = True
2488
- self.notify("Press Ctrl+C again to quit", timeout=3)
2499
+ quit_timeout = 3
2500
+ self.notify("Press Ctrl+C again to quit", timeout=quit_timeout)
2501
+ self.set_timer(quit_timeout, lambda: setattr(self, "_quit_pending", False))
2489
2502
 
2490
2503
  def action_interrupt(self) -> None:
2491
2504
  """Handle escape key.
@@ -2733,6 +2746,7 @@ class DeepAgentsApp(App):
2733
2746
  screen = ModelSelectorScreen(
2734
2747
  current_model=settings.model_name,
2735
2748
  current_provider=settings.model_provider,
2749
+ cli_profile_override=self._profile_override,
2736
2750
  )
2737
2751
  self.push_screen(screen, handle_result)
2738
2752
 
@@ -2982,7 +2996,11 @@ class DeepAgentsApp(App):
2982
2996
  return
2983
2997
 
2984
2998
  try:
2985
- result = create_model(model_spec, extra_kwargs=extra_kwargs)
2999
+ result = create_model(
3000
+ model_spec,
3001
+ extra_kwargs=extra_kwargs,
3002
+ profile_overrides=self._profile_override,
3003
+ )
2986
3004
  except ModelConfigError as e:
2987
3005
  await self._mount_message(ErrorMessage(str(e)))
2988
3006
  return
@@ -3140,6 +3158,7 @@ async def run_textual_app(
3140
3158
  sandbox: SandboxBackendProtocol | None = None,
3141
3159
  sandbox_type: str | None = None,
3142
3160
  mcp_server_info: list[MCPServerInfo] | None = None,
3161
+ profile_override: dict[str, Any] | None = None,
3143
3162
  ) -> AppResult:
3144
3163
  """Run the Textual application.
3145
3164
 
@@ -3156,6 +3175,8 @@ async def run_textual_app(
3156
3175
  sandbox: Sandbox backend (for model hot-swap)
3157
3176
  sandbox_type: Type of sandbox provider (for model hot-swap)
3158
3177
  mcp_server_info: MCP server metadata for the `/mcp` viewer.
3178
+ profile_override: Extra profile fields from `--profile-override`,
3179
+ retained for model hot-swap and footer display.
3159
3180
 
3160
3181
  Returns:
3161
3182
  An `AppResult` with the return code and final thread ID.
@@ -3173,6 +3194,7 @@ async def run_textual_app(
3173
3194
  sandbox=sandbox,
3174
3195
  sandbox_type=sandbox_type,
3175
3196
  mcp_server_info=mcp_server_info,
3197
+ profile_override=profile_override,
3176
3198
  )
3177
3199
  await app.run_async()
3178
3200
  return AppResult(
@@ -436,6 +436,12 @@ def parse_args() -> argparse.Namespace:
436
436
  except Exception:
437
437
  logger.warning("Unexpected error looking up SDK version", exc_info=True)
438
438
  sdk_version = "unknown"
439
+ parser.add_argument(
440
+ "--acp",
441
+ action="store_true",
442
+ help="Run as an ACP server over stdio instead of launching the Textual UI",
443
+ )
444
+
439
445
  parser.add_argument(
440
446
  "-v",
441
447
  "--version",
@@ -629,6 +635,7 @@ async def run_textual_cli_async(
629
635
  sandbox=sandbox_backend,
630
636
  sandbox_type=sandbox_type if sandbox_type != "none" else None,
631
637
  mcp_server_info=mcp_server_info,
638
+ profile_override=profile_override,
632
639
  )
633
640
  finally:
634
641
  # Clean up MCP session manager if initialized
@@ -647,6 +654,114 @@ async def run_textual_cli_async(
647
654
  return result
648
655
 
649
656
 
657
+ async def _run_acp_cli_async(
658
+ assistant_id: str,
659
+ *,
660
+ run_acp_agent: Callable[[Any], Any],
661
+ agent_server_cls: type[Any],
662
+ model_name: str | None = None,
663
+ model_params: dict[str, Any] | None = None,
664
+ profile_override: dict[str, Any] | None = None,
665
+ mcp_config_path: str | None = None,
666
+ no_mcp: bool = False,
667
+ trust_project_mcp: bool | None = None,
668
+ ) -> int:
669
+ """Run ACP server mode and return a process exit code.
670
+
671
+ Args:
672
+ assistant_id: Agent identifier to initialize.
673
+ run_acp_agent: ACP server runner function.
674
+ agent_server_cls: ACP server class constructor.
675
+ model_name: Optional model name to use.
676
+ model_params: Extra kwargs from `--model-params` to pass to the model.
677
+ profile_override: Extra profile fields from `--profile-override`.
678
+ mcp_config_path: Optional path to MCP servers JSON configuration file.
679
+ no_mcp: Disable all MCP tool loading.
680
+ trust_project_mcp: Controls project-level stdio server trust.
681
+
682
+ Returns:
683
+ Exit code for ACP mode.
684
+ """
685
+ from deepagents_cli.agent import create_cli_agent
686
+ from deepagents_cli.config import create_model, settings
687
+ from deepagents_cli.model_config import ModelConfigError
688
+ from deepagents_cli.tools import fetch_url, http_request, web_search
689
+
690
+ try:
691
+ model_result = create_model(
692
+ model_name,
693
+ extra_kwargs=model_params,
694
+ profile_overrides=profile_override,
695
+ )
696
+ except ModelConfigError as exc:
697
+ sys.stderr.write(f"Error: {exc}\n")
698
+ sys.stderr.flush()
699
+ return 1
700
+ model_result.apply_to_settings()
701
+
702
+ tools: list[Any] = [http_request, fetch_url]
703
+ if settings.has_tavily:
704
+ tools.append(web_search)
705
+
706
+ mcp_session_manager = None
707
+ mcp_server_info = None
708
+ try:
709
+ from deepagents_cli.mcp_tools import resolve_and_load_mcp_tools
710
+
711
+ (
712
+ mcp_tools,
713
+ mcp_session_manager,
714
+ mcp_server_info,
715
+ ) = await resolve_and_load_mcp_tools(
716
+ explicit_config_path=mcp_config_path,
717
+ no_mcp=no_mcp,
718
+ trust_project_mcp=trust_project_mcp,
719
+ )
720
+ tools.extend(mcp_tools)
721
+ except FileNotFoundError as exc:
722
+ msg = f"Error: MCP config file not found: {exc}\n"
723
+ sys.stderr.write(msg)
724
+ sys.stderr.flush()
725
+ return 1
726
+ except RuntimeError as exc:
727
+ msg = f"Error: Failed to load MCP tools: {exc}\n"
728
+ sys.stderr.write(msg)
729
+ sys.stderr.flush()
730
+ return 1
731
+
732
+ try:
733
+ agent_graph, _backend = create_cli_agent(
734
+ model=model_result.model,
735
+ assistant_id=assistant_id,
736
+ tools=tools,
737
+ mcp_server_info=mcp_server_info,
738
+ )
739
+ except Exception as exc:
740
+ sys.stderr.write(f"Error: failed to create agent: {exc}\n")
741
+ sys.stderr.flush()
742
+ logger.debug("ACP agent creation failed", exc_info=True)
743
+ return 1
744
+
745
+ server = agent_server_cls(agent_graph) # Pregel is a CompiledStateGraph at runtime
746
+ exit_code = 0
747
+ try:
748
+ await run_acp_agent(server)
749
+ except KeyboardInterrupt:
750
+ pass
751
+ except Exception as exc:
752
+ sys.stderr.write(f"Error: ACP server failed: {exc}\n")
753
+ sys.stderr.flush()
754
+ logger.exception("ACP server crashed")
755
+ exit_code = 1
756
+ finally:
757
+ if mcp_session_manager is not None:
758
+ try:
759
+ await mcp_session_manager.cleanup()
760
+ except Exception:
761
+ logger.warning("MCP session cleanup failed", exc_info=True)
762
+ return exit_code
763
+
764
+
650
765
  def apply_stdin_pipe(args: argparse.Namespace) -> None:
651
766
  r"""Read piped stdin and merge it into the parsed CLI arguments.
652
767
 
@@ -888,20 +1003,16 @@ def cli_main() -> None:
888
1003
  print(f"deepagents-cli {__version__}\ndeepagents (SDK) {sdk_version}") # noqa: T201 # CLI version output
889
1004
  sys.exit(0)
890
1005
 
891
- # Check dependencies first
892
- check_cli_dependencies()
1006
+ # ACP mode does not require Textual, so skip UI dependency checks when
1007
+ # the flag is present in raw argv.
1008
+ if "--acp" not in sys.argv[1:]:
1009
+ check_cli_dependencies()
893
1010
 
894
1011
  from deepagents_cli.config import console, settings
895
1012
 
896
1013
  try:
897
1014
  args = parse_args()
898
1015
 
899
- # Apply shell-allow-list from command line if provided (overrides env var)
900
- if args.shell_allow_list:
901
- from deepagents_cli.config import parse_shell_allow_list
902
-
903
- settings.shell_allow_list = parse_shell_allow_list(args.shell_allow_list)
904
-
905
1016
  model_params: dict[str, Any] | None = None
906
1017
  raw_kwargs = getattr(args, "model_params", None)
907
1018
  if raw_kwargs:
@@ -936,6 +1047,46 @@ def cli_main() -> None:
936
1047
  )
937
1048
  sys.exit(1)
938
1049
 
1050
+ if getattr(args, "acp", False):
1051
+ try:
1052
+ from acp import run_agent as run_acp_agent
1053
+ from deepagents_acp.server import AgentServerACP
1054
+ except ImportError as exc:
1055
+ msg = (
1056
+ f"ACP dependencies not available: {exc}\n"
1057
+ "Install with: pip install deepagents-acp\n"
1058
+ )
1059
+ sys.stderr.write(msg)
1060
+ sys.stderr.flush()
1061
+ sys.exit(1)
1062
+
1063
+ if getattr(args, "no_mcp", False) and getattr(args, "mcp_config", None):
1064
+ msg = "Error: --no-mcp and --mcp-config are mutually exclusive\n"
1065
+ sys.stderr.write(msg)
1066
+ sys.stderr.flush()
1067
+ sys.exit(2)
1068
+
1069
+ exit_code = asyncio.run(
1070
+ _run_acp_cli_async(
1071
+ assistant_id=args.agent,
1072
+ run_acp_agent=run_acp_agent,
1073
+ agent_server_cls=AgentServerACP,
1074
+ model_name=getattr(args, "model", None),
1075
+ model_params=model_params,
1076
+ profile_override=profile_override,
1077
+ mcp_config_path=getattr(args, "mcp_config", None),
1078
+ no_mcp=getattr(args, "no_mcp", False),
1079
+ trust_project_mcp=getattr(args, "trust_project_mcp", False),
1080
+ )
1081
+ )
1082
+ sys.exit(exit_code)
1083
+
1084
+ # Apply shell-allow-list from command line if provided (overrides env var)
1085
+ if args.shell_allow_list:
1086
+ from deepagents_cli.config import parse_shell_allow_list
1087
+
1088
+ settings.shell_allow_list = parse_shell_allow_list(args.shell_allow_list)
1089
+
939
1090
  apply_stdin_pipe(args)
940
1091
 
941
1092
  if getattr(args, "no_mcp", False) and getattr(args, "mcp_config", None):
@@ -104,6 +104,20 @@ class ModelSpec:
104
104
  return f"{self.provider}:{self.model}"
105
105
 
106
106
 
107
+ class ModelProfileEntry(TypedDict):
108
+ """Profile data for a model with override tracking."""
109
+
110
+ profile: dict[str, Any]
111
+ """Merged profile dict (upstream defaults + config.toml overrides).
112
+
113
+ Keys vary by provider (e.g., `max_input_tokens`, `tool_calling`).
114
+ """
115
+
116
+ overridden_keys: frozenset[str]
117
+ """Keys in `profile` whose values came from config.toml rather than the
118
+ upstream provider package."""
119
+
120
+
107
121
  class ProviderConfig(TypedDict, total=False):
108
122
  """Configuration for a model provider.
109
123
 
@@ -194,6 +208,7 @@ registry fallback.
194
208
  _available_models_cache: dict[str, list[str]] | None = None
195
209
  _builtin_providers_cache: dict[str, Any] | None = None
196
210
  _default_config_cache: ModelConfig | None = None
211
+ _profiles_cache: Mapping[str, ModelProfileEntry] | None = None
197
212
 
198
213
 
199
214
  def clear_caches() -> None:
@@ -201,10 +216,11 @@ def clear_caches() -> None:
201
216
 
202
217
  Intended for tests and for a hypothetical "refresh models" UI action.
203
218
  """
204
- global _available_models_cache, _builtin_providers_cache, _default_config_cache # noqa: PLW0603 # Module-level caches require global statement
219
+ global _available_models_cache, _builtin_providers_cache, _default_config_cache, _profiles_cache # noqa: PLW0603, E501 # Module-level caches require global statement
205
220
  _available_models_cache = None
206
221
  _builtin_providers_cache = None
207
222
  _default_config_cache = None
223
+ _profiles_cache = None
208
224
 
209
225
 
210
226
  def _get_builtin_providers() -> dict[str, Any]:
@@ -384,6 +400,111 @@ def get_available_models() -> dict[str, list[str]]:
384
400
  return available
385
401
 
386
402
 
403
+ def _build_entry(
404
+ base: dict[str, Any],
405
+ overrides: dict[str, Any],
406
+ cli_override: dict[str, Any] | None,
407
+ ) -> ModelProfileEntry:
408
+ """Build a profile entry by merging base, overrides, and CLI override.
409
+
410
+ Args:
411
+ base: Upstream profile dict (empty for config-only models).
412
+ overrides: `config.toml` profile overrides.
413
+ cli_override: Extra fields from `--profile-override`.
414
+
415
+ Returns:
416
+ Profile entry with merged data and override tracking.
417
+ """
418
+ merged = {**base, **overrides}
419
+ overridden_keys = set(overrides)
420
+ if cli_override:
421
+ merged = {**merged, **cli_override}
422
+ overridden_keys |= set(cli_override)
423
+ return ModelProfileEntry(
424
+ profile=merged,
425
+ overridden_keys=frozenset(overridden_keys),
426
+ )
427
+
428
+
429
+ def get_model_profiles(
430
+ *,
431
+ cli_override: dict[str, Any] | None = None,
432
+ ) -> Mapping[str, ModelProfileEntry]:
433
+ """Load upstream profiles merged with config.toml overrides.
434
+
435
+ Keyed by `provider:model` spec string. Each entry contains the
436
+ merged profile dict and the set of keys overridden by config.toml.
437
+
438
+ Unlike `get_available_models()`, this includes all models from upstream
439
+ profiles regardless of capability filters (tool calling, text I/O).
440
+
441
+ Results are cached when `cli_override` is None; use `clear_caches()`
442
+ to reset. When `cli_override` is provided the cache is bypassed
443
+ because CLI overrides are session-specific.
444
+
445
+ Args:
446
+ cli_override: Extra profile fields from `--profile-override`.
447
+
448
+ When provided, these are merged on top of every profile entry
449
+ (after upstream + config.toml) and their keys are added to
450
+ `overridden_keys`.
451
+
452
+ Returns:
453
+ Read-only mapping of spec strings to profile entries.
454
+ """
455
+ global _profiles_cache # noqa: PLW0603 # Module-level cache requires global statement
456
+ if cli_override is None and _profiles_cache is not None:
457
+ return _profiles_cache
458
+
459
+ result: dict[str, ModelProfileEntry] = {}
460
+ config = ModelConfig.load()
461
+
462
+ # Collect upstream profiles from provider packages.
463
+ seen_specs: set[str] = set()
464
+ provider_modules = _get_provider_profile_modules()
465
+ for provider, module_path in provider_modules:
466
+ try:
467
+ profiles = _load_provider_profiles(module_path)
468
+ except ImportError:
469
+ logger.debug(
470
+ "Could not import profiles from %s for provider '%s'",
471
+ module_path,
472
+ provider,
473
+ )
474
+ continue
475
+ except Exception:
476
+ logger.warning(
477
+ "Failed to load profiles from %s for provider '%s'",
478
+ module_path,
479
+ provider,
480
+ exc_info=True,
481
+ )
482
+ continue
483
+
484
+ for model_name, upstream_profile in profiles.items():
485
+ spec = f"{provider}:{model_name}"
486
+ seen_specs.add(spec)
487
+ overrides = config.get_profile_overrides(provider, model_name=model_name)
488
+ result[spec] = _build_entry(upstream_profile, overrides, cli_override)
489
+
490
+ # Add config-only models that have no upstream profile.
491
+ for provider_name, provider_config in config.providers.items():
492
+ config_models = provider_config.get("models", [])
493
+ for model_name in config_models:
494
+ spec = f"{provider_name}:{model_name}"
495
+ if spec not in seen_specs:
496
+ overrides = config.get_profile_overrides(
497
+ provider_name, model_name=model_name
498
+ )
499
+ result[spec] = _build_entry({}, overrides, cli_override)
500
+
501
+ frozen = MappingProxyType(result)
502
+ # Only populate cache for the static (no CLI override) path.
503
+ if cli_override is None:
504
+ _profiles_cache = frozen
505
+ return frozen
506
+
507
+
387
508
  def _is_langchain_supported_provider(provider: str) -> bool:
388
509
  """Check if a provider is in langchain's built-in provider registry.
389
510
 
@@ -114,6 +114,7 @@ def show_help() -> None:
114
114
  )
115
115
  console.print(" --default-model [MODEL] Set, show, or manage the default model")
116
116
  console.print(" --clear-default-model Clear the default model")
117
+ console.print(" --acp Run as an ACP server over stdio")
117
118
  console.print(" -v, --version Show deepagents CLI and SDK versions")
118
119
  console.print(" -h, --help Show this help message and exit")
119
120
  console.print()
@@ -1483,8 +1483,6 @@ class ChatInput(Vertical):
1483
1483
  if self._completion_manager:
1484
1484
  self._completion_manager.reset()
1485
1485
  self.clear_completion_suggestions()
1486
- if self._text_area:
1487
- self._text_area.clear()
1488
1486
  return True
1489
1487
 
1490
1488
  def dismiss_completion(self) -> bool:
@@ -37,6 +37,46 @@ if TYPE_CHECKING:
37
37
  logger = logging.getLogger(__name__)
38
38
 
39
39
 
40
+ def _show_timestamp_toast(widget: Static | Vertical) -> None:
41
+ """Show a toast with the message's creation timestamp.
42
+
43
+ No-ops silently if the widget is not mounted or has no associated message
44
+ data in the store.
45
+
46
+ Args:
47
+ widget: The message widget whose timestamp to display.
48
+ """
49
+ from datetime import UTC, datetime
50
+
51
+ try:
52
+ app = widget.app
53
+ except Exception: # noqa: BLE001 # Textual raises when widget has no app
54
+ return
55
+ if not widget.id:
56
+ return
57
+ store = app._message_store # type: ignore[attr-defined]
58
+ data = store.get_message(widget.id)
59
+ if not data:
60
+ return
61
+ dt = datetime.fromtimestamp(data.timestamp, tz=UTC).astimezone()
62
+ label = f"{dt:%b} {dt.day}, {dt.hour % 12 or 12}:{dt:%M:%S} {dt:%p}"
63
+ app.notify(label, timeout=3)
64
+
65
+
66
+ class _TimestampClickMixin:
67
+ """Mixin that shows a timestamp toast on click.
68
+
69
+ Add to any message widget that should display its creation timestamp when
70
+ clicked. Widgets needing additional click behavior (e.g. `ToolCallMessage`,
71
+ `AppMessage`) should override `on_click` and call `_show_timestamp_toast`
72
+ directly instead.
73
+ """
74
+
75
+ def on_click(self, event: Click) -> None: # noqa: ARG002 # Textual event handler
76
+ """Show timestamp toast on click."""
77
+ _show_timestamp_toast(self) # type: ignore[arg-type]
78
+
79
+
40
80
  def _mode_color(mode: str | None) -> str:
41
81
  """Return the color string for a mode, falling back to primary.
42
82
 
@@ -101,7 +141,7 @@ _TOOLS_WITH_HEADER_INFO: set[str] = {
101
141
  }
102
142
 
103
143
 
104
- class UserMessage(Static):
144
+ class UserMessage(_TimestampClickMixin, Static):
105
145
  """Widget displaying a user message."""
106
146
 
107
147
  DEFAULT_CSS = """
@@ -231,7 +271,7 @@ class QueuedUserMessage(Static):
231
271
  yield Static(text)
232
272
 
233
273
 
234
- class AssistantMessage(Vertical):
274
+ class AssistantMessage(_TimestampClickMixin, Vertical):
235
275
  """Widget displaying an assistant message with markdown support.
236
276
 
237
277
  Uses MarkdownStream for smoother streaming instead of re-rendering
@@ -652,9 +692,12 @@ class ToolCallMessage(Vertical):
652
692
  self._update_output_display()
653
693
 
654
694
  def on_click(self, event: Click) -> None:
655
- """Handle click to toggle output expansion."""
695
+ """Toggle output expansion, or show timestamp if no output."""
656
696
  event.stop() # Prevent click from bubbling up and scrolling
657
- self.toggle_output()
697
+ if self._output:
698
+ self.toggle_output()
699
+ else:
700
+ _show_timestamp_toast(self)
658
701
 
659
702
  def _format_output(
660
703
  self, output: str, *, is_preview: bool = False
@@ -1168,7 +1211,7 @@ class ToolCallMessage(Vertical):
1168
1211
  return filtered
1169
1212
 
1170
1213
 
1171
- class DiffMessage(Static):
1214
+ class DiffMessage(_TimestampClickMixin, Static):
1172
1215
  """Widget displaying a diff with syntax highlighting."""
1173
1216
 
1174
1217
  DEFAULT_CSS = """
@@ -1239,7 +1282,7 @@ class DiffMessage(Static):
1239
1282
  self.styles.border = ("ascii", "cyan")
1240
1283
 
1241
1284
 
1242
- class ErrorMessage(Static):
1285
+ class ErrorMessage(_TimestampClickMixin, Static):
1243
1286
  """Widget displaying an error message."""
1244
1287
 
1245
1288
  DEFAULT_CSS = """
@@ -1307,9 +1350,10 @@ class AppMessage(Static):
1307
1350
  )
1308
1351
  super().__init__(content, **kwargs)
1309
1352
 
1310
- def on_click(self, event: Click) -> None: # noqa: PLR6301 # Textual event handler
1311
- """Open Rich-style hyperlinks on single click."""
1353
+ def on_click(self, event: Click) -> None:
1354
+ """Open Rich-style hyperlinks on single click and show timestamp."""
1312
1355
  open_style_link(event)
1356
+ _show_timestamp_toast(self)
1313
1357
 
1314
1358
 
1315
1359
  class SummarizationMessage(AppMessage):