soothe-cli 0.7.2__tar.gz → 0.7.4__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.2 → soothe_cli-0.7.4}/PKG-INFO +1 -1
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/cli/commands/autopilot_cmd.py +1 -31
- soothe_cli-0.7.4/src/soothe_cli/cli/commands/config_cmd.py +103 -0
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/cli/commands/cron_cmd.py +13 -4
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/cli/commands/loop_cmd.py +13 -0
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/cli/commands/run_cmd.py +2 -2
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/cli/commands/status_cmd.py +26 -55
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/cli/execution/daemon.py +2 -2
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/cli/main.py +2 -0
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/runtime/headless/processor.py +1 -1
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/runtime/parse/tool_call_resolution.py +12 -2
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/runtime/presentation/engine.py +1 -1
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/runtime/presentation/renderer_protocol.py +1 -1
- soothe_cli-0.7.4/src/soothe_cli/runtime/presentation/subagent_task_display.py +112 -0
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/runtime/state/session_stats.py +89 -44
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/runtime/state/step_router.py +1 -1
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/runtime/transport/session.py +64 -0
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/tui/_env_vars.py +28 -0
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/tui/app/_app.py +19 -5
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/tui/app/_execution.py +50 -49
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/tui/app/_history.py +103 -31
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/tui/app/_messages_mixin.py +203 -93
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/tui/app/_model.py +15 -1
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/tui/app/_module_init.py +3 -0
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/tui/app/_startup.py +29 -30
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/tui/app/_ui.py +47 -6
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/tui/app/app.tcss +16 -6
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/tui/command_registry.py +8 -8
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/tui/commands/command_router.py +1 -1
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/tui/commands/slash_commands.py +37 -10
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/tui/commands/subagent_routing.py +3 -8
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/tui/config.py +4 -23
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/tui/file_change_notify.py +23 -1
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/tui/file_change_renderers.py +41 -0
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/tui/preview_limits.py +4 -4
- soothe_cli-0.7.4/src/soothe_cli/tui/shell_color.py +48 -0
- soothe_cli-0.7.4/src/soothe_cli/tui/spinner_labels.py +55 -0
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/tui/textual_adapter.py +705 -205
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/tui/theme.py +23 -89
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/tui/tips.py +1 -1
- soothe_cli-0.7.4/src/soothe_cli/tui/widgets/diff.py +228 -0
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/tui/widgets/file_change_preview.py +133 -91
- soothe_cli-0.7.4/src/soothe_cli/tui/widgets/help_screen.py +210 -0
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/tui/widgets/loading.py +42 -10
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/tui/widgets/message_store.py +10 -1
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/tui/widgets/messages/_helpers.py +74 -8
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/tui/widgets/messages/app.py +16 -7
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/tui/widgets/messages/assistant.py +81 -15
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/tui/widgets/messages/clarification.py +32 -23
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/tui/widgets/messages/cognition_goal_tree.py +160 -36
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/tui/widgets/messages/cognition_reason.py +3 -22
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/tui/widgets/messages/cognition_step.py +18 -41
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/tui/widgets/messages/cognition_step_activity.py +5 -0
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/tui/widgets/messages/cognition_subagent.py +7 -3
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/tui/widgets/messages/error.py +9 -10
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/tui/widgets/messages/skill.py +7 -17
- soothe_cli-0.7.4/src/soothe_cli/tui/widgets/plan_quick_view_overlay.py +150 -0
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/tui/widgets/welcome.py +44 -77
- soothe_cli-0.7.2/src/soothe_cli/runtime/presentation/explore_task_display.py +0 -89
- soothe_cli-0.7.2/src/soothe_cli/tui/widgets/autopilot_screen.py +0 -65
- soothe_cli-0.7.2/src/soothe_cli/tui/widgets/diff.py +0 -143
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/.gitignore +0 -0
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/README.md +0 -0
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/pyproject.toml +0 -0
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/__init__.py +0 -0
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/cli/__init__.py +0 -0
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/cli/commands/__init__.py +0 -0
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/cli/execution/__init__.py +0 -0
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/cli/execution/daemon_errors.py +0 -0
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/cli/execution/headless.py +0 -0
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/cli/execution/headless_renderer.py +0 -0
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/cli/execution/launcher.py +0 -0
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/config/__init__.py +0 -0
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/config/cli_config.py +0 -0
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/config/loader.py +0 -0
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/config/logging_setup.py +0 -0
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/runtime/__init__.py +0 -0
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/runtime/headless/processor_state.py +0 -0
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/runtime/parse/message_processing.py +0 -0
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/runtime/parse/tool_result.py +0 -0
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/runtime/policy/display_policy.py +0 -0
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/runtime/policy/essential_events.py +0 -0
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/runtime/policy/tui_trace_log.py +0 -0
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/runtime/presentation/duration_format.py +0 -0
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/runtime/presentation/renderer_base.py +0 -0
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/runtime/state/file_tracker.py +0 -0
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/runtime/state/stream_accumulator.py +0 -0
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/runtime/state/transcript.py +0 -0
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/runtime/turn/pipeline.py +0 -0
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/runtime/turn/prepare.py +0 -0
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/runtime/wire/chunk_filter.py +0 -0
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/runtime/wire/display_text.py +0 -0
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/runtime/wire/message_text.py +0 -0
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/runtime/wire/messages.py +0 -0
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/tui/__init__.py +0 -0
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/tui/_cli_context.py +0 -0
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/tui/_version.py +0 -0
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/tui/app/__init__.py +0 -0
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/tui/app/_commands.py +0 -0
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/tui/binding.py +0 -0
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/tui/commands/__init__.py +0 -0
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/tui/hooks.py +0 -0
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/tui/input.py +0 -0
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/tui/markdown_theme.py +0 -0
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/tui/media_utils.py +0 -0
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/tui/model_config.py +0 -0
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/tui/path_utils.py +0 -0
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/tui/project_utils.py +0 -0
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/tui/sessions.py +0 -0
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/tui/skills/__init__.py +0 -0
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/tui/skills/invocation.py +0 -0
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/tui/skills/load.py +0 -0
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/tui/tool_display.py +0 -0
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/tui/unicode_security.py +0 -0
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/tui/update_check.py +0 -0
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/tui/widgets/__init__.py +0 -0
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/tui/widgets/_links.py +0 -0
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/tui/widgets/autocomplete.py +0 -0
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/tui/widgets/autopilot_dashboard.py +0 -0
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/tui/widgets/chat_input.py +0 -0
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/tui/widgets/clipboard.py +0 -0
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/tui/widgets/context_viewer.py +0 -0
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/tui/widgets/editor.py +0 -0
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/tui/widgets/history.py +0 -0
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/tui/widgets/loop_selector.py +0 -0
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/tui/widgets/mcp_viewer.py +0 -0
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/tui/widgets/messages/__init__.py +0 -0
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/tui/widgets/messages/diff_message.py +0 -0
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/tui/widgets/messages/user.py +0 -0
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/tui/widgets/model_selector.py +0 -0
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/tui/widgets/notification_settings.py +0 -0
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/tui/widgets/status.py +0 -0
- {soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/tui/widgets/theme_selector.py +0 -0
|
@@ -55,7 +55,7 @@ def run(
|
|
|
55
55
|
max_iterations: int | None = typer.Option(
|
|
56
56
|
None,
|
|
57
57
|
"--max-iterations",
|
|
58
|
-
help="Ignored — use daemon config agent.
|
|
58
|
+
help="Ignored — use daemon config agent.autopilot.max_iterations.",
|
|
59
59
|
),
|
|
60
60
|
workspace: str | None = typer.Option(
|
|
61
61
|
None,
|
|
@@ -333,36 +333,6 @@ def cancel_goal(
|
|
|
333
333
|
typer.echo(f"Cancel result: {result.get('status', result)}")
|
|
334
334
|
|
|
335
335
|
|
|
336
|
-
@app.command("approve")
|
|
337
|
-
def approve_goal(
|
|
338
|
-
goal_id: str = typer.Argument(
|
|
339
|
-
..., help="Confirmation ID to approve (use 'inbox' to list pending)."
|
|
340
|
-
),
|
|
341
|
-
) -> None:
|
|
342
|
-
"""Approve a MUST-confirmation goal."""
|
|
343
|
-
client = _require_daemon_ws()
|
|
344
|
-
try:
|
|
345
|
-
result = client.autopilot_approve(goal_id)
|
|
346
|
-
except RuntimeError as exc:
|
|
347
|
-
typer.echo(str(exc), err=True)
|
|
348
|
-
raise typer.Exit(1) from exc
|
|
349
|
-
typer.echo(f"Confirmation approved: {result.get('goal_id', goal_id)}")
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
@app.command("reject")
|
|
353
|
-
def reject_goal(
|
|
354
|
-
goal_id: str = typer.Argument(..., help="Confirmation ID to reject."),
|
|
355
|
-
) -> None:
|
|
356
|
-
"""Reject a proposed goal."""
|
|
357
|
-
client = _require_daemon_ws()
|
|
358
|
-
try:
|
|
359
|
-
result = client.autopilot_reject(goal_id)
|
|
360
|
-
except RuntimeError as exc:
|
|
361
|
-
typer.echo(str(exc), err=True)
|
|
362
|
-
raise typer.Exit(1) from exc
|
|
363
|
-
typer.echo(f"Confirmation rejected: {result.get('goal_id', goal_id)}")
|
|
364
|
-
|
|
365
|
-
|
|
366
336
|
@app.command("resume")
|
|
367
337
|
def resume_goal(
|
|
368
338
|
goal_id: str = typer.Argument(..., help="Goal ID to resume."),
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""Config management CLI commands for the soothe daemon."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import json
|
|
7
|
+
import sys
|
|
8
|
+
from typing import Annotated, Any
|
|
9
|
+
|
|
10
|
+
import typer
|
|
11
|
+
from rich.console import Console
|
|
12
|
+
from rich.panel import Panel
|
|
13
|
+
from soothe_sdk.client import (
|
|
14
|
+
WebSocketClient,
|
|
15
|
+
request_daemon_config_reload,
|
|
16
|
+
websocket_url_from_config,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
from soothe_cli.config.loader import load_config
|
|
20
|
+
|
|
21
|
+
console = Console()
|
|
22
|
+
|
|
23
|
+
# Create config command group
|
|
24
|
+
config_app = typer.Typer(help="Manage daemon configuration", no_args_is_help=True)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
async def _trigger_config_reload(ws_url: str, timeout: float = 5.0) -> dict[str, Any]:
|
|
28
|
+
"""Trigger config reload via WebSocket RPC.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
ws_url: WebSocket URL to connect to.
|
|
32
|
+
timeout: Request timeout in seconds.
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
Response dict from daemon, or error dict on failure.
|
|
36
|
+
"""
|
|
37
|
+
client = WebSocketClient(url=ws_url)
|
|
38
|
+
try:
|
|
39
|
+
await client.connect()
|
|
40
|
+
return await request_daemon_config_reload(client, timeout=timeout)
|
|
41
|
+
except Exception as e:
|
|
42
|
+
return {"success": False, "error": str(e)}
|
|
43
|
+
finally:
|
|
44
|
+
await client.close()
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@config_app.command("reload")
|
|
48
|
+
def config_reload(
|
|
49
|
+
json_output: Annotated[
|
|
50
|
+
bool,
|
|
51
|
+
typer.Option("--json", help="Output as JSON."),
|
|
52
|
+
] = False,
|
|
53
|
+
) -> None:
|
|
54
|
+
"""Trigger config hot-reload on the running daemon.
|
|
55
|
+
|
|
56
|
+
Sends a config_reload RPC request to the daemon, which triggers immediate
|
|
57
|
+
reload of watched config files (config.yml and daemon.yml). Requires the
|
|
58
|
+
daemon to have hot-reload enabled via daemon.enable_config_reload().
|
|
59
|
+
|
|
60
|
+
Examples:
|
|
61
|
+
soothe config reload
|
|
62
|
+
soothe config reload --json
|
|
63
|
+
"""
|
|
64
|
+
config = load_config()
|
|
65
|
+
ws_url = websocket_url_from_config(config)
|
|
66
|
+
|
|
67
|
+
# Trigger reload
|
|
68
|
+
result = asyncio.run(_trigger_config_reload(ws_url, timeout=5.0))
|
|
69
|
+
|
|
70
|
+
if json_output:
|
|
71
|
+
output = {
|
|
72
|
+
"websocket_url": ws_url,
|
|
73
|
+
"success": result.get("success", False),
|
|
74
|
+
}
|
|
75
|
+
if result.get("error"):
|
|
76
|
+
output["error"] = result.get("error")
|
|
77
|
+
if result.get("message"):
|
|
78
|
+
output["message"] = result.get("message")
|
|
79
|
+
console.print_json(json.dumps(output))
|
|
80
|
+
return
|
|
81
|
+
|
|
82
|
+
if result.get("success"):
|
|
83
|
+
console.print(
|
|
84
|
+
Panel(
|
|
85
|
+
f"WebSocket URL: {ws_url}\n"
|
|
86
|
+
f"Status: [green]Reload triggered[/green]\n"
|
|
87
|
+
f"Message: {result.get('message', 'Config reload initiated')}",
|
|
88
|
+
title="Config Reload",
|
|
89
|
+
border_style="green",
|
|
90
|
+
)
|
|
91
|
+
)
|
|
92
|
+
else:
|
|
93
|
+
error_msg = result.get("error", "Unknown error")
|
|
94
|
+
if result.get("message"):
|
|
95
|
+
error_msg = result.get("message")
|
|
96
|
+
console.print(
|
|
97
|
+
Panel(
|
|
98
|
+
f"WebSocket URL: {ws_url}\nStatus: [red]Failed[/red]\nError: {error_msg}",
|
|
99
|
+
title="Config Reload",
|
|
100
|
+
border_style="red",
|
|
101
|
+
)
|
|
102
|
+
)
|
|
103
|
+
sys.exit(1)
|
|
@@ -85,11 +85,16 @@ def add_job(
|
|
|
85
85
|
try:
|
|
86
86
|
result = client.cron_add(text, priority=priority)
|
|
87
87
|
except RuntimeError as exc:
|
|
88
|
-
|
|
88
|
+
message = str(exc)
|
|
89
|
+
if "Autopilot is disabled" in message:
|
|
90
|
+
console.print(f"[yellow]{message}[/yellow]")
|
|
91
|
+
else:
|
|
92
|
+
typer.echo(f"Error: {message}", err=True)
|
|
89
93
|
sys.exit(1)
|
|
90
94
|
|
|
91
95
|
job = result.get("job") or {}
|
|
92
96
|
job_id = job.get("id", "?")
|
|
97
|
+
is_duplicate = bool(result.get("duplicate"))
|
|
93
98
|
console.print(
|
|
94
99
|
Panel(
|
|
95
100
|
"\n".join(
|
|
@@ -101,10 +106,14 @@ def add_job(
|
|
|
101
106
|
f"[bold]Status:[/bold] [green]{job.get('status', 'pending')}[/green]",
|
|
102
107
|
]
|
|
103
108
|
),
|
|
104
|
-
title="Scheduled Job Created",
|
|
105
|
-
border_style="green",
|
|
109
|
+
title="Scheduled Job Already Exists" if is_duplicate else "Scheduled Job Created",
|
|
110
|
+
border_style="yellow" if is_duplicate else "green",
|
|
106
111
|
)
|
|
107
112
|
)
|
|
113
|
+
if is_duplicate:
|
|
114
|
+
console.print(
|
|
115
|
+
"[yellow]An identical active job is already scheduled; no new job was created.[/yellow]"
|
|
116
|
+
)
|
|
108
117
|
|
|
109
118
|
|
|
110
119
|
@app.command("list")
|
|
@@ -128,7 +137,7 @@ def list_jobs(
|
|
|
128
137
|
"""
|
|
129
138
|
client = _require_cron_client()
|
|
130
139
|
try:
|
|
131
|
-
result = client.
|
|
140
|
+
result = client.cron_list(status=status)
|
|
132
141
|
except RuntimeError as exc:
|
|
133
142
|
typer.echo(f"Error: {exc}", err=True)
|
|
134
143
|
sys.exit(1)
|
|
@@ -720,6 +720,18 @@ def continue_loop(
|
|
|
720
720
|
)
|
|
721
721
|
|
|
722
722
|
|
|
723
|
+
@loop_app.command("resume")
|
|
724
|
+
def resume_loop(
|
|
725
|
+
loop_id: Annotated[str | None, typer.Argument(help="Loop identifier to continue")] = None,
|
|
726
|
+
prompt: Annotated[
|
|
727
|
+
str | None,
|
|
728
|
+
typer.Option("--prompt", "-p", help="Optional prompt to send after continuing."),
|
|
729
|
+
] = None,
|
|
730
|
+
) -> None:
|
|
731
|
+
"""Alias for ``continue`` — resume execution on existing loop."""
|
|
732
|
+
continue_loop(loop_id, prompt)
|
|
733
|
+
|
|
734
|
+
|
|
723
735
|
@loop_app.command("detach")
|
|
724
736
|
def detach_loop(
|
|
725
737
|
loop_id: Annotated[str, typer.Argument(help="Loop identifier to detach")],
|
|
@@ -873,6 +885,7 @@ __all__ = [
|
|
|
873
885
|
"prune_loop_branches",
|
|
874
886
|
"delete_loop",
|
|
875
887
|
"continue_loop",
|
|
888
|
+
"resume_loop",
|
|
876
889
|
"detach_loop",
|
|
877
890
|
"attach_loop",
|
|
878
891
|
"new_loop",
|
|
@@ -100,7 +100,7 @@ def run_impl(
|
|
|
100
100
|
result = get_last_app_result()
|
|
101
101
|
if result is not None and result.loop_id:
|
|
102
102
|
typer.echo(
|
|
103
|
-
f"💡 To continue this loop: soothe loop
|
|
103
|
+
f"💡 To continue this loop: soothe loop resume {result.loop_id}",
|
|
104
104
|
err=True,
|
|
105
105
|
)
|
|
106
106
|
|
|
@@ -113,7 +113,7 @@ def run_impl(
|
|
|
113
113
|
result = get_last_app_result()
|
|
114
114
|
if result is not None and result.loop_id:
|
|
115
115
|
typer.echo(
|
|
116
|
-
f"💡 To continue this loop: soothe loop
|
|
116
|
+
f"💡 To continue this loop: soothe loop resume {result.loop_id}",
|
|
117
117
|
err=True,
|
|
118
118
|
)
|
|
119
119
|
sys.exit(130)
|
|
@@ -50,47 +50,6 @@ async def _fetch_status(ws_url: str, timeout: float = 5.0) -> dict[str, Any]:
|
|
|
50
50
|
await client.close()
|
|
51
51
|
|
|
52
52
|
|
|
53
|
-
async def _fetch_ready_state(ws_url: str, timeout: float = 5.0) -> dict[str, Any] | None:
|
|
54
|
-
"""Fetch daemon readiness state via WebSocket connection_init/ack handshake.
|
|
55
|
-
|
|
56
|
-
Sends ``connection_init`` and waits for ``connection_ack`` which carries
|
|
57
|
-
the daemon's ``readiness_state``.
|
|
58
|
-
|
|
59
|
-
Args:
|
|
60
|
-
ws_url: WebSocket URL.
|
|
61
|
-
timeout: Timeout for handshake.
|
|
62
|
-
|
|
63
|
-
Returns:
|
|
64
|
-
``connection_ack`` result dict (containing ``readiness_state``) or None.
|
|
65
|
-
"""
|
|
66
|
-
import websockets
|
|
67
|
-
|
|
68
|
-
init_msg = json.dumps(
|
|
69
|
-
{
|
|
70
|
-
"proto": "1",
|
|
71
|
-
"type": "connection_init",
|
|
72
|
-
"params": {
|
|
73
|
-
"client_version": "0.5.0",
|
|
74
|
-
"accept_proto": ["1"],
|
|
75
|
-
"capabilities": ["streaming"],
|
|
76
|
-
},
|
|
77
|
-
}
|
|
78
|
-
)
|
|
79
|
-
|
|
80
|
-
try:
|
|
81
|
-
async with asyncio.timeout(timeout):
|
|
82
|
-
async with websockets.connect(ws_url) as ws:
|
|
83
|
-
await ws.send(init_msg)
|
|
84
|
-
for _ in range(3):
|
|
85
|
-
msg = await ws.recv()
|
|
86
|
-
data = json.loads(msg)
|
|
87
|
-
if data.get("type") == "connection_ack":
|
|
88
|
-
return data.get("result", data)
|
|
89
|
-
except Exception:
|
|
90
|
-
pass
|
|
91
|
-
return None
|
|
92
|
-
|
|
93
|
-
|
|
94
53
|
def _render_unified_status_table(
|
|
95
54
|
config: Any,
|
|
96
55
|
ws_url: str,
|
|
@@ -156,10 +115,6 @@ def daemon_status(
|
|
|
156
115
|
bool,
|
|
157
116
|
typer.Option("--json", help="Output as JSON."),
|
|
158
117
|
] = False,
|
|
159
|
-
verbose: Annotated[
|
|
160
|
-
bool,
|
|
161
|
-
typer.Option("--verbose", "-v", help="Show detailed status."),
|
|
162
|
-
] = False,
|
|
163
118
|
) -> None:
|
|
164
119
|
"""Check daemon status from client side.
|
|
165
120
|
|
|
@@ -168,7 +123,6 @@ def daemon_status(
|
|
|
168
123
|
Examples:
|
|
169
124
|
soothe status daemon
|
|
170
125
|
soothe status daemon --json
|
|
171
|
-
soothe status daemon -v
|
|
172
126
|
"""
|
|
173
127
|
config = load_config()
|
|
174
128
|
ws_url = websocket_url_from_config(config)
|
|
@@ -217,11 +171,6 @@ def daemon_status(
|
|
|
217
171
|
)
|
|
218
172
|
sys.exit(1)
|
|
219
173
|
|
|
220
|
-
# Get readiness state for verbose mode
|
|
221
|
-
ready_state = None
|
|
222
|
-
if verbose:
|
|
223
|
-
ready_state = asyncio.run(_fetch_ready_state(ws_url, timeout=5.0))
|
|
224
|
-
|
|
225
174
|
if json_output:
|
|
226
175
|
output = {
|
|
227
176
|
"status": "running",
|
|
@@ -232,10 +181,10 @@ def daemon_status(
|
|
|
232
181
|
"daemon_pid": status.get("daemon_pid"),
|
|
233
182
|
"daemon_version": status.get("daemon_version"),
|
|
234
183
|
"core_version": status.get("core_version"),
|
|
184
|
+
"readiness_state": status.get("readiness_state", "unknown"),
|
|
235
185
|
}
|
|
236
|
-
if
|
|
237
|
-
output["
|
|
238
|
-
output["readiness_message"] = ready_state.get("message")
|
|
186
|
+
if status.get("readiness_message"):
|
|
187
|
+
output["readiness_message"] = status.get("readiness_message")
|
|
239
188
|
console.print_json(json.dumps(output))
|
|
240
189
|
return
|
|
241
190
|
|
|
@@ -246,6 +195,15 @@ def daemon_status(
|
|
|
246
195
|
daemon_pid = status.get("daemon_pid")
|
|
247
196
|
daemon_version = status.get("daemon_version")
|
|
248
197
|
core_version = status.get("core_version")
|
|
198
|
+
# Use readiness_state from daemon_status RPC (already includes state + message)
|
|
199
|
+
readiness_state_from_status = (
|
|
200
|
+
{
|
|
201
|
+
"state": status.get("readiness_state", "unknown"),
|
|
202
|
+
"message": status.get("readiness_message"),
|
|
203
|
+
}
|
|
204
|
+
if status.get("readiness_state")
|
|
205
|
+
else None
|
|
206
|
+
)
|
|
249
207
|
|
|
250
208
|
table = _render_unified_status_table(
|
|
251
209
|
config,
|
|
@@ -254,7 +212,7 @@ def daemon_status(
|
|
|
254
212
|
port_live,
|
|
255
213
|
active_threads,
|
|
256
214
|
daemon_pid,
|
|
257
|
-
|
|
215
|
+
readiness_state_from_status,
|
|
258
216
|
daemon_live=True,
|
|
259
217
|
daemon_version=daemon_version,
|
|
260
218
|
core_version=core_version,
|
|
@@ -349,6 +307,9 @@ def status_main(
|
|
|
349
307
|
output["daemon"]["daemon_pid"] = status.get("daemon_pid")
|
|
350
308
|
output["daemon"]["daemon_version"] = status.get("daemon_version")
|
|
351
309
|
output["daemon"]["core_version"] = status.get("core_version")
|
|
310
|
+
output["daemon"]["readiness_state"] = status.get("readiness_state", "unknown")
|
|
311
|
+
if status.get("readiness_message"):
|
|
312
|
+
output["daemon"]["readiness_message"] = status.get("readiness_message")
|
|
352
313
|
console.print_json(json.dumps(output))
|
|
353
314
|
return
|
|
354
315
|
|
|
@@ -378,6 +339,15 @@ def status_main(
|
|
|
378
339
|
daemon_pid = status.get("daemon_pid")
|
|
379
340
|
daemon_version = status.get("daemon_version")
|
|
380
341
|
core_version = status.get("core_version")
|
|
342
|
+
# Use readiness_state from daemon_status RPC (already includes state + message)
|
|
343
|
+
readiness_state_from_status = (
|
|
344
|
+
{
|
|
345
|
+
"state": status.get("readiness_state", "unknown"),
|
|
346
|
+
"message": status.get("readiness_message"),
|
|
347
|
+
}
|
|
348
|
+
if status.get("readiness_state")
|
|
349
|
+
else None
|
|
350
|
+
)
|
|
381
351
|
|
|
382
352
|
table = _render_unified_status_table(
|
|
383
353
|
config,
|
|
@@ -386,6 +356,7 @@ def status_main(
|
|
|
386
356
|
port_live,
|
|
387
357
|
active_threads,
|
|
388
358
|
daemon_pid,
|
|
359
|
+
readiness_state_from_status,
|
|
389
360
|
daemon_live=True,
|
|
390
361
|
daemon_version=daemon_version,
|
|
391
362
|
core_version=core_version,
|
|
@@ -8,7 +8,6 @@ from __future__ import annotations
|
|
|
8
8
|
|
|
9
9
|
import asyncio
|
|
10
10
|
import logging
|
|
11
|
-
import os
|
|
12
11
|
import signal
|
|
13
12
|
from typing import Any
|
|
14
13
|
|
|
@@ -28,6 +27,7 @@ from soothe_cli.cli.execution.daemon_errors import (
|
|
|
28
27
|
from soothe_cli.cli.execution.headless_renderer import HeadlessCliRenderer
|
|
29
28
|
from soothe_cli.runtime import EventProcessor
|
|
30
29
|
from soothe_cli.runtime.presentation.engine import PresentationEngine
|
|
30
|
+
from soothe_cli.tui._env_vars import resolve_cli_loop_workspace
|
|
31
31
|
from soothe_cli.tui.commands.subagent_routing import parse_subagent_from_input
|
|
32
32
|
|
|
33
33
|
logger = logging.getLogger(__name__)
|
|
@@ -151,7 +151,7 @@ async def _run_headless_session_once(
|
|
|
151
151
|
|
|
152
152
|
try:
|
|
153
153
|
await connect_websocket_with_retries(client)
|
|
154
|
-
cli_ws =
|
|
154
|
+
cli_ws = resolve_cli_loop_workspace()
|
|
155
155
|
# IG-441: three first-class modes (batch / adaptive / streaming).
|
|
156
156
|
# Default is ``adaptive`` for headless runs as well — it gives smooth
|
|
157
157
|
# progress on long synthesis while keeping wire traffic bounded.
|
|
@@ -228,6 +228,7 @@ def main(
|
|
|
228
228
|
# ---------------------------------------------------------------------------
|
|
229
229
|
|
|
230
230
|
from soothe_cli.cli.commands.autopilot_cmd import app as _autopilot_app # noqa: E402
|
|
231
|
+
from soothe_cli.cli.commands.config_cmd import config_app as _config_app # noqa: E402
|
|
231
232
|
from soothe_cli.cli.commands.cron_cmd import app as _cron_app # noqa: E402
|
|
232
233
|
from soothe_cli.cli.commands.loop_cmd import loop_app as _loop_app # noqa: E402
|
|
233
234
|
from soothe_cli.cli.commands.status_cmd import status_app as _status_app # noqa: E402
|
|
@@ -237,6 +238,7 @@ for _sub_app, _name in (
|
|
|
237
238
|
(_loop_app, "loop"),
|
|
238
239
|
(_autopilot_app, "autopilot"),
|
|
239
240
|
(_cron_app, "cron"),
|
|
241
|
+
(_config_app, "config"),
|
|
240
242
|
):
|
|
241
243
|
add_help_alias(_sub_app)
|
|
242
244
|
app.add_typer(_sub_app, name=_name)
|
|
@@ -242,7 +242,7 @@ class EventProcessor:
|
|
|
242
242
|
"""Headless stdout: only RFC-614 loop-tagged finals (IG-343 / IG-345).
|
|
243
243
|
|
|
244
244
|
Suppresses unphased execute-wave narration on the main graph; ``goal_completion``,
|
|
245
|
-
``
|
|
245
|
+
``goal_completion``, ``chitchat``, etc. are routed via ``assistant_output_phase`` before this path.
|
|
246
246
|
|
|
247
247
|
Subgraph streams without RFC-614 phases are skipped in ``_handle_ai_message`` before
|
|
248
248
|
this runs; loop-tagged subgraph finals use ``_dispatch_loop_tagged_assistant_text``.
|
|
@@ -152,7 +152,7 @@ def should_ingest_tool_for_step_stats(
|
|
|
152
152
|
"""Whether the TUI should register a tool row for step-card stats.
|
|
153
153
|
|
|
154
154
|
Main execute-graph tools are tracked as soon as ``tool_call_id`` and ``tool_name``
|
|
155
|
-
are known; display does not wait for streamed args. Subgraph
|
|
155
|
+
are known; display does not wait for streamed args. Subgraph tools often
|
|
156
156
|
arrive with placeholder kwargs before real args.
|
|
157
157
|
"""
|
|
158
158
|
from soothe_sdk.ux.task_namespace import is_step_level_task_tool_id
|
|
@@ -165,7 +165,7 @@ def should_ingest_tool_for_step_stats(
|
|
|
165
165
|
return True
|
|
166
166
|
if is_step_card_scope:
|
|
167
167
|
return not is_step_level_task_tool_id(tcid)
|
|
168
|
-
# Subgraph
|
|
168
|
+
# Subgraph tools often arrive with ``{"_subgraph_tool": true}`` before real args.
|
|
169
169
|
return is_task_level_subgraph_tool_call_id(tcid)
|
|
170
170
|
|
|
171
171
|
|
|
@@ -456,6 +456,11 @@ def materialize_ai_blocks_with_resolved_tools(
|
|
|
456
456
|
append + backfill + merge passes).
|
|
457
457
|
"""
|
|
458
458
|
tool_only = [b for b in expanded_blocks if isinstance(b, dict) and is_toolish_display_block(b)]
|
|
459
|
+
is_text_only_chunk = (
|
|
460
|
+
bool(expanded_blocks)
|
|
461
|
+
and not tool_only
|
|
462
|
+
and all(isinstance(b, dict) and b.get("type") == "text" for b in expanded_blocks)
|
|
463
|
+
)
|
|
459
464
|
resolved = resolve_tool_invocations_for_display(
|
|
460
465
|
message,
|
|
461
466
|
tool_only,
|
|
@@ -491,6 +496,11 @@ def materialize_ai_blocks_with_resolved_tools(
|
|
|
491
496
|
|
|
492
497
|
for r in resolved:
|
|
493
498
|
if r.tool_call_id not in seen_tool_ids:
|
|
499
|
+
# Plain text stream deltas must not inherit stale ``tool_calls`` from
|
|
500
|
+
# the LangChain message object — that appended phantom tool blocks and
|
|
501
|
+
# forced a flush+pop between every text token (weather-style trivial).
|
|
502
|
+
if is_text_only_chunk:
|
|
503
|
+
continue
|
|
494
504
|
out.append(
|
|
495
505
|
{
|
|
496
506
|
"type": "tool_call",
|
|
@@ -49,7 +49,7 @@ class PresentationEngine:
|
|
|
49
49
|
|
|
50
50
|
@property
|
|
51
51
|
def final_answer_locked(self) -> bool:
|
|
52
|
-
"""True after a custom final or
|
|
52
|
+
"""True after a custom final or chitchat-phase response was emitted for this turn."""
|
|
53
53
|
return self._state.final_answer_locked
|
|
54
54
|
|
|
55
55
|
def mark_final_answer_locked(self) -> None:
|
{soothe_cli-0.7.2 → soothe_cli-0.7.4}/src/soothe_cli/runtime/presentation/renderer_protocol.py
RENAMED
|
@@ -141,7 +141,7 @@ class RendererProtocol(Protocol):
|
|
|
141
141
|
Catch-all for events not covered by specific callbacks.
|
|
142
142
|
|
|
143
143
|
Args:
|
|
144
|
-
event_type: Full event type string (e.g., ``soothe.subagent.
|
|
144
|
+
event_type: Full event type string (e.g., ``soothe.subagent.tacitus.started``).
|
|
145
145
|
data: Event payload.
|
|
146
146
|
namespace: Subagent namespace tuple (empty for main agent).
|
|
147
147
|
task_scope: When subgraph streams are bound to a Task tool call (IG-334).
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"""Format subagent Task-scope assistant blobs for CLI/TUI.
|
|
2
|
+
|
|
3
|
+
Tacitus emits structured JSON in subgraph assistant streams. Clients show
|
|
4
|
+
user-facing summaries only; internal planning JSON is suppressed.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
from collections.abc import Iterator
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _is_tacitus_internal_json_object(obj: dict[str, Any]) -> bool:
|
|
15
|
+
"""True when ``obj`` is Tacitus engine scratch (not a user-facing report)."""
|
|
16
|
+
keys = set(obj.keys())
|
|
17
|
+
if "sub_questions" in keys:
|
|
18
|
+
return True
|
|
19
|
+
if "queries" in keys and "query" not in keys:
|
|
20
|
+
return True
|
|
21
|
+
if "is_sufficient" in keys or "follow_up_queries" in keys or "knowledge_gap" in keys:
|
|
22
|
+
return True
|
|
23
|
+
return False
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _iter_embedded_json_objects(raw: str) -> Iterator[dict[str, Any]]:
|
|
27
|
+
"""Yield dict objects embedded anywhere in ``raw`` (prose + concatenated JSON)."""
|
|
28
|
+
dec = json.JSONDecoder()
|
|
29
|
+
i = 0
|
|
30
|
+
s = raw
|
|
31
|
+
n = len(s)
|
|
32
|
+
while i < n:
|
|
33
|
+
brace = s.find("{", i)
|
|
34
|
+
if brace < 0:
|
|
35
|
+
break
|
|
36
|
+
try:
|
|
37
|
+
obj, end = dec.raw_decode(s, brace)
|
|
38
|
+
except json.JSONDecodeError:
|
|
39
|
+
i = brace + 1
|
|
40
|
+
continue
|
|
41
|
+
if isinstance(obj, dict):
|
|
42
|
+
yield obj
|
|
43
|
+
i = end
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _strip_concatenated_json_objects(raw: str, *, predicate) -> str:
|
|
47
|
+
"""Remove JSON objects matching ``predicate``; keep surrounding prose."""
|
|
48
|
+
stripped = raw.strip()
|
|
49
|
+
if not stripped:
|
|
50
|
+
return raw
|
|
51
|
+
|
|
52
|
+
dec_objects: list[tuple[int, int, dict[str, Any]]] = []
|
|
53
|
+
dec = json.JSONDecoder()
|
|
54
|
+
i = 0
|
|
55
|
+
n = len(stripped)
|
|
56
|
+
while i < n:
|
|
57
|
+
while i < n and stripped[i].isspace():
|
|
58
|
+
i += 1
|
|
59
|
+
if i >= n:
|
|
60
|
+
break
|
|
61
|
+
try:
|
|
62
|
+
obj, end = dec.raw_decode(stripped, i)
|
|
63
|
+
except json.JSONDecodeError:
|
|
64
|
+
break
|
|
65
|
+
if isinstance(obj, dict) and predicate(obj):
|
|
66
|
+
dec_objects.append((i, end, obj))
|
|
67
|
+
i = end
|
|
68
|
+
|
|
69
|
+
if not dec_objects:
|
|
70
|
+
return raw
|
|
71
|
+
|
|
72
|
+
parts: list[str] = []
|
|
73
|
+
cursor = 0
|
|
74
|
+
for start, end, _obj in dec_objects:
|
|
75
|
+
if start > cursor:
|
|
76
|
+
parts.append(stripped[cursor:start])
|
|
77
|
+
cursor = end
|
|
78
|
+
if cursor < n:
|
|
79
|
+
parts.append(stripped[cursor:])
|
|
80
|
+
return " ".join("".join(parts).split())
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def format_subagent_task_assistant_for_display(
|
|
84
|
+
raw: str,
|
|
85
|
+
*,
|
|
86
|
+
subagent_type: str | None = None,
|
|
87
|
+
) -> str:
|
|
88
|
+
"""Return display-safe assistant text for a delegated task namespace.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
raw: Full assistant text from a subgraph namespace.
|
|
92
|
+
subagent_type: Built-in subagent id when known (``tacitus``, …).
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
Scrubbed one-line text, or ``""`` when only internal payloads remain.
|
|
96
|
+
"""
|
|
97
|
+
agent = (subagent_type or "").strip().lower()
|
|
98
|
+
text = raw
|
|
99
|
+
if agent == "tacitus":
|
|
100
|
+
stripped = text.strip()
|
|
101
|
+
internal_present = any(
|
|
102
|
+
_is_tacitus_internal_json_object(obj) for obj in _iter_embedded_json_objects(stripped)
|
|
103
|
+
)
|
|
104
|
+
if internal_present:
|
|
105
|
+
return ""
|
|
106
|
+
text = _strip_concatenated_json_objects(text, predicate=_is_tacitus_internal_json_object)
|
|
107
|
+
return (text or "").strip()
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
__all__ = [
|
|
111
|
+
"format_subagent_task_assistant_for_display",
|
|
112
|
+
]
|