soothe-cli 0.3.4__tar.gz → 0.3.5__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 (110) hide show
  1. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/PKG-INFO +3 -3
  2. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/pyproject.toml +2 -2
  3. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/cli/commands/config_cmd.py +6 -1
  4. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/cli/stream/display_line.py +0 -6
  5. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/cli/stream/formatter.py +0 -7
  6. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/shared/message_processing.py +65 -22
  7. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/shared/tool_call_resolution.py +20 -4
  8. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/tui/_version.py +2 -2
  9. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/tui/app.py +419 -18
  10. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/tui/daemon_session.py +57 -47
  11. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/tui/model_config.py +38 -6
  12. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/tui/sessions.py +145 -5
  13. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/tui/textual_adapter.py +27 -14
  14. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/tui/tool_display.py +58 -16
  15. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/tui/widgets/chat_input.py +2 -2
  16. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/tui/widgets/messages.py +2 -1
  17. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/tui/widgets/thread_selector.py +21 -4
  18. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/.gitignore +0 -0
  19. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/README.md +0 -0
  20. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/__init__.py +0 -0
  21. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/cli/__init__.py +0 -0
  22. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/cli/commands/__init__.py +0 -0
  23. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/cli/commands/autopilot_cmd.py +0 -0
  24. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/cli/commands/run_cmd.py +0 -0
  25. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/cli/commands/status_cmd.py +0 -0
  26. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/cli/commands/subagent_names.py +0 -0
  27. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/cli/commands/thread_cmd.py +0 -0
  28. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/cli/execution/__init__.py +0 -0
  29. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/cli/execution/daemon.py +0 -0
  30. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/cli/execution/headless.py +0 -0
  31. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/cli/execution/launcher.py +0 -0
  32. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/cli/main.py +0 -0
  33. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/cli/renderer.py +0 -0
  34. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/cli/stream/__init__.py +0 -0
  35. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/cli/stream/context.py +0 -0
  36. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/cli/stream/pipeline.py +0 -0
  37. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/cli/utils.py +0 -0
  38. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/config/__init__.py +0 -0
  39. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/config/cli_config.py +0 -0
  40. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/plan/__init__.py +0 -0
  41. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/plan/rich_tree.py +0 -0
  42. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/shared/__init__.py +0 -0
  43. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/shared/command_router.py +0 -0
  44. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/shared/config_loader.py +0 -0
  45. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/shared/display_policy.py +0 -0
  46. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/shared/essential_events.py +0 -0
  47. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/shared/event_processor.py +0 -0
  48. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/shared/presentation_engine.py +0 -0
  49. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/shared/processor_state.py +0 -0
  50. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/shared/renderer_protocol.py +0 -0
  51. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/shared/rendering.py +0 -0
  52. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/shared/slash_commands.py +0 -0
  53. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/shared/subagent_routing.py +0 -0
  54. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/shared/suppression_state.py +0 -0
  55. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/shared/tool_card_payload.py +0 -0
  56. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/shared/tool_formatters/__init__.py +0 -0
  57. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/shared/tool_formatters/base.py +0 -0
  58. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/shared/tool_formatters/execution.py +0 -0
  59. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/shared/tool_formatters/fallback.py +0 -0
  60. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/shared/tool_formatters/file_ops.py +0 -0
  61. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/shared/tool_formatters/goal_formatter.py +0 -0
  62. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/shared/tool_formatters/media.py +0 -0
  63. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/shared/tool_formatters/structured.py +0 -0
  64. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/shared/tool_formatters/web.py +0 -0
  65. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/shared/tool_message_format.py +0 -0
  66. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/shared/tool_output_formatter.py +0 -0
  67. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/shared/tui_trace_log.py +0 -0
  68. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/tui/__init__.py +0 -0
  69. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/tui/_ask_user_types.py +0 -0
  70. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/tui/_cli_context.py +0 -0
  71. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/tui/_env_vars.py +0 -0
  72. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/tui/_session_stats.py +0 -0
  73. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/tui/app.tcss +0 -0
  74. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/tui/command_registry.py +0 -0
  75. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/tui/config.py +0 -0
  76. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/tui/file_ops.py +0 -0
  77. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/tui/formatting.py +0 -0
  78. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/tui/hooks.py +0 -0
  79. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/tui/input.py +0 -0
  80. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/tui/media_utils.py +0 -0
  81. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/tui/output.py +0 -0
  82. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/tui/project_utils.py +0 -0
  83. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/tui/skills/__init__.py +0 -0
  84. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/tui/skills/invocation.py +0 -0
  85. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/tui/skills/load.py +0 -0
  86. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/tui/theme.py +0 -0
  87. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/tui/unicode_security.py +0 -0
  88. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/tui/update_check.py +0 -0
  89. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/tui/widgets/__init__.py +0 -0
  90. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/tui/widgets/_links.py +0 -0
  91. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/tui/widgets/approval.py +0 -0
  92. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/tui/widgets/ask_user.py +0 -0
  93. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/tui/widgets/autocomplete.py +0 -0
  94. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/tui/widgets/autopilot_dashboard.py +0 -0
  95. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/tui/widgets/autopilot_screen.py +0 -0
  96. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/tui/widgets/clipboard.py +0 -0
  97. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/tui/widgets/diff.py +0 -0
  98. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/tui/widgets/editor.py +0 -0
  99. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/tui/widgets/history.py +0 -0
  100. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/tui/widgets/loading.py +0 -0
  101. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/tui/widgets/mcp_viewer.py +0 -0
  102. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/tui/widgets/message_store.py +0 -0
  103. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/tui/widgets/model_selector.py +0 -0
  104. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/tui/widgets/notification_settings.py +0 -0
  105. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/tui/widgets/status.py +0 -0
  106. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/tui/widgets/theme_selector.py +0 -0
  107. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/tui/widgets/tool_renderers.py +0 -0
  108. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/tui/widgets/tool_widgets.py +0 -0
  109. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/tui/widgets/tools.py +0 -0
  110. {soothe_cli-0.3.4 → soothe_cli-0.3.5}/src/soothe_cli/tui/widgets/welcome.py +0 -0
@@ -1,10 +1,10 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: soothe-cli
3
- Version: 0.3.4
3
+ Version: 0.3.5
4
4
  Summary: Soothe CLI client - communicates with daemon via WebSocket
5
- Project-URL: Homepage, https://github.com/caesar0301/soothe
5
+ Project-URL: Homepage, https://github.com/OpenSoothe/soothe
6
6
  Project-URL: Documentation, https://soothe.readthedocs.io
7
- Project-URL: Repository, https://github.com/caesar0301/soothe
7
+ Project-URL: Repository, https://github.com/OpenSoothe/soothe
8
8
  License: MIT
9
9
  Keywords: cli,client,soothe,tui,websocket
10
10
  Classifier: Development Status :: 3 - Alpha
@@ -45,9 +45,9 @@ dev = [
45
45
  soothe = "soothe_cli.cli.main:app"
46
46
 
47
47
  [project.urls]
48
- Homepage = "https://github.com/caesar0301/soothe"
48
+ Homepage = "https://github.com/OpenSoothe/soothe"
49
49
  Documentation = "https://soothe.readthedocs.io"
50
- Repository = "https://github.com/caesar0301/soothe"
50
+ Repository = "https://github.com/OpenSoothe/soothe"
51
51
 
52
52
  [tool.hatch.build.targets.wheel]
53
53
  packages = ["src/soothe_cli"]
@@ -189,9 +189,14 @@ def config_init(
189
189
  target.write_text("# Soothe configuration\n# See docs/user_guide.md for options\n")
190
190
  typer.echo(f"Created minimal {target}")
191
191
 
192
- for subdir in ("runs", "generated_agents", "logs"):
192
+ for subdir in ("runs", "generated_agents", "logs", "data"):
193
193
  (home / subdir).mkdir(parents=True, exist_ok=True)
194
194
 
195
+ # Migrate runtime data files from root to data/ subdirectory
196
+ from soothe_sdk.client.config import migrate_data_to_subdir
197
+
198
+ migrate_data_to_subdir()
199
+
195
200
  typer.echo(f"Soothe home initialized at {home}")
196
201
 
197
202
 
@@ -19,7 +19,6 @@ class DisplayLine:
19
19
  status: Optional status suffix ("running" for parallel tools).
20
20
  duration_ms: Optional duration in milliseconds.
21
21
  source_prefix: Optional source identifier for debug mode (e.g., "[main]", "[subagent:research]").
22
- newline_before: Add newline separator before this line for improved readability.
23
22
  """
24
23
 
25
24
  level: int
@@ -29,7 +28,6 @@ class DisplayLine:
29
28
  status: str | None = None
30
29
  duration_ms: int | None = None
31
30
  source_prefix: str | None = None
32
- newline_before: bool = False
33
31
 
34
32
  def format(self) -> str:
35
33
  """Format the display line as a string.
@@ -39,10 +37,6 @@ class DisplayLine:
39
37
  """
40
38
  parts = []
41
39
 
42
- # Add newline separator before if requested (for improved readability)
43
- if self.newline_before:
44
- parts.append("\n")
45
-
46
40
  # Add source prefix first if present (debug mode)
47
41
  if self.source_prefix:
48
42
  parts.append(self.source_prefix)
@@ -102,7 +102,6 @@ def format_goal_header(
102
102
  icon="●",
103
103
  indent=indent_for_level(1),
104
104
  source_prefix=_derive_source_prefix(namespace, verbosity_tier),
105
- newline_before=True, # Add separator for goal start
106
105
  )
107
106
 
108
107
 
@@ -133,7 +132,6 @@ def format_step_header(
133
132
  icon="○", # Hollow circle for in-progress step
134
133
  indent=indent_for_level(2),
135
134
  source_prefix=_derive_source_prefix(namespace, verbosity_tier),
136
- newline_before=True, # Add separator for step start
137
135
  )
138
136
 
139
137
 
@@ -226,7 +224,6 @@ def format_subagent_milestone(
226
224
  icon="✓",
227
225
  indent=indent_for_level(3),
228
226
  source_prefix=_derive_source_prefix(namespace, verbosity_tier),
229
- newline_before=True, # Add separator before subagent milestone
230
227
  )
231
228
 
232
229
 
@@ -276,7 +273,6 @@ def format_plan_phase_reasoning(
276
273
  icon="•",
277
274
  indent=indent_for_level(3),
278
275
  source_prefix=_derive_source_prefix(namespace, verbosity_tier),
279
- newline_before=True,
280
276
  )
281
277
 
282
278
 
@@ -307,7 +303,6 @@ def format_reasoning(
307
303
  icon="•",
308
304
  indent=indent_for_level(3),
309
305
  source_prefix=_derive_source_prefix(namespace, verbosity_tier),
310
- newline_before=True, # Add separator for reasoning display
311
306
  )
312
307
 
313
308
 
@@ -349,7 +344,6 @@ def format_judgement(
349
344
  icon=action_icon,
350
345
  indent=indent_for_level(2),
351
346
  source_prefix=_derive_source_prefix(namespace, verbosity_tier),
352
- newline_before=True, # Add separator before judgement indicator
353
347
  )
354
348
 
355
349
 
@@ -452,7 +446,6 @@ def format_goal_done(
452
446
  indent=indent_for_level(1),
453
447
  duration_ms=duration_ms,
454
448
  source_prefix=_derive_source_prefix(namespace, verbosity_tier),
455
- newline_before=True, # Add separator before final report
456
449
  )
457
450
 
458
451
 
@@ -49,22 +49,33 @@ def accumulate_tool_call_chunks(
49
49
  if tc_name and tc_id and tc_id not in pending_tool_calls:
50
50
  if isinstance(tc_args, str):
51
51
  args_str = tc_args
52
+ is_complete = False # String may be partial JSON
52
53
  elif isinstance(tc_args, dict) and tc_args:
53
54
  args_str = json.dumps(tc_args)
55
+ is_complete = True # Dict yields complete JSON
54
56
  else:
55
57
  args_str = ""
58
+ is_complete = False # Empty or missing args
56
59
  pending_tool_calls[tc_id] = {
57
60
  "name": tc_name,
58
61
  "args_str": args_str,
62
+ "is_complete_json": is_complete,
59
63
  "emitted": False,
60
64
  "is_main": is_main,
61
65
  }
62
- # Some providers send final args as a dict on a later chunk
66
+ # Some providers send final args as a dict on a later chunk (replace previous)
63
67
  elif tc_id and tc_id in pending_tool_calls and isinstance(tc_args, dict) and tc_args:
64
68
  pending_tool_calls[tc_id]["args_str"] = json.dumps(tc_args)
69
+ pending_tool_calls[tc_id]["is_complete_json"] = True
65
70
  # Subsequent chunks: accumulate partial JSON strings for this tool call id
66
71
  elif tc_id and tc_id in pending_tool_calls and isinstance(tc_args, str) and tc_args:
67
- pending_tool_calls[tc_id]["args_str"] += tc_args
72
+ # If args_str already contains complete JSON, provider refined args → restart
73
+ if pending_tool_calls[tc_id].get("is_complete_json"):
74
+ pending_tool_calls[tc_id]["args_str"] = tc_args
75
+ pending_tool_calls[tc_id]["is_complete_json"] = False
76
+ else:
77
+ # Normal partial accumulation
78
+ pending_tool_calls[tc_id]["args_str"] += tc_args
68
79
  elif tc_args and isinstance(tc_args, str) and tc_args:
69
80
  # Legacy: chunks missing ``id`` — attach to the first non-emitted pending call
70
81
  for pending in pending_tool_calls.values():
@@ -279,7 +290,7 @@ def tool_calls_have_any_arg_dict(tc_list: list[Any]) -> bool:
279
290
  # Maps tool name to list of argument keys to display (supports multiple args)
280
291
  _ARG_DISPLAY_MAP: dict[str, list[str]] = {
281
292
  # File operations — deepagents uses ``file_path`` for read/write/edit (see IG-053)
282
- "read_file": ["file_path", "path", "path_name"],
293
+ "read_file": ["file_path", "path", "path_name", "target_file", "filename", "relative_path"],
283
294
  "write_file": ["file_path", "path"],
284
295
  "delete_file": ["file_path", "path"],
285
296
  "file_info": ["path", "file_path"],
@@ -312,6 +323,8 @@ _ARG_DISPLAY_MAP: dict[str, list[str]] = {
312
323
  "create_goal": ["description"],
313
324
  "complete_goal": ["goal_id"],
314
325
  "fail_goal": ["goal_id"],
326
+ # Subagent delegation (deepagents ``task`` tool)
327
+ "task": ["subagent_type", "description", "prompt"],
315
328
  }
316
329
 
317
330
 
@@ -323,6 +336,43 @@ def _normalize_tool_name_for_arg_map(tool_name: str) -> str:
323
336
  return re.sub(r"(?<!^)(?=[A-Z])", "_", tool_name).lower()
324
337
 
325
338
 
339
+ _ARG_SUMMARY_SKIP_KEYS: frozenset[str] = frozenset({"_raw", "_internal", "raw_args_str"})
340
+
341
+
342
+ def _compact_tool_args_display_values(
343
+ args: dict[str, Any],
344
+ *,
345
+ max_value_length: int = 40,
346
+ max_items: int = 3,
347
+ ) -> str:
348
+ """Build comma-separated display values from the first tool parameters (values only)."""
349
+ from soothe_sdk.utils import convert_and_abbreviate_path
350
+ from soothe_sdk.utils.parsing import is_path_argument as _path_arg_name_pattern
351
+
352
+ def _is_path_arg_name(key: str) -> bool:
353
+ return _path_arg_name_pattern.match(key) is not None
354
+
355
+ def _display_path_value(raw: str) -> str:
356
+ out = convert_and_abbreviate_path(raw)
357
+ if len(out) > max_value_length:
358
+ return out[: max_value_length - 3] + "..."
359
+ return out
360
+
361
+ parts: list[str] = []
362
+ for k, v in args.items():
363
+ if len(parts) >= max_items:
364
+ break
365
+ if k in _ARG_SUMMARY_SKIP_KEYS:
366
+ continue
367
+ s = str(v)
368
+ if _is_path_arg_name(k):
369
+ s = _display_path_value(s)
370
+ elif len(s) > max_value_length:
371
+ s = s[: max_value_length - 3] + "..."
372
+ parts.append(s)
373
+ return ", ".join(parts)
374
+
375
+
326
376
  def format_tool_call_args(tool_name: str, tool_call: dict[str, Any]) -> str:
327
377
  """Format key tool arguments for display (see IG-053).
328
378
 
@@ -340,8 +390,10 @@ def format_tool_call_args(tool_name: str, tool_call: dict[str, Any]) -> str:
340
390
  Returns:
341
391
  Formatted argument string like "file_name.md" or "/Users/dev/.../file.md, pattern"
342
392
  (without outer parentheses - caller adds them).
343
- Returns "..." when args are empty but tool is known.
344
- Returns "" if tool is unknown and no args.
393
+ Returns "..." when args are empty but tool is known (or placeholders while streaming).
394
+ Returns "" when the tool is not in the display map and there are no usable args,
395
+ or when parsed args exist but only contain internal/skip keys.
396
+ For unmapped tools with parameters, returns a compact comma-separated value summary.
345
397
 
346
398
  Examples:
347
399
  >>> format_tool_call_args("read_file", {"args": {"path": "config.yml"}})
@@ -399,10 +451,13 @@ def format_tool_call_args(tool_name: str, tool_call: dict[str, Any]) -> str:
399
451
  val = val[: max_value_length - 3] + "..."
400
452
  return val
401
453
  return "..."
402
- return ""
454
+ return ""
403
455
 
404
456
  if not key_args:
405
- return ""
457
+ if args:
458
+ compact = _compact_tool_args_display_values(args, max_value_length=max_value_length)
459
+ return compact if compact else "…"
460
+ return "…"
406
461
 
407
462
  # Extract values for all configured argument keys
408
463
  values = []
@@ -420,21 +475,9 @@ def format_tool_call_args(tool_name: str, tool_call: dict[str, Any]) -> str:
420
475
  if not values:
421
476
  # Model may use different arg names than _ARG_DISPLAY_MAP; still show something useful.
422
477
  if args:
423
- parts: list[str] = []
424
- # Skip internal keys like _raw, _internal, etc.
425
- skip_keys = {"_raw", "_internal", "raw_args_str"}
426
- for k, v in list(args.items())[:3]:
427
- if k in skip_keys:
428
- continue
429
- s = str(v)
430
- # Convert and abbreviate path arguments
431
- if _is_path_arg_name(k):
432
- s = _display_path_value(s)
433
- elif len(s) > max_value_length:
434
- s = s[: max_value_length - 3] + "..."
435
- parts.append(s)
436
- if parts:
437
- return ", ".join(parts)
478
+ compact = _compact_tool_args_display_values(args, max_value_length=max_value_length)
479
+ if compact:
480
+ return compact
438
481
  # All args were internal keys, check for raw_args_str
439
482
  raw = args.get("_raw") or args.get("raw_args_str", "")
440
483
  if raw:
@@ -12,6 +12,7 @@ duplicate precedence rules across merge/backfill helpers.
12
12
 
13
13
  from __future__ import annotations
14
14
 
15
+ import logging
15
16
  from collections.abc import Mapping
16
17
  from dataclasses import dataclass
17
18
  from typing import Any
@@ -22,6 +23,8 @@ from soothe_cli.shared.message_processing import (
22
23
  try_parse_pending_tool_call_args,
23
24
  )
24
25
 
26
+ logger = logging.getLogger(__name__)
27
+
25
28
 
26
29
  def infer_tool_name_from_call_id(tool_call_id: str) -> str | None:
27
30
  """Recover a real tool name from common ``functions.<name>:<idx>`` id shapes.
@@ -243,7 +246,13 @@ def build_streaming_args_overlay(
243
246
  message: Any,
244
247
  pending_tool_calls_lc: dict[str, dict[str, Any]],
245
248
  ) -> dict[str, dict[str, Any]]:
246
- """Map ``tool_call_id`` → parsed args dict from ``tool_call_chunks`` accumulation."""
249
+ """Map ``tool_call_id`` → parsed args dict from ``tool_call_chunks`` accumulation.
250
+
251
+ Updates the overlay on every chunk whenever JSON parses successfully. A prior
252
+ version stopped after the first parse (``tui_stream_mounted``), which froze the
253
+ overlay when ``args_str`` grew across chunks so the TUI kept ``read_file(…)``
254
+ headers even after the path arrived in the accumulated string.
255
+ """
247
256
  from langchain_core.messages import AIMessageChunk
248
257
 
249
258
  overlay: dict[str, dict[str, Any]] = {}
@@ -252,8 +261,6 @@ def build_streaming_args_overlay(
252
261
  )
253
262
 
254
263
  for tc_id, pend in list(pending_tool_calls_lc.items()):
255
- if pend.get("tui_stream_mounted"):
256
- continue
257
264
  parsed = try_parse_pending_tool_call_args(pend)
258
265
  if parsed is None:
259
266
  continue
@@ -262,9 +269,18 @@ def build_streaming_args_overlay(
262
269
  continue
263
270
  if not parsed and not streaming_final:
264
271
  continue
265
- pend["tui_stream_mounted"] = True
266
272
  str_id = str(tc_id)
267
273
  overlay[str_id] = parsed
274
+ if logger.isEnabledFor(logging.DEBUG):
275
+ args_preview = str(parsed)[:200]
276
+ logger.debug(
277
+ "tool_stream_overlay id=%s name=%s keys=%s streaming_final=%s preview=%s",
278
+ str_id,
279
+ name,
280
+ sorted(parsed.keys()) if isinstance(parsed, dict) else "?",
281
+ streaming_final,
282
+ args_preview,
283
+ )
268
284
  return overlay
269
285
 
270
286
 
@@ -8,13 +8,13 @@ except PackageNotFoundError:
8
8
  # Fallback for development/editable installs
9
9
  __version__ = "0.0.0"
10
10
 
11
- DOCS_URL = "https://github.com/caesar0301/soothe/docs"
11
+ DOCS_URL = "https://github.com/OpenSoothe/soothe/docs"
12
12
  """URL for Soothe documentation."""
13
13
 
14
14
  PYPI_URL = "https://pypi.org/pypi/soothe/json"
15
15
  """PyPI JSON API endpoint for version checks."""
16
16
 
17
- CHANGELOG_URL = "https://github.com/caesar0301/soothe/blob/main/CHANGELOG.md"
17
+ CHANGELOG_URL = "https://github.com/OpenSoothe/soothe/blob/main/CHANGELOG.md"
18
18
  """URL for the full changelog."""
19
19
 
20
20
  USER_AGENT = f"soothe/{__version__} update-check"