soothe-cli 0.7.7__tar.gz → 0.7.9__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.
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/PKG-INFO +1 -1
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/runtime/policy/display_policy.py +0 -15
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/runtime/state/stream_accumulator.py +0 -3
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/tui/app/_app.py +0 -2
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/tui/app/_execution.py +31 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/tui/config.py +0 -45
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/tui/preview_limits.py +2 -5
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/tui/textual_adapter.py +7 -3
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/tui/widgets/context_viewer.py +1 -1
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/tui/widgets/messages/cognition_goal_tree.py +166 -29
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/tui/widgets/plan_quick_view_overlay.py +27 -1
- soothe_cli-0.7.7/src/soothe_cli/runtime/wire/display_text.py +0 -15
- soothe_cli-0.7.7/src/soothe_cli/tui/app/_commands.py +0 -50
- soothe_cli-0.7.7/src/soothe_cli/tui/widgets/autopilot_dashboard.py +0 -341
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/.gitignore +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/README.md +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/pyproject.toml +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/__init__.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/cli/__init__.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/cli/commands/__init__.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/cli/commands/autopilot_cmd.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/cli/commands/config_cmd.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/cli/commands/cron_cmd.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/cli/commands/loop_cmd.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/cli/commands/run_cmd.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/cli/commands/status_cmd.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/cli/execution/__init__.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/cli/execution/daemon.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/cli/execution/daemon_errors.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/cli/execution/headless.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/cli/execution/headless_renderer.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/cli/execution/launcher.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/cli/main.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/config/__init__.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/config/cli_config.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/config/loader.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/config/logging_setup.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/runtime/__init__.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/runtime/headless/processor.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/runtime/headless/processor_state.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/runtime/parse/message_processing.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/runtime/parse/tool_call_resolution.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/runtime/parse/tool_result.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/runtime/policy/essential_events.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/runtime/policy/tui_trace_log.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/runtime/presentation/duration_format.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/runtime/presentation/engine.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/runtime/presentation/renderer_base.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/runtime/presentation/renderer_protocol.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/runtime/presentation/subagent_task_display.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/runtime/state/file_tracker.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/runtime/state/session_stats.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/runtime/state/step_router.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/runtime/state/transcript.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/runtime/transport/session.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/runtime/turn/pipeline.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/runtime/turn/prepare.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/runtime/wire/chunk_filter.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/runtime/wire/message_text.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/runtime/wire/messages.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/tui/__init__.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/tui/_cli_context.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/tui/_env_vars.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/tui/_version.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/tui/app/__init__.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/tui/app/_history.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/tui/app/_messages_mixin.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/tui/app/_model.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/tui/app/_module_init.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/tui/app/_startup.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/tui/app/_ui.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/tui/app/app.tcss +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/tui/binding.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/tui/command_registry.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/tui/commands/__init__.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/tui/commands/command_router.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/tui/commands/slash_commands.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/tui/commands/subagent_routing.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/tui/file_change_notify.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/tui/file_change_renderers.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/tui/hooks.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/tui/input.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/tui/markdown_theme.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/tui/media_utils.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/tui/model_config.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/tui/path_utils.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/tui/project_utils.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/tui/sessions.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/tui/shell_color.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/tui/skills/__init__.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/tui/skills/invocation.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/tui/skills/load.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/tui/spinner_labels.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/tui/theme.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/tui/tips.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/tui/tool_display.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/tui/unicode_security.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/tui/update_check.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/tui/widgets/__init__.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/tui/widgets/_links.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/tui/widgets/autocomplete.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/tui/widgets/chat_input.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/tui/widgets/clipboard.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/tui/widgets/diff.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/tui/widgets/editor.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/tui/widgets/file_change_preview.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/tui/widgets/help_screen.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/tui/widgets/history.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/tui/widgets/loading.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/tui/widgets/loop_selector.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/tui/widgets/mcp_viewer.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/tui/widgets/message_store.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/tui/widgets/messages/__init__.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/tui/widgets/messages/_helpers.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/tui/widgets/messages/app.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/tui/widgets/messages/assistant.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/tui/widgets/messages/clarification.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/tui/widgets/messages/cognition_reason.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/tui/widgets/messages/cognition_step.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/tui/widgets/messages/cognition_step_activity.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/tui/widgets/messages/cognition_subagent.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/tui/widgets/messages/diff_message.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/tui/widgets/messages/error.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/tui/widgets/messages/skill.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/tui/widgets/messages/user.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/tui/widgets/model_selector.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/tui/widgets/notification_settings.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/tui/widgets/status.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/tui/widgets/theme_selector.py +0 -0
- {soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/tui/widgets/welcome.py +0 -0
|
@@ -143,24 +143,9 @@ class DisplayPolicy:
|
|
|
143
143
|
return event_type in INTERNAL_EVENT_TYPES or event_type.startswith("soothe.internal.")
|
|
144
144
|
|
|
145
145
|
|
|
146
|
-
# =============================================================================
|
|
147
|
-
# Factory Function
|
|
148
|
-
# =============================================================================
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
def create_display_policy() -> DisplayPolicy:
|
|
152
|
-
"""Create the shared display policy instance (single fixed UX tier)."""
|
|
153
|
-
return DisplayPolicy()
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
# =============================================================================
|
|
157
|
-
# Exports
|
|
158
|
-
# =============================================================================
|
|
159
|
-
|
|
160
146
|
__all__ = [
|
|
161
147
|
"INTERNAL_EVENT_TYPES",
|
|
162
148
|
"SKIP_EVENT_TYPES",
|
|
163
149
|
"DisplayPolicy",
|
|
164
150
|
"VerbosityTier",
|
|
165
|
-
"create_display_policy",
|
|
166
151
|
]
|
|
@@ -23,7 +23,6 @@ from textual.message import Message
|
|
|
23
23
|
|
|
24
24
|
from soothe_cli.runtime.state.session_stats import SessionStats
|
|
25
25
|
from soothe_cli.tui import theme
|
|
26
|
-
from soothe_cli.tui.app._commands import _CommandsMixin
|
|
27
26
|
from soothe_cli.tui.app._execution import _ExecutionMixin
|
|
28
27
|
from soothe_cli.tui.app._history import _HistoryMixin
|
|
29
28
|
from soothe_cli.tui.app._messages_mixin import _MessagesMixin
|
|
@@ -55,7 +54,6 @@ class SootheApp(
|
|
|
55
54
|
App,
|
|
56
55
|
_StartupMixin,
|
|
57
56
|
_HistoryMixin,
|
|
58
|
-
_CommandsMixin,
|
|
59
57
|
_ModelMixin,
|
|
60
58
|
_ExecutionMixin,
|
|
61
59
|
_UIMixin,
|
|
@@ -450,6 +450,37 @@ class _ExecutionMixin:
|
|
|
450
450
|
"""
|
|
451
451
|
return f"{prefix}: {loop_id}"
|
|
452
452
|
|
|
453
|
+
async def _get_conversation_token_count(self) -> int | None:
|
|
454
|
+
"""Return the approximate conversation-only token count.
|
|
455
|
+
|
|
456
|
+
Returns:
|
|
457
|
+
Token count as an integer, or `None` if state is unavailable.
|
|
458
|
+
"""
|
|
459
|
+
if not self._lc_loop_id:
|
|
460
|
+
return None
|
|
461
|
+
try:
|
|
462
|
+
from langchain_core.messages import messages_from_dict
|
|
463
|
+
from langchain_core.messages.utils import count_tokens_approximately
|
|
464
|
+
|
|
465
|
+
if self._daemon_session is None:
|
|
466
|
+
return None
|
|
467
|
+
snap = await self._daemon_session.aget_loop_state(self._lc_loop_id)
|
|
468
|
+
vals = getattr(snap, "values", None)
|
|
469
|
+
if not isinstance(vals, dict):
|
|
470
|
+
return None
|
|
471
|
+
raw = vals.get("messages")
|
|
472
|
+
if not isinstance(raw, list) or not raw:
|
|
473
|
+
return None
|
|
474
|
+
if isinstance(raw[0], dict):
|
|
475
|
+
messages = messages_from_dict(raw)
|
|
476
|
+
else:
|
|
477
|
+
messages = raw
|
|
478
|
+
|
|
479
|
+
return count_tokens_approximately(messages)
|
|
480
|
+
except Exception: # best-effort for /tokens display
|
|
481
|
+
logger.debug("Failed to retrieve conversation token count", exc_info=True)
|
|
482
|
+
return None
|
|
483
|
+
|
|
453
484
|
async def _handle_command(self, command: str) -> None:
|
|
454
485
|
"""Handle a slash command.
|
|
455
486
|
|
|
@@ -1242,51 +1242,6 @@ class Settings:
|
|
|
1242
1242
|
return None
|
|
1243
1243
|
return self.project_root / ".agents" / "skills"
|
|
1244
1244
|
|
|
1245
|
-
@staticmethod
|
|
1246
|
-
def get_user_claude_skills_dir() -> Path:
|
|
1247
|
-
"""Get user-level `~/.claude/skills/` directory (experimental).
|
|
1248
|
-
|
|
1249
|
-
Convenience bridge for cross-tool skill sharing with Claude Code.
|
|
1250
|
-
This is experimental and may be removed.
|
|
1251
|
-
|
|
1252
|
-
Returns:
|
|
1253
|
-
Path to `~/.claude/skills/`
|
|
1254
|
-
"""
|
|
1255
|
-
return Path.home() / ".claude" / "skills"
|
|
1256
|
-
|
|
1257
|
-
def get_project_claude_skills_dir(self) -> Path | None:
|
|
1258
|
-
"""Get project-level `.claude/skills/` directory (experimental).
|
|
1259
|
-
|
|
1260
|
-
Convenience bridge for cross-tool skill sharing with Claude Code.
|
|
1261
|
-
This is experimental and may be removed.
|
|
1262
|
-
|
|
1263
|
-
Returns:
|
|
1264
|
-
Path to `{project_root}/.claude/skills/`, or `None` if not in a project.
|
|
1265
|
-
"""
|
|
1266
|
-
if not self.project_root:
|
|
1267
|
-
return None
|
|
1268
|
-
return self.project_root / ".claude" / "skills"
|
|
1269
|
-
|
|
1270
|
-
@staticmethod
|
|
1271
|
-
def get_built_in_skills_dir() -> Path:
|
|
1272
|
-
"""Get the directory containing built-in skills that ship with the CLI.
|
|
1273
|
-
|
|
1274
|
-
Returns:
|
|
1275
|
-
Path to the `built_in_skills/` directory within the package.
|
|
1276
|
-
"""
|
|
1277
|
-
return Path(__file__).parent / "built_in_skills"
|
|
1278
|
-
|
|
1279
|
-
def get_extra_skills_dirs(self) -> list[Path]:
|
|
1280
|
-
"""Get user-configured extra skill directories.
|
|
1281
|
-
|
|
1282
|
-
Set via `SOOTHE_EXTRA_SKILLS_DIRS` (colon-separated paths) or
|
|
1283
|
-
`[skills].extra_allowed_dirs` in `SOOTHE_HOME/config/config.yml`.
|
|
1284
|
-
|
|
1285
|
-
Returns:
|
|
1286
|
-
List of extra skill directory paths, or empty list if not configured.
|
|
1287
|
-
"""
|
|
1288
|
-
return self.extra_skills_dirs or []
|
|
1289
|
-
|
|
1290
1245
|
|
|
1291
1246
|
class SessionState:
|
|
1292
1247
|
"""Mutable session state shared across the app, adapter, and agent."""
|
|
@@ -48,8 +48,5 @@ APPROVAL_WARNING_TEXT_TRUNCATE_CHARS: Final[int] = 220
|
|
|
48
48
|
# --- Unified diff snippets in chat (`file_ops`, DiffMessage) ---
|
|
49
49
|
APPROVAL_DIFF_MAX_LINES: Final[int] = 15
|
|
50
50
|
|
|
51
|
-
# ---
|
|
52
|
-
|
|
53
|
-
AUTOPILOT_FINDING_LINE_PREVIEW_CHARS: Final[int] = 80
|
|
54
|
-
AUTOPILOT_FINDINGS_VISIBLE_COUNT: Final[int] = 20
|
|
55
|
-
AUTOPILOT_GRAPH_EDGE_PREVIEW_COUNT: Final[int] = 3
|
|
51
|
+
# --- Plan quick-view overlay (Ctrl+T goal tree step rows) ---
|
|
52
|
+
PLAN_QUICK_VIEW_STEP_LINE_MAX_CHARS: Final[int] = 76
|
|
@@ -148,6 +148,7 @@ from soothe_cli.tui.widgets.messages import (
|
|
|
148
148
|
flush_deferred_tools_refreshes,
|
|
149
149
|
reset_turn_tool_refresh_state,
|
|
150
150
|
)
|
|
151
|
+
from soothe_cli.tui.widgets.messages.cognition_goal_tree import _normalize_step_dependencies
|
|
151
152
|
|
|
152
153
|
logger = logging.getLogger(__name__)
|
|
153
154
|
|
|
@@ -1522,8 +1523,9 @@ def _record_plan_step_dag(adapter: TextualUIAdapter, raw_steps: list[Any]) -> No
|
|
|
1522
1523
|
order.append(sid)
|
|
1523
1524
|
in_plan.add(sid)
|
|
1524
1525
|
raw_deps = raw.get("dependencies")
|
|
1525
|
-
|
|
1526
|
-
|
|
1526
|
+
deps = _normalize_step_dependencies(raw_deps)
|
|
1527
|
+
if deps:
|
|
1528
|
+
dep_map[sid] = deps
|
|
1527
1529
|
adapter._plan_step_order = order
|
|
1528
1530
|
adapter._plan_step_ids = in_plan
|
|
1529
1531
|
adapter._plan_step_dependencies = dep_map
|
|
@@ -1743,7 +1745,7 @@ def _tui_effective_ai_blocks(
|
|
|
1743
1745
|
"""Build content blocks for TUI streaming (text + tool calls).
|
|
1744
1746
|
|
|
1745
1747
|
Tool kwargs are merged in
|
|
1746
|
-
:func:`soothe_cli.
|
|
1748
|
+
:func:`soothe_cli.runtime.parse.tool_call_resolution.materialize_ai_blocks_with_resolved_tools`.
|
|
1747
1749
|
"""
|
|
1748
1750
|
from langchain_core.messages import AIMessageChunk
|
|
1749
1751
|
|
|
@@ -2256,6 +2258,7 @@ async def _try_mount_instant_loop_assistant_phase(
|
|
|
2256
2258
|
await current_msg.append_content(text)
|
|
2257
2259
|
if adapter._sync_message_content and current_msg.id:
|
|
2258
2260
|
adapter._sync_message_content(current_msg.id, current_msg._content)
|
|
2261
|
+
await current_msg.stop_stream()
|
|
2259
2262
|
if adapter._set_active_message:
|
|
2260
2263
|
adapter._set_active_message(None)
|
|
2261
2264
|
await _maybe_set_thinking_spinner(adapter, clarification_pending=clarification_pending)
|
|
@@ -2270,6 +2273,7 @@ async def _try_mount_instant_loop_assistant_phase(
|
|
|
2270
2273
|
assistant_message_by_namespace[ns_key] = output_widget
|
|
2271
2274
|
if repaired:
|
|
2272
2275
|
await output_widget.append_content(repaired)
|
|
2276
|
+
await output_widget.stop_stream()
|
|
2273
2277
|
if adapter._sync_message_content and output_widget.id:
|
|
2274
2278
|
adapter._sync_message_content(output_widget.id, output_widget._content)
|
|
2275
2279
|
if adapter._set_active_message:
|
|
@@ -28,7 +28,7 @@ from soothe_cli.tui.config import get_glyphs, is_ascii_mode
|
|
|
28
28
|
logger = logging.getLogger(__name__)
|
|
29
29
|
_REFRESH_INTERVAL_S = 1.0
|
|
30
30
|
|
|
31
|
-
# Status color mapping
|
|
31
|
+
# Status color mapping for goal/context display
|
|
32
32
|
STATUS_COLORS: dict[str, str] = {
|
|
33
33
|
"pending": "dim",
|
|
34
34
|
"active": "yellow",
|
{soothe_cli-0.7.7 → soothe_cli-0.7.9}/src/soothe_cli/tui/widgets/messages/cognition_goal_tree.py
RENAMED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import logging
|
|
6
|
+
import re
|
|
7
|
+
from time import time
|
|
6
8
|
from typing import TYPE_CHECKING, Any
|
|
7
9
|
|
|
8
10
|
from textual.containers import Vertical
|
|
@@ -12,6 +14,7 @@ from textual.widgets import Static
|
|
|
12
14
|
from soothe_cli.runtime.presentation.duration_format import format_duration
|
|
13
15
|
from soothe_cli.tui import theme
|
|
14
16
|
from soothe_cli.tui.config import get_glyphs
|
|
17
|
+
from soothe_cli.tui.preview_limits import PLAN_QUICK_VIEW_STEP_LINE_MAX_CHARS
|
|
15
18
|
from soothe_cli.tui.widgets.messages._helpers import _assemble_card_header
|
|
16
19
|
|
|
17
20
|
if TYPE_CHECKING:
|
|
@@ -20,14 +23,36 @@ if TYPE_CHECKING:
|
|
|
20
23
|
logger = logging.getLogger(__name__)
|
|
21
24
|
|
|
22
25
|
_MAX_GOAL_HEADER = 100
|
|
23
|
-
_MAX_GOAL_STEP_DESC =
|
|
26
|
+
_MAX_GOAL_STEP_DESC = 80
|
|
27
|
+
_MAX_STEP_SUMMARY_TAIL = 80
|
|
28
|
+
_MAX_DEPENDENCY_IDS = 3
|
|
24
29
|
|
|
30
|
+
_DONE_WITH_TOOLS_RE = re.compile(r"^Done(?:\s*\[\d+\s+tools?\])?$", re.IGNORECASE)
|
|
25
31
|
|
|
26
|
-
|
|
32
|
+
|
|
33
|
+
def _dependency_suffix(deps: tuple[str, ...]) -> str:
|
|
34
|
+
"""Return a compact dependency hint for plan quick-view rows."""
|
|
35
|
+
if not deps:
|
|
36
|
+
return ""
|
|
37
|
+
shown = deps[:_MAX_DEPENDENCY_IDS]
|
|
38
|
+
text = ", ".join(shown)
|
|
39
|
+
if len(deps) > _MAX_DEPENDENCY_IDS:
|
|
40
|
+
text += ", …"
|
|
41
|
+
return f" (→ {text})"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _plan_quick_view_step_summary(
|
|
45
|
+
success: bool,
|
|
46
|
+
summary: str,
|
|
47
|
+
*,
|
|
48
|
+
tool_call_count: int = 0,
|
|
49
|
+
) -> str:
|
|
27
50
|
"""Return a Ctrl+T-safe step summary tail (hide recoverable tool errors)."""
|
|
28
51
|
tail = (summary or "").strip()
|
|
29
52
|
if not tail or tail in ("Done", "Failed"):
|
|
30
53
|
return ""
|
|
54
|
+
if success and tool_call_count > 0 and _DONE_WITH_TOOLS_RE.match(tail):
|
|
55
|
+
return ""
|
|
31
56
|
if success:
|
|
32
57
|
try:
|
|
33
58
|
from soothe.foundation.sloop.engine.act_wave_finalize import is_error_tool_result_text
|
|
@@ -39,6 +64,12 @@ def _plan_quick_view_step_summary(success: bool, summary: str) -> str:
|
|
|
39
64
|
return tail
|
|
40
65
|
|
|
41
66
|
|
|
67
|
+
def _normalize_step_dependencies(raw_deps: Any) -> tuple[str, ...]:
|
|
68
|
+
if not isinstance(raw_deps, list):
|
|
69
|
+
return ()
|
|
70
|
+
return tuple(str(dep).strip() for dep in raw_deps if str(dep).strip())
|
|
71
|
+
|
|
72
|
+
|
|
42
73
|
class _StepLineState:
|
|
43
74
|
"""Mutable row state for the goal → steps aggregate."""
|
|
44
75
|
|
|
@@ -50,6 +81,8 @@ class _StepLineState:
|
|
|
50
81
|
"duration_ms",
|
|
51
82
|
"tool_call_count",
|
|
52
83
|
"summary",
|
|
84
|
+
"dependencies",
|
|
85
|
+
"started_at",
|
|
53
86
|
)
|
|
54
87
|
|
|
55
88
|
def __init__(
|
|
@@ -62,6 +95,8 @@ class _StepLineState:
|
|
|
62
95
|
duration_ms: int = 0,
|
|
63
96
|
tool_call_count: int = 0,
|
|
64
97
|
summary: str = "",
|
|
98
|
+
dependencies: tuple[str, ...] = (),
|
|
99
|
+
started_at: float | None = None,
|
|
65
100
|
) -> None:
|
|
66
101
|
self.step_id = step_id
|
|
67
102
|
self.description = description
|
|
@@ -70,6 +105,8 @@ class _StepLineState:
|
|
|
70
105
|
self.duration_ms = duration_ms
|
|
71
106
|
self.tool_call_count = tool_call_count
|
|
72
107
|
self.summary = summary
|
|
108
|
+
self.dependencies = dependencies
|
|
109
|
+
self.started_at = started_at
|
|
73
110
|
|
|
74
111
|
|
|
75
112
|
class CognitionGoalTreeMessage(Vertical):
|
|
@@ -138,8 +175,12 @@ class CognitionGoalTreeMessage(Vertical):
|
|
|
138
175
|
@staticmethod
|
|
139
176
|
def _clip(text: str, max_len: int) -> str:
|
|
140
177
|
t = (text or "").strip().replace("\n", " ")
|
|
178
|
+
if max_len <= 0:
|
|
179
|
+
return "…"
|
|
141
180
|
if len(t) <= max_len:
|
|
142
181
|
return t
|
|
182
|
+
if max_len == 1:
|
|
183
|
+
return "…"
|
|
143
184
|
return t[: max_len - 1].rstrip() + "…"
|
|
144
185
|
|
|
145
186
|
def _goal_tree_status(self) -> str:
|
|
@@ -198,45 +239,111 @@ class CognitionGoalTreeMessage(Vertical):
|
|
|
198
239
|
g = get_glyphs()
|
|
199
240
|
return f"{g.output_prefix} "
|
|
200
241
|
|
|
201
|
-
def
|
|
202
|
-
"""One goal→step row: dim tree gutter, foreground body (parity with ``CognitionStepMessage`` tool rows)."""
|
|
242
|
+
def _step_icon(self, st: _StepLineState) -> str:
|
|
203
243
|
g = get_glyphs()
|
|
244
|
+
if st.phase == "pending" or st.phase == "queued":
|
|
245
|
+
return g.circle_empty
|
|
246
|
+
if st.phase == "running":
|
|
247
|
+
frames = g.spinner_frames
|
|
248
|
+
return frames[self._spinner_position % len(frames)]
|
|
249
|
+
return g.checkmark if st.success else g.error
|
|
250
|
+
|
|
251
|
+
def _step_stats_suffix(self, st: _StepLineState) -> str:
|
|
252
|
+
"""Duration and tool-count suffix for running or completed rows."""
|
|
253
|
+
if st.phase == "running":
|
|
254
|
+
parts: list[str] = []
|
|
255
|
+
if st.started_at is not None:
|
|
256
|
+
elapsed_s = max(0.001, time() - st.started_at)
|
|
257
|
+
parts.append(format_duration(elapsed_s))
|
|
258
|
+
if st.tool_call_count > 0:
|
|
259
|
+
parts.append(f"{st.tool_call_count} tools")
|
|
260
|
+
if not parts:
|
|
261
|
+
return ""
|
|
262
|
+
return " · " + " · ".join(parts)
|
|
263
|
+
if st.phase not in ("done", "error"):
|
|
264
|
+
return ""
|
|
265
|
+
dur_s = max(0.001, st.duration_ms / 1000.0)
|
|
266
|
+
parts = [format_duration(dur_s)]
|
|
267
|
+
if st.tool_call_count > 0:
|
|
268
|
+
parts.append(f"{st.tool_call_count} tools")
|
|
269
|
+
return " · " + " · ".join(parts)
|
|
270
|
+
|
|
271
|
+
def _step_summary_suffix(self, st: _StepLineState) -> str:
|
|
272
|
+
if st.phase not in ("done", "error"):
|
|
273
|
+
return ""
|
|
274
|
+
tail = _plan_quick_view_step_summary(
|
|
275
|
+
st.success,
|
|
276
|
+
st.summary,
|
|
277
|
+
tool_call_count=st.tool_call_count,
|
|
278
|
+
)
|
|
279
|
+
if not tail:
|
|
280
|
+
return ""
|
|
281
|
+
return f" — {self._clip(tail, _MAX_STEP_SUMMARY_TAIL)}"
|
|
282
|
+
|
|
283
|
+
def _fit_step_line(
|
|
284
|
+
self,
|
|
285
|
+
st: _StepLineState,
|
|
286
|
+
*,
|
|
287
|
+
icon: str,
|
|
288
|
+
max_line_width: int | None,
|
|
289
|
+
) -> str:
|
|
290
|
+
"""Build a single-line step row, clipping description to fit ``max_line_width``."""
|
|
291
|
+
step_prefix = f"{st.step_id}: " if st.step_id else ""
|
|
292
|
+
dep_suffix = _dependency_suffix(st.dependencies)
|
|
293
|
+
queued_suffix = " · queued" if st.phase == "queued" else ""
|
|
294
|
+
stats_suffix = self._step_stats_suffix(st)
|
|
295
|
+
summary_suffix = self._step_summary_suffix(st)
|
|
296
|
+
lead = f"{icon} {step_prefix}"
|
|
297
|
+
|
|
298
|
+
def assemble(desc: str, *, include_summary: bool) -> str:
|
|
299
|
+
summary = summary_suffix if include_summary else ""
|
|
300
|
+
return f"{lead}{desc}{dep_suffix}{queued_suffix}{stats_suffix}{summary}"
|
|
301
|
+
|
|
302
|
+
width = max_line_width or PLAN_QUICK_VIEW_STEP_LINE_MAX_CHARS
|
|
303
|
+
desc = self._clip(st.description, _MAX_GOAL_STEP_DESC)
|
|
304
|
+
line = assemble(desc, include_summary=True)
|
|
305
|
+
if len(line) <= width:
|
|
306
|
+
return line
|
|
307
|
+
|
|
308
|
+
line = assemble(desc, include_summary=False)
|
|
309
|
+
if len(line) <= width:
|
|
310
|
+
return line
|
|
311
|
+
|
|
312
|
+
fixed_len = len(f"{lead}{dep_suffix}{queued_suffix}{stats_suffix}")
|
|
313
|
+
budget = width - fixed_len
|
|
314
|
+
clipped_desc = self._clip(st.description, budget)
|
|
315
|
+
return f"{lead}{clipped_desc}{dep_suffix}{queued_suffix}{stats_suffix}"
|
|
316
|
+
|
|
317
|
+
def _goal_tree_step_line_content(
|
|
318
|
+
self,
|
|
319
|
+
st: _StepLineState,
|
|
320
|
+
*,
|
|
321
|
+
max_line_width: int | None = None,
|
|
322
|
+
) -> Content:
|
|
323
|
+
"""One goal→step row: dim tree gutter, foreground body (parity with ``CognitionStepMessage`` tool rows)."""
|
|
204
324
|
try:
|
|
205
325
|
colors = theme.get_theme_colors(self)
|
|
206
326
|
except Exception: # noqa: BLE001
|
|
207
327
|
colors = theme.DARK_COLORS
|
|
208
328
|
gutter = self._indent_prefix()
|
|
209
|
-
|
|
210
|
-
|
|
329
|
+
icon = self._step_icon(st)
|
|
330
|
+
rest = self._fit_step_line(st, icon=icon, max_line_width=max_line_width)
|
|
331
|
+
|
|
211
332
|
if st.phase == "pending":
|
|
212
|
-
rest = f"{g.circle_empty} {label}"
|
|
213
333
|
return Content.assemble(
|
|
214
334
|
Content.styled(gutter, "dim"),
|
|
215
335
|
Content.styled(rest, "dim"),
|
|
216
336
|
)
|
|
217
337
|
if st.phase == "queued":
|
|
218
|
-
rest = f"{g.circle_empty} {label} · queued"
|
|
219
338
|
return Content.assemble(
|
|
220
339
|
Content.styled(gutter, "dim"),
|
|
221
340
|
Content.styled(rest, colors.cognition),
|
|
222
341
|
)
|
|
223
342
|
if st.phase == "running":
|
|
224
|
-
frames = g.spinner_frames
|
|
225
|
-
frame = frames[self._spinner_position % len(frames)]
|
|
226
|
-
rest = f"{frame} {label}"
|
|
227
343
|
return Content.assemble(
|
|
228
344
|
Content.styled(gutter, "dim"),
|
|
229
345
|
Content.styled(rest, colors.foreground),
|
|
230
346
|
)
|
|
231
|
-
icon = g.checkmark if st.success else g.error
|
|
232
|
-
dur_s = max(0.001, st.duration_ms / 1000.0)
|
|
233
|
-
dur = format_duration(dur_s)
|
|
234
|
-
rest = f"{icon} {label} · {dur}"
|
|
235
|
-
if st.tool_call_count > 0:
|
|
236
|
-
rest += f" · {st.tool_call_count} tools"
|
|
237
|
-
tail = _plan_quick_view_step_summary(st.success, st.summary)
|
|
238
|
-
if tail:
|
|
239
|
-
rest += f" — {self._clip(tail, 80)}"
|
|
240
347
|
if st.phase == "error" or (st.phase == "done" and not st.success):
|
|
241
348
|
return Content.assemble(
|
|
242
349
|
Content.styled(gutter, "dim"),
|
|
@@ -247,13 +354,15 @@ class CognitionGoalTreeMessage(Vertical):
|
|
|
247
354
|
Content.styled(rest, colors.foreground),
|
|
248
355
|
)
|
|
249
356
|
|
|
250
|
-
def _assemble_steps_content(self) -> Content:
|
|
357
|
+
def _assemble_steps_content(self, *, max_line_width: int | None = None) -> Content:
|
|
251
358
|
line_contents: list[Content] = []
|
|
252
359
|
for sid in self._step_order:
|
|
253
360
|
st = self._steps.get(sid)
|
|
254
361
|
if st is None:
|
|
255
362
|
continue
|
|
256
|
-
line_contents.append(
|
|
363
|
+
line_contents.append(
|
|
364
|
+
self._goal_tree_step_line_content(st, max_line_width=max_line_width)
|
|
365
|
+
)
|
|
257
366
|
if not line_contents:
|
|
258
367
|
return Content("")
|
|
259
368
|
parts: list[object] = []
|
|
@@ -273,16 +382,29 @@ class CognitionGoalTreeMessage(Vertical):
|
|
|
273
382
|
except Exception:
|
|
274
383
|
pass
|
|
275
384
|
|
|
276
|
-
def plan_quick_view_content(self) -> Content:
|
|
385
|
+
def plan_quick_view_content(self, *, max_line_width: int | None = None) -> Content:
|
|
277
386
|
"""Full goal tree snapshot for the sticky plan quick-view overlay."""
|
|
278
387
|
parts: list[object] = [self._goal_header_content()]
|
|
279
|
-
steps = self._assemble_steps_content()
|
|
388
|
+
steps = self._assemble_steps_content(max_line_width=max_line_width)
|
|
280
389
|
if steps.plain.strip():
|
|
281
390
|
parts.extend([Content("\n"), steps])
|
|
282
391
|
if self._footer_visible and self._footer_plain:
|
|
283
392
|
parts.extend([Content("\n"), self._goal_footer_styled_content()])
|
|
284
393
|
return Content.assemble(*parts)
|
|
285
394
|
|
|
395
|
+
def sync_running_live_stats(
|
|
396
|
+
self,
|
|
397
|
+
stats: dict[str, tuple[int, float | None]],
|
|
398
|
+
) -> None:
|
|
399
|
+
"""Update in-flight tool counts and started_at from step cards."""
|
|
400
|
+
for sid, (tool_count, started_at) in stats.items():
|
|
401
|
+
st = self._steps.get(sid)
|
|
402
|
+
if st is None or st.phase != "running":
|
|
403
|
+
continue
|
|
404
|
+
st.tool_call_count = max(0, int(tool_count))
|
|
405
|
+
if started_at is not None:
|
|
406
|
+
st.started_at = started_at
|
|
407
|
+
|
|
286
408
|
def tick_running_spinner(self) -> None:
|
|
287
409
|
"""Advance the running-row spinner when any step is in flight."""
|
|
288
410
|
if not any(st.phase == "running" for st in self._steps.values()):
|
|
@@ -339,6 +461,8 @@ class CognitionGoalTreeMessage(Vertical):
|
|
|
339
461
|
"duration_ms": st.duration_ms,
|
|
340
462
|
"tool_call_count": st.tool_call_count,
|
|
341
463
|
"summary": st.summary,
|
|
464
|
+
"dependencies": list(st.dependencies),
|
|
465
|
+
"started_at": st.started_at,
|
|
342
466
|
}
|
|
343
467
|
)
|
|
344
468
|
return {
|
|
@@ -366,6 +490,8 @@ class CognitionGoalTreeMessage(Vertical):
|
|
|
366
490
|
sid = str(row.get("id", "")).strip()
|
|
367
491
|
if not sid:
|
|
368
492
|
continue
|
|
493
|
+
started_raw = row.get("started_at")
|
|
494
|
+
started_at = float(started_raw) if started_raw is not None else None
|
|
369
495
|
st = _StepLineState(
|
|
370
496
|
sid,
|
|
371
497
|
str(row.get("description", "")),
|
|
@@ -374,6 +500,8 @@ class CognitionGoalTreeMessage(Vertical):
|
|
|
374
500
|
duration_ms=int(row.get("duration_ms", 0)),
|
|
375
501
|
tool_call_count=int(row.get("tool_call_count", 0)),
|
|
376
502
|
summary=str(row.get("summary", "")),
|
|
503
|
+
dependencies=_normalize_step_dependencies(row.get("dependencies")),
|
|
504
|
+
started_at=started_at,
|
|
377
505
|
)
|
|
378
506
|
self._step_order.append(sid)
|
|
379
507
|
self._steps[sid] = st
|
|
@@ -398,14 +526,23 @@ class CognitionGoalTreeMessage(Vertical):
|
|
|
398
526
|
continue
|
|
399
527
|
planned_ids.add(sid)
|
|
400
528
|
desc = str(row.get("description", "")).strip() or "(step)"
|
|
529
|
+
deps = _normalize_step_dependencies(row.get("dependencies"))
|
|
401
530
|
existing = self._steps.get(sid)
|
|
402
531
|
if existing is None:
|
|
403
532
|
self._step_order.append(sid)
|
|
404
|
-
self._steps[sid] = _StepLineState(
|
|
533
|
+
self._steps[sid] = _StepLineState(
|
|
534
|
+
sid,
|
|
535
|
+
desc,
|
|
536
|
+
phase="pending",
|
|
537
|
+
dependencies=deps,
|
|
538
|
+
)
|
|
405
539
|
elif existing.phase in ("pending", "queued"):
|
|
406
540
|
existing.description = desc
|
|
541
|
+
existing.dependencies = deps
|
|
407
542
|
elif existing.phase == "running" and desc:
|
|
408
543
|
existing.description = desc
|
|
544
|
+
if deps:
|
|
545
|
+
existing.dependencies = deps
|
|
409
546
|
for sid in list(self._step_order):
|
|
410
547
|
st = self._steps.get(sid)
|
|
411
548
|
if st is not None and st.phase == "pending" and sid not in planned_ids:
|
|
@@ -440,12 +577,10 @@ class CognitionGoalTreeMessage(Vertical):
|
|
|
440
577
|
st.phase = phase
|
|
441
578
|
if desc:
|
|
442
579
|
st.description = desc
|
|
580
|
+
if phase == "running" and st.started_at is None:
|
|
581
|
+
st.started_at = time()
|
|
443
582
|
self._refresh_steps_display()
|
|
444
583
|
|
|
445
|
-
def add_step_running(self, step_id: str, description: str) -> None:
|
|
446
|
-
"""Register a step in running state and refresh the aggregate."""
|
|
447
|
-
self.set_step_phase(step_id, "running", description=description)
|
|
448
|
-
|
|
449
584
|
def complete_step(
|
|
450
585
|
self,
|
|
451
586
|
step_id: str,
|
|
@@ -468,6 +603,7 @@ class CognitionGoalTreeMessage(Vertical):
|
|
|
468
603
|
st.duration_ms = duration_ms
|
|
469
604
|
st.tool_call_count = tool_call_count
|
|
470
605
|
st.summary = summary or ""
|
|
606
|
+
st.started_at = None
|
|
471
607
|
self._refresh_steps_display()
|
|
472
608
|
|
|
473
609
|
def set_loop_finished(
|
|
@@ -523,6 +659,7 @@ class CognitionGoalTreeMessage(Vertical):
|
|
|
523
659
|
st.success = False
|
|
524
660
|
st.duration_ms = 0
|
|
525
661
|
st.summary = msg
|
|
662
|
+
st.started_at = None
|
|
526
663
|
self._refresh_steps_display()
|
|
527
664
|
self._footer_plain = self._clip(msg, 120)
|
|
528
665
|
self._footer_visible = True
|
|
@@ -9,6 +9,8 @@ from textual.containers import Vertical, VerticalScroll
|
|
|
9
9
|
from textual.content import Content
|
|
10
10
|
from textual.widgets import Static
|
|
11
11
|
|
|
12
|
+
from soothe_cli.tui.config import get_glyphs
|
|
13
|
+
from soothe_cli.tui.preview_limits import PLAN_QUICK_VIEW_STEP_LINE_MAX_CHARS
|
|
12
14
|
from soothe_cli.tui.widgets.messages._helpers import _RUNNING_SPINNER_INTERVAL_SECONDS
|
|
13
15
|
|
|
14
16
|
if TYPE_CHECKING:
|
|
@@ -29,6 +31,17 @@ def get_live_goal_tree(app: Any) -> CognitionGoalTreeMessage | None:
|
|
|
29
31
|
return tree
|
|
30
32
|
|
|
31
33
|
|
|
34
|
+
def _goal_tree_running_live_stats(adapter: Any) -> dict[str, tuple[int, float | None]]:
|
|
35
|
+
"""Collect live tool counts and start times from running step cards."""
|
|
36
|
+
stats: dict[str, tuple[int, float | None]] = {}
|
|
37
|
+
for sid, card in getattr(adapter, "_current_step_messages", {}).items():
|
|
38
|
+
if card._status != "running":
|
|
39
|
+
continue
|
|
40
|
+
idx = card._build_row_index()
|
|
41
|
+
stats[sid] = (idx.main_tool_count, card._start_time)
|
|
42
|
+
return stats
|
|
43
|
+
|
|
44
|
+
|
|
32
45
|
class PlanQuickViewOverlay(Vertical):
|
|
33
46
|
"""Floating quick-view panel of the full plan above the chat prompt.
|
|
34
47
|
|
|
@@ -143,8 +156,21 @@ class PlanQuickViewOverlay(Vertical):
|
|
|
143
156
|
self._content.update(Content.styled("No active plan.", "dim"))
|
|
144
157
|
return
|
|
145
158
|
try:
|
|
159
|
+
adapter = getattr(self.app, "_ui_adapter", None)
|
|
160
|
+
if adapter is not None:
|
|
161
|
+
tree.sync_running_live_stats(_goal_tree_running_live_stats(adapter))
|
|
146
162
|
tree.tick_running_spinner()
|
|
147
|
-
self.
|
|
163
|
+
max_line_width = self._plan_quick_view_line_width()
|
|
164
|
+
self._content.update(tree.plan_quick_view_content(max_line_width=max_line_width))
|
|
148
165
|
except Exception: # noqa: BLE001
|
|
149
166
|
logger.debug("Failed to render plan quick view", exc_info=True)
|
|
150
167
|
self._content.update(Content.styled("(plan view unavailable)", "dim"))
|
|
168
|
+
|
|
169
|
+
def _plan_quick_view_line_width(self) -> int:
|
|
170
|
+
"""Available columns for one plan step row inside the overlay."""
|
|
171
|
+
overlay_padding = 4
|
|
172
|
+
gutter_len = len(get_glyphs().output_prefix) + 1
|
|
173
|
+
width = self.size.width if self.size.width else 0
|
|
174
|
+
if width > overlay_padding + gutter_len:
|
|
175
|
+
return max(PLAN_QUICK_VIEW_STEP_LINE_MAX_CHARS, width - overlay_padding - gutter_len)
|
|
176
|
+
return PLAN_QUICK_VIEW_STEP_LINE_MAX_CHARS
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
"""Re-export shim for ``soothe_sdk.display.text_extract`` (RFC-413)."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
from soothe_sdk.display.text_extract import (
|
|
6
|
-
extract_ai_text_for_display,
|
|
7
|
-
extract_user_text_for_display,
|
|
8
|
-
normalize_stream_message,
|
|
9
|
-
)
|
|
10
|
-
|
|
11
|
-
__all__ = [
|
|
12
|
-
"extract_ai_text_for_display",
|
|
13
|
-
"extract_user_text_for_display",
|
|
14
|
-
"normalize_stream_message",
|
|
15
|
-
]
|