soothe-cli 0.4.0__tar.gz → 0.4.1__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 (115) hide show
  1. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/PKG-INFO +1 -1
  2. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/cli/renderer.py +2 -2
  3. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/cli/stream/formatter.py +15 -15
  4. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/cli/stream/pipeline.py +10 -3
  5. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/shared/event_processor.py +1 -1
  6. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/shared/message_processing.py +11 -1
  7. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/shared/tool_formatters/__init__.py +2 -0
  8. soothe_cli-0.4.1/src/soothe_cli/shared/tool_formatters/subagent.py +122 -0
  9. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/shared/tool_output_formatter.py +4 -0
  10. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/tui/tool_display.py +2 -2
  11. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/.gitignore +0 -0
  12. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/README.md +0 -0
  13. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/pyproject.toml +0 -0
  14. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/__init__.py +0 -0
  15. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/cli/__init__.py +0 -0
  16. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/cli/commands/__init__.py +0 -0
  17. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/cli/commands/autopilot_cmd.py +0 -0
  18. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/cli/commands/config_cmd.py +0 -0
  19. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/cli/commands/run_cmd.py +0 -0
  20. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/cli/commands/status_cmd.py +0 -0
  21. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/cli/commands/subagent_names.py +0 -0
  22. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/cli/commands/thread_cmd.py +0 -0
  23. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/cli/execution/__init__.py +0 -0
  24. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/cli/execution/daemon.py +0 -0
  25. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/cli/execution/headless.py +0 -0
  26. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/cli/execution/launcher.py +0 -0
  27. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/cli/main.py +0 -0
  28. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/cli/stream/__init__.py +0 -0
  29. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/cli/stream/context.py +0 -0
  30. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/cli/stream/display_line.py +0 -0
  31. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/cli/utils.py +0 -0
  32. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/config/__init__.py +0 -0
  33. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/config/cli_config.py +0 -0
  34. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/loop_commands.py +0 -0
  35. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/plan/__init__.py +0 -0
  36. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/plan/rich_tree.py +0 -0
  37. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/shared/__init__.py +0 -0
  38. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/shared/command_router.py +0 -0
  39. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/shared/config_loader.py +0 -0
  40. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/shared/display_policy.py +0 -0
  41. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/shared/essential_events.py +0 -0
  42. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/shared/presentation_engine.py +0 -0
  43. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/shared/processor_state.py +0 -0
  44. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/shared/renderer_protocol.py +0 -0
  45. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/shared/rendering.py +0 -0
  46. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/shared/slash_commands.py +0 -0
  47. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/shared/subagent_routing.py +0 -0
  48. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/shared/suppression_state.py +0 -0
  49. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/shared/tool_call_resolution.py +0 -0
  50. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/shared/tool_card_payload.py +0 -0
  51. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/shared/tool_formatters/base.py +0 -0
  52. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/shared/tool_formatters/execution.py +0 -0
  53. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/shared/tool_formatters/fallback.py +0 -0
  54. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/shared/tool_formatters/file_ops.py +0 -0
  55. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/shared/tool_formatters/goal_formatter.py +0 -0
  56. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/shared/tool_formatters/media.py +0 -0
  57. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/shared/tool_formatters/structured.py +0 -0
  58. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/shared/tool_formatters/web.py +0 -0
  59. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/shared/tool_message_format.py +0 -0
  60. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/shared/tui_trace_log.py +0 -0
  61. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/tui/__init__.py +0 -0
  62. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/tui/_ask_user_types.py +0 -0
  63. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/tui/_cli_context.py +0 -0
  64. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/tui/_env_vars.py +0 -0
  65. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/tui/_session_stats.py +0 -0
  66. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/tui/_version.py +0 -0
  67. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/tui/app.py +0 -0
  68. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/tui/app.tcss +0 -0
  69. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/tui/command_registry.py +0 -0
  70. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/tui/config.py +0 -0
  71. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/tui/daemon_session.py +0 -0
  72. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/tui/file_ops.py +0 -0
  73. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/tui/formatting.py +0 -0
  74. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/tui/hooks.py +0 -0
  75. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/tui/input.py +0 -0
  76. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/tui/media_utils.py +0 -0
  77. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/tui/message_display_filter.py +0 -0
  78. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/tui/model_config.py +0 -0
  79. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/tui/output.py +0 -0
  80. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/tui/preview_limits.py +0 -0
  81. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/tui/project_utils.py +0 -0
  82. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/tui/sessions.py +0 -0
  83. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/tui/skills/__init__.py +0 -0
  84. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/tui/skills/invocation.py +0 -0
  85. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/tui/skills/load.py +0 -0
  86. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/tui/textual_adapter.py +0 -0
  87. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/tui/theme.py +0 -0
  88. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/tui/unicode_security.py +0 -0
  89. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/tui/update_check.py +0 -0
  90. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/tui/widgets/__init__.py +0 -0
  91. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/tui/widgets/_links.py +0 -0
  92. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/tui/widgets/approval.py +0 -0
  93. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/tui/widgets/ask_user.py +0 -0
  94. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/tui/widgets/autocomplete.py +0 -0
  95. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/tui/widgets/autopilot_dashboard.py +0 -0
  96. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/tui/widgets/autopilot_screen.py +0 -0
  97. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/tui/widgets/chat_input.py +0 -0
  98. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/tui/widgets/clipboard.py +0 -0
  99. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/tui/widgets/diff.py +0 -0
  100. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/tui/widgets/editor.py +0 -0
  101. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/tui/widgets/history.py +0 -0
  102. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/tui/widgets/loading.py +0 -0
  103. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/tui/widgets/loop_selector.py +0 -0
  104. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/tui/widgets/mcp_viewer.py +0 -0
  105. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/tui/widgets/message_store.py +0 -0
  106. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/tui/widgets/messages.py +0 -0
  107. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/tui/widgets/model_selector.py +0 -0
  108. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/tui/widgets/notification_settings.py +0 -0
  109. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/tui/widgets/status.py +0 -0
  110. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/tui/widgets/theme_selector.py +0 -0
  111. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/tui/widgets/thread_selector.py +0 -0
  112. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/tui/widgets/tool_renderers.py +0 -0
  113. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/tui/widgets/tool_widgets.py +0 -0
  114. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/src/soothe_cli/tui/widgets/tools.py +0 -0
  115. {soothe_cli-0.4.0 → soothe_cli-0.4.1}/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.4.0
3
+ Version: 0.4.1
4
4
  Summary: Soothe CLI client - communicates with daemon via WebSocket
5
5
  Project-URL: Homepage, https://github.com/OpenSoothe/soothe
6
6
  Project-URL: Documentation, https://soothe.readthedocs.io
@@ -13,7 +13,7 @@ from dataclasses import dataclass, field
13
13
  from typing import TYPE_CHECKING, Any
14
14
 
15
15
  from soothe_sdk.core.verbosity import VerbosityTier
16
- from soothe_sdk.utils import get_tool_pascal_name
16
+ from soothe_sdk.utils import get_tool_display_name
17
17
 
18
18
  from soothe_cli.cli.stream import DisplayLine, StreamDisplayPipeline
19
19
  from soothe_cli.cli.utils import make_tool_block
@@ -223,7 +223,7 @@ class CliRenderer:
223
223
 
224
224
  self._stderr_begin_icon_block()
225
225
 
226
- display_name = get_tool_pascal_name(name)
226
+ display_name = get_tool_display_name(name)
227
227
 
228
228
  # Pass args directly, including any _raw fallback
229
229
  args_str = format_tool_call_args(name, {"args": args, "_raw": args.get("_raw", "")})
@@ -3,7 +3,7 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  from soothe_sdk.core.verbosity import VerbosityTier
6
- from soothe_sdk.utils import get_tool_pascal_name
6
+ from soothe_sdk.utils import get_tool_display_name
7
7
 
8
8
  from soothe_cli.cli.stream.display_line import DisplayLine, indent_for_level
9
9
 
@@ -96,7 +96,7 @@ def format_goal_header(
96
96
  DisplayLine for goal header.
97
97
  """
98
98
  # Add inline symbol for goal marker
99
- content = f"⌯⌲ {goal}"
99
+ content = f"{goal}"
100
100
  return DisplayLine(
101
101
  level=1,
102
102
  content=content,
@@ -126,7 +126,7 @@ def format_step_header(
126
126
  """
127
127
  suffix = " (parallel)" if parallel else ""
128
128
  # Add inline symbol for step progression
129
- content = f" {description}{suffix}"
129
+ content = f"❇️ {description}{suffix}"
130
130
  return DisplayLine(
131
131
  level=2,
132
132
  content=content,
@@ -160,7 +160,7 @@ def format_tool_call(
160
160
  DisplayLine for tool/subagent call with uniform wrench icon.
161
161
  """
162
162
  # Transform to PascalCase for display
163
- display_name = get_tool_pascal_name(name)
163
+ display_name = get_tool_display_name(name)
164
164
 
165
165
  # IG-256: No differentiation - use wrench for all tools/subagents
166
166
  icon_emoji = "🔧"
@@ -318,6 +318,9 @@ def format_reasoning(
318
318
  IG-XXX: Shows technical reasoning with "Reasoning:" prefix for clarity.
319
319
  Uses solid bullet ● (matching goal) to indicate reasoning is active phase.
320
320
 
321
+ IG-262: Uses level=2 (flat layout) for consistency with judgement lines.
322
+ Reasoning is a sibling to judgement, not a child.
323
+
321
324
  Args:
322
325
  reasoning: Internal technical analysis text.
323
326
  namespace: Event namespace.
@@ -330,10 +333,10 @@ def format_reasoning(
330
333
  content = f"💭 {reasoning}"
331
334
 
332
335
  return DisplayLine(
333
- level=3, # Use level 3 for less prominence (subordinate to next_action)
336
+ level=2, # IG-262: Use level 2 for flat layout (sibling to judgement, not child)
334
337
  content=content,
335
338
  icon="●", # Solid bullet matching goal icon (polish)
336
- indent=indent_for_level(3),
339
+ indent=indent_for_level(2),
337
340
  source_prefix=_derive_source_prefix(namespace, verbosity_tier),
338
341
  )
339
342
 
@@ -350,11 +353,12 @@ def format_judgement(
350
353
 
351
354
  IG-089: Shows meaningful judgement info without raw intermediate data.
352
355
  IG-XXX: Prominent reasoning display with "Reason:" prefix for clarity.
356
+ IG-265: Removed [new]/[keep] badge from CLI display (kept in event data for logs).
353
357
 
354
358
  Args:
355
359
  judgement: Human-readable summary of the decision.
356
360
  action: Action taken ("continue" or "complete").
357
- plan_action: When set, show ``[keep]`` or ``[new]`` before the judgement text.
361
+ plan_action: Ignored (kept for backward compatibility, appears in logs only).
358
362
  namespace: Event namespace.
359
363
  verbosity_tier: Current verbosity tier.
360
364
 
@@ -363,12 +367,8 @@ def format_judgement(
363
367
  """
364
368
  action_icon = "○" if action == "continue" else "●" # Polish: ○ for continue, ● for complete
365
369
 
366
- badge = ""
367
- if plan_action in ("keep", "new"):
368
- badge = f"[{plan_action}] "
369
-
370
- # Polish: Add "Reason:" prefix to make LLM reasoning prominent
371
- content = f"🌀 {badge}{judgement}"
370
+ # IG-265: Remove badge from CLI display (plan_action kept in event data for logs)
371
+ content = f"🌟 {judgement}"
372
372
 
373
373
  return DisplayLine(
374
374
  level=2, # Use level 2 for more prominence (like step headers)
@@ -411,7 +411,7 @@ def format_step_done(
411
411
 
412
412
  # Success case: single line
413
413
  if success:
414
- content = f"Done{tool_info}"
414
+ content = f"Done{tool_info}"
415
415
  return [
416
416
  DisplayLine(
417
417
  level=3, # Child node of step header (level 2)
@@ -427,7 +427,7 @@ def format_step_done(
427
427
  lines = [
428
428
  DisplayLine(
429
429
  level=3,
430
- content=f"Failed{tool_info}",
430
+ content=f"Failed{tool_info}",
431
431
  icon="└─", # IG-257: Unicode tree branch (U+2514)
432
432
  indent=indent_for_level(3),
433
433
  duration_ms=duration_ms,
@@ -42,6 +42,9 @@ GOAL_COMPLETE_EVENTS = {
42
42
  "soothe.cognition.agent_loop.completed",
43
43
  }
44
44
 
45
+ # IG-264: Default goal completion message (skip display to avoid redundancy)
46
+ DEFAULT_GOAL_ACHIEVED_MESSAGE = "Goal achieved successfully"
47
+
45
48
  # Verbosity tier mapping
46
49
  _VERBOSITY_TO_TIER = {
47
50
  "quiet": VerbosityTier.QUIET,
@@ -197,13 +200,16 @@ class StreamDisplayPipeline:
197
200
  Returns:
198
201
  Display lines for goal header.
199
202
  """
200
- goal = event.get("goal", event.get("goal_description", ""))
203
+ # IG-262: Prefer friendly_message over goal/goal_description
204
+ friendly_message = event.get("friendly_message")
205
+ goal = friendly_message or event.get("goal", event.get("goal_description", ""))
201
206
  if not goal:
202
207
  return []
203
208
 
204
209
  # Reset context for new goal
205
210
  self._context.reset_goal()
206
- self._context.current_goal = goal
211
+ # Store the actual goal description (not friendly message) for context tracking
212
+ self._context.current_goal = event.get("goal", event.get("goal_description", goal))
207
213
  self._context.goal_start_time = time.time()
208
214
 
209
215
  # Get steps count if available
@@ -636,7 +642,8 @@ class StreamDisplayPipeline:
636
642
  )
637
643
  else:
638
644
  reasoning = event.get("reasoning", "").strip()
639
- if reasoning:
645
+ # IG-264: Skip redundant reasoning when it's the default goal message
646
+ if reasoning and reasoning != DEFAULT_GOAL_ACHIEVED_MESSAGE:
640
647
  lines.append(
641
648
  format_reasoning(
642
649
  reasoning,
@@ -705,7 +705,7 @@ class EventProcessor:
705
705
 
706
706
  # Handle output events (chitchat, quiz, final report, etc.) through unified registry
707
707
  # IG-254: Single source of truth for user-visible output events
708
- if is_output_event(etype):
708
+ if is_output_event(etype) and etype != "soothe.cognition.agent_loop.completed":
709
709
  content = extract_output_text(etype, data)
710
710
  if content and self._presentation.tier_visible(VerbosityTier.QUIET, self._verbosity):
711
711
  cleaned = self._clean_assistant_text(content)
@@ -444,13 +444,23 @@ def format_tool_call_args(tool_name: str, tool_call: dict[str, Any]) -> str:
444
444
  values = []
445
445
  for key_arg in key_args:
446
446
  if key_arg in args:
447
- value = str(args[key_arg])
447
+ raw_value = args[key_arg]
448
+ value = str(raw_value)
448
449
  # Convert and abbreviate path arguments
449
450
  if _is_path_arg_name(key_arg):
450
451
  value = _display_path_value(value)
451
452
  elif len(value) > max_value_length:
452
453
  # Truncate non-path long values
453
454
  value = value[: max_value_length - 3] + "..."
455
+ # IG-261: Quote string arguments for Task tool (except subagent_type)
456
+ # Only quote if the original value is a string and not already quoted
457
+ if (
458
+ internal == "task"
459
+ and key_arg != "subagent_type"
460
+ and isinstance(raw_value, str)
461
+ and not (value.startswith('"') and value.endswith('"'))
462
+ ):
463
+ value = f'"{value}"'
454
464
  values.append(value)
455
465
 
456
466
  if not values:
@@ -13,6 +13,7 @@ from soothe_cli.shared.tool_formatters.file_ops import FileOpsFormatter
13
13
  from soothe_cli.shared.tool_formatters.goal_formatter import GoalFormatter
14
14
  from soothe_cli.shared.tool_formatters.media import MediaFormatter
15
15
  from soothe_cli.shared.tool_formatters.structured import StructuredFormatter
16
+ from soothe_cli.shared.tool_formatters.subagent import SubagentFormatter
16
17
  from soothe_cli.shared.tool_formatters.web import WebFormatter
17
18
 
18
19
  __all__ = [
@@ -22,6 +23,7 @@ __all__ = [
22
23
  "FileOpsFormatter",
23
24
  "GoalFormatter",
24
25
  "MediaFormatter",
26
+ "SubagentFormatter",
25
27
  "StructuredFormatter",
26
28
  "WebFormatter",
27
29
  ]
@@ -0,0 +1,122 @@
1
+ """Subagent tool formatter for brief result display.
2
+
3
+ Task and Research tools should show brief status, not full result content.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from typing import Any
9
+
10
+ from soothe_cli.shared.tool_formatters.base import BaseFormatter
11
+ from soothe_cli.shared.tool_output_formatter import ToolBrief
12
+
13
+
14
+ class SubagentFormatter(BaseFormatter):
15
+ """Formatter for subagent tools (task, research).
16
+
17
+ Shows brief completion status without full result content.
18
+ """
19
+
20
+ def format(self, tool_name: str, result: Any) -> ToolBrief:
21
+ """Format subagent result with brief status.
22
+
23
+ Args:
24
+ tool_name: Name of the subagent tool (task, research).
25
+ result: Tool result (typically long text output).
26
+
27
+ Returns:
28
+ ToolBrief with brief status and optional short preview.
29
+
30
+ Example:
31
+ >>> formatter = SubagentFormatter()
32
+ >>> brief = formatter.format("task", "Long result text...")
33
+ >>> brief.to_display()
34
+ '✓ Completed'
35
+ """
36
+ # Handle string results
37
+ if isinstance(result, str):
38
+ # Check for error indicators
39
+ error_indicators = ["error:", "failed:", "exception:", "traceback"]
40
+ is_error = any(indicator in result.lower() for indicator in error_indicators)
41
+
42
+ if is_error:
43
+ # Extract first line of error
44
+ first_line = result.split("\n")[0].strip()
45
+ error_preview = first_line[:80] if len(first_line) > 80 else first_line
46
+ return ToolBrief(
47
+ icon="✗",
48
+ summary="Failed",
49
+ detail=error_preview,
50
+ metrics={"error": True},
51
+ )
52
+
53
+ # Success - show brief status only, no content preview
54
+ # Subagent results are typically very long and should not be shown inline
55
+ return ToolBrief(
56
+ icon="✓",
57
+ summary="Completed",
58
+ detail=None, # No content preview
59
+ metrics={"result_length": len(result)},
60
+ )
61
+
62
+ # Handle dict results
63
+ if isinstance(result, dict):
64
+ # Check for error field
65
+ if "error" in result:
66
+ error_msg = str(result["error"])[:80]
67
+ return ToolBrief(
68
+ icon="✗",
69
+ summary="Failed",
70
+ detail=error_msg,
71
+ metrics={"error": True},
72
+ )
73
+
74
+ # Success - show brief status
75
+ field_count = len(result)
76
+ return ToolBrief(
77
+ icon="✓",
78
+ summary="Completed",
79
+ detail=f"{field_count} fields",
80
+ metrics={"field_count": field_count},
81
+ )
82
+
83
+ # Handle ToolOutput (if available)
84
+ try:
85
+ from soothe_sdk.client.schemas import ToolOutput
86
+
87
+ if isinstance(result, ToolOutput):
88
+ if not result.success:
89
+ error_msg = result.error[:80] if result.error else "Unknown error"
90
+ return ToolBrief(
91
+ icon="✗",
92
+ summary="Failed",
93
+ detail=error_msg,
94
+ metrics={"error": True, "error_type": result.error_type},
95
+ )
96
+
97
+ # Success with ToolOutput
98
+ if result.is_silent_failure():
99
+ return ToolBrief(
100
+ icon="⚠",
101
+ summary="No result",
102
+ detail="Tool succeeded but returned no data",
103
+ metrics={"silent_failure": True},
104
+ )
105
+
106
+ # Has data - show brief status
107
+ return ToolBrief(
108
+ icon="✓",
109
+ summary="Completed",
110
+ detail=None, # No content preview
111
+ metrics={"has_data": result.data is not None},
112
+ )
113
+ except ImportError:
114
+ pass # ToolOutput not available
115
+
116
+ # Fallback for unknown types
117
+ return ToolBrief(
118
+ icon="✓",
119
+ summary="Completed",
120
+ detail=None,
121
+ metrics={},
122
+ )
@@ -161,6 +161,7 @@ class ToolOutputFormatter:
161
161
  GoalFormatter,
162
162
  MediaFormatter,
163
163
  StructuredFormatter,
164
+ SubagentFormatter,
164
165
  WebFormatter,
165
166
  )
166
167
 
@@ -176,6 +177,9 @@ class ToolOutputFormatter:
176
177
  if category == "execution":
177
178
  formatter = ExecutionFormatter()
178
179
  return formatter.format(tool_name, result)
180
+ if category == "subagent":
181
+ formatter = SubagentFormatter()
182
+ return formatter.format(tool_name, result)
179
183
  if category == "media":
180
184
  formatter = MediaFormatter()
181
185
  return formatter.format(tool_name, result)
@@ -10,7 +10,7 @@ from contextlib import suppress
10
10
  from pathlib import Path
11
11
  from typing import Any
12
12
 
13
- from soothe_sdk.utils import get_all_path_arg_keys, get_tool_meta, get_tool_pascal_name
13
+ from soothe_sdk.utils import get_all_path_arg_keys, get_tool_display_name, get_tool_meta
14
14
 
15
15
  from soothe_cli.shared.message_processing import (
16
16
  _normalize_tool_name_for_arg_map,
@@ -136,7 +136,7 @@ def format_tool_display(tool_name: str, tool_args: dict) -> str:
136
136
  tool_args = extract_tool_args_dict(tool_args) if tool_args else {}
137
137
  tool_key = _normalize_tool_name_for_arg_map(tool_name or "")
138
138
  # Get PascalCase display name for all return statements
139
- pascal_name = get_tool_pascal_name(tool_key)
139
+ pascal_name = get_tool_display_name(tool_key)
140
140
 
141
141
  def abbreviate_path(path_str: str, max_length: int = 60) -> str:
142
142
  """Abbreviate a file path intelligently - show basename or relative path.
File without changes
File without changes
File without changes