deepy-cli 0.2.27__tar.gz → 0.2.29__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 (117) hide show
  1. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/PKG-INFO +1 -1
  2. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/pyproject.toml +1 -1
  3. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/__init__.py +1 -1
  4. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/tui/app.py +117 -3
  5. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/tui/widgets.py +69 -5
  6. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/README.md +0 -0
  7. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/__main__.py +0 -0
  8. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/audit.py +0 -0
  9. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/background_tasks.py +0 -0
  10. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/cli.py +0 -0
  11. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/config/__init__.py +0 -0
  12. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/config/settings.py +0 -0
  13. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/data/__init__.py +0 -0
  14. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/data/skills/skill-creator/SKILL.md +0 -0
  15. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/data/skills/skill-installer/SKILL.md +0 -0
  16. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/data/tools/AskUserQuestion.md +0 -0
  17. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/data/tools/Read.md +0 -0
  18. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/data/tools/Search.md +0 -0
  19. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/data/tools/Update.md +0 -0
  20. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/data/tools/WebFetch.md +0 -0
  21. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/data/tools/WebSearch.md +0 -0
  22. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/data/tools/Write.md +0 -0
  23. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/data/tools/__init__.py +0 -0
  24. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/data/tools/shell.md +0 -0
  25. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/data/tools/task_list.md +0 -0
  26. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/data/tools/task_output.md +0 -0
  27. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/data/tools/task_stop.md +0 -0
  28. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/data/tools/test_shell.md +0 -0
  29. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/data/tools/todo_write.md +0 -0
  30. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/errors.py +0 -0
  31. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/input_suggestions.py +0 -0
  32. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/llm/__init__.py +0 -0
  33. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/llm/agent.py +0 -0
  34. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/llm/cache_context.py +0 -0
  35. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/llm/compaction.py +0 -0
  36. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/llm/context.py +0 -0
  37. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/llm/events.py +0 -0
  38. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/llm/model_capabilities.py +0 -0
  39. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/llm/multimodal.py +0 -0
  40. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/llm/provider.py +0 -0
  41. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/llm/replay.py +0 -0
  42. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/llm/runner.py +0 -0
  43. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/llm/thinking.py +0 -0
  44. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/mcp.py +0 -0
  45. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/prompts/__init__.py +0 -0
  46. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/prompts/compact.py +0 -0
  47. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/prompts/init_agents.py +0 -0
  48. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/prompts/rules.py +0 -0
  49. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/prompts/runtime_context.py +0 -0
  50. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/prompts/system.py +0 -0
  51. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/prompts/tool_docs.py +0 -0
  52. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/session_cost.py +0 -0
  53. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/sessions/__init__.py +0 -0
  54. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/sessions/index.py +0 -0
  55. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/sessions/manager.py +0 -0
  56. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/sessions/session.py +0 -0
  57. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/sessions/store_helpers.py +0 -0
  58. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/skill_market.py +0 -0
  59. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/skills.py +0 -0
  60. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/status.py +0 -0
  61. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/subagents.py +0 -0
  62. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/todos.py +0 -0
  63. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/tools/__init__.py +0 -0
  64. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/tools/agents.py +0 -0
  65. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/tools/builtin.py +0 -0
  66. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/tools/file_state.py +0 -0
  67. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/tools/result.py +0 -0
  68. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/tools/search.py +0 -0
  69. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/tools/shell_output.py +0 -0
  70. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/tools/shell_utils.py +0 -0
  71. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/tools/test_shell.py +0 -0
  72. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/tui/__init__.py +0 -0
  73. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/tui/commands.py +0 -0
  74. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/tui/compat.py +0 -0
  75. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/tui/diff.py +0 -0
  76. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/tui/interaction_surfaces.py +0 -0
  77. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/tui/runner.py +0 -0
  78. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/tui/screens.py +0 -0
  79. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/tui/state.py +0 -0
  80. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/tui/theme.py +0 -0
  81. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/tui/transcript.py +0 -0
  82. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/types/__init__.py +0 -0
  83. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/types/sdk.py +0 -0
  84. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/types/tool_payloads.py +0 -0
  85. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/ui/__init__.py +0 -0
  86. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/ui/app.py +0 -0
  87. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/ui/ask_user_question.py +0 -0
  88. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/ui/audit_approval_panel.py +0 -0
  89. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/ui/audit_approval_picker.py +0 -0
  90. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/ui/exit_summary.py +0 -0
  91. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/ui/file_mentions.py +0 -0
  92. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/ui/image_input.py +0 -0
  93. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/ui/loading_text.py +0 -0
  94. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/ui/local_command.py +0 -0
  95. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/ui/markdown.py +0 -0
  96. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/ui/message_view.py +0 -0
  97. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/ui/model_picker.py +0 -0
  98. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/ui/prompt_buffer.py +0 -0
  99. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/ui/prompt_input.py +0 -0
  100. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/ui/session_list.py +0 -0
  101. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/ui/session_picker.py +0 -0
  102. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/ui/skill_picker.py +0 -0
  103. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/ui/slash_commands.py +0 -0
  104. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/ui/status_footer.py +0 -0
  105. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/ui/styles.py +0 -0
  106. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/ui/syntax.py +0 -0
  107. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/ui/terminal.py +0 -0
  108. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/ui/theme_picker.py +0 -0
  109. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/ui/thinking_state.py +0 -0
  110. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/ui/welcome.py +0 -0
  111. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/update_check.py +0 -0
  112. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/usage.py +0 -0
  113. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/utils/__init__.py +0 -0
  114. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/utils/debug_logger.py +0 -0
  115. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/utils/error_logger.py +0 -0
  116. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/utils/json.py +0 -0
  117. {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/utils/notify.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: deepy-cli
3
- Version: 0.2.27
3
+ Version: 0.2.29
4
4
  Summary: Deepy - Vibe coding for DeepSeek models in your terminal
5
5
  Keywords: deepseek,coding-agent,terminal,cli,agents
6
6
  Author: kirineko
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "deepy-cli"
3
- version = "0.2.27"
3
+ version = "0.2.29"
4
4
  description = "Deepy - Vibe coding for DeepSeek models in your terminal"
5
5
  readme = "README.md"
6
6
  authors = [
@@ -1,6 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.2.27"
3
+ __version__ = "0.2.29"
4
4
 
5
5
 
6
6
  def main() -> None:
@@ -224,6 +224,7 @@ class DeepyTuiApp(App[None]):
224
224
  BINDINGS = [
225
225
  Binding("ctrl+d", "confirm_quit", "Quit", priority=True),
226
226
  Binding("escape", "interrupt_or_focus_prompt", "Interrupt"),
227
+ Binding("ctrl+c,super+c", "copy_focused_block", "Copy", show=False),
227
228
  Binding("ctrl+o", "toggle_help_panel", "Panel"),
228
229
  Binding("shift+tab", "cycle_audit_mode", "Audit", priority=True),
229
230
  Binding("alt+up", "focus_previous_block", "Previous block"),
@@ -472,12 +473,38 @@ class DeepyTuiApp(App[None]):
472
473
  color: $text;
473
474
  }
474
475
 
476
+ .subagent-parameters {
477
+ margin: 0 0 0 2;
478
+ padding: 0 0 0 1;
479
+ border-left: solid $secondary;
480
+ color: $text-muted;
481
+ }
482
+
475
483
  .tool-details {
476
484
  margin: 1 0 0 0;
477
485
  color: $text-muted;
478
486
  display: none;
479
487
  }
480
488
 
489
+ .subagent-block .tool-details {
490
+ margin: 0 0 0 2;
491
+ padding: 0 0 0 1;
492
+ border-left: solid $accent;
493
+ color: $text;
494
+ }
495
+
496
+ .subagent-block.-running .tool-details {
497
+ border-left: solid $accent;
498
+ }
499
+
500
+ .subagent-block.-ok .tool-details {
501
+ border-left: solid $success;
502
+ }
503
+
504
+ .subagent-block.-failed .tool-details {
505
+ border-left: solid $error;
506
+ }
507
+
481
508
  .tool-block.-retryable .block-title {
482
509
  color: $warning;
483
510
  }
@@ -678,6 +705,9 @@ class DeepyTuiApp(App[None]):
678
705
  self._todo_text = ""
679
706
  self._stream_tokens = 0
680
707
  self._local_command_sequence = 0
708
+ self._status_session_entry: SessionEntry | None = None
709
+ self._status_session_entry_id: str | None = None
710
+ self._status_session_entry_loaded = False
681
711
  self.background_tasks = BackgroundTaskManager()
682
712
  self.mcp_runtime = DeepyMcpRuntime(
683
713
  settings,
@@ -718,6 +748,7 @@ class DeepyTuiApp(App[None]):
718
748
  )
719
749
  self._scroll_transcript_to_end(force=True)
720
750
  self.query_one("#prompt-input", PromptTextArea).focus()
751
+ self._refresh_status_session_entry()
721
752
  self._update_status("Idle")
722
753
  if self.guide_missing_config and not self.settings.model.api_key:
723
754
  self.call_after_refresh(self._start_initial_setup)
@@ -1079,6 +1110,7 @@ class DeepyTuiApp(App[None]):
1079
1110
  self.state = set_session_id(set_pending_questions(reset_turn_buffers(self.state), []), None)
1080
1111
  self.controller.reset_session_state()
1081
1112
  self._pending_question_answers.clear()
1113
+ self._refresh_status_session_entry()
1082
1114
  await self._clear_transcript()
1083
1115
  await self._append_block(InfoBlock("Started a new TUI session."))
1084
1116
  self._update_status("New session")
@@ -1098,6 +1130,7 @@ class DeepyTuiApp(App[None]):
1098
1130
  await self._append_block(ErrorBlock(f"Session not found: {target}"))
1099
1131
  return
1100
1132
  self.state = set_session_id(self.state, target)
1133
+ self._refresh_status_session_entry()
1101
1134
  await self._restore_transcript(target)
1102
1135
  self._update_status(f"Resumed {target}")
1103
1136
 
@@ -1151,6 +1184,7 @@ class DeepyTuiApp(App[None]):
1151
1184
  f"preserved {result.preserved_item_count} items."
1152
1185
  )
1153
1186
  )
1187
+ self._refresh_status_session_entry()
1154
1188
  self._update_status("Idle")
1155
1189
 
1156
1190
  async def _theme_command(self, argument: str) -> None:
@@ -1987,6 +2021,7 @@ class DeepyTuiApp(App[None]):
1987
2021
  await self._flush_assistant_block()
1988
2022
  self.state = set_session_id(self.state, summary.session_id)
1989
2023
  self._record_pending_session_cost_start(summary.session_id)
2024
+ self._refresh_status_session_entry()
1990
2025
  self.state = set_usage(self.state, summary.usage)
1991
2026
  self.state = set_pending_questions(self.state, summary.pending_questions)
1992
2027
  self.state = set_busy(self.state, False, "Idle")
@@ -2599,6 +2634,7 @@ class DeepyTuiApp(App[None]):
2599
2634
 
2600
2635
  def _update_status(self, status: str) -> None:
2601
2636
  self.state = set_status(self.state, status)
2637
+ session_entry = self._cached_status_session_entry()
2602
2638
  self._set_status_bar(status)
2603
2639
  try:
2604
2640
  side_status = self.query_one("#side-status", Static)
@@ -2612,6 +2648,7 @@ class DeepyTuiApp(App[None]):
2612
2648
  self.controller.loaded_skill_names,
2613
2649
  self._todo_text,
2614
2650
  audit_state=self.audit_state,
2651
+ session_entry=session_entry,
2615
2652
  )
2616
2653
  )
2617
2654
 
@@ -2634,9 +2671,23 @@ class DeepyTuiApp(App[None]):
2634
2671
  settings=self.settings,
2635
2672
  background_tasks=self.background_tasks,
2636
2673
  audit_state=self.audit_state,
2674
+ session_entry=self._cached_status_session_entry(),
2637
2675
  )
2638
2676
  return context
2639
2677
 
2678
+ def _cached_status_session_entry(self) -> SessionEntry | None:
2679
+ if (
2680
+ not self._status_session_entry_loaded
2681
+ or self._status_session_entry_id != self.state.session_id
2682
+ ):
2683
+ self._refresh_status_session_entry()
2684
+ return self._status_session_entry
2685
+
2686
+ def _refresh_status_session_entry(self) -> None:
2687
+ self._status_session_entry_id = self.state.session_id
2688
+ self._status_session_entry_loaded = True
2689
+ self._status_session_entry = _tui_session_entry(self.project_root, self.state.session_id)
2690
+
2640
2691
  def action_cycle_audit_mode(self) -> None:
2641
2692
  mode = self.audit_state.cycle()
2642
2693
  self._update_status(f"Audit {mode.value}")
@@ -2707,6 +2758,7 @@ class DeepyTuiApp(App[None]):
2707
2758
  pass
2708
2759
  finally:
2709
2760
  self._pending_session_cost_start = None
2761
+ self._refresh_status_session_entry()
2710
2762
 
2711
2763
  def _record_session_cost_end(self) -> None:
2712
2764
  session_id = self.state.session_id
@@ -2724,6 +2776,7 @@ class DeepyTuiApp(App[None]):
2724
2776
  DeepySession.open(self.project_root, session_id).record_session_cost_end(snapshot)
2725
2777
  except Exception:
2726
2778
  return
2779
+ self._refresh_status_session_entry()
2727
2780
 
2728
2781
  def _session_cost_has_start(self, session_id: str) -> bool:
2729
2782
  return any(
@@ -2749,6 +2802,27 @@ class DeepyTuiApp(App[None]):
2749
2802
  prompt.prepare_clear_on_next_delete()
2750
2803
  prompt.focus()
2751
2804
 
2805
+ def action_copy_focused_block(self) -> None:
2806
+ block = self._focused_transcript_block()
2807
+ if block is None:
2808
+ self._update_status("Focus a transcript block to copy")
2809
+ return
2810
+ text = _transcript_block_copy_text(block).strip()
2811
+ if not text:
2812
+ self._update_status("Nothing to copy")
2813
+ return
2814
+ self.copy_to_clipboard(text)
2815
+ self._update_status("Copied transcript block")
2816
+
2817
+ def _focused_transcript_block(self) -> Widget | None:
2818
+ node = self.focused
2819
+ while isinstance(node, Widget):
2820
+ if node.has_class("transcript-block"):
2821
+ return node
2822
+ parent = node.parent
2823
+ node = parent if isinstance(parent, Widget) else None
2824
+ return None
2825
+
2752
2826
  def action_focus_next_block(self) -> None:
2753
2827
  blocks = list(self.query(".transcript-block"))
2754
2828
  if not blocks:
@@ -2771,6 +2845,33 @@ class DeepyTuiApp(App[None]):
2771
2845
  panel.toggle_class("-visible")
2772
2846
 
2773
2847
 
2848
+ def _transcript_block_copy_text(block: Widget) -> str:
2849
+ if isinstance(block, UserBlock):
2850
+ return block.body
2851
+ if isinstance(block, AssistantBlock):
2852
+ return block.markdown
2853
+ if isinstance(block, ThinkingBlock):
2854
+ return block.body
2855
+ if isinstance(block, LocalCommandBlock):
2856
+ parts = [block.title, block.output_body]
2857
+ if block.meta_body:
2858
+ parts.append(block.meta_body)
2859
+ return "\n".join(part for part in parts if part)
2860
+ if isinstance(block, ToolBlock):
2861
+ parts = [block.title]
2862
+ if block.tool_name == "todo_write" and block.output_body:
2863
+ parts.append(block.output_body)
2864
+ if block.expanded and block.details:
2865
+ parts.append(block.details)
2866
+ return "\n\n".join(part for part in parts if part)
2867
+ if isinstance(block, DiffBlock):
2868
+ return block.body
2869
+ if isinstance(block, InfoBlock):
2870
+ return block.body
2871
+ body = getattr(block, "body", "")
2872
+ return body if isinstance(body, str) else ""
2873
+
2874
+
2774
2875
  async def _load_session_items(project_root: Path, session_id: str) -> list[dict[str, Any]]:
2775
2876
  try:
2776
2877
  return await DeepySession.open(project_root, session_id).get_items()
@@ -3120,6 +3221,9 @@ def _format_tui_ui_interface_label(interface: str) -> str:
3120
3221
  return "Modern UI" if interface == "modern" else "Classic UI"
3121
3222
 
3122
3223
 
3224
+ _SESSION_ENTRY_UNSET = object()
3225
+
3226
+
3123
3227
  def _build_tui_status_context(
3124
3228
  session_id: str | None,
3125
3229
  *,
@@ -3127,6 +3231,7 @@ def _build_tui_status_context(
3127
3231
  settings: Settings,
3128
3232
  background_tasks: BackgroundTaskManager | None = None,
3129
3233
  audit_state: AuditModeState | None = None,
3234
+ session_entry: Any = _SESSION_ENTRY_UNSET,
3130
3235
  ) -> str:
3131
3236
  segments = [
3132
3237
  f"provider {settings.model.provider}",
@@ -3143,7 +3248,8 @@ def _build_tui_status_context(
3143
3248
  running = background_tasks.running_count()
3144
3249
  if running:
3145
3250
  segments.append(f"bg {running}")
3146
- session_entry = _tui_session_entry(project_root, session_id)
3251
+ if session_entry is _SESSION_ENTRY_UNSET:
3252
+ session_entry = _tui_session_entry(project_root, session_id)
3147
3253
  segments.append(
3148
3254
  _format_tui_context_window_status(
3149
3255
  session_entry,
@@ -3172,8 +3278,10 @@ def _format_tui_side_status(
3172
3278
  todo_text: str,
3173
3279
  *,
3174
3280
  audit_state: AuditModeState | None = None,
3281
+ session_entry: Any = _SESSION_ENTRY_UNSET,
3175
3282
  ) -> str:
3176
- session_entry = _tui_session_entry(project_root, session_id)
3283
+ if session_entry is _SESSION_ENTRY_UNSET:
3284
+ session_entry = _tui_session_entry(project_root, session_id)
3177
3285
  lines = [
3178
3286
  f"Project: {project_root}",
3179
3287
  f"Provider: {settings.model.provider}",
@@ -3222,7 +3330,13 @@ def _format_tui_cache_status(session_entry: Any | None) -> str:
3222
3330
  def _tui_session_entry(project_root: Path, session_id: str | None) -> Any | None:
3223
3331
  if not session_id:
3224
3332
  return None
3225
- return next((entry for entry in list_session_entries(project_root) if entry.id == session_id), None)
3333
+ try:
3334
+ return next(
3335
+ (entry for entry in list_session_entries(project_root) if entry.id == session_id),
3336
+ None,
3337
+ )
3338
+ except Exception:
3339
+ return None
3226
3340
 
3227
3341
 
3228
3342
  def _configured_mcp_server_count(settings: Settings, project_root: Path) -> int:
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import contextlib
3
4
  import time
4
5
  from dataclasses import dataclass
5
6
  from pathlib import Path
@@ -743,6 +744,8 @@ class ToolBlock(TranscriptBlock):
743
744
  recovered_from_retry: bool = False,
744
745
  ) -> None:
745
746
  classes = "tool-block todo-block" if tool_name == "todo_write" else transcript_display("tool").css_class
747
+ if _is_subagent_tool_name(tool_name):
748
+ classes += " subagent-block"
746
749
  if retryable:
747
750
  classes += " -retryable"
748
751
  super().__init__(label, body, classes=classes, kind="tool")
@@ -765,6 +768,7 @@ class ToolBlock(TranscriptBlock):
765
768
  title = f"{display_name} running"
766
769
  if name == "shell" and params:
767
770
  title = f"{title} - {params}"
771
+ details = _subagent_details(params, "") if _is_subagent_tool_name(name) else ""
768
772
  body = (
769
773
  "Waiting for user input."
770
774
  if name == "AskUserQuestion"
@@ -777,6 +781,7 @@ class ToolBlock(TranscriptBlock):
777
781
  body,
778
782
  call_id=call_id,
779
783
  arguments=params,
784
+ details=details,
780
785
  tool_name=name or "tool",
781
786
  )
782
787
 
@@ -809,7 +814,7 @@ class ToolBlock(TranscriptBlock):
809
814
  self.title = f"{self.title} - {params}"
810
815
  self.output_body = "Running"
811
816
  self.body = ""
812
- self.details = ""
817
+ self.details = _subagent_details(params, "") if _is_subagent_tool_name(self.tool_name) else ""
813
818
  self.waiting_for_user = False
814
819
  self.retryable = False
815
820
  self.recovered_from_retry = True
@@ -818,7 +823,7 @@ class ToolBlock(TranscriptBlock):
818
823
  self._update_visible_output()
819
824
  details = self.query_one(".tool-details", Static)
820
825
  details.update(self.details)
821
- details.display = False
826
+ details.display = self._details_visible()
822
827
  self.set_class(False, "-waiting")
823
828
  self.set_class(False, "-retryable")
824
829
  self._sync_status_classes()
@@ -832,7 +837,11 @@ class ToolBlock(TranscriptBlock):
832
837
  )
833
838
  output_body = _tool_output_body(view)
834
839
  self.output_body = output_body
835
- self.details = _tool_output_details(view)
840
+ self.details = (
841
+ _subagent_details(self.arguments, output_body)
842
+ if _is_subagent_tool_name(view.name)
843
+ else _tool_output_details(view)
844
+ )
836
845
  self.waiting_for_user = view.await_user_response
837
846
  self.retryable = view.status == "retryable"
838
847
  self.status_state = "waiting" if self.waiting_for_user else view.status
@@ -841,16 +850,20 @@ class ToolBlock(TranscriptBlock):
841
850
  self._update_visible_output()
842
851
  details = self.query_one(".tool-details", Static)
843
852
  details.update(self.details)
844
- details.display = bool(self.details and self.expanded)
853
+ details.display = self._details_visible()
845
854
  self.set_class(self.waiting_for_user, "-waiting")
846
855
  self.set_class(self.retryable, "-retryable")
847
856
  self._sync_status_classes()
848
857
  self.set_class(view.name == "todo_write", "todo-block")
858
+ self.set_class(_is_subagent_tool_name(view.name), "subagent-block")
849
859
 
850
860
  def compose(self) -> ComposeResult:
851
861
  with Horizontal(classes="role-line tool-role-line"):
852
862
  yield Label(self.display_model.label, classes="block-title role-marker tool-marker")
853
863
  yield Static(self.title, classes="block-body tool-summary")
864
+ params = Static(_subagent_parameters(self.arguments), classes="block-body subagent-parameters")
865
+ params.display = self._subagent_parameters_visible()
866
+ yield params
854
867
  visible_output = _tool_output_visible(self.tool_name, self.output_body)
855
868
  output = Static(
856
869
  _tool_output_renderable(self.tool_name, self.output_body) if visible_output else "",
@@ -864,7 +877,7 @@ class ToolBlock(TranscriptBlock):
864
877
 
865
878
  def action_toggle_expand(self) -> None:
866
879
  super().action_toggle_expand()
867
- self.query_one(".tool-details", Static).display = False
880
+ self.query_one(".tool-details", Static).display = self._details_visible()
868
881
 
869
882
  def _sync_status_classes(self) -> None:
870
883
  self.set_class(self.status_state == "running", "-running")
@@ -874,11 +887,21 @@ class ToolBlock(TranscriptBlock):
874
887
  self.set_class(self.status_state == "failed", "-failed")
875
888
 
876
889
  def _update_visible_output(self) -> None:
890
+ with contextlib.suppress(NoMatches):
891
+ params = self.query_one(".subagent-parameters", Static)
892
+ params.update(_subagent_parameters(self.arguments))
893
+ params.display = self._subagent_parameters_visible()
877
894
  output = self.query_one(".tool-output", Static)
878
895
  visible_output = _tool_output_visible(self.tool_name, self.output_body)
879
896
  output.update(_tool_output_renderable(self.tool_name, self.output_body) if visible_output else "")
880
897
  output.display = visible_output
881
898
 
899
+ def _details_visible(self) -> bool:
900
+ return bool(self.details and self.expanded and _is_subagent_tool_name(self.tool_name))
901
+
902
+ def _subagent_parameters_visible(self) -> bool:
903
+ return bool(self.arguments and _is_subagent_tool_name(self.tool_name))
904
+
882
905
 
883
906
  class LocalCommandBlock(Vertical, can_focus=True):
884
907
  def __init__(self, view: ToolOutputView, *, call_id: str = "") -> None:
@@ -1430,6 +1453,10 @@ def _tool_arguments_body(name: str, arguments: str) -> str:
1430
1453
  return ""
1431
1454
  if not arguments.strip():
1432
1455
  return ""
1456
+ if _is_subagent_tool_name(name):
1457
+ task = _subagent_input_argument(arguments)
1458
+ if task:
1459
+ return task
1433
1460
  if name == "shell":
1434
1461
  command = _shell_command_argument(arguments)
1435
1462
  if command:
@@ -1448,6 +1475,41 @@ def _shell_command_argument(arguments: str) -> str:
1448
1475
  return command.strip() if isinstance(command, str) else ""
1449
1476
 
1450
1477
 
1478
+ def _is_subagent_tool_name(name: str) -> bool:
1479
+ return name.startswith("subagent_")
1480
+
1481
+
1482
+ def _subagent_input_argument(arguments: str) -> str:
1483
+ try:
1484
+ args = json_utils.loads(arguments)
1485
+ except json_utils.JSONDecodeError:
1486
+ return ""
1487
+ if not isinstance(args, dict):
1488
+ return ""
1489
+ value = args.get("input") or args.get("task") or args.get("prompt")
1490
+ return value.strip() if isinstance(value, str) else ""
1491
+
1492
+
1493
+ def _subagent_details(task: str, report: str) -> str:
1494
+ parts: list[str] = []
1495
+ compact_task = _compact_text(task, max_lines=4, max_chars=500)
1496
+ if compact_task:
1497
+ parts.extend(["Task", _indent_block(compact_task)])
1498
+ compact_report = _compact_text(report, max_lines=16, max_chars=1600)
1499
+ if compact_report:
1500
+ if parts:
1501
+ parts.append("")
1502
+ parts.extend(["Report", _indent_block(compact_report)])
1503
+ return "\n".join(parts)
1504
+
1505
+
1506
+ def _subagent_parameters(task: str) -> str:
1507
+ compact = _compact_text(task, max_lines=4, max_chars=700)
1508
+ if not compact:
1509
+ return ""
1510
+ return "Subagent Parameters\n" + _indent_block(compact)
1511
+
1512
+
1451
1513
  def _tool_output_body(view: ToolOutputView) -> str:
1452
1514
  if view.name == "AskUserQuestion":
1453
1515
  return "Waiting for user input." if view.await_user_response else _compact_text(view.output or view.summary)
@@ -1470,6 +1532,8 @@ def _tool_output_body(view: ToolOutputView) -> str:
1470
1532
  return _todo_body(view)
1471
1533
  if view.name in {"WebSearch", "WebFetch"}:
1472
1534
  return _web_body(view)
1535
+ if _is_subagent_tool_name(view.name):
1536
+ return (view.error or view.output or view.summary).strip()
1473
1537
  if _is_mcp_view(view):
1474
1538
  return _mcp_body(view)
1475
1539
  if view.ok is False and view.metadata:
File without changes
File without changes
File without changes