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.
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/PKG-INFO +1 -1
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/pyproject.toml +1 -1
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/__init__.py +1 -1
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/tui/app.py +117 -3
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/tui/widgets.py +69 -5
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/README.md +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/__main__.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/audit.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/background_tasks.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/cli.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/config/__init__.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/config/settings.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/data/__init__.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/data/skills/skill-creator/SKILL.md +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/data/skills/skill-installer/SKILL.md +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/data/tools/AskUserQuestion.md +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/data/tools/Read.md +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/data/tools/Search.md +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/data/tools/Update.md +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/data/tools/WebFetch.md +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/data/tools/WebSearch.md +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/data/tools/Write.md +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/data/tools/__init__.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/data/tools/shell.md +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/data/tools/task_list.md +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/data/tools/task_output.md +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/data/tools/task_stop.md +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/data/tools/test_shell.md +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/data/tools/todo_write.md +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/errors.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/input_suggestions.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/llm/__init__.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/llm/agent.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/llm/cache_context.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/llm/compaction.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/llm/context.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/llm/events.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/llm/model_capabilities.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/llm/multimodal.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/llm/provider.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/llm/replay.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/llm/runner.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/llm/thinking.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/mcp.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/prompts/__init__.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/prompts/compact.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/prompts/init_agents.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/prompts/rules.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/prompts/runtime_context.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/prompts/system.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/prompts/tool_docs.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/session_cost.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/sessions/__init__.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/sessions/index.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/sessions/manager.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/sessions/session.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/sessions/store_helpers.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/skill_market.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/skills.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/status.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/subagents.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/todos.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/tools/__init__.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/tools/agents.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/tools/builtin.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/tools/file_state.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/tools/result.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/tools/search.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/tools/shell_output.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/tools/shell_utils.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/tools/test_shell.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/tui/__init__.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/tui/commands.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/tui/compat.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/tui/diff.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/tui/interaction_surfaces.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/tui/runner.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/tui/screens.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/tui/state.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/tui/theme.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/tui/transcript.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/types/__init__.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/types/sdk.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/types/tool_payloads.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/ui/__init__.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/ui/app.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/ui/ask_user_question.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/ui/audit_approval_panel.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/ui/audit_approval_picker.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/ui/exit_summary.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/ui/file_mentions.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/ui/image_input.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/ui/loading_text.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/ui/local_command.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/ui/markdown.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/ui/message_view.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/ui/model_picker.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/ui/prompt_buffer.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/ui/prompt_input.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/ui/session_list.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/ui/session_picker.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/ui/skill_picker.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/ui/slash_commands.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/ui/status_footer.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/ui/styles.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/ui/syntax.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/ui/terminal.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/ui/theme_picker.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/ui/thinking_state.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/ui/welcome.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/update_check.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/usage.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/utils/__init__.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/utils/debug_logger.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/utils/error_logger.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/utils/json.py +0 -0
- {deepy_cli-0.2.27 → deepy_cli-0.2.29}/src/deepy/utils/notify.py +0 -0
|
@@ -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
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|