soothe-cli 0.7.1__tar.gz → 0.7.2__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.1 → soothe_cli-0.7.2}/.gitignore +9 -3
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/PKG-INFO +1 -1
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/cli/main.py +27 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/config/cli_config.py +3 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/runtime/policy/essential_events.py +3 -1
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/runtime/transport/session.py +0 -8
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/runtime/turn/pipeline.py +99 -4
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/runtime/turn/prepare.py +16 -1
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/tui/app/_app.py +3 -1
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/tui/app/_execution.py +0 -1
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/tui/app/_model.py +0 -9
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/tui/app/_startup.py +89 -37
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/tui/app/_ui.py +14 -1
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/tui/binding.py +0 -2
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/tui/commands/slash_commands.py +0 -7
- soothe_cli-0.7.2/src/soothe_cli/tui/markdown_theme.py +358 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/tui/model_config.py +8 -10
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/tui/sessions.py +8 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/tui/textual_adapter.py +29 -8
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/tui/widgets/autopilot_dashboard.py +3 -8
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/tui/widgets/loading.py +30 -7
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/tui/widgets/loop_selector.py +22 -131
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/tui/widgets/messages/__init__.py +0 -2
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/tui/widgets/messages/_helpers.py +0 -42
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/tui/widgets/messages/assistant.py +4 -5
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/tui/widgets/messages/cognition_reason.py +3 -10
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/tui/widgets/messages/skill.py +2 -4
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/README.md +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/pyproject.toml +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/__init__.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/cli/__init__.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/cli/commands/__init__.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/cli/commands/autopilot_cmd.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/cli/commands/cron_cmd.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/cli/commands/loop_cmd.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/cli/commands/run_cmd.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/cli/commands/status_cmd.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/cli/execution/__init__.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/cli/execution/daemon.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/cli/execution/daemon_errors.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/cli/execution/headless.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/cli/execution/headless_renderer.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/cli/execution/launcher.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/config/__init__.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/config/loader.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/config/logging_setup.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/runtime/__init__.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/runtime/headless/processor.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/runtime/headless/processor_state.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/runtime/parse/message_processing.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/runtime/parse/tool_call_resolution.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/runtime/parse/tool_result.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/runtime/policy/display_policy.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/runtime/policy/tui_trace_log.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/runtime/presentation/duration_format.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/runtime/presentation/engine.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/runtime/presentation/explore_task_display.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/runtime/presentation/renderer_base.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/runtime/presentation/renderer_protocol.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/runtime/state/file_tracker.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/runtime/state/session_stats.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/runtime/state/step_router.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/runtime/state/stream_accumulator.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/runtime/state/transcript.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/runtime/wire/chunk_filter.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/runtime/wire/display_text.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/runtime/wire/message_text.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/runtime/wire/messages.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/tui/__init__.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/tui/_cli_context.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/tui/_env_vars.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/tui/_version.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/tui/app/__init__.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/tui/app/_commands.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/tui/app/_history.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/tui/app/_messages_mixin.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/tui/app/_module_init.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/tui/app/app.tcss +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/tui/command_registry.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/tui/commands/__init__.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/tui/commands/command_router.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/tui/commands/subagent_routing.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/tui/config.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/tui/file_change_notify.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/tui/file_change_renderers.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/tui/hooks.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/tui/input.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/tui/media_utils.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/tui/path_utils.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/tui/preview_limits.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/tui/project_utils.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/tui/skills/__init__.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/tui/skills/invocation.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/tui/skills/load.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/tui/theme.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/tui/tips.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/tui/tool_display.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/tui/unicode_security.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/tui/update_check.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/tui/widgets/__init__.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/tui/widgets/_links.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/tui/widgets/autocomplete.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/tui/widgets/autopilot_screen.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/tui/widgets/chat_input.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/tui/widgets/clipboard.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/tui/widgets/context_viewer.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/tui/widgets/diff.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/tui/widgets/editor.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/tui/widgets/file_change_preview.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/tui/widgets/history.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/tui/widgets/mcp_viewer.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/tui/widgets/message_store.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/tui/widgets/messages/app.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/tui/widgets/messages/clarification.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/tui/widgets/messages/cognition_goal_tree.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/tui/widgets/messages/cognition_step.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/tui/widgets/messages/cognition_step_activity.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/tui/widgets/messages/cognition_subagent.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/tui/widgets/messages/diff_message.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/tui/widgets/messages/error.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/tui/widgets/messages/user.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/tui/widgets/model_selector.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/tui/widgets/notification_settings.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/tui/widgets/status.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/tui/widgets/theme_selector.py +0 -0
- {soothe_cli-0.7.1 → soothe_cli-0.7.2}/src/soothe_cli/tui/widgets/welcome.py +0 -0
|
@@ -191,6 +191,15 @@ cython_debug/
|
|
|
191
191
|
# PyPI configuration file
|
|
192
192
|
.pypirc
|
|
193
193
|
|
|
194
|
+
# Soothe local runtime data
|
|
195
|
+
.soothe/
|
|
196
|
+
|
|
197
|
+
# Tool backup files (created by file editing tools)
|
|
198
|
+
.backups/
|
|
199
|
+
|
|
200
|
+
# Large tool result cache
|
|
201
|
+
large_tool_results/
|
|
202
|
+
|
|
194
203
|
# Cursor
|
|
195
204
|
# Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to
|
|
196
205
|
# exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
|
|
@@ -206,7 +215,6 @@ __marimo__/
|
|
|
206
215
|
.python-version
|
|
207
216
|
thirdparty/
|
|
208
217
|
.cursor/
|
|
209
|
-
large_tool_results/
|
|
210
218
|
plot_*
|
|
211
219
|
.claude/
|
|
212
220
|
.DS_Store
|
|
@@ -216,5 +224,3 @@ manifest.json
|
|
|
216
224
|
_bmad
|
|
217
225
|
__MACOSX
|
|
218
226
|
.qoder
|
|
219
|
-
.soothe
|
|
220
|
-
.backups/
|
|
@@ -15,6 +15,12 @@ from soothe_sdk.client.config import SOOTHE_HOME # noqa: E402
|
|
|
15
15
|
|
|
16
16
|
from soothe_cli.config.cli_config import CLIConfig # noqa: E402
|
|
17
17
|
from soothe_cli.config.loader import set_runtime_config # noqa: E402
|
|
18
|
+
from soothe_cli.tui.markdown_theme import ( # noqa: E402
|
|
19
|
+
DEFAULT_MARKDOWN_THEME,
|
|
20
|
+
REGISTRY,
|
|
21
|
+
load_markdown_theme_preference,
|
|
22
|
+
markdown_theme_help,
|
|
23
|
+
)
|
|
18
24
|
|
|
19
25
|
app = typer.Typer(
|
|
20
26
|
name="soothe",
|
|
@@ -101,6 +107,16 @@ def main(
|
|
|
101
107
|
help="Render assistant messages as Markdown in the TUI.",
|
|
102
108
|
),
|
|
103
109
|
] = True,
|
|
110
|
+
markdown_theme: Annotated[
|
|
111
|
+
str | None,
|
|
112
|
+
typer.Option(
|
|
113
|
+
"--markdown-theme",
|
|
114
|
+
help=(
|
|
115
|
+
"Markdown appearance preset for TUI cards "
|
|
116
|
+
f"({markdown_theme_help()}). Default: {DEFAULT_MARKDOWN_THEME}."
|
|
117
|
+
),
|
|
118
|
+
),
|
|
119
|
+
] = None,
|
|
104
120
|
soothe_home: Annotated[
|
|
105
121
|
str | None,
|
|
106
122
|
typer.Option("--soothe-home", help="Soothe home directory (default: ~/.soothe)."),
|
|
@@ -168,11 +184,22 @@ def main(
|
|
|
168
184
|
if mode is not None and mode not in ("manual", "auto"):
|
|
169
185
|
typer.echo(f"Invalid --mode {mode!r}; expected 'manual' or 'auto'.", err=True)
|
|
170
186
|
raise typer.Exit(code=2)
|
|
187
|
+
if markdown_theme is not None and markdown_theme not in REGISTRY:
|
|
188
|
+
typer.echo(
|
|
189
|
+
f"Invalid --markdown-theme {markdown_theme!r}; "
|
|
190
|
+
f"expected one of: {markdown_theme_help()}.",
|
|
191
|
+
err=True,
|
|
192
|
+
)
|
|
193
|
+
raise typer.Exit(code=2)
|
|
194
|
+
resolved_markdown_theme = (
|
|
195
|
+
markdown_theme if markdown_theme is not None else load_markdown_theme_preference()
|
|
196
|
+
)
|
|
171
197
|
cli_cfg = CLIConfig(
|
|
172
198
|
daemon_host=daemon_host,
|
|
173
199
|
daemon_port=daemon_port,
|
|
174
200
|
logging_level=log_level,
|
|
175
201
|
render_markdown=render_markdown,
|
|
202
|
+
markdown_theme=resolved_markdown_theme,
|
|
176
203
|
output_streaming_enabled=streaming,
|
|
177
204
|
output_streaming_mode=streaming_mode,
|
|
178
205
|
clarification_mode=mode,
|
|
@@ -29,6 +29,9 @@ class CLIConfig:
|
|
|
29
29
|
render_markdown: bool = True
|
|
30
30
|
"""Render assistant messages as Markdown in TUI (default True)."""
|
|
31
31
|
|
|
32
|
+
markdown_theme: str = "match-app"
|
|
33
|
+
"""Markdown appearance preset (``match-app``, ``langchain``, ``standard``, …)."""
|
|
34
|
+
|
|
32
35
|
# Output streaming overrides (RFC-614)
|
|
33
36
|
output_streaming_enabled: bool | None = None
|
|
34
37
|
"""Override daemon streaming enabled setting."""
|
|
@@ -24,12 +24,13 @@ STEP_COMPLETE_EVENT_TYPES: Final[frozenset[str]] = frozenset(
|
|
|
24
24
|
)
|
|
25
25
|
|
|
26
26
|
LOOP_REASON_EVENT_TYPE: Final[str] = "soothe.cognition.strange_loop.reasoned"
|
|
27
|
+
INTENT_CLASSIFIED_EVENT_TYPE: Final[str] = "soothe.cognition.intent.classified"
|
|
27
28
|
|
|
28
29
|
ESSENTIAL_PROGRESS_EVENT_TYPES: Final[frozenset[str]] = frozenset(
|
|
29
30
|
set(GOAL_START_EVENT_TYPES)
|
|
30
31
|
| set(STEP_START_EVENT_TYPES)
|
|
31
32
|
| set(STEP_COMPLETE_EVENT_TYPES)
|
|
32
|
-
| {LOOP_REASON_EVENT_TYPE}
|
|
33
|
+
| {LOOP_REASON_EVENT_TYPE, INTENT_CLASSIFIED_EVENT_TYPE}
|
|
33
34
|
)
|
|
34
35
|
|
|
35
36
|
|
|
@@ -56,6 +57,7 @@ def is_step_complete_event_type(event_type: str) -> bool:
|
|
|
56
57
|
__all__ = [
|
|
57
58
|
"ESSENTIAL_PROGRESS_EVENT_TYPES",
|
|
58
59
|
"GOAL_START_EVENT_TYPES",
|
|
60
|
+
"INTENT_CLASSIFIED_EVENT_TYPE",
|
|
59
61
|
"LOOP_REASON_EVENT_TYPE",
|
|
60
62
|
"STEP_COMPLETE_EVENT_TYPES",
|
|
61
63
|
"STEP_START_EVENT_TYPES",
|
|
@@ -81,7 +81,6 @@ class TuiDaemonSession:
|
|
|
81
81
|
self._client = WebSocketClient(url=ws_url)
|
|
82
82
|
self._rpc_client = WebSocketClient(url=ws_url)
|
|
83
83
|
self._loop_id: str | None = None
|
|
84
|
-
self._autopilot_mode: str | None = None
|
|
85
84
|
self._read_lock = asyncio.Lock()
|
|
86
85
|
self._rpc_lock = asyncio.Lock()
|
|
87
86
|
self._rpc_connected = False
|
|
@@ -98,11 +97,6 @@ class TuiDaemonSession:
|
|
|
98
97
|
"""Active StrangeLoop id for this WebSocket session."""
|
|
99
98
|
return self._loop_id
|
|
100
99
|
|
|
101
|
-
@property
|
|
102
|
-
def autopilot_mode(self) -> str | None:
|
|
103
|
-
"""Active loop Solo/Autopilot mode when known."""
|
|
104
|
-
return self._autopilot_mode
|
|
105
|
-
|
|
106
100
|
async def connect(self, *, resume_loop_id: str | None = None) -> dict[str, Any]:
|
|
107
101
|
"""Connect and bootstrap a daemon loop session."""
|
|
108
102
|
await connect_websocket_with_retries(self._client)
|
|
@@ -123,8 +117,6 @@ class TuiDaemonSession:
|
|
|
123
117
|
if status_event.get("type") == "error":
|
|
124
118
|
raise RuntimeError(str(status_event.get("message", "daemon bootstrap failed")))
|
|
125
119
|
self._loop_id = status_event.get("loop_id")
|
|
126
|
-
mode = status_event.get("autopilot_mode")
|
|
127
|
-
self._autopilot_mode = str(mode) if mode in ("solo", "autopilot") else None
|
|
128
120
|
return status_event
|
|
129
121
|
|
|
130
122
|
def _resolve_stream_delivery_mode(self) -> str:
|
|
@@ -35,6 +35,7 @@ T = TypeVar("T")
|
|
|
35
35
|
_SENTINEL = object()
|
|
36
36
|
|
|
37
37
|
# Lower number = higher priority (matches asyncio.PriorityQueue ordering).
|
|
38
|
+
PRIORITY_CRITICAL = -1 # step lifecycle / plan progress — never evict from queue
|
|
38
39
|
PRIORITY_HIGH = 0
|
|
39
40
|
PRIORITY_NORMAL = 1
|
|
40
41
|
PRIORITY_LOW = 2
|
|
@@ -82,7 +83,7 @@ class TurnApplyBatcher(Generic[T]):
|
|
|
82
83
|
"""
|
|
83
84
|
self._pending.append(prepared)
|
|
84
85
|
priority = getattr(prepared, "priority", PRIORITY_LOW)
|
|
85
|
-
if priority
|
|
86
|
+
if priority <= PRIORITY_HIGH:
|
|
86
87
|
self._high_priority_count += 1
|
|
87
88
|
|
|
88
89
|
now = time.monotonic()
|
|
@@ -129,6 +130,7 @@ class TurnEventPipeline(Generic[T]):
|
|
|
129
130
|
maxsize=outbound_maxsize
|
|
130
131
|
)
|
|
131
132
|
self._outbound_seq = 0
|
|
133
|
+
self._outbound_dropped = 0
|
|
132
134
|
self._stop = threading.Event()
|
|
133
135
|
self._thread: threading.Thread | None = None
|
|
134
136
|
self._processor_error: BaseException | None = None
|
|
@@ -182,13 +184,105 @@ class TurnEventPipeline(Generic[T]):
|
|
|
182
184
|
self._thread.start()
|
|
183
185
|
|
|
184
186
|
def _put_outbound(self, priority: int, item: Any) -> None:
|
|
185
|
-
"""Enqueue a prepared chunk from the processor thread (thread-safe
|
|
187
|
+
"""Enqueue a prepared chunk from the processor thread (thread-safe).
|
|
188
|
+
|
|
189
|
+
When the outbound queue is full, evict the lowest-priority buffered chunk
|
|
190
|
+
(streaming text) instead of dropping the incoming chunk. Step lifecycle
|
|
191
|
+
events use ``PRIORITY_CRITICAL`` and are never evicted.
|
|
192
|
+
"""
|
|
186
193
|
seq = self._outbound_seq
|
|
187
194
|
self._outbound_seq += 1
|
|
195
|
+
entry = (priority, seq, item)
|
|
196
|
+
try:
|
|
197
|
+
self._outbound.put_nowait(entry)
|
|
198
|
+
return
|
|
199
|
+
except queue.Full:
|
|
200
|
+
pass
|
|
201
|
+
|
|
202
|
+
if not self._evict_outbound_drop_candidate(incoming_priority=priority):
|
|
203
|
+
if priority <= PRIORITY_HIGH:
|
|
204
|
+
# Block briefly so the applier can drain HIGH/CRITICAL backlog.
|
|
205
|
+
try:
|
|
206
|
+
self._outbound.put(entry, block=True, timeout=5.0)
|
|
207
|
+
return
|
|
208
|
+
except queue.Full:
|
|
209
|
+
logger.warning(
|
|
210
|
+
"Turn outbound queue still full after wait; dropping prepared chunk "
|
|
211
|
+
"(priority=%d)",
|
|
212
|
+
priority,
|
|
213
|
+
)
|
|
214
|
+
self._outbound_dropped += 1
|
|
215
|
+
return
|
|
216
|
+
logger.warning(
|
|
217
|
+
"Turn outbound queue full; dropping low-priority prepared chunk (priority=%d)",
|
|
218
|
+
priority,
|
|
219
|
+
)
|
|
220
|
+
self._outbound_dropped += 1
|
|
221
|
+
return
|
|
222
|
+
|
|
188
223
|
try:
|
|
189
|
-
self._outbound.put_nowait(
|
|
224
|
+
self._outbound.put_nowait(entry)
|
|
190
225
|
except queue.Full:
|
|
191
|
-
|
|
226
|
+
if priority <= PRIORITY_HIGH:
|
|
227
|
+
try:
|
|
228
|
+
self._outbound.put(entry, block=True, timeout=5.0)
|
|
229
|
+
except queue.Full:
|
|
230
|
+
logger.warning(
|
|
231
|
+
"Turn outbound queue full after eviction; dropping prepared chunk "
|
|
232
|
+
"(priority=%d)",
|
|
233
|
+
priority,
|
|
234
|
+
)
|
|
235
|
+
self._outbound_dropped += 1
|
|
236
|
+
else:
|
|
237
|
+
logger.warning(
|
|
238
|
+
"Turn outbound queue full after eviction; dropping prepared chunk "
|
|
239
|
+
"(priority=%d)",
|
|
240
|
+
priority,
|
|
241
|
+
)
|
|
242
|
+
self._outbound_dropped += 1
|
|
243
|
+
|
|
244
|
+
def _evict_outbound_drop_candidate(self, *, incoming_priority: int) -> bool:
|
|
245
|
+
"""Drop one evictable chunk from the outbound queue to make room.
|
|
246
|
+
|
|
247
|
+
Returns:
|
|
248
|
+
True when a chunk was evicted and the caller should retry ``put_nowait``.
|
|
249
|
+
"""
|
|
250
|
+
temp: list[tuple[int, int, Any]] = []
|
|
251
|
+
drop_target: tuple[int, int, Any] | None = None
|
|
252
|
+
drop_priority = PRIORITY_CRITICAL - 1
|
|
253
|
+
|
|
254
|
+
while True:
|
|
255
|
+
try:
|
|
256
|
+
temp.append(self._outbound.get_nowait())
|
|
257
|
+
except queue.Empty:
|
|
258
|
+
break
|
|
259
|
+
|
|
260
|
+
for queued in temp:
|
|
261
|
+
queued_priority = queued[0]
|
|
262
|
+
if queued_priority > drop_priority:
|
|
263
|
+
drop_priority = queued_priority
|
|
264
|
+
drop_target = queued
|
|
265
|
+
|
|
266
|
+
# Never evict CRITICAL/HIGH; only drop NORMAL/LOW when admitting progress events.
|
|
267
|
+
min_evictable = PRIORITY_NORMAL if incoming_priority <= PRIORITY_HIGH else PRIORITY_LOW
|
|
268
|
+
if drop_target is None or drop_priority < min_evictable:
|
|
269
|
+
for queued in temp:
|
|
270
|
+
self._outbound.put_nowait(queued)
|
|
271
|
+
return False
|
|
272
|
+
|
|
273
|
+
self._outbound_dropped += 1
|
|
274
|
+
if self._outbound_dropped == 1 or self._outbound_dropped % 500 == 0:
|
|
275
|
+
logger.warning(
|
|
276
|
+
"Turn outbound queue overflow: evicted buffered chunk (priority=%d, dropped=%d)",
|
|
277
|
+
drop_priority,
|
|
278
|
+
self._outbound_dropped,
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
for queued in temp:
|
|
282
|
+
if queued is drop_target:
|
|
283
|
+
continue
|
|
284
|
+
self._outbound.put_nowait(queued)
|
|
285
|
+
return True
|
|
192
286
|
|
|
193
287
|
async def iter_prepared(self) -> AsyncIterator[T]:
|
|
194
288
|
"""Yield prepared chunk plans until the stream ends."""
|
|
@@ -296,6 +390,7 @@ async def run_turn_pipeline(
|
|
|
296
390
|
|
|
297
391
|
|
|
298
392
|
__all__ = [
|
|
393
|
+
"PRIORITY_CRITICAL",
|
|
299
394
|
"PRIORITY_HIGH",
|
|
300
395
|
"PRIORITY_LOW",
|
|
301
396
|
"PRIORITY_NORMAL",
|
|
@@ -24,7 +24,12 @@ from soothe_sdk.ux.stream_tool_wire import STREAM_TOOL_CALL_UPDATE, TOOL_CALL_UP
|
|
|
24
24
|
|
|
25
25
|
from soothe_cli.runtime.presentation.engine import PresentationEngine
|
|
26
26
|
from soothe_cli.runtime.state.session_stats import TurnEventStats
|
|
27
|
-
from soothe_cli.runtime.turn.pipeline import
|
|
27
|
+
from soothe_cli.runtime.turn.pipeline import (
|
|
28
|
+
PRIORITY_CRITICAL,
|
|
29
|
+
PRIORITY_HIGH,
|
|
30
|
+
PRIORITY_LOW,
|
|
31
|
+
PRIORITY_NORMAL,
|
|
32
|
+
)
|
|
28
33
|
from soothe_cli.runtime.wire.chunk_filter import (
|
|
29
34
|
message_has_tool_invocation_metadata,
|
|
30
35
|
updates_chunk_is_noop,
|
|
@@ -154,6 +159,16 @@ def _prepare_custom_chunk(
|
|
|
154
159
|
prepared.priority = PRIORITY_HIGH
|
|
155
160
|
return prepared
|
|
156
161
|
|
|
162
|
+
if event_type in (
|
|
163
|
+
STRANGE_LOOP_STEP_STARTED,
|
|
164
|
+
STRANGE_LOOP_STEP_QUEUED,
|
|
165
|
+
STRANGE_LOOP_STEP_COMPLETED,
|
|
166
|
+
STRANGE_LOOP_PLAN_DECISION,
|
|
167
|
+
):
|
|
168
|
+
prepared.priority = PRIORITY_CRITICAL
|
|
169
|
+
prepared.skip_custom_progress = True
|
|
170
|
+
return prepared
|
|
171
|
+
|
|
157
172
|
if event_type in _MAIN_LOOP_CUSTOM_TYPES or event_type == TOOL_CALL_UPDATES_BATCH:
|
|
158
173
|
prepared.priority = PRIORITY_HIGH
|
|
159
174
|
prepared.skip_custom_progress = True
|
|
@@ -173,7 +173,6 @@ class SootheApp(
|
|
|
173
173
|
# Active StrangeLoop id; LangGraph stores it as configurable.thread_id.
|
|
174
174
|
# Named `_lc_loop_id` to avoid colliding with Textual's App._thread_id.
|
|
175
175
|
self._lc_loop_id = resume_loop_id
|
|
176
|
-
self._loop_autopilot_mode: str = "solo"
|
|
177
176
|
|
|
178
177
|
self._initial_prompt = initial_prompt
|
|
179
178
|
|
|
@@ -244,6 +243,9 @@ class SootheApp(
|
|
|
244
243
|
|
|
245
244
|
self._loading_widget: LoadingWidget | None = None
|
|
246
245
|
|
|
246
|
+
self._connect_spinner_start_mono: float | None = None
|
|
247
|
+
"""Monotonic anchor for startup daemon-connect elapsed time in the thinking row."""
|
|
248
|
+
|
|
247
249
|
self._context_tokens: int = 0
|
|
248
250
|
"""Local cache of the last total-context token count.
|
|
249
251
|
|
|
@@ -578,7 +578,6 @@ class _ExecutionMixin:
|
|
|
578
578
|
)
|
|
579
579
|
self._session_state.loop_id = new_loop_id
|
|
580
580
|
self._lc_loop_id = new_loop_id
|
|
581
|
-
self._apply_loop_autopilot_mode(status_event.get("autopilot_mode"))
|
|
582
581
|
try:
|
|
583
582
|
banner = self.query_one("#welcome-banner", WelcomeBanner)
|
|
584
583
|
banner.update_loop_id(new_loop_id)
|
|
@@ -259,15 +259,6 @@ class _ModelMixin:
|
|
|
259
259
|
return None
|
|
260
260
|
return resp.get("servers")
|
|
261
261
|
|
|
262
|
-
def _apply_loop_autopilot_mode(self, mode: str | None) -> None:
|
|
263
|
-
"""Sync local Solo/Autopilot mode from daemon bootstrap or toggle events."""
|
|
264
|
-
if mode not in ("solo", "autopilot"):
|
|
265
|
-
return
|
|
266
|
-
self._loop_autopilot_mode = mode
|
|
267
|
-
if self._status_bar is not None:
|
|
268
|
-
label = "Autopilot" if mode == "autopilot" else "Solo"
|
|
269
|
-
self._status_bar.set_session_tip(f"Mode: {label}")
|
|
270
|
-
|
|
271
262
|
async def _show_context_viewer(self) -> None:
|
|
272
263
|
"""Show context engine goal DAG and status as a modal screen."""
|
|
273
264
|
from soothe_cli.tui.widgets.context_viewer import ContextViewerScreen
|
|
@@ -35,6 +35,11 @@ from soothe_cli.tui.widgets.welcome import WelcomeBanner
|
|
|
35
35
|
|
|
36
36
|
logger = logging.getLogger(__name__)
|
|
37
37
|
|
|
38
|
+
# Cold-start: daemon may bind WebSocket ~30s after PID file (CoreAgent pool warmup).
|
|
39
|
+
_DAEMON_READY_TIMEOUT_S = 60.0
|
|
40
|
+
_DAEMON_CONNECT_MAX_ATTEMPTS = 3
|
|
41
|
+
_DAEMON_CONNECT_RETRY_DELAY_S = 2.0
|
|
42
|
+
|
|
38
43
|
|
|
39
44
|
class _StartupMixin:
|
|
40
45
|
"""Startup, server/daemon workers, skills discovery, prewarm and update methods."""
|
|
@@ -405,51 +410,105 @@ class _StartupMixin:
|
|
|
405
410
|
|
|
406
411
|
return await discover_skills_async(daemon_config=self._daemon_config)
|
|
407
412
|
|
|
413
|
+
@staticmethod
|
|
414
|
+
def _daemon_connect_attempt_label(*, phase: str, attempt: int, max_attempts: int) -> str:
|
|
415
|
+
"""Format a daemon-connect spinner label with attempt progress.
|
|
416
|
+
|
|
417
|
+
The first try shows only ``phase``; attempt suffixes appear after a
|
|
418
|
+
timeout or failure triggers a retry.
|
|
419
|
+
"""
|
|
420
|
+
if max_attempts <= 1 or attempt <= 1:
|
|
421
|
+
return phase
|
|
422
|
+
return f"{phase} (attempt {attempt}/{max_attempts})"
|
|
423
|
+
|
|
408
424
|
async def _connect_daemon_background(self) -> None:
|
|
409
425
|
"""Background worker: connect the TUI directly to the daemon."""
|
|
410
426
|
from soothe_cli.tui.textual_adapter import (
|
|
411
|
-
SPINNER_LABEL_CONNECTING_DAEMON,
|
|
412
427
|
SPINNER_LABEL_WAITING_AGENT_READY,
|
|
413
428
|
)
|
|
414
429
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
430
|
+
for attempt in range(1, _DAEMON_CONNECT_MAX_ATTEMPTS + 1):
|
|
431
|
+
waiting_label = self._daemon_connect_attempt_label(
|
|
432
|
+
phase=SPINNER_LABEL_WAITING_AGENT_READY,
|
|
433
|
+
attempt=attempt,
|
|
434
|
+
max_attempts=_DAEMON_CONNECT_MAX_ATTEMPTS,
|
|
420
435
|
)
|
|
436
|
+
await self._set_spinner(waiting_label, show_interrupt_hint=False)
|
|
437
|
+
try:
|
|
438
|
+
session, status_event = await self._connect_daemon_once(attempt=attempt)
|
|
439
|
+
except Exception as exc:
|
|
440
|
+
if attempt < _DAEMON_CONNECT_MAX_ATTEMPTS:
|
|
441
|
+
logger.warning(
|
|
442
|
+
"Daemon connect attempt %d/%d failed: %s",
|
|
443
|
+
attempt,
|
|
444
|
+
_DAEMON_CONNECT_MAX_ATTEMPTS,
|
|
445
|
+
exc,
|
|
446
|
+
)
|
|
447
|
+
await asyncio.sleep(_DAEMON_CONNECT_RETRY_DELAY_S)
|
|
448
|
+
continue
|
|
449
|
+
self.post_message(self.ServerStartFailed(error=exc))
|
|
450
|
+
return
|
|
421
451
|
|
|
422
|
-
|
|
452
|
+
self.post_message(self.DaemonReady(session=session, status_event=status_event))
|
|
453
|
+
return
|
|
423
454
|
|
|
424
|
-
|
|
455
|
+
async def _connect_daemon_once(
|
|
456
|
+
self,
|
|
457
|
+
*,
|
|
458
|
+
attempt: int,
|
|
459
|
+
) -> tuple[Any, dict[str, Any]]:
|
|
460
|
+
"""Wait for daemon readiness and bootstrap one TUI session.
|
|
425
461
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
daemon_live = await is_daemon_live(
|
|
429
|
-
ws_url, timeout=5.0, wait_for_ready=True, ready_timeout=30.0
|
|
430
|
-
)
|
|
462
|
+
Args:
|
|
463
|
+
attempt: Current connect attempt (1-based).
|
|
431
464
|
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
# Show helpful error message instead
|
|
435
|
-
raise ConnectionError(
|
|
436
|
-
f"Soothe daemon not running at {ws_url}. "
|
|
437
|
-
f"Please start the daemon with: soothed start"
|
|
438
|
-
)
|
|
465
|
+
Returns:
|
|
466
|
+
Tuple of ``(TuiDaemonSession, status_event)``.
|
|
439
467
|
|
|
440
|
-
|
|
468
|
+
Raises:
|
|
469
|
+
ConnectionError: If the daemon is not reachable or ready in time.
|
|
470
|
+
Exception: If loop bootstrap fails.
|
|
471
|
+
"""
|
|
472
|
+
from soothe_sdk.client import (
|
|
473
|
+
is_daemon_live,
|
|
474
|
+
websocket_url_from_config,
|
|
475
|
+
)
|
|
476
|
+
|
|
477
|
+
from soothe_cli.runtime.transport.session import TuiDaemonSession
|
|
478
|
+
from soothe_cli.tui.textual_adapter import SPINNER_LABEL_CONNECTING_DAEMON
|
|
479
|
+
|
|
480
|
+
ws_url = websocket_url_from_config(self._daemon_config)
|
|
441
481
|
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
482
|
+
# Check daemon status via WebSocket RPC (IG-174 Phase 1)
|
|
483
|
+
# Wait for daemon to be fully ready, not just port-live (IG-489)
|
|
484
|
+
daemon_live = await is_daemon_live(
|
|
485
|
+
ws_url,
|
|
486
|
+
timeout=5.0,
|
|
487
|
+
wait_for_ready=True,
|
|
488
|
+
ready_timeout=_DAEMON_READY_TIMEOUT_S,
|
|
489
|
+
)
|
|
490
|
+
|
|
491
|
+
if not daemon_live:
|
|
492
|
+
# CLI does NOT control daemon start/stop per architectural separation (IG-174/IG-175)
|
|
493
|
+
raise ConnectionError(
|
|
494
|
+
f"Soothe daemon not running at {ws_url}. "
|
|
495
|
+
f"Please start the daemon with: soothed start"
|
|
446
496
|
)
|
|
447
|
-
status_event = await session.connect(resume_loop_id=self._lc_loop_id)
|
|
448
|
-
except Exception as exc:
|
|
449
|
-
self.post_message(self.ServerStartFailed(error=exc))
|
|
450
|
-
return
|
|
451
497
|
|
|
452
|
-
self.
|
|
498
|
+
connecting_label = self._daemon_connect_attempt_label(
|
|
499
|
+
phase=SPINNER_LABEL_CONNECTING_DAEMON,
|
|
500
|
+
attempt=attempt,
|
|
501
|
+
max_attempts=_DAEMON_CONNECT_MAX_ATTEMPTS,
|
|
502
|
+
)
|
|
503
|
+
await self._set_spinner(connecting_label, show_interrupt_hint=False)
|
|
504
|
+
|
|
505
|
+
session = TuiDaemonSession(
|
|
506
|
+
self._daemon_config,
|
|
507
|
+
workspace=self._cwd,
|
|
508
|
+
post_idle_drain_deadline=0.3,
|
|
509
|
+
)
|
|
510
|
+
status_event = await session.connect(resume_loop_id=self._lc_loop_id)
|
|
511
|
+
return session, status_event
|
|
453
512
|
|
|
454
513
|
def on_soothe_app_daemon_ready(self, event: DaemonReady) -> None:
|
|
455
514
|
"""Handle successful daemon bootstrap for the TUI."""
|
|
@@ -463,13 +522,6 @@ class _StartupMixin:
|
|
|
463
522
|
if self._session_state is not None:
|
|
464
523
|
self._session_state.loop_id = status_loop_id
|
|
465
524
|
|
|
466
|
-
autopilot_mode = event.status_event.get("autopilot_mode")
|
|
467
|
-
if autopilot_mode not in ("solo", "autopilot") and event.session is not None:
|
|
468
|
-
autopilot_mode = getattr(event.session, "autopilot_mode", None)
|
|
469
|
-
self._apply_loop_autopilot_mode(
|
|
470
|
-
str(autopilot_mode) if autopilot_mode in ("solo", "autopilot") else None
|
|
471
|
-
)
|
|
472
|
-
|
|
473
525
|
try:
|
|
474
526
|
banner = self.query_one("#welcome-banner", WelcomeBanner)
|
|
475
527
|
banner.set_connected(self._mcp_tool_count)
|
|
@@ -313,13 +313,20 @@ class _UIMixin:
|
|
|
313
313
|
if self._loading_widget:
|
|
314
314
|
await self._loading_widget.remove()
|
|
315
315
|
self._loading_widget = None
|
|
316
|
+
self._connect_spinner_start_mono = None
|
|
316
317
|
return
|
|
317
318
|
|
|
318
319
|
thinking_status = self.query_one("#thinking-status", Container)
|
|
319
320
|
|
|
321
|
+
connect_start_mono: float | None = None
|
|
322
|
+
if not self._agent_running:
|
|
323
|
+
if self._connect_spinner_start_mono is None:
|
|
324
|
+
self._connect_spinner_start_mono = _monotonic()
|
|
325
|
+
connect_start_mono = self._connect_spinner_start_mono
|
|
326
|
+
|
|
320
327
|
if self._loading_widget is None:
|
|
321
328
|
# Create new
|
|
322
|
-
turn_mono = self._inflight_turn_start if self._agent_running else
|
|
329
|
+
turn_mono = self._inflight_turn_start if self._agent_running else connect_start_mono
|
|
323
330
|
self._loading_widget = LoadingWidget(
|
|
324
331
|
status,
|
|
325
332
|
turn_start_mono=turn_mono,
|
|
@@ -329,6 +336,12 @@ class _UIMixin:
|
|
|
329
336
|
else:
|
|
330
337
|
if self._agent_running:
|
|
331
338
|
self._loading_widget.set_turn_start_mono(self._inflight_turn_start)
|
|
339
|
+
elif connect_start_mono is not None:
|
|
340
|
+
self._loading_widget.set_turn_start_mono(connect_start_mono)
|
|
341
|
+
if self._loading_widget.parent is not thinking_status:
|
|
342
|
+
if self._loading_widget.is_attached:
|
|
343
|
+
await self._loading_widget.remove()
|
|
344
|
+
await thinking_status.mount(self._loading_widget)
|
|
332
345
|
# Update existing (also clears a clarification pause)
|
|
333
346
|
self._loading_widget.activate_status(status, show_interrupt_hint=show_interrupt_hint)
|
|
334
347
|
# NOTE: Don't call anchor() here - it would re-anchor and drag user back
|
|
@@ -111,7 +111,6 @@ def message_to_widget(data: MessageData) -> Widget:
|
|
|
111
111
|
|
|
112
112
|
case MessageType.COGNITION_REASON:
|
|
113
113
|
return CognitionReasonMessage(
|
|
114
|
-
next_action=data.cognition_plan_next_action or "",
|
|
115
114
|
status=data.cognition_plan_status or "",
|
|
116
115
|
iteration=int(data.cognition_plan_iteration or 0),
|
|
117
116
|
plan_action=data.cognition_plan_action or "",
|
|
@@ -198,7 +197,6 @@ def message_from_widget(widget: Widget) -> MessageData:
|
|
|
198
197
|
type=MessageType.COGNITION_REASON,
|
|
199
198
|
content="",
|
|
200
199
|
id=widget_id,
|
|
201
|
-
cognition_plan_next_action=widget._next_action,
|
|
202
200
|
cognition_plan_status=widget._status,
|
|
203
201
|
cognition_plan_iteration=widget._iteration,
|
|
204
202
|
cognition_plan_action=widget._plan_action,
|
|
@@ -240,13 +240,6 @@ COMMANDS: dict[str, dict[str, Any]] = {
|
|
|
240
240
|
"description": "Submit autopilot job (usage: /autopilot <task>)",
|
|
241
241
|
"requires_query": True,
|
|
242
242
|
},
|
|
243
|
-
"/autopilot-toggle": {
|
|
244
|
-
"location": "daemon",
|
|
245
|
-
"type": "rpc",
|
|
246
|
-
"daemon_command": "autopilot_toggle",
|
|
247
|
-
"description": "Toggle autopilot mode (solo ↔ autopilot)",
|
|
248
|
-
"requires_loop": True,
|
|
249
|
-
},
|
|
250
243
|
# Daemon routing commands (3)
|
|
251
244
|
"/plan": {"location": "daemon", "type": "routing", "description": "Trigger plan mode"},
|
|
252
245
|
"/tacitus": {
|