soothe-cli 0.7.9__tar.gz → 0.7.11__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 (127) hide show
  1. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/PKG-INFO +1 -1
  2. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/runtime/policy/display_policy.py +3 -3
  3. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/runtime/state/step_router.py +2 -3
  4. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/tui/preview_limits.py +1 -1
  5. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/tui/textual_adapter.py +80 -97
  6. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/tui/widgets/messages/cognition_step.py +2 -37
  7. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/tui/widgets/messages/cognition_step_activity.py +0 -17
  8. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/.gitignore +0 -0
  9. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/README.md +0 -0
  10. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/pyproject.toml +0 -0
  11. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/__init__.py +0 -0
  12. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/cli/__init__.py +0 -0
  13. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/cli/commands/__init__.py +0 -0
  14. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/cli/commands/autopilot_cmd.py +0 -0
  15. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/cli/commands/config_cmd.py +0 -0
  16. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/cli/commands/cron_cmd.py +0 -0
  17. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/cli/commands/loop_cmd.py +0 -0
  18. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/cli/commands/run_cmd.py +0 -0
  19. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/cli/commands/status_cmd.py +0 -0
  20. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/cli/execution/__init__.py +0 -0
  21. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/cli/execution/daemon.py +0 -0
  22. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/cli/execution/daemon_errors.py +0 -0
  23. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/cli/execution/headless.py +0 -0
  24. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/cli/execution/headless_renderer.py +0 -0
  25. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/cli/execution/launcher.py +0 -0
  26. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/cli/main.py +0 -0
  27. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/config/__init__.py +0 -0
  28. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/config/cli_config.py +0 -0
  29. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/config/loader.py +0 -0
  30. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/config/logging_setup.py +0 -0
  31. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/runtime/__init__.py +0 -0
  32. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/runtime/headless/processor.py +0 -0
  33. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/runtime/headless/processor_state.py +0 -0
  34. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/runtime/parse/message_processing.py +0 -0
  35. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/runtime/parse/tool_call_resolution.py +0 -0
  36. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/runtime/parse/tool_result.py +0 -0
  37. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/runtime/policy/essential_events.py +0 -0
  38. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/runtime/policy/tui_trace_log.py +0 -0
  39. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/runtime/presentation/duration_format.py +0 -0
  40. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/runtime/presentation/engine.py +0 -0
  41. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/runtime/presentation/renderer_base.py +0 -0
  42. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/runtime/presentation/renderer_protocol.py +0 -0
  43. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/runtime/presentation/subagent_task_display.py +0 -0
  44. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/runtime/state/file_tracker.py +0 -0
  45. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/runtime/state/session_stats.py +0 -0
  46. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/runtime/state/stream_accumulator.py +0 -0
  47. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/runtime/state/transcript.py +0 -0
  48. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/runtime/transport/session.py +0 -0
  49. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/runtime/turn/pipeline.py +0 -0
  50. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/runtime/turn/prepare.py +0 -0
  51. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/runtime/wire/chunk_filter.py +0 -0
  52. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/runtime/wire/message_text.py +0 -0
  53. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/runtime/wire/messages.py +0 -0
  54. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/tui/__init__.py +0 -0
  55. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/tui/_cli_context.py +0 -0
  56. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/tui/_env_vars.py +0 -0
  57. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/tui/_version.py +0 -0
  58. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/tui/app/__init__.py +0 -0
  59. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/tui/app/_app.py +0 -0
  60. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/tui/app/_execution.py +0 -0
  61. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/tui/app/_history.py +0 -0
  62. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/tui/app/_messages_mixin.py +0 -0
  63. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/tui/app/_model.py +0 -0
  64. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/tui/app/_module_init.py +0 -0
  65. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/tui/app/_startup.py +0 -0
  66. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/tui/app/_ui.py +0 -0
  67. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/tui/app/app.tcss +0 -0
  68. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/tui/binding.py +0 -0
  69. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/tui/command_registry.py +0 -0
  70. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/tui/commands/__init__.py +0 -0
  71. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/tui/commands/command_router.py +0 -0
  72. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/tui/commands/slash_commands.py +0 -0
  73. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/tui/commands/subagent_routing.py +0 -0
  74. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/tui/config.py +0 -0
  75. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/tui/file_change_notify.py +0 -0
  76. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/tui/file_change_renderers.py +0 -0
  77. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/tui/hooks.py +0 -0
  78. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/tui/input.py +0 -0
  79. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/tui/markdown_theme.py +0 -0
  80. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/tui/media_utils.py +0 -0
  81. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/tui/model_config.py +0 -0
  82. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/tui/path_utils.py +0 -0
  83. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/tui/project_utils.py +0 -0
  84. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/tui/sessions.py +0 -0
  85. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/tui/shell_color.py +0 -0
  86. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/tui/skills/__init__.py +0 -0
  87. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/tui/skills/invocation.py +0 -0
  88. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/tui/skills/load.py +0 -0
  89. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/tui/spinner_labels.py +0 -0
  90. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/tui/theme.py +0 -0
  91. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/tui/tips.py +0 -0
  92. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/tui/tool_display.py +0 -0
  93. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/tui/unicode_security.py +0 -0
  94. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/tui/update_check.py +0 -0
  95. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/tui/widgets/__init__.py +0 -0
  96. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/tui/widgets/_links.py +0 -0
  97. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/tui/widgets/autocomplete.py +0 -0
  98. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/tui/widgets/chat_input.py +0 -0
  99. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/tui/widgets/clipboard.py +0 -0
  100. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/tui/widgets/context_viewer.py +0 -0
  101. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/tui/widgets/diff.py +0 -0
  102. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/tui/widgets/editor.py +0 -0
  103. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/tui/widgets/file_change_preview.py +0 -0
  104. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/tui/widgets/help_screen.py +0 -0
  105. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/tui/widgets/history.py +0 -0
  106. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/tui/widgets/loading.py +0 -0
  107. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/tui/widgets/loop_selector.py +0 -0
  108. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/tui/widgets/mcp_viewer.py +0 -0
  109. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/tui/widgets/message_store.py +0 -0
  110. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/tui/widgets/messages/__init__.py +0 -0
  111. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/tui/widgets/messages/_helpers.py +0 -0
  112. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/tui/widgets/messages/app.py +0 -0
  113. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/tui/widgets/messages/assistant.py +0 -0
  114. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/tui/widgets/messages/clarification.py +0 -0
  115. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/tui/widgets/messages/cognition_goal_tree.py +0 -0
  116. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/tui/widgets/messages/cognition_reason.py +0 -0
  117. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/tui/widgets/messages/cognition_subagent.py +0 -0
  118. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/tui/widgets/messages/diff_message.py +0 -0
  119. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/tui/widgets/messages/error.py +0 -0
  120. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/tui/widgets/messages/skill.py +0 -0
  121. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/tui/widgets/messages/user.py +0 -0
  122. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/tui/widgets/model_selector.py +0 -0
  123. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/tui/widgets/notification_settings.py +0 -0
  124. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/tui/widgets/plan_quick_view_overlay.py +0 -0
  125. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/tui/widgets/status.py +0 -0
  126. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/tui/widgets/theme_selector.py +0 -0
  127. {soothe_cli-0.7.9 → soothe_cli-0.7.11}/src/soothe_cli/tui/widgets/welcome.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: soothe-cli
3
- Version: 0.7.9
3
+ Version: 0.7.11
4
4
  Summary: Soothe CLI client - communicates with daemon via WebSocket
5
5
  Project-URL: Homepage, https://github.com/mirasoth/soothe
6
6
  Project-URL: Documentation, https://soothe.readthedocs.io
@@ -134,9 +134,9 @@ class DisplayPolicy:
134
134
  """Check if this is a plan-related event."""
135
135
  return event_type.startswith("soothe.cognition.plan.")
136
136
 
137
- def is_deep_research_event(self, event_type: str) -> bool:
138
- """Check if this is a Deep Research subagent event."""
139
- return event_type.startswith("soothe.subagent.deep_research.")
137
+ def is_subagent_wire_event(self, event_type: str) -> bool:
138
+ """Check if this is a curated subagent wire event."""
139
+ return event_type.startswith("soothe.subagent.")
140
140
 
141
141
  def is_internal_event(self, event_type: str) -> bool:
142
142
  """Check if this is an internal (never-shown) event."""
@@ -174,9 +174,8 @@ class StepTaskRouter:
174
174
  ) -> None:
175
175
  """Promote a pending step card to running only when it is executing (RFC-628).
176
176
 
177
- Pre-mounted future steps must stay ``pending`` until ``step.started`` even when
178
- tools are stamped with their unified step id. The pre-``step.started`` race for
179
- the active step is allowed only while no sibling step card is already running.
177
+ Future steps are not pre-mounted in the message list; only the active step
178
+ may transition to ``running`` before ``step.started`` when tools arrive early.
180
179
  """
181
180
  if getattr(step_w, "_status", "") != "pending":
182
181
  return
@@ -15,7 +15,7 @@ from typing import Final
15
15
  STEP_CARD_SHOW_TOOL_ROW_DETAILS: Final[bool] = False
16
16
 
17
17
  # Latest per-tool invocation lines on step and SubAgent cards (shared activity tree).
18
- STEP_CARD_TOOL_ACTIVITY_PREVIEW_COUNT: Final[int] = 3
18
+ STEP_CARD_TOOL_ACTIVITY_PREVIEW_COUNT: Final[int] = 2
19
19
 
20
20
  # Optional manual full tool-list folding threshold (not auto-collapse).
21
21
  STEP_TASK_CARD_COLLAPSE_LINE_THRESHOLD: Final[int] = 3
@@ -52,6 +52,11 @@ from soothe_sdk.ux.loop_stream import (
52
52
  is_stream_terminal,
53
53
  )
54
54
  from soothe_sdk.ux.stream_tool_wire import STREAM_TOOL_CALL_UPDATE, TOOL_CALL_UPDATES_BATCH
55
+ from soothe_sdk.ux.subagent_wire_display import (
56
+ SubagentWireRenderKind,
57
+ classify_subagent_wire_render,
58
+ subagent_wire_row_params,
59
+ )
55
60
  from soothe_sdk.ux.task_namespace import (
56
61
  TaskScope,
57
62
  is_inner_subgraph_task_tool_id,
@@ -293,9 +298,6 @@ class TextualUIAdapter:
293
298
  self._execute_wave_completed: int = 0
294
299
  """Completed steps in the current execute batch."""
295
300
 
296
- self._successful_step_ids: set[str] = set()
297
- """Step ids that finished successfully this loop (survives plan=keep replays)."""
298
-
299
301
  self._clarification_input_by_step: dict[str, ClarificationInputMessage] = {}
300
302
  """Active inline ``ClarificationInputMessage`` widgets keyed by step id.
301
303
 
@@ -401,7 +403,7 @@ def _stream_end_pending_error_message(
401
403
  if is_daemon_worker_thread_lost(err_msg) or is_daemon_worker_subprocess_lost(err_msg):
402
404
  return "Worker stopped during stream"
403
405
  if end_state == "idle" and any(
404
- getattr(widget, "_status", "") in {"running", "queued"}
406
+ getattr(widget, "_status", "") == "running"
405
407
  for widget in adapter._current_step_messages.values()
406
408
  ):
407
409
  return "Stream ended before steps completed"
@@ -951,34 +953,6 @@ def _ingest_wire_step_on_subagent_card(
951
953
  subagent_card.set_tool_success(row_id, "", duration_ms=duration_ms)
952
954
 
953
955
 
954
- def _subagent_wire_step_params(
955
- event_type: str,
956
- data: dict[str, Any],
957
- ) -> tuple[str, dict[str, Any], str, int] | None:
958
- """Map curated subagent wire events to SubAgent card row fields."""
959
- et = str(event_type or "").strip()
960
- if et.endswith(".gather.summary"):
961
- query = str(data.get("query_preview", "") or "").strip()
962
- rc = int(data.get("result_count", 0) or 0)
963
- st = int(data.get("sources_touched", 0) or 0)
964
- preview = query
965
- if rc or st:
966
- tail = f"{rc} hits, {st} sources"
967
- preview = f"{query} → {tail}" if query else tail
968
- return "WebSearch", {"query": preview or query}, "success", 0
969
- if et.endswith(".step.completed"):
970
- tool_name = str(data.get("tool_name", "") or "").strip() or "Step"
971
- args_preview = str(data.get("args_preview", "") or "").strip()
972
- status = str(data.get("status", "done") or "done").strip().lower()
973
- phase = "success" if status in ("done", "success", "complete") else "running"
974
- if status in ("error", "failed"):
975
- phase = "error"
976
- duration_ms = int(data.get("duration_ms", 0) or 0)
977
- args = {"preview": args_preview} if args_preview else {}
978
- return tool_name, args, phase, duration_ms
979
- return None
980
-
981
-
982
956
  def _apply_subagent_wire_step_event(
983
957
  adapter: TextualUIAdapter,
984
958
  *,
@@ -986,8 +960,8 @@ def _apply_subagent_wire_step_event(
986
960
  data: dict[str, Any],
987
961
  task_scope: TaskScope,
988
962
  ) -> bool:
989
- """Render ``*.step.completed`` / ``*.gather.summary`` on the SubAgent (TASK) card."""
990
- params = _subagent_wire_step_params(event_type, data)
963
+ """Render row-style subagent wire events on the SubAgent (TASK) card."""
964
+ params = subagent_wire_row_params(event_type, data)
991
965
  if params is None:
992
966
  return False
993
967
  tool_name, args, phase, duration_ms = params
@@ -1011,6 +985,29 @@ def _apply_subagent_wire_step_event(
1011
985
  return True
1012
986
 
1013
987
 
988
+ def _apply_subagent_wire_activity_event(
989
+ adapter: TextualUIAdapter,
990
+ *,
991
+ event_type: str,
992
+ data: dict[str, Any],
993
+ task_scope: TaskScope,
994
+ ) -> bool:
995
+ """Render note-style subagent wire events on the SubAgent card."""
996
+ from soothe_sdk.ux.subagent_progress import summarize_subagent_wire_activity
997
+
998
+ line = summarize_subagent_wire_activity(str(event_type or "").strip(), data).strip()
999
+ if not line:
1000
+ return False
1001
+ step_id = task_scope_step_id(task_scope)
1002
+ if not step_id:
1003
+ return True
1004
+ subagent_card = _ensure_subagent_card_for_task_scope(adapter, task_scope)
1005
+ if subagent_card is None:
1006
+ return True
1007
+ subagent_card.append_subagent_activity(line)
1008
+ return True
1009
+
1010
+
1014
1011
  def _apply_subagent_wire_lifecycle_event(
1015
1012
  adapter: TextualUIAdapter,
1016
1013
  *,
@@ -1021,7 +1018,7 @@ def _apply_subagent_wire_lifecycle_event(
1021
1018
  """Handle subagent ``*.completed`` / ``*.failed`` wire events (RFC-628, IG-513)."""
1022
1019
  et = str(event_type or "").strip()
1023
1020
  if not (et.endswith(".completed") or et.endswith(".failed")):
1024
- return True
1021
+ return False
1025
1022
  step_id = task_scope_step_id(task_scope)
1026
1023
  task_idx = task_scope_task_idx(task_scope, step_id)
1027
1024
  card = adapter._subagent_cards_by_key.get(_subagent_registry_key(step_id, task_idx))
@@ -1048,6 +1045,39 @@ def _apply_subagent_wire_lifecycle_event(
1048
1045
  return True
1049
1046
 
1050
1047
 
1048
+ def _route_subagent_wire_event(
1049
+ adapter: TextualUIAdapter,
1050
+ *,
1051
+ event_type: str,
1052
+ data: dict[str, Any],
1053
+ task_scope: TaskScope,
1054
+ ) -> bool:
1055
+ """Route a curated subagent wire event to the unified display protocol handlers."""
1056
+ kind = classify_subagent_wire_render(event_type)
1057
+ if kind is SubagentWireRenderKind.ACTIVITY_ROW:
1058
+ return _apply_subagent_wire_step_event(
1059
+ adapter,
1060
+ event_type=event_type,
1061
+ data=data,
1062
+ task_scope=task_scope,
1063
+ )
1064
+ if kind is SubagentWireRenderKind.ACTIVITY_NOTE:
1065
+ return _apply_subagent_wire_activity_event(
1066
+ adapter,
1067
+ event_type=event_type,
1068
+ data=data,
1069
+ task_scope=task_scope,
1070
+ )
1071
+ if kind is SubagentWireRenderKind.LIFECYCLE_END:
1072
+ return _apply_subagent_wire_lifecycle_event(
1073
+ adapter,
1074
+ event_type=event_type,
1075
+ data=data,
1076
+ task_scope=task_scope,
1077
+ )
1078
+ return False
1079
+
1080
+
1051
1081
  def _route_subgraph_tool_call(
1052
1082
  adapter: TextualUIAdapter,
1053
1083
  router: StepTaskRouter,
@@ -1415,12 +1445,16 @@ async def _ensure_goal_tree_message(
1415
1445
  return widget
1416
1446
 
1417
1447
 
1418
- async def sync_pending_step_cards_from_plan(
1448
+ async def cleanup_stale_plan_step_cards(
1419
1449
  adapter: TextualUIAdapter,
1420
1450
  *,
1421
1451
  steps: list[dict[str, Any]],
1422
1452
  ) -> None:
1423
- """Mount step cards in ``pending`` state for planned steps not yet executing."""
1453
+ """Drop stale pending step cards after replan; do not mount future steps.
1454
+
1455
+ Planned and queued steps appear only in the Ctrl+T plan quick view (goal tree).
1456
+ Step cards mount in the message list when ``step_started`` fires.
1457
+ """
1424
1458
  planned_ids = {
1425
1459
  str(row.get("id", "")).strip()
1426
1460
  for row in steps
@@ -1435,23 +1469,6 @@ async def sync_pending_step_cards_from_plan(
1435
1469
  if bound is widget:
1436
1470
  adapter._step_by_namespace.pop(ns, None)
1437
1471
 
1438
- for row in steps:
1439
- if not isinstance(row, dict):
1440
- continue
1441
- sid = str(row.get("id", "")).strip()
1442
- if not sid or sid in adapter._current_step_messages:
1443
- continue
1444
- if _is_successful_step_id(adapter, sid):
1445
- continue
1446
- desc = str(row.get("description", "")).strip() or "(step)"
1447
- step_widget = CognitionStepMessage(
1448
- step_id=sid,
1449
- description=desc,
1450
- id=f"step-{uuid.uuid4().hex[:8]}",
1451
- )
1452
- await adapter._mount_message(step_widget)
1453
- adapter._current_step_messages[sid] = step_widget
1454
-
1455
1472
 
1456
1473
  def _step_card_lookup_keys(step_id: str) -> list[str]:
1457
1474
  """Return dict lookup keys for a step id (canonical and wire variants)."""
@@ -1468,17 +1485,6 @@ def _step_card_lookup_keys(step_id: str) -> list[str]:
1468
1485
  return keys
1469
1486
 
1470
1487
 
1471
- def _register_successful_step_id(adapter: TextualUIAdapter, step_id: str) -> None:
1472
- """Remember a step that completed successfully so plan=keep does not remount it."""
1473
- for key in _step_card_lookup_keys(step_id):
1474
- adapter._successful_step_ids.add(key)
1475
-
1476
-
1477
- def _is_successful_step_id(adapter: TextualUIAdapter, step_id: str) -> bool:
1478
- """True when this step already succeeded earlier in the current loop."""
1479
- return any(key in adapter._successful_step_ids for key in _step_card_lookup_keys(step_id))
1480
-
1481
-
1482
1488
  def _lookup_step_card(
1483
1489
  adapter: TextualUIAdapter,
1484
1490
  step_id: str,
@@ -2823,7 +2829,6 @@ async def execute_task_textual(
2823
2829
  router.reset_turn()
2824
2830
  adapter._execute_wave_total = 0
2825
2831
  adapter._execute_wave_completed = 0
2826
- adapter._successful_step_ids.clear()
2827
2832
  ui_coalesce = TurnToolUiCoalescer()
2828
2833
  adapter._goal_completion_mounted_this_turn = False
2829
2834
  adapter._goal_tree_message = None
@@ -3832,7 +3837,7 @@ async def execute_task_textual(
3832
3837
  done_steps,
3833
3838
  len(raw_steps),
3834
3839
  )
3835
- await sync_pending_step_cards_from_plan(
3840
+ await cleanup_stale_plan_step_cards(
3836
3841
  adapter,
3837
3842
  steps=raw_steps,
3838
3843
  )
@@ -3850,26 +3855,13 @@ async def execute_task_textual(
3850
3855
  if event_type == STRANGE_LOOP_STEP_QUEUED:
3851
3856
  step_id = str(data.get("step_id", "")).strip()
3852
3857
  description = str(data.get("description", "")).strip()
3853
- if step_id:
3854
- step_widget = adapter._current_step_messages.get(step_id)
3855
- if step_widget is None:
3856
- step_widget = CognitionStepMessage(
3857
- step_id=step_id,
3858
- description=description or "(step)",
3859
- id=f"step-{uuid.uuid4().hex[:8]}",
3860
- )
3861
- await adapter._mount_message(step_widget)
3862
- adapter._current_step_messages[step_id] = step_widget
3863
- elif description:
3864
- step_widget.set_description(description)
3865
- step_widget.set_queued()
3866
- if not ns_key:
3867
- _sync_goal_tree_step_phase(
3868
- adapter,
3869
- step_id,
3870
- "queued",
3871
- description=description,
3872
- )
3858
+ if step_id and not ns_key:
3859
+ _sync_goal_tree_step_phase(
3860
+ adapter,
3861
+ step_id,
3862
+ "queued",
3863
+ description=description,
3864
+ )
3873
3865
  continue
3874
3866
 
3875
3867
  if event_type == STRANGE_LOOP_STEP_STARTED:
@@ -3985,8 +3977,6 @@ async def execute_task_textual(
3985
3977
  pending_text_by_namespace[ns_key] = ""
3986
3978
  assistant_message_by_namespace.pop(ns_key, None)
3987
3979
  success = bool(data.get("success", True))
3988
- if success:
3989
- _register_successful_step_id(adapter, step_id)
3990
3980
  duration_ms = int(data.get("duration_ms", 0))
3991
3981
  tool_call_count = int(data.get("tool_call_count", 0))
3992
3982
  summary = str(
@@ -4172,20 +4162,13 @@ async def execute_task_textual(
4172
4162
  and event_type.startswith("soothe.subagent.")
4173
4163
  and is_allowlisted_subagent_event_type(event_type)
4174
4164
  ):
4175
- if _apply_subagent_wire_step_event(
4165
+ if _route_subagent_wire_event(
4176
4166
  adapter,
4177
4167
  event_type=event_type,
4178
4168
  data=data,
4179
4169
  task_scope=task_scope,
4180
4170
  ):
4181
4171
  continue
4182
- _apply_subagent_wire_lifecycle_event(
4183
- adapter,
4184
- event_type=event_type,
4185
- data=data,
4186
- task_scope=task_scope,
4187
- )
4188
- continue
4189
4172
  finally:
4190
4173
  await ui_coalesce.after_chunk()
4191
4174
 
@@ -135,7 +135,7 @@ class CognitionStepMessage(Vertical):
135
135
  super().__init__(**kwargs)
136
136
  self._step_id = step_id
137
137
  self._description = description.strip()
138
- self._status = "pending" # pending | queued | running | success | error
138
+ self._status = "pending" # pending | running | success | error
139
139
  self._spinner_position = 0
140
140
  self._start_time: float | None = None
141
141
  self._animation_timer: Timer | None = None
@@ -217,7 +217,7 @@ class CognitionStepMessage(Vertical):
217
217
  return
218
218
  self._input_tokens += input_tokens
219
219
  self._output_tokens += output_tokens
220
- if self._status in ("running", "pending", "queued"):
220
+ if self._status in ("running", "pending"):
221
221
  self._sync_running_status_line()
222
222
 
223
223
  def _token_budget_suffix(self) -> str:
@@ -283,8 +283,6 @@ class CognitionStepMessage(Vertical):
283
283
  return
284
284
  if self._status == "running":
285
285
  self._ensure_running_ui()
286
- elif self._status == "queued":
287
- self._sync_step_card_surface()
288
286
  elif self._status == "pending":
289
287
  self._sync_step_card_surface()
290
288
 
@@ -466,8 +464,6 @@ class CognitionStepMessage(Vertical):
466
464
  self._sync_running_status_text(index)
467
465
  elif self._status == "pending":
468
466
  self._refresh_pending_display(index)
469
- elif self._status == "queued":
470
- self._refresh_queued_display(index)
471
467
 
472
468
  if STEP_CARD_SHOW_TOOL_ROW_DETAILS:
473
469
  self._refresh_tools_display()
@@ -1156,37 +1152,6 @@ class CognitionStepMessage(Vertical):
1156
1152
  )
1157
1153
  self._status_widget.display = True
1158
1154
 
1159
- def _refresh_queued_display(self, index: StepRowIndex | None = None) -> None:
1160
- """Show ready steps waiting for a concurrency slot (``max_parallel_steps``)."""
1161
- if self._status != "queued" or self._status_widget is None:
1162
- return
1163
- if index is None:
1164
- index = self._build_row_index()
1165
- try:
1166
- colors = theme.get_theme_colors(self)
1167
- except Exception: # noqa: BLE001
1168
- colors = theme.DARK_COLORS
1169
- g = get_glyphs()
1170
- gutter = f"{g.output_prefix} "
1171
- self._status_widget.update(
1172
- StepCardStatusLine.footer_queued(
1173
- gutter=gutter,
1174
- circle_empty=g.circle_empty,
1175
- stats_suffix=stats_title_suffix(index),
1176
- token_suffix=self._token_budget_suffix(),
1177
- colors=colors,
1178
- )
1179
- )
1180
- self._status_widget.display = True
1181
-
1182
- def set_queued(self) -> None:
1183
- """Mark a ready step as waiting for an execute batch slot."""
1184
- if self._status in ("running", "success", "error"):
1185
- return
1186
- self._status = "queued"
1187
- self._refresh_header_title()
1188
- self._sync_step_card_surface()
1189
-
1190
1155
  def _maybe_start_running_timer(self) -> None:
1191
1156
  """Start the spinner timer when the step is running and mounted."""
1192
1157
  if self._status != "running":
@@ -458,23 +458,6 @@ class StepCardStatusLine:
458
458
  parts.append(Content.styled(tail, theme.SECONDARY_TEXT_STYLE))
459
459
  return Content.assemble(*parts)
460
460
 
461
- @staticmethod
462
- def footer_queued(
463
- *,
464
- gutter: str,
465
- circle_empty: str,
466
- stats_suffix: str,
467
- token_suffix: str = "",
468
- colors: Any,
469
- ) -> Content:
470
- """Step card footer for ready steps waiting for a concurrency slot."""
471
- head = f"{gutter}{circle_empty} Queued..."
472
- tail = f"{stats_suffix}{token_suffix}"
473
- parts: list[object] = [Content.styled(head, colors.cognition)]
474
- if tail:
475
- parts.append(Content.styled(tail, theme.SECONDARY_TEXT_STYLE))
476
- return Content.assemble(*parts)
477
-
478
461
 
479
462
  # Activity tree renderer
480
463
  # ---------------------------------------------------------------------------
File without changes
File without changes
File without changes