soothe-cli 0.6.15__tar.gz → 0.6.17__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.15 → soothe_cli-0.6.17}/PKG-INFO +1 -1
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/cli/commands/autopilot_cmd.py +0 -93
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/config/cli_config.py +0 -36
- soothe_cli-0.6.17/src/soothe_cli/runtime/__init__.py +17 -0
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/runtime/parse/message_processing.py +0 -6
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/runtime/parse/tool_call_resolution.py +80 -11
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/runtime/parse/tool_result.py +0 -2
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/runtime/state/step_router.py +68 -8
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/tui/app/_app.py +0 -4
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/tui/app/_execution.py +0 -2
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/tui/app/_messages_mixin.py +26 -14
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/tui/app/_model.py +0 -42
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/tui/command_registry.py +0 -16
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/tui/config.py +0 -141
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/tui/media_utils.py +0 -20
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/tui/model_config.py +0 -10
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/tui/preview_limits.py +1 -1
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/tui/sessions.py +1 -182
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/tui/textual_adapter.py +527 -125
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/tui/theme.py +0 -12
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/tui/unicode_security.py +0 -49
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/tui/widgets/autocomplete.py +0 -4
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/tui/widgets/autopilot_dashboard.py +0 -268
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/tui/widgets/chat_input.py +1 -1
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/tui/widgets/diff.py +3 -101
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/tui/widgets/file_change_preview.py +122 -34
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/tui/widgets/loop_selector.py +0 -21
- soothe_cli-0.6.17/src/soothe_cli/tui/widgets/messages/__init__.py +83 -0
- soothe_cli-0.6.17/src/soothe_cli/tui/widgets/messages/_helpers.py +239 -0
- soothe_cli-0.6.17/src/soothe_cli/tui/widgets/messages/app.py +100 -0
- soothe_cli-0.6.17/src/soothe_cli/tui/widgets/messages/assistant.py +271 -0
- soothe_cli-0.6.17/src/soothe_cli/tui/widgets/messages/clarification.py +215 -0
- soothe_cli-0.6.17/src/soothe_cli/tui/widgets/messages/cognition_goal_tree.py +395 -0
- soothe_cli-0.6.17/src/soothe_cli/tui/widgets/messages/cognition_reason.py +111 -0
- soothe_cli-0.6.17/src/soothe_cli/tui/widgets/messages/cognition_step.py +1430 -0
- soothe_cli-0.6.17/src/soothe_cli/tui/widgets/messages/cognition_step_activity.py +552 -0
- soothe_cli-0.6.17/src/soothe_cli/tui/widgets/messages/cognition_subagent.py +122 -0
- soothe_cli-0.6.17/src/soothe_cli/tui/widgets/messages/diff_message.py +110 -0
- soothe_cli-0.6.17/src/soothe_cli/tui/widgets/messages/error.py +59 -0
- soothe_cli-0.6.17/src/soothe_cli/tui/widgets/messages/skill.py +312 -0
- soothe_cli-0.6.17/src/soothe_cli/tui/widgets/messages/user.py +170 -0
- soothe_cli-0.6.15/src/soothe_cli/runtime/__init__.py +0 -82
- soothe_cli-0.6.15/src/soothe_cli/runtime/parse/_utils.py +0 -14
- soothe_cli-0.6.15/src/soothe_cli/runtime/parse/tool_message_format.py +0 -17
- soothe_cli-0.6.15/src/soothe_cli/tui/widgets/messages.py +0 -4001
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/.gitignore +0 -0
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/README.md +0 -0
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/pyproject.toml +0 -0
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/__init__.py +0 -0
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/cli/__init__.py +0 -0
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/cli/commands/__init__.py +0 -0
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/cli/commands/cron_cmd.py +0 -0
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/cli/commands/loop_cmd.py +0 -0
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/cli/commands/run_cmd.py +0 -0
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/cli/commands/status_cmd.py +0 -0
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/cli/execution/__init__.py +0 -0
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/cli/execution/daemon.py +0 -0
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/cli/execution/daemon_errors.py +0 -0
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/cli/execution/headless.py +0 -0
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/cli/execution/headless_renderer.py +0 -0
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/cli/execution/launcher.py +0 -0
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/cli/main.py +0 -0
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/config/__init__.py +0 -0
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/config/loader.py +0 -0
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/config/logging_setup.py +0 -0
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/runtime/headless/processor.py +0 -0
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/runtime/headless/processor_state.py +0 -0
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/runtime/policy/display_policy.py +0 -0
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/runtime/policy/essential_events.py +0 -0
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/runtime/policy/tui_trace_log.py +0 -0
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/runtime/presentation/duration_format.py +0 -0
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/runtime/presentation/engine.py +0 -0
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/runtime/presentation/explore_task_display.py +0 -0
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/runtime/presentation/renderer_base.py +0 -0
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/runtime/presentation/renderer_protocol.py +0 -0
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/runtime/state/file_tracker.py +0 -0
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/runtime/state/session_stats.py +0 -0
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/runtime/state/stream_accumulator.py +0 -0
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/runtime/state/transcript.py +0 -0
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/runtime/transport/session.py +0 -0
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/runtime/turn/pipeline.py +0 -0
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/runtime/turn/prepare.py +0 -0
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/runtime/wire/chunk_filter.py +0 -0
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/runtime/wire/display_text.py +0 -0
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/runtime/wire/message_text.py +0 -0
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/runtime/wire/messages.py +0 -0
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/tui/__init__.py +0 -0
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/tui/_cli_context.py +0 -0
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/tui/_env_vars.py +0 -0
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/tui/_version.py +0 -0
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/tui/app/__init__.py +0 -0
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/tui/app/_commands.py +0 -0
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/tui/app/_history.py +0 -0
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/tui/app/_module_init.py +0 -0
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/tui/app/_startup.py +0 -0
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/tui/app/_ui.py +0 -0
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/tui/app/app.tcss +0 -0
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/tui/binding.py +0 -0
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/tui/commands/__init__.py +0 -0
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/tui/commands/command_router.py +0 -0
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/tui/commands/slash_commands.py +0 -0
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/tui/commands/subagent_routing.py +0 -0
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/tui/file_change_notify.py +0 -0
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/tui/file_change_renderers.py +0 -0
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/tui/hooks.py +0 -0
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/tui/input.py +0 -0
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/tui/path_utils.py +0 -0
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/tui/project_utils.py +0 -0
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/tui/skills/__init__.py +0 -0
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/tui/skills/invocation.py +0 -0
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/tui/skills/load.py +0 -0
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/tui/tips.py +0 -0
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/tui/tool_display.py +0 -0
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/tui/update_check.py +0 -0
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/tui/widgets/__init__.py +0 -0
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/tui/widgets/_links.py +0 -0
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/tui/widgets/autopilot_screen.py +0 -0
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/tui/widgets/clipboard.py +0 -0
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/tui/widgets/editor.py +0 -0
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/tui/widgets/history.py +0 -0
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/tui/widgets/loading.py +0 -0
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/tui/widgets/mcp_viewer.py +0 -0
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/tui/widgets/message_store.py +0 -0
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/tui/widgets/model_selector.py +0 -0
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/tui/widgets/notification_settings.py +0 -0
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/tui/widgets/status.py +0 -0
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/tui/widgets/theme_selector.py +0 -0
- {soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/tui/widgets/welcome.py +0 -0
|
@@ -397,96 +397,3 @@ def dream() -> None:
|
|
|
397
397
|
client = _require_daemon_ws()
|
|
398
398
|
client.autopilot_dream()
|
|
399
399
|
typer.echo("Dream signal sent.")
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
def _discover_goals(autopilot_dir: Path) -> list[dict]:
|
|
403
|
-
"""Parse goals from GOAL.md/GOALS.md files for offline CLI display."""
|
|
404
|
-
import re
|
|
405
|
-
|
|
406
|
-
goals: list[dict] = []
|
|
407
|
-
|
|
408
|
-
goal_file = autopilot_dir / "GOAL.md"
|
|
409
|
-
if goal_file.exists():
|
|
410
|
-
g = _parse_single_goal(goal_file.read_text(), str(goal_file))
|
|
411
|
-
if g:
|
|
412
|
-
return [g]
|
|
413
|
-
|
|
414
|
-
goals_file = autopilot_dir / "GOALS.md"
|
|
415
|
-
if goals_file.exists():
|
|
416
|
-
text = goals_file.read_text()
|
|
417
|
-
for section in re.split(r"## Goal:", text)[1:]:
|
|
418
|
-
g = _parse_goals_section(section.strip(), str(goals_file))
|
|
419
|
-
if g:
|
|
420
|
-
goals.append(g)
|
|
421
|
-
|
|
422
|
-
goals_dir = autopilot_dir / "goals"
|
|
423
|
-
if goals_dir.exists():
|
|
424
|
-
for subdir in sorted(goals_dir.iterdir()):
|
|
425
|
-
gfile = subdir / "GOAL.md"
|
|
426
|
-
if gfile.exists():
|
|
427
|
-
g = _parse_single_goal(gfile.read_text(), str(gfile))
|
|
428
|
-
if g:
|
|
429
|
-
goals.append(g)
|
|
430
|
-
|
|
431
|
-
return goals
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
def _parse_single_goal(text: str, source: str) -> dict | None:
|
|
435
|
-
"""Parse a single GOAL.md file."""
|
|
436
|
-
if not text.startswith("---"):
|
|
437
|
-
return None
|
|
438
|
-
|
|
439
|
-
parts = text.split("---", 2)
|
|
440
|
-
if len(parts) < 3: # noqa: PLR2004
|
|
441
|
-
return None
|
|
442
|
-
|
|
443
|
-
import yaml
|
|
444
|
-
|
|
445
|
-
fm = yaml.safe_load(parts[1]) or {}
|
|
446
|
-
body = parts[2].strip()
|
|
447
|
-
|
|
448
|
-
desc = ""
|
|
449
|
-
for line in body.splitlines():
|
|
450
|
-
s = line.strip()
|
|
451
|
-
if s.startswith("# "):
|
|
452
|
-
desc = s[2:]
|
|
453
|
-
break
|
|
454
|
-
|
|
455
|
-
return {
|
|
456
|
-
"id": fm.get("id", source.split("/")[-2]),
|
|
457
|
-
"description": desc or preview_first(body, 100),
|
|
458
|
-
"priority": int(fm.get("priority", 50)),
|
|
459
|
-
"status": fm.get("status", "pending"),
|
|
460
|
-
"depends_on": fm.get("depends_on", []),
|
|
461
|
-
"source_file": source,
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
def _parse_goals_section(text: str, source: str) -> dict | None:
|
|
466
|
-
"""Parse a single goal section from GOALS.md."""
|
|
467
|
-
lines = text.splitlines()
|
|
468
|
-
name = lines[0].strip() if lines else ""
|
|
469
|
-
metadata: dict = {}
|
|
470
|
-
|
|
471
|
-
for line in lines[1:]:
|
|
472
|
-
s = line.strip()
|
|
473
|
-
if s.startswith("- id:"):
|
|
474
|
-
metadata["id"] = s.split(":", 1)[1].strip()
|
|
475
|
-
elif s.startswith("- priority:"):
|
|
476
|
-
metadata["priority"] = int(s.split(":", 1)[1].strip())
|
|
477
|
-
elif s.startswith("- depends_on:"):
|
|
478
|
-
raw = s.split(":", 1)[1].strip()
|
|
479
|
-
if raw.startswith("[") and raw.endswith("]"):
|
|
480
|
-
inner = raw[1:-1].strip()
|
|
481
|
-
metadata["depends_on"] = (
|
|
482
|
-
[x.strip() for x in inner.split(",") if x.strip()] if inner else []
|
|
483
|
-
)
|
|
484
|
-
|
|
485
|
-
return {
|
|
486
|
-
"id": metadata.get("id", name.lower().replace(" ", "-")),
|
|
487
|
-
"description": name,
|
|
488
|
-
"priority": metadata.get("priority", 50),
|
|
489
|
-
"status": "pending",
|
|
490
|
-
"depends_on": metadata.get("depends_on", []),
|
|
491
|
-
"source_file": source,
|
|
492
|
-
}
|
|
@@ -4,7 +4,6 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
from dataclasses import dataclass, field
|
|
6
6
|
from pathlib import Path
|
|
7
|
-
from typing import Any
|
|
8
7
|
|
|
9
8
|
from soothe_sdk.client.config import SOOTHE_HOME
|
|
10
9
|
|
|
@@ -44,41 +43,6 @@ class CLIConfig:
|
|
|
44
43
|
# Paths
|
|
45
44
|
soothe_home: Path = field(default_factory=lambda: Path(SOOTHE_HOME))
|
|
46
45
|
|
|
47
|
-
# Daemon config cache (fetched via RPC)
|
|
48
|
-
_daemon_config_cache: dict[str, Any] = field(default_factory=dict)
|
|
49
|
-
|
|
50
46
|
def websocket_url(self) -> str:
|
|
51
47
|
"""Construct WebSocket URL for daemon connection."""
|
|
52
48
|
return f"ws://{self.daemon_host}:{self.daemon_port}"
|
|
53
|
-
|
|
54
|
-
async def fetch_daemon_config(self, section: str = "all") -> dict[str, Any]:
|
|
55
|
-
"""Fetch daemon config section via WebSocket RPC.
|
|
56
|
-
|
|
57
|
-
Args:
|
|
58
|
-
section: Config section name (e.g., "providers", "defaults", "all").
|
|
59
|
-
|
|
60
|
-
Returns:
|
|
61
|
-
Wire-safe config section dict.
|
|
62
|
-
"""
|
|
63
|
-
from soothe_sdk.client import WebSocketClient, fetch_config_section
|
|
64
|
-
|
|
65
|
-
client = WebSocketClient(url=self.websocket_url())
|
|
66
|
-
await client.connect()
|
|
67
|
-
|
|
68
|
-
try:
|
|
69
|
-
config_section = await fetch_config_section(client, section, timeout=5.0)
|
|
70
|
-
self._daemon_config_cache[section] = config_section
|
|
71
|
-
return config_section
|
|
72
|
-
finally:
|
|
73
|
-
await client.close()
|
|
74
|
-
|
|
75
|
-
def get_cached_config(self, section: str) -> dict[str, Any]:
|
|
76
|
-
"""Get cached daemon config section.
|
|
77
|
-
|
|
78
|
-
Args:
|
|
79
|
-
section: Config section name.
|
|
80
|
-
|
|
81
|
-
Returns:
|
|
82
|
-
Cached config section dict, or empty dict if not cached.
|
|
83
|
-
"""
|
|
84
|
-
return self._daemon_config_cache.get(section, {})
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Daemon event processing and display state for the CLI (source of truth for TUI).
|
|
2
|
+
|
|
3
|
+
Bridges daemon events/messages to in-memory state. ``soothe_cli.tui`` owns widgets
|
|
4
|
+
and layout only.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from soothe_cli.config.loader import load_config
|
|
8
|
+
from soothe_cli.config.logging_setup import setup_logging
|
|
9
|
+
from soothe_cli.runtime.headless.processor import EventProcessor
|
|
10
|
+
from soothe_cli.runtime.headless.processor_state import ProcessorState
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"EventProcessor",
|
|
14
|
+
"ProcessorState",
|
|
15
|
+
"load_config",
|
|
16
|
+
"setup_logging",
|
|
17
|
+
]
|
|
@@ -16,17 +16,14 @@ from soothe_sdk.display.message_processing import (
|
|
|
16
16
|
_resolve_pending_lookup_tool_name,
|
|
17
17
|
accumulate_tool_call_chunks,
|
|
18
18
|
coerce_tool_call_args_to_dict,
|
|
19
|
-
coerce_tool_call_entry_to_dict,
|
|
20
19
|
extract_tool_args_dict,
|
|
21
20
|
extract_tool_brief,
|
|
22
21
|
finalize_pending_tool_call,
|
|
23
22
|
ingest_tool_call_stream_state,
|
|
24
23
|
normalize_tool_calls_list,
|
|
25
24
|
richest_pending_args_for_lookup,
|
|
26
|
-
seed_pending_tool_calls_from_message,
|
|
27
25
|
tool_calls_have_any_arg_dict,
|
|
28
26
|
tool_ids_touched_by_stream_message,
|
|
29
|
-
tool_lookup_step_id,
|
|
30
27
|
try_parse_pending_tool_call_args,
|
|
31
28
|
)
|
|
32
29
|
|
|
@@ -36,16 +33,13 @@ __all__ = [
|
|
|
36
33
|
"_resolve_pending_lookup_tool_name",
|
|
37
34
|
"accumulate_tool_call_chunks",
|
|
38
35
|
"coerce_tool_call_args_to_dict",
|
|
39
|
-
"coerce_tool_call_entry_to_dict",
|
|
40
36
|
"extract_tool_args_dict",
|
|
41
37
|
"extract_tool_brief",
|
|
42
38
|
"finalize_pending_tool_call",
|
|
43
39
|
"ingest_tool_call_stream_state",
|
|
44
40
|
"normalize_tool_calls_list",
|
|
45
41
|
"richest_pending_args_for_lookup",
|
|
46
|
-
"seed_pending_tool_calls_from_message",
|
|
47
42
|
"tool_calls_have_any_arg_dict",
|
|
48
43
|
"tool_ids_touched_by_stream_message",
|
|
49
|
-
"tool_lookup_step_id",
|
|
50
44
|
"try_parse_pending_tool_call_args",
|
|
51
45
|
]
|
{soothe_cli-0.6.15 → soothe_cli-0.6.17}/src/soothe_cli/runtime/parse/tool_call_resolution.py
RENAMED
|
@@ -17,7 +17,15 @@ from collections.abc import Mapping
|
|
|
17
17
|
from dataclasses import dataclass
|
|
18
18
|
from typing import Any
|
|
19
19
|
|
|
20
|
-
from soothe_sdk.ux.
|
|
20
|
+
from soothe_sdk.ux.execute_namespace import (
|
|
21
|
+
is_root_execute_namespace_key,
|
|
22
|
+
is_step_level_execute_namespace_key,
|
|
23
|
+
)
|
|
24
|
+
from soothe_sdk.ux.task_namespace import (
|
|
25
|
+
TaskScope,
|
|
26
|
+
parse_unified_tool_call_id,
|
|
27
|
+
row_key_for_subgraph_tool,
|
|
28
|
+
)
|
|
21
29
|
|
|
22
30
|
from soothe_cli.runtime.parse.message_processing import (
|
|
23
31
|
extract_tool_args_dict,
|
|
@@ -40,15 +48,32 @@ def tool_args_meaningful(raw: Any) -> bool:
|
|
|
40
48
|
|
|
41
49
|
|
|
42
50
|
def is_execute_step_namespace(ns_key: tuple[str, ...]) -> bool:
|
|
43
|
-
"""True for CoreAgent execute root namespace (``execute:{run_id}``), not nested ``tools:`` subgraphs."""
|
|
44
|
-
|
|
45
|
-
return False
|
|
46
|
-
return str(ns_key[0] or "").startswith("execute:")
|
|
51
|
+
"""True for CoreAgent execute root namespace (``execute:{run_id}``), not nested ``/N`` or ``tools:`` subgraphs."""
|
|
52
|
+
return is_root_execute_namespace_key(ns_key)
|
|
47
53
|
|
|
48
54
|
|
|
49
55
|
def is_step_card_tool_scope(*, ns_key: tuple[str, ...]) -> bool:
|
|
50
56
|
"""True when tool activity belongs on the step card as main execute-graph tools."""
|
|
51
|
-
return ns_key == () or
|
|
57
|
+
return ns_key == () or is_step_level_execute_namespace_key(ns_key)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def resolve_tool_result_row_key(
|
|
61
|
+
*,
|
|
62
|
+
ns_key: tuple[str, ...],
|
|
63
|
+
tool_call_id: str,
|
|
64
|
+
task_scope: TaskScope | None = None,
|
|
65
|
+
) -> str:
|
|
66
|
+
"""Row key for completing a tool row from a streamed ``ToolMessage``.
|
|
67
|
+
|
|
68
|
+
Execute-graph namespaces (``()`` or ``execute:…``) keep the unified ``s:`` id.
|
|
69
|
+
Subagent ``tools:…`` namespaces remap via :func:`row_key_for_subgraph_tool`.
|
|
70
|
+
"""
|
|
71
|
+
sid = str(tool_call_id or "").strip()
|
|
72
|
+
if not sid:
|
|
73
|
+
return ""
|
|
74
|
+
if is_step_card_tool_scope(ns_key=ns_key):
|
|
75
|
+
return sid
|
|
76
|
+
return row_key_for_subgraph_tool(ns_key, sid, task_scope=task_scope)
|
|
52
77
|
|
|
53
78
|
|
|
54
79
|
def is_main_step_level_tool_call_id(tool_call_id: str) -> bool:
|
|
@@ -64,6 +89,45 @@ def is_main_step_level_tool_call_id(tool_call_id: str) -> bool:
|
|
|
64
89
|
return not is_step_level_task_tool_id(tcid)
|
|
65
90
|
|
|
66
91
|
|
|
92
|
+
def predict_main_execute_tool_call_id(
|
|
93
|
+
step_id: str,
|
|
94
|
+
*,
|
|
95
|
+
tool_call_id: str = "",
|
|
96
|
+
tool_name: str = "",
|
|
97
|
+
chunk_index: Any = None,
|
|
98
|
+
) -> str:
|
|
99
|
+
"""Best-effort unified id for main-graph execute tools during streaming."""
|
|
100
|
+
from soothe_sdk.ux.task_namespace import (
|
|
101
|
+
_format_unified_tool_call_id,
|
|
102
|
+
_provider_tool_fragment,
|
|
103
|
+
is_unified_tool_call_id,
|
|
104
|
+
parse_unified_tool_call_id,
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
tcid = str(tool_call_id or "").strip()
|
|
108
|
+
if tcid and is_unified_tool_call_id(tcid):
|
|
109
|
+
parsed_sid, type_code, _, _ = parse_unified_tool_call_id(tcid)
|
|
110
|
+
if parsed_sid and type_code == "s":
|
|
111
|
+
return tcid
|
|
112
|
+
sid = str(step_id or "").strip()
|
|
113
|
+
if not sid:
|
|
114
|
+
return tcid
|
|
115
|
+
raw_tid = tcid
|
|
116
|
+
if not raw_tid:
|
|
117
|
+
name = str(tool_name or "").strip()
|
|
118
|
+
if name and chunk_index is not None:
|
|
119
|
+
try:
|
|
120
|
+
idx = int(chunk_index)
|
|
121
|
+
except (TypeError, ValueError):
|
|
122
|
+
idx = None
|
|
123
|
+
if idx is not None:
|
|
124
|
+
raw_tid = f"{name}:{idx}"
|
|
125
|
+
if not raw_tid:
|
|
126
|
+
return ""
|
|
127
|
+
frag = _provider_tool_fragment(raw_tid)
|
|
128
|
+
return _format_unified_tool_call_id(sid, "s", frag)
|
|
129
|
+
|
|
130
|
+
|
|
67
131
|
def is_task_level_subgraph_tool_call_id(tool_call_id: str) -> bool:
|
|
68
132
|
"""True for unified subgraph tools (``{step}:t{n}:{tool}:{seq}``), not nested ``task`` rows."""
|
|
69
133
|
from soothe_sdk.ux.task_namespace import (
|
|
@@ -87,19 +151,22 @@ def should_ingest_tool_for_step_stats(
|
|
|
87
151
|
) -> bool:
|
|
88
152
|
"""Whether the TUI should register a tool row for step-card stats.
|
|
89
153
|
|
|
90
|
-
Main
|
|
91
|
-
are known; display does not wait for streamed args.
|
|
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 explore tools often
|
|
156
|
+
arrive with placeholder kwargs before real args.
|
|
92
157
|
"""
|
|
158
|
+
from soothe_sdk.ux.task_namespace import is_step_level_task_tool_id
|
|
159
|
+
|
|
93
160
|
name = str(tool_name or "").strip()
|
|
94
161
|
tcid = str(tool_call_id or "").strip()
|
|
95
162
|
if not name or not tcid or name == "task":
|
|
96
163
|
return False
|
|
97
164
|
if args_meaningful:
|
|
98
165
|
return True
|
|
99
|
-
if is_step_card_scope
|
|
100
|
-
return
|
|
166
|
+
if is_step_card_scope:
|
|
167
|
+
return not is_step_level_task_tool_id(tcid)
|
|
101
168
|
# Subgraph explore tools often arrive with ``{"_subgraph_tool": true}`` before real args.
|
|
102
|
-
return
|
|
169
|
+
return is_task_level_subgraph_tool_call_id(tcid)
|
|
103
170
|
|
|
104
171
|
|
|
105
172
|
def _args_from_toolish_block(block: dict[str, Any]) -> dict[str, Any]:
|
|
@@ -498,11 +565,13 @@ __all__ = [
|
|
|
498
565
|
"is_main_step_level_tool_call_id",
|
|
499
566
|
"is_step_card_tool_scope",
|
|
500
567
|
"is_task_level_subgraph_tool_call_id",
|
|
568
|
+
"predict_main_execute_tool_call_id",
|
|
501
569
|
"is_toolish_display_block",
|
|
502
570
|
"materialize_ai_blocks_with_resolved_tools",
|
|
503
571
|
"merge_tool_display_args",
|
|
504
572
|
"resolve_stream_tool_name",
|
|
505
573
|
"resolve_tool_invocations_for_display",
|
|
574
|
+
"resolve_tool_result_row_key",
|
|
506
575
|
"should_ingest_tool_for_step_stats",
|
|
507
576
|
"tool_args_meaningful",
|
|
508
577
|
]
|
|
@@ -3,13 +3,11 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
from soothe_sdk.display.tool_result import (
|
|
6
|
-
ToolResultPayload,
|
|
7
6
|
extract_tool_result_payload,
|
|
8
7
|
infer_tool_output_suggests_error,
|
|
9
8
|
)
|
|
10
9
|
|
|
11
10
|
__all__ = [
|
|
12
|
-
"ToolResultPayload",
|
|
13
11
|
"extract_tool_result_payload",
|
|
14
12
|
"infer_tool_output_suggests_error",
|
|
15
13
|
]
|
|
@@ -165,6 +165,47 @@ class StepTaskRouter:
|
|
|
165
165
|
if sid:
|
|
166
166
|
self.active_step_ids.add(sid)
|
|
167
167
|
|
|
168
|
+
def maybe_promote_step_to_running(
|
|
169
|
+
self,
|
|
170
|
+
step_w: StepWidget,
|
|
171
|
+
tool_call_id: str,
|
|
172
|
+
*,
|
|
173
|
+
step_cards: dict[str, StepWidget],
|
|
174
|
+
) -> None:
|
|
175
|
+
"""Promote a pending step card to running only when it is executing (RFC-628).
|
|
176
|
+
|
|
177
|
+
Pre-mounted future steps must stay ``pending`` until ``step.started`` even when
|
|
178
|
+
tools are stamped with their unified step id. The pre-``step.started`` race for
|
|
179
|
+
the active step is allowed only while no sibling step card is already running.
|
|
180
|
+
"""
|
|
181
|
+
if getattr(step_w, "_status", "") != "pending":
|
|
182
|
+
return
|
|
183
|
+
step_id = str(getattr(step_w, "_step_id", "") or "").strip()
|
|
184
|
+
if not step_id:
|
|
185
|
+
return
|
|
186
|
+
|
|
187
|
+
tcid = str(tool_call_id or "").strip()
|
|
188
|
+
parsed_sid, _, _, _ = parse_unified_tool_call_id(tcid)
|
|
189
|
+
if parsed_sid and parsed_sid != step_id:
|
|
190
|
+
return
|
|
191
|
+
|
|
192
|
+
if step_id in self.active_step_ids:
|
|
193
|
+
allowed = True
|
|
194
|
+
elif self.active_step_ids:
|
|
195
|
+
allowed = False
|
|
196
|
+
else:
|
|
197
|
+
allowed = not any(
|
|
198
|
+
getattr(w, "_status", "") == "running"
|
|
199
|
+
and str(getattr(w, "_step_id", "") or "").strip() != step_id
|
|
200
|
+
for w in step_cards.values()
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
if not allowed:
|
|
204
|
+
return
|
|
205
|
+
promote = getattr(step_w, "promote_to_running_if_pending", None)
|
|
206
|
+
if callable(promote):
|
|
207
|
+
promote()
|
|
208
|
+
|
|
168
209
|
def on_step_completed(self, step_id: str) -> None:
|
|
169
210
|
"""Drop step from the active set and spawn registry."""
|
|
170
211
|
sid = step_id.strip()
|
|
@@ -353,6 +394,11 @@ class StepTaskRouter:
|
|
|
353
394
|
item.args,
|
|
354
395
|
raw_args=item.raw_args,
|
|
355
396
|
)
|
|
397
|
+
self.maybe_promote_step_to_running(
|
|
398
|
+
step_w,
|
|
399
|
+
item.tool_call_id,
|
|
400
|
+
step_cards=step_cards,
|
|
401
|
+
)
|
|
356
402
|
tool_to_step[item.tool_call_id] = step_w
|
|
357
403
|
existing = tool_display_by_call_id.get(item.tool_call_id)
|
|
358
404
|
if existing is None:
|
|
@@ -394,6 +440,8 @@ class StepTaskRouter:
|
|
|
394
440
|
parent: ParentWidget,
|
|
395
441
|
scope: TaskScope,
|
|
396
442
|
tool_to_step: dict[str, ParentWidget],
|
|
443
|
+
*,
|
|
444
|
+
step_cards: dict[str, StepWidget] | None = None,
|
|
397
445
|
) -> bool:
|
|
398
446
|
"""Register one subgraph tool row on an already-resolved parent step card."""
|
|
399
447
|
if _is_task_metadata_subgraph_tool(item):
|
|
@@ -432,7 +480,6 @@ class StepTaskRouter:
|
|
|
432
480
|
resolved_args = parsed
|
|
433
481
|
update(row_id, resolved_args)
|
|
434
482
|
else:
|
|
435
|
-
parent_task_id = str(scope[0]).strip()
|
|
436
483
|
resolved_args = dict(item.args or {})
|
|
437
484
|
from soothe_cli.runtime.parse.message_processing import extract_tool_args_dict
|
|
438
485
|
|
|
@@ -446,9 +493,10 @@ class StepTaskRouter:
|
|
|
446
493
|
item.tool_name,
|
|
447
494
|
resolved_args,
|
|
448
495
|
raw_args=item.raw_args,
|
|
449
|
-
parent_tool_call_id=parent_task_id or None,
|
|
450
496
|
)
|
|
451
497
|
tool_to_step[row_id] = parent
|
|
498
|
+
if step_cards is not None:
|
|
499
|
+
self.maybe_promote_step_to_running(parent, row_id, step_cards=step_cards)
|
|
452
500
|
return True
|
|
453
501
|
|
|
454
502
|
def try_route_subgraph_tool(
|
|
@@ -522,7 +570,13 @@ class StepTaskRouter:
|
|
|
522
570
|
return False
|
|
523
571
|
pending_key = _subgraph_pending_key(ns_key, item.lookup_id)
|
|
524
572
|
self._pending_subgraph_tools.pop(pending_key, None)
|
|
525
|
-
return self._ingest_subgraph_tool_on_parent(
|
|
573
|
+
return self._ingest_subgraph_tool_on_parent(
|
|
574
|
+
item,
|
|
575
|
+
parent,
|
|
576
|
+
scope,
|
|
577
|
+
tool_to_step,
|
|
578
|
+
step_cards=step_cards,
|
|
579
|
+
)
|
|
526
580
|
|
|
527
581
|
def route_pending_subgraph_tools(
|
|
528
582
|
self,
|
|
@@ -575,7 +629,13 @@ class StepTaskRouter:
|
|
|
575
629
|
if parent is None:
|
|
576
630
|
still[key] = item
|
|
577
631
|
continue
|
|
578
|
-
if self._ingest_subgraph_tool_on_parent(
|
|
632
|
+
if self._ingest_subgraph_tool_on_parent(
|
|
633
|
+
item,
|
|
634
|
+
parent,
|
|
635
|
+
scope,
|
|
636
|
+
tool_to_step,
|
|
637
|
+
step_cards=step_cards,
|
|
638
|
+
):
|
|
579
639
|
routed += 1
|
|
580
640
|
else:
|
|
581
641
|
still[key] = item
|
|
@@ -586,15 +646,15 @@ class StepTaskRouter:
|
|
|
586
646
|
"""Snapshot of subgraph tools still awaiting parent resolution."""
|
|
587
647
|
return list(self._pending_subgraph_tools.values())
|
|
588
648
|
|
|
649
|
+
def discard_pending_subgraph_tool(self, ns_key: tuple[str, ...], lookup_id: str) -> None:
|
|
650
|
+
"""Drop a buffered subgraph tool after routing it to a SubAgent card."""
|
|
651
|
+
self._pending_subgraph_tools.pop(_subgraph_pending_key(ns_key, lookup_id), None)
|
|
652
|
+
|
|
589
653
|
@property
|
|
590
654
|
def pending_main_tool_count(self) -> int:
|
|
591
655
|
"""Number of root tools still awaiting step card routing."""
|
|
592
656
|
return len(self._pending_main_tools)
|
|
593
657
|
|
|
594
|
-
def clear_step_tool_bindings(self, step_id: str) -> None:
|
|
595
|
-
"""No-op: step routing uses unified tool_call_id encoding only."""
|
|
596
|
-
_ = step_id
|
|
597
|
-
|
|
598
658
|
|
|
599
659
|
__all__ = [
|
|
600
660
|
"ParentWidget",
|
|
@@ -49,10 +49,6 @@ from soothe_cli.tui.widgets.welcome import WelcomeBanner
|
|
|
49
49
|
logger = logging.getLogger(__name__)
|
|
50
50
|
_monotonic = time.monotonic
|
|
51
51
|
|
|
52
|
-
InputMode = (
|
|
53
|
-
"normal" # Literal type alias — actual value used in _module_init; here for isinstance guards
|
|
54
|
-
)
|
|
55
|
-
|
|
56
52
|
|
|
57
53
|
class SootheApp(
|
|
58
54
|
App,
|
|
@@ -664,8 +664,6 @@ class _ExecutionMixin:
|
|
|
664
664
|
await self._submit_cron_job(args, slash_input=command)
|
|
665
665
|
elif cmd == "/autopilot-dashboard":
|
|
666
666
|
await self._show_autopilot_dashboard()
|
|
667
|
-
elif cmd == "/autopilot-toggle":
|
|
668
|
-
await self._toggle_autopilot_mode()
|
|
669
667
|
elif cmd == "/mcp":
|
|
670
668
|
await self._show_mcp_viewer()
|
|
671
669
|
elif cmd == "/theme":
|
|
@@ -393,25 +393,42 @@ class _MessagesMixin:
|
|
|
393
393
|
copy_selection_to_clipboard(self, notify_if_empty=True)
|
|
394
394
|
|
|
395
395
|
def action_quit_or_interrupt(self) -> None:
|
|
396
|
-
"""Handle Ctrl+C - interrupt agent or quit on double press.
|
|
396
|
+
"""Handle Ctrl+C - clear input, interrupt agent, or quit on double press.
|
|
397
397
|
|
|
398
|
-
Priority order:
|
|
399
|
-
1. If
|
|
400
|
-
2. If
|
|
401
|
-
|
|
402
|
-
|
|
398
|
+
Priority order when task is running (agent/shell):
|
|
399
|
+
1. If input has pending text, clear it (first Ctrl+C)
|
|
400
|
+
2. If input is empty, interrupt the running task (second Ctrl+C)
|
|
401
|
+
|
|
402
|
+
Priority order when idle:
|
|
403
|
+
1. If double press (quit_pending), quit
|
|
404
|
+
2. Otherwise clear pending input and show quit hint
|
|
403
405
|
|
|
404
406
|
Note: Copying selected text is bound to Ctrl+Y (`action_copy_selection`)
|
|
405
407
|
so Ctrl+C is reserved for interrupt/quit only.
|
|
406
408
|
"""
|
|
407
|
-
#
|
|
409
|
+
# Check if input has pending content (text, mode, or completion)
|
|
410
|
+
has_pending_input = self._chat_input and (
|
|
411
|
+
self._chat_input.value.strip()
|
|
412
|
+
or self._chat_input.mode != "normal"
|
|
413
|
+
or self._chat_input._current_suggestions
|
|
414
|
+
)
|
|
415
|
+
|
|
416
|
+
# If shell command is running: clear input first, then kill shell
|
|
408
417
|
if self._shell_running and self._shell_worker:
|
|
418
|
+
if has_pending_input:
|
|
419
|
+
self._chat_input.clear_input()
|
|
420
|
+
self._quit_pending = False
|
|
421
|
+
return
|
|
409
422
|
self._cancel_worker(self._shell_worker)
|
|
410
423
|
self._quit_pending = False
|
|
411
424
|
return
|
|
412
425
|
|
|
413
|
-
# If agent is running
|
|
426
|
+
# If agent is running: clear input first, then interrupt
|
|
414
427
|
if self._agent_running and self._agent_worker:
|
|
428
|
+
if has_pending_input:
|
|
429
|
+
self._chat_input.clear_input()
|
|
430
|
+
self._quit_pending = False
|
|
431
|
+
return
|
|
415
432
|
if self._daemon_session is not None:
|
|
416
433
|
self.run_worker(
|
|
417
434
|
self._interrupt_daemon_agent_turn(),
|
|
@@ -430,7 +447,7 @@ class _MessagesMixin:
|
|
|
430
447
|
self._arm_quit_pending("Ctrl+C")
|
|
431
448
|
|
|
432
449
|
def _arm_quit_pending(self, shortcut: str) -> None:
|
|
433
|
-
"""Set the pending-quit flag, clear
|
|
450
|
+
"""Set the pending-quit flag, clear pending input, and show a matching hint.
|
|
434
451
|
|
|
435
452
|
Args:
|
|
436
453
|
shortcut: The key chord to show in the quit hint.
|
|
@@ -743,8 +760,3 @@ class _MessagesMixin:
|
|
|
743
760
|
# =========================================================================
|
|
744
761
|
# Model Switching
|
|
745
762
|
# =========================================================================
|
|
746
|
-
# SOOTHE: Slash command actions
|
|
747
|
-
|
|
748
|
-
def action_detach(self) -> None:
|
|
749
|
-
"""Exit TUI but leave daemon running."""
|
|
750
|
-
self._detach_or_exit()
|
|
@@ -293,48 +293,6 @@ class _ModelMixin:
|
|
|
293
293
|
|
|
294
294
|
self.push_screen(screen, handle_result)
|
|
295
295
|
|
|
296
|
-
async def _toggle_autopilot_mode(self) -> None:
|
|
297
|
-
"""Toggle autopilot mode (solo ↔ autopilot) via daemon RPC.
|
|
298
|
-
|
|
299
|
-
Sends command_request to daemon and updates local dashboard mode.
|
|
300
|
-
"""
|
|
301
|
-
from soothe_cli.tui.widgets.messages import ErrorMessage, UserMessage
|
|
302
|
-
|
|
303
|
-
await self._mount_message(UserMessage("/autopilot-toggle"))
|
|
304
|
-
|
|
305
|
-
lid = self._loop_id
|
|
306
|
-
if not lid:
|
|
307
|
-
await self._mount_message(ErrorMessage("No active loop for autopilot toggle"))
|
|
308
|
-
return
|
|
309
|
-
|
|
310
|
-
if not self._client:
|
|
311
|
-
await self._mount_message(ErrorMessage("Not connected to daemon"))
|
|
312
|
-
return
|
|
313
|
-
|
|
314
|
-
try:
|
|
315
|
-
from soothe_cli.tui.commands.command_router import handle_rpc_command
|
|
316
|
-
from soothe_cli.tui.commands.slash_commands import COMMANDS
|
|
317
|
-
|
|
318
|
-
entry = COMMANDS.get("/autopilot-toggle")
|
|
319
|
-
if not entry:
|
|
320
|
-
await self._mount_message(ErrorMessage("Command /autopilot-toggle not found"))
|
|
321
|
-
return
|
|
322
|
-
|
|
323
|
-
await handle_rpc_command(
|
|
324
|
-
entry,
|
|
325
|
-
"autopilot-toggle",
|
|
326
|
-
None,
|
|
327
|
-
self._console,
|
|
328
|
-
self._client,
|
|
329
|
-
loop_id=lid,
|
|
330
|
-
)
|
|
331
|
-
new_mode = "solo" if self._loop_autopilot_mode == "autopilot" else "autopilot"
|
|
332
|
-
self._apply_loop_autopilot_mode(new_mode)
|
|
333
|
-
|
|
334
|
-
except Exception as exc:
|
|
335
|
-
logger.exception("Autopilot toggle failed")
|
|
336
|
-
await self._mount_message(ErrorMessage(f"Failed to toggle autopilot: {exc}"))
|
|
337
|
-
|
|
338
296
|
async def _submit_autopilot_job(self, task: str) -> None:
|
|
339
297
|
"""Submit an autopilot job via WebSocket (like CLI `soothe autopilot run`).
|
|
340
298
|
|
|
@@ -222,22 +222,6 @@ IMMEDIATE_UI: frozenset[str] = _build_bypass_set(BypassTier.IMMEDIATE_UI)
|
|
|
222
222
|
SIDE_EFFECT_FREE: frozenset[str] = _build_bypass_set(BypassTier.SIDE_EFFECT_FREE)
|
|
223
223
|
"""Commands whose side effect fires immediately; chat output deferred until idle."""
|
|
224
224
|
|
|
225
|
-
QUEUE_BOUND: frozenset[str] = _build_bypass_set(BypassTier.QUEUED)
|
|
226
|
-
"""Commands that must wait in the queue when the app is busy."""
|
|
227
|
-
|
|
228
|
-
HIDDEN_DEBUG: frozenset[str] = frozenset({"/debug-error"})
|
|
229
|
-
"""Hidden debug commands not exposed in autocomplete or help."""
|
|
230
|
-
|
|
231
|
-
ALL_CLASSIFIED: frozenset[str] = (
|
|
232
|
-
ALWAYS_IMMEDIATE
|
|
233
|
-
| BYPASS_WHEN_CONNECTING
|
|
234
|
-
| IMMEDIATE_UI
|
|
235
|
-
| SIDE_EFFECT_FREE
|
|
236
|
-
| QUEUE_BOUND
|
|
237
|
-
| HIDDEN_DEBUG
|
|
238
|
-
)
|
|
239
|
-
"""Union of all tiers plus hidden debug commands — used by drift tests."""
|
|
240
|
-
|
|
241
225
|
|
|
242
226
|
# ---------------------------------------------------------------------------
|
|
243
227
|
# Autocomplete tuples
|