soothe-cli 0.6.7__tar.gz → 0.6.8__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.6.7 → soothe_cli-0.6.8}/PKG-INFO +1 -1
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/cli/commands/loop_cmd.py +4 -4
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/cli/execution/daemon.py +1 -1
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/cli/execution/headless.py +8 -9
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/cli/main.py +1 -1
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/runtime/headless/processor.py +1 -1
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/runtime/headless/processor_state.py +1 -1
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/runtime/policy/essential_events.py +4 -4
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/runtime/policy/tui_trace_log.py +1 -1
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/runtime/state/step_router.py +1 -1
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/runtime/transport/session.py +6 -6
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/runtime/turn/prepare.py +12 -12
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/tui/app/_app.py +2 -2
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/tui/app/_module_init.py +1 -1
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/tui/app/_startup.py +4 -1
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/tui/command_registry.py +1 -1
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/tui/commands/command_router.py +2 -2
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/tui/config.py +1 -1
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/tui/sessions.py +2 -2
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/tui/textual_adapter.py +18 -18
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/tui/tips.py +1 -1
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/tui/widgets/welcome.py +1 -1
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/.gitignore +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/README.md +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/pyproject.toml +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/__init__.py +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/cli/__init__.py +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/cli/commands/__init__.py +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/cli/commands/autopilot_cmd.py +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/cli/commands/run_cmd.py +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/cli/commands/status_cmd.py +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/cli/execution/__init__.py +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/cli/execution/daemon_errors.py +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/cli/execution/headless_renderer.py +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/cli/execution/launcher.py +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/config/__init__.py +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/config/cli_config.py +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/config/loader.py +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/config/logging_setup.py +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/runtime/__init__.py +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/runtime/parse/_utils.py +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/runtime/parse/message_processing.py +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/runtime/parse/tool_call_resolution.py +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/runtime/parse/tool_message_format.py +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/runtime/parse/tool_result.py +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/runtime/policy/display_policy.py +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/runtime/presentation/async_renderer_protocol.py +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/runtime/presentation/duration_format.py +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/runtime/presentation/engine.py +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/runtime/presentation/explore_task_display.py +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/runtime/presentation/renderer_base.py +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/runtime/presentation/renderer_protocol.py +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/runtime/state/file_tracker.py +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/runtime/state/session_stats.py +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/runtime/state/stream_accumulator.py +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/runtime/state/transcript.py +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/runtime/task_scope.py +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/runtime/turn/pipeline.py +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/runtime/wire/chunk_filter.py +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/runtime/wire/display_text.py +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/runtime/wire/message_text.py +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/runtime/wire/messages.py +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/tui/__init__.py +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/tui/_cli_context.py +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/tui/_env_vars.py +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/tui/_version.py +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/tui/app/__init__.py +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/tui/app/_commands.py +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/tui/app/_execution.py +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/tui/app/_history.py +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/tui/app/_messages_mixin.py +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/tui/app/_model.py +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/tui/app/_ui.py +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/tui/app/app.tcss +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/tui/binding.py +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/tui/commands/__init__.py +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/tui/commands/slash_commands.py +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/tui/commands/subagent_routing.py +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/tui/file_change_notify.py +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/tui/file_change_renderers.py +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/tui/hooks.py +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/tui/input.py +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/tui/media_utils.py +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/tui/model_config.py +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/tui/path_utils.py +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/tui/preview_limits.py +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/tui/project_utils.py +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/tui/skills/__init__.py +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/tui/skills/invocation.py +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/tui/skills/load.py +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/tui/theme.py +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/tui/tool_display.py +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/tui/unicode_security.py +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/tui/update_check.py +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/tui/widgets/__init__.py +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/tui/widgets/_links.py +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/tui/widgets/autocomplete.py +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/tui/widgets/autopilot_dashboard.py +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/tui/widgets/autopilot_screen.py +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/tui/widgets/chat_input.py +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/tui/widgets/clipboard.py +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/tui/widgets/diff.py +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/tui/widgets/editor.py +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/tui/widgets/file_change_preview.py +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/tui/widgets/history.py +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/tui/widgets/loading.py +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/tui/widgets/loop_selector.py +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/tui/widgets/mcp_viewer.py +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/tui/widgets/message_store.py +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/tui/widgets/messages.py +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/tui/widgets/model_selector.py +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/tui/widgets/notification_settings.py +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/tui/widgets/status.py +0 -0
- {soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/tui/widgets/theme_selector.py +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Loop management CLI commands for
|
|
1
|
+
"""Loop management CLI commands for StrangeLoop instances.
|
|
2
2
|
|
|
3
3
|
RFC-503: Loop-First User Experience
|
|
4
4
|
RFC-504: Loop Management CLI Commands
|
|
@@ -24,7 +24,7 @@ from soothe_cli.runtime import load_config
|
|
|
24
24
|
console = Console()
|
|
25
25
|
|
|
26
26
|
# Create loop command group
|
|
27
|
-
loop_app = typer.Typer(help="Manage
|
|
27
|
+
loop_app = typer.Typer(help="Manage StrangeLoop instances")
|
|
28
28
|
|
|
29
29
|
|
|
30
30
|
def _require_daemon(ws_url: str) -> None:
|
|
@@ -139,7 +139,7 @@ def list_loops(
|
|
|
139
139
|
typer.Option("--limit", "-l", help="Limit number of results."),
|
|
140
140
|
] = 20,
|
|
141
141
|
) -> None:
|
|
142
|
-
"""List all
|
|
142
|
+
"""List all StrangeLoop instances.
|
|
143
143
|
|
|
144
144
|
Examples:
|
|
145
145
|
soothe loop list
|
|
@@ -169,7 +169,7 @@ def list_loops(
|
|
|
169
169
|
return
|
|
170
170
|
|
|
171
171
|
# Render table
|
|
172
|
-
table = Table(title="
|
|
172
|
+
table = Table(title="StrangeLoops")
|
|
173
173
|
table.add_column("Loop ID", style="cyan")
|
|
174
174
|
table.add_column("Status", style="green")
|
|
175
175
|
table.add_column("Contexts", justify="right")
|
|
@@ -39,7 +39,7 @@ _CANCEL_SEND_TIMEOUT_S = 3.0
|
|
|
39
39
|
|
|
40
40
|
|
|
41
41
|
def _is_loop_scoped_event(event: dict[str, Any], *, active_loop_id: str) -> bool:
|
|
42
|
-
"""Return whether a daemon frame belongs to the active
|
|
42
|
+
"""Return whether a daemon frame belongs to the active StrangeLoop session."""
|
|
43
43
|
event_type = event.get("type", "")
|
|
44
44
|
if event_type not in {"status", "event"}:
|
|
45
45
|
return True
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import sys
|
|
5
|
-
import time
|
|
6
5
|
|
|
7
6
|
import typer
|
|
8
7
|
from soothe_sdk.client import (
|
|
@@ -42,7 +41,10 @@ def run_headless(
|
|
|
42
41
|
# Auto-start daemon if not running (RFC-0013) - WebSocket RPC checks (IG-174 Phase 1)
|
|
43
42
|
async def _run_headless_pipeline() -> int:
|
|
44
43
|
"""Ensure daemon is reachable, then run the headless daemon session."""
|
|
45
|
-
|
|
44
|
+
# Check if daemon is live and ready (IG-489: wait for readiness, not just port-live)
|
|
45
|
+
daemon_live = await is_daemon_live(
|
|
46
|
+
ws_url, timeout=5.0, wait_for_ready=True, ready_timeout=30.0
|
|
47
|
+
)
|
|
46
48
|
|
|
47
49
|
if not daemon_live:
|
|
48
50
|
# Attempt cleanup if stale daemon (connection exists but daemon not responsive)
|
|
@@ -64,13 +66,10 @@ def run_headless(
|
|
|
64
66
|
stderr=subprocess.DEVNULL,
|
|
65
67
|
)
|
|
66
68
|
|
|
67
|
-
# Wait for daemon to become fully ready with timeout
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
if daemon_live:
|
|
72
|
-
break
|
|
73
|
-
await asyncio.sleep(0.5)
|
|
69
|
+
# Wait for daemon to become fully ready with timeout (IG-489)
|
|
70
|
+
daemon_live = await is_daemon_live(
|
|
71
|
+
ws_url, timeout=2.0, wait_for_ready=True, ready_timeout=_DAEMON_START_WAIT_TIMEOUT
|
|
72
|
+
)
|
|
74
73
|
# Note: We don't fail here - let the connection attempt handle errors
|
|
75
74
|
# This allows tests and edge cases to proceed with mocked daemons
|
|
76
75
|
|
|
@@ -152,7 +152,7 @@ def main(
|
|
|
152
152
|
soothe -p "Research AI advances" # One-shot headless (non-TUI) query
|
|
153
153
|
soothe -p "Hello" --tui # TUI with an auto-submitted prompt
|
|
154
154
|
soothe --daemon-port 9000 loop list # Subcommands inherit global flags
|
|
155
|
-
soothe loop list # List
|
|
155
|
+
soothe loop list # List StrangeLoop instances
|
|
156
156
|
"""
|
|
157
157
|
# Handle -h/--help flag
|
|
158
158
|
if show_help:
|
|
@@ -38,7 +38,7 @@ class ProcessorState:
|
|
|
38
38
|
# Current plan state (updated on plan events)
|
|
39
39
|
current_plan: Plan | None = None
|
|
40
40
|
|
|
41
|
-
# Active
|
|
41
|
+
# Active StrangeLoop id from daemon status frames (``loop_id``)
|
|
42
42
|
loop_id: str = ""
|
|
43
43
|
|
|
44
44
|
# Internal context tracking (suppress internal LLM responses)
|
|
@@ -6,24 +6,24 @@ from typing import Final
|
|
|
6
6
|
|
|
7
7
|
GOAL_START_EVENT_TYPES: Final[frozenset[str]] = frozenset(
|
|
8
8
|
{
|
|
9
|
-
"soothe.cognition.
|
|
9
|
+
"soothe.cognition.strange_loop.started",
|
|
10
10
|
"soothe.cognition.plan.creating",
|
|
11
11
|
}
|
|
12
12
|
)
|
|
13
13
|
|
|
14
14
|
STEP_START_EVENT_TYPES: Final[frozenset[str]] = frozenset(
|
|
15
15
|
{
|
|
16
|
-
"soothe.cognition.
|
|
16
|
+
"soothe.cognition.strange_loop.step.started",
|
|
17
17
|
}
|
|
18
18
|
)
|
|
19
19
|
|
|
20
20
|
STEP_COMPLETE_EVENT_TYPES: Final[frozenset[str]] = frozenset(
|
|
21
21
|
{
|
|
22
|
-
"soothe.cognition.
|
|
22
|
+
"soothe.cognition.strange_loop.step.completed",
|
|
23
23
|
}
|
|
24
24
|
)
|
|
25
25
|
|
|
26
|
-
LOOP_REASON_EVENT_TYPE: Final[str] = "soothe.cognition.
|
|
26
|
+
LOOP_REASON_EVENT_TYPE: Final[str] = "soothe.cognition.strange_loop.reasoned"
|
|
27
27
|
|
|
28
28
|
ESSENTIAL_PROGRESS_EVENT_TYPES: Final[frozenset[str]] = frozenset(
|
|
29
29
|
set(GOAL_START_EVENT_TYPES)
|
|
@@ -30,7 +30,7 @@ def log_tui_trace(*, tui_debug: bool, event: str, **fields: Any) -> None:
|
|
|
30
30
|
tui_debug: Whether tracing is enabled (from config).
|
|
31
31
|
event: Short event name (e.g. ``renderer.assistant_text``).
|
|
32
32
|
fields: Key/value pairs appended as ``key='value'`` (strings truncated).
|
|
33
|
-
Common fields include ``loop_id`` for the active
|
|
33
|
+
Common fields include ``loop_id`` for the active StrangeLoop id.
|
|
34
34
|
"""
|
|
35
35
|
if not tui_debug:
|
|
36
36
|
return
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Per-turn routing for
|
|
1
|
+
"""Per-turn routing for StrangeLoop steps, root tools, and subagent task namespaces.
|
|
2
2
|
|
|
3
3
|
Owns associations between execute ``step_id``, root ``tool_call_id``, LangGraph
|
|
4
4
|
subgraph ``namespace``, and ``task`` delegations. Designed for parallel execute waves
|
|
@@ -54,7 +54,7 @@ class TuiDaemonSession:
|
|
|
54
54
|
|
|
55
55
|
@property
|
|
56
56
|
def loop_id(self) -> str | None:
|
|
57
|
-
"""Active
|
|
57
|
+
"""Active StrangeLoop id for this WebSocket session."""
|
|
58
58
|
return self._loop_id
|
|
59
59
|
|
|
60
60
|
async def connect(self, *, resume_loop_id: str | None = None) -> dict[str, Any]:
|
|
@@ -100,7 +100,7 @@ class TuiDaemonSession:
|
|
|
100
100
|
return "adaptive"
|
|
101
101
|
|
|
102
102
|
async def new_loop(self) -> dict[str, Any]:
|
|
103
|
-
"""Start a new
|
|
103
|
+
"""Start a new StrangeLoop conversation."""
|
|
104
104
|
return await self._bootstrap_loop(resume_loop_id=None)
|
|
105
105
|
|
|
106
106
|
async def switch_loop(self, loop_id: str) -> dict[str, Any]:
|
|
@@ -398,7 +398,7 @@ class TuiDaemonSession:
|
|
|
398
398
|
binder that produced the original cards.
|
|
399
399
|
|
|
400
400
|
Args:
|
|
401
|
-
loop_id:
|
|
401
|
+
loop_id: StrangeLoop id.
|
|
402
402
|
|
|
403
403
|
Returns:
|
|
404
404
|
``SimpleNamespace`` with ``cards: list[dict]``, ``seq: int``,
|
|
@@ -442,14 +442,14 @@ class TuiDaemonSession:
|
|
|
442
442
|
)
|
|
443
443
|
|
|
444
444
|
async def aget_loop_state(self, loop_id: str) -> Any:
|
|
445
|
-
"""Load
|
|
445
|
+
"""Load StrangeLoop state channels from the daemon (``loop_state_get`` RPC).
|
|
446
446
|
|
|
447
447
|
Returns a namespace with a ``values`` mapping so history code can share the
|
|
448
448
|
same consumption pattern as the in-process agent snapshot, without passing
|
|
449
449
|
graph config objects over the wire.
|
|
450
450
|
|
|
451
451
|
Args:
|
|
452
|
-
loop_id:
|
|
452
|
+
loop_id: StrangeLoop id.
|
|
453
453
|
|
|
454
454
|
Returns:
|
|
455
455
|
``types.SimpleNamespace`` with ``values: dict[str, Any]``.
|
|
@@ -489,7 +489,7 @@ class TuiDaemonSession:
|
|
|
489
489
|
"""Merge partial state into the loop on the daemon host (``loop_state_update`` RPC).
|
|
490
490
|
|
|
491
491
|
Args:
|
|
492
|
-
loop_id:
|
|
492
|
+
loop_id: StrangeLoop id.
|
|
493
493
|
values: Channel updates (e.g. ``messages``) in JSON-serializable form.
|
|
494
494
|
timeout: RPC wait budget in seconds.
|
|
495
495
|
as_node: Optional LangGraph node to attribute the write to. When
|
|
@@ -11,12 +11,12 @@ from typing import Any
|
|
|
11
11
|
|
|
12
12
|
from langchain_core.messages import ToolMessage
|
|
13
13
|
from soothe_sdk.core.events import (
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
14
|
+
STRANGE_LOOP_COMPLETED,
|
|
15
|
+
STRANGE_LOOP_PLAN_DECISION,
|
|
16
|
+
STRANGE_LOOP_STARTED,
|
|
17
|
+
STRANGE_LOOP_STEP_COMPLETED,
|
|
18
|
+
STRANGE_LOOP_STEP_QUEUED,
|
|
19
|
+
STRANGE_LOOP_STEP_STARTED,
|
|
20
20
|
)
|
|
21
21
|
from soothe_sdk.ux.classification import classify_event_to_tier
|
|
22
22
|
from soothe_sdk.ux.loop_stream import assistant_output_phase
|
|
@@ -38,12 +38,12 @@ _MAIN_LOOP_CUSTOM_TYPES = frozenset(
|
|
|
38
38
|
{
|
|
39
39
|
STREAM_TOOL_CALL_UPDATE,
|
|
40
40
|
TOOL_CALL_UPDATES_BATCH,
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
41
|
+
STRANGE_LOOP_STARTED,
|
|
42
|
+
STRANGE_LOOP_COMPLETED,
|
|
43
|
+
STRANGE_LOOP_PLAN_DECISION,
|
|
44
|
+
STRANGE_LOOP_STEP_STARTED,
|
|
45
|
+
STRANGE_LOOP_STEP_QUEUED,
|
|
46
|
+
STRANGE_LOOP_STEP_COMPLETED,
|
|
47
47
|
}
|
|
48
48
|
)
|
|
49
49
|
|
|
@@ -156,7 +156,7 @@ class SootheApp(
|
|
|
156
156
|
daemon_config: Loaded Soothe configuration (WebSocket URL, etc.).
|
|
157
157
|
assistant_id: Agent identifier for memory storage.
|
|
158
158
|
cwd: Current working directory to display.
|
|
159
|
-
resume_loop_id: Initial
|
|
159
|
+
resume_loop_id: Initial StrangeLoop id when attaching to an existing loop.
|
|
160
160
|
initial_prompt: Optional prompt to auto-submit when session starts.
|
|
161
161
|
initial_skill: Optional skill name to invoke when session starts.
|
|
162
162
|
mcp_server_info: MCP server metadata for the `/mcp` viewer.
|
|
@@ -174,7 +174,7 @@ class SootheApp(
|
|
|
174
174
|
|
|
175
175
|
self._cwd = str(cwd) if cwd else str(Path.cwd())
|
|
176
176
|
|
|
177
|
-
# Active
|
|
177
|
+
# Active StrangeLoop id; LangGraph stores it as configurable.thread_id.
|
|
178
178
|
# Named `_lc_loop_id` to avoid colliding with Textual's App._thread_id.
|
|
179
179
|
self._lc_loop_id = resume_loop_id
|
|
180
180
|
|
|
@@ -355,7 +355,7 @@ class AppResult:
|
|
|
355
355
|
"""Exit code (0 for success, non-zero for error)."""
|
|
356
356
|
|
|
357
357
|
loop_id: str | None
|
|
358
|
-
"""The final
|
|
358
|
+
"""The final StrangeLoop id at shutdown (may change if the user switched loops)."""
|
|
359
359
|
|
|
360
360
|
session_stats: SessionStats = field(default_factory=SessionStats)
|
|
361
361
|
"""Cumulative usage stats across all turns in the session."""
|
|
@@ -442,7 +442,10 @@ class _StartupMixin:
|
|
|
442
442
|
ws_url = websocket_url_from_config(self._daemon_config)
|
|
443
443
|
|
|
444
444
|
# Check daemon status via WebSocket RPC (IG-174 Phase 1)
|
|
445
|
-
|
|
445
|
+
# Wait for daemon to be fully ready, not just port-live (IG-489)
|
|
446
|
+
daemon_live = await is_daemon_live(
|
|
447
|
+
ws_url, timeout=5.0, wait_for_ready=True, ready_timeout=30.0
|
|
448
|
+
)
|
|
446
449
|
|
|
447
450
|
if not daemon_live:
|
|
448
451
|
# CLI does NOT control daemon start/stop per architectural separation (IG-174/IG-175)
|
|
@@ -97,7 +97,7 @@ COMMANDS: tuple[SlashCommand, ...] = (
|
|
|
97
97
|
),
|
|
98
98
|
SlashCommand(
|
|
99
99
|
name="/resume",
|
|
100
|
-
description="Browse and resume
|
|
100
|
+
description="Browse and resume StrangeLoop instances",
|
|
101
101
|
bypass_tier=BypassTier.IMMEDIATE_UI,
|
|
102
102
|
hidden_keywords="continue history sessions loops",
|
|
103
103
|
),
|
|
@@ -50,7 +50,7 @@ def validate_command(
|
|
|
50
50
|
entry: Command registry entry
|
|
51
51
|
command: Command name
|
|
52
52
|
query: Query parameter (if present)
|
|
53
|
-
loop_id: Active
|
|
53
|
+
loop_id: Active StrangeLoop id for this session
|
|
54
54
|
|
|
55
55
|
Returns:
|
|
56
56
|
Tuple of (is_valid, error_message)
|
|
@@ -245,7 +245,7 @@ async def handle_routing_command(
|
|
|
245
245
|
|
|
246
246
|
For routing commands that map to a configured subagent id (e.g. ``/research``, ``/explore``),
|
|
247
247
|
sets the WebSocket ``preferred_subagent`` field so the daemon merges a subagent hint into
|
|
248
|
-
|
|
248
|
+
StrangeLoop (IG-349). Other routing commands (e.g. ``/plan``) are sent as plain text unchanged.
|
|
249
249
|
|
|
250
250
|
Args:
|
|
251
251
|
cmd_input: Full command input (e.g., "/research topic summary")
|
|
@@ -578,7 +578,7 @@ def build_stream_config(
|
|
|
578
578
|
* Including the SDK version here ensures it survives the merge.
|
|
579
579
|
|
|
580
580
|
Args:
|
|
581
|
-
loop_id: Active
|
|
581
|
+
loop_id: Active StrangeLoop id (stored under LangGraph ``configurable.thread_id``).
|
|
582
582
|
assistant_id: The agent/assistant identifier, if any.
|
|
583
583
|
sandbox_type: Sandbox provider name for trace metadata, or `None` if no
|
|
584
584
|
sandbox is active.
|
|
@@ -73,7 +73,7 @@ class LoopInfo(TypedDict, total=False):
|
|
|
73
73
|
"""Loop metadata returned by `list_loops_via_daemon_rpc`."""
|
|
74
74
|
|
|
75
75
|
loop_id: str
|
|
76
|
-
"""Unique identifier for the
|
|
76
|
+
"""Unique identifier for the StrangeLoop."""
|
|
77
77
|
|
|
78
78
|
status: str
|
|
79
79
|
"""Loop status (running, paused, completed, etc.)."""
|
|
@@ -230,7 +230,7 @@ async def list_loops_via_daemon_rpc(
|
|
|
230
230
|
limit: int = 20,
|
|
231
231
|
sort_by: str = "updated",
|
|
232
232
|
) -> list[LoopInfo]:
|
|
233
|
-
"""List
|
|
233
|
+
"""List StrangeLoop instances via daemon WebSocket RPC (RFC-504).
|
|
234
234
|
|
|
235
235
|
Queries daemon's loop persistence (per-loop metadata.json files)
|
|
236
236
|
instead of only local SQLite checkpoint walks.
|
|
@@ -31,15 +31,15 @@ if TYPE_CHECKING:
|
|
|
31
31
|
|
|
32
32
|
from langchain_core.messages import AIMessage, HumanMessage
|
|
33
33
|
from soothe_sdk.core.events import (
|
|
34
|
-
AGENT_LOOP_COMPLETED,
|
|
35
|
-
AGENT_LOOP_PLAN_DECISION,
|
|
36
|
-
AGENT_LOOP_STARTED,
|
|
37
|
-
AGENT_LOOP_STEP_COMPLETED,
|
|
38
|
-
AGENT_LOOP_STEP_QUEUED,
|
|
39
|
-
AGENT_LOOP_STEP_STARTED,
|
|
40
34
|
LOOP_CLARIFICATION_ANSWERED,
|
|
41
35
|
LOOP_CLARIFICATION_DEFERRED,
|
|
42
36
|
LOOP_CLARIFICATION_REQUESTED,
|
|
37
|
+
STRANGE_LOOP_COMPLETED,
|
|
38
|
+
STRANGE_LOOP_PLAN_DECISION,
|
|
39
|
+
STRANGE_LOOP_STARTED,
|
|
40
|
+
STRANGE_LOOP_STEP_COMPLETED,
|
|
41
|
+
STRANGE_LOOP_STEP_QUEUED,
|
|
42
|
+
STRANGE_LOOP_STEP_STARTED,
|
|
43
43
|
)
|
|
44
44
|
from soothe_sdk.core.subagent_wire import is_allowlisted_subagent_event_type
|
|
45
45
|
from soothe_sdk.langchain_wire import (
|
|
@@ -158,7 +158,7 @@ class TextualUIAdapter:
|
|
|
158
158
|
"""Stable tool_call_id → step card for subagent activity and pending-tool routing."""
|
|
159
159
|
|
|
160
160
|
self._current_step_messages: dict[str, CognitionStepMessage] = {}
|
|
161
|
-
"""Map of
|
|
161
|
+
"""Map of StrangeLoop act step IDs to step card widgets."""
|
|
162
162
|
|
|
163
163
|
self._step_by_namespace: dict[tuple[Any, ...], CognitionStepMessage] = {}
|
|
164
164
|
"""Active step card per stream namespace (main-agent tool aggregation, IG-402)."""
|
|
@@ -2475,7 +2475,7 @@ async def execute_task_textual(
|
|
|
2475
2475
|
await adapter._set_spinner(None)
|
|
2476
2476
|
continue
|
|
2477
2477
|
|
|
2478
|
-
if event_type ==
|
|
2478
|
+
if event_type == STRANGE_LOOP_STARTED:
|
|
2479
2479
|
if not ns_key:
|
|
2480
2480
|
goal_loop_start_monotonic = time.monotonic()
|
|
2481
2481
|
ui_coalesce.execute_wave_active = True
|
|
@@ -2494,7 +2494,7 @@ async def execute_task_textual(
|
|
|
2494
2494
|
assistant_message_by_namespace.pop(ns_key, None)
|
|
2495
2495
|
continue
|
|
2496
2496
|
|
|
2497
|
-
if event_type ==
|
|
2497
|
+
if event_type == STRANGE_LOOP_COMPLETED:
|
|
2498
2498
|
continue
|
|
2499
2499
|
|
|
2500
2500
|
if event_type in (
|
|
@@ -2553,7 +2553,7 @@ async def execute_task_textual(
|
|
|
2553
2553
|
adapter._clarification_pending = False
|
|
2554
2554
|
continue
|
|
2555
2555
|
|
|
2556
|
-
if event_type ==
|
|
2556
|
+
if event_type == STRANGE_LOOP_PLAN_DECISION and not ns_key:
|
|
2557
2557
|
raw_steps = data.get("steps")
|
|
2558
2558
|
if isinstance(raw_steps, list):
|
|
2559
2559
|
execution_mode = str(data.get("execution_mode", "")).strip()
|
|
@@ -2566,7 +2566,7 @@ async def execute_task_textual(
|
|
|
2566
2566
|
ui_coalesce.execute_wave_active = True
|
|
2567
2567
|
continue
|
|
2568
2568
|
|
|
2569
|
-
if event_type ==
|
|
2569
|
+
if event_type == STRANGE_LOOP_STEP_QUEUED:
|
|
2570
2570
|
step_id = str(data.get("step_id", "")).strip()
|
|
2571
2571
|
description = str(data.get("description", "")).strip()
|
|
2572
2572
|
if step_id:
|
|
@@ -2584,7 +2584,7 @@ async def execute_task_textual(
|
|
|
2584
2584
|
step_widget.set_queued()
|
|
2585
2585
|
continue
|
|
2586
2586
|
|
|
2587
|
-
if event_type ==
|
|
2587
|
+
if event_type == STRANGE_LOOP_STEP_STARTED:
|
|
2588
2588
|
ui_coalesce.execute_wave_active = True
|
|
2589
2589
|
step_id = str(data.get("step_id", "")).strip()
|
|
2590
2590
|
description = str(data.get("description", "")).strip()
|
|
@@ -2637,7 +2637,7 @@ async def execute_task_textual(
|
|
|
2637
2637
|
|
|
2638
2638
|
continue
|
|
2639
2639
|
|
|
2640
|
-
if event_type ==
|
|
2640
|
+
if event_type == STRANGE_LOOP_STEP_COMPLETED:
|
|
2641
2641
|
step_id = str(data.get("step_id", "")).strip()
|
|
2642
2642
|
if step_id:
|
|
2643
2643
|
# Drain buffered tools that still reference this
|
|
@@ -2924,11 +2924,11 @@ __all__ = [
|
|
|
2924
2924
|
"SessionStats",
|
|
2925
2925
|
"SpinnerStatus",
|
|
2926
2926
|
"format_token_count",
|
|
2927
|
-
"
|
|
2928
|
-
"
|
|
2929
|
-
"
|
|
2930
|
-
"
|
|
2931
|
-
"
|
|
2927
|
+
"STRANGE_LOOP_COMPLETED",
|
|
2928
|
+
"STRANGE_LOOP_STARTED",
|
|
2929
|
+
"STRANGE_LOOP_STEP_COMPLETED",
|
|
2930
|
+
"STRANGE_LOOP_STEP_QUEUED",
|
|
2931
|
+
"STRANGE_LOOP_STEP_STARTED",
|
|
2932
2932
|
"TurnToolUiCoalescer",
|
|
2933
2933
|
]
|
|
2934
2934
|
|
|
@@ -6,7 +6,7 @@ import random
|
|
|
6
6
|
|
|
7
7
|
SESSION_TIPS: list[str] = [
|
|
8
8
|
"Use @ to reference files and / for commands",
|
|
9
|
-
"Try /resume to pick up a previous
|
|
9
|
+
"Try /resume to pick up a previous StrangeLoop instance",
|
|
10
10
|
"After Ctrl+C exit, use 'soothe loop continue' to resume",
|
|
11
11
|
"Use /tokens to check context usage",
|
|
12
12
|
"Use /mcp to see your loaded tools and servers",
|
|
@@ -55,7 +55,7 @@ class WelcomeBanner(Static):
|
|
|
55
55
|
"""Initialize the welcome banner.
|
|
56
56
|
|
|
57
57
|
Args:
|
|
58
|
-
loop_id: Optional
|
|
58
|
+
loop_id: Optional StrangeLoop id to display in the banner.
|
|
59
59
|
mcp_tool_count: Number of MCP tools loaded at startup.
|
|
60
60
|
workspace_path: Session workspace path shown in the source row.
|
|
61
61
|
connecting: When `True`, show a connecting footer instead of
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/runtime/presentation/async_renderer_protocol.py
RENAMED
|
File without changes
|
{soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/runtime/presentation/duration_format.py
RENAMED
|
File without changes
|
|
File without changes
|
{soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/runtime/presentation/explore_task_display.py
RENAMED
|
File without changes
|
|
File without changes
|
{soothe_cli-0.6.7 → soothe_cli-0.6.8}/src/soothe_cli/runtime/presentation/renderer_protocol.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|