soothe-cli 0.6.14__tar.gz → 0.6.16__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.14 → soothe_cli-0.6.16}/PKG-INFO +1 -1
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/cli/commands/autopilot_cmd.py +0 -93
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/config/cli_config.py +0 -36
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/runtime/parse/tool_call_resolution.py +48 -5
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/tui/app/_app.py +0 -4
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/tui/app/_execution.py +0 -2
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/tui/app/_messages_mixin.py +0 -5
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/tui/app/_model.py +0 -42
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/tui/command_registry.py +0 -16
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/tui/preview_limits.py +1 -1
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/tui/sessions.py +1 -182
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/tui/textual_adapter.py +35 -2
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/tui/theme.py +0 -12
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/tui/unicode_security.py +0 -11
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/tui/widgets/autocomplete.py +0 -4
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/tui/widgets/autopilot_dashboard.py +0 -268
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/tui/widgets/diff.py +3 -101
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/tui/widgets/loop_selector.py +0 -21
- soothe_cli-0.6.16/src/soothe_cli/tui/widgets/messages/__init__.py +81 -0
- soothe_cli-0.6.16/src/soothe_cli/tui/widgets/messages/_helpers.py +239 -0
- soothe_cli-0.6.16/src/soothe_cli/tui/widgets/messages/app.py +100 -0
- soothe_cli-0.6.16/src/soothe_cli/tui/widgets/messages/assistant.py +271 -0
- soothe_cli-0.6.16/src/soothe_cli/tui/widgets/messages/clarification.py +215 -0
- soothe_cli-0.6.16/src/soothe_cli/tui/widgets/messages/cognition_goal_tree.py +395 -0
- soothe_cli-0.6.16/src/soothe_cli/tui/widgets/messages/cognition_reason.py +111 -0
- soothe_cli-0.6.16/src/soothe_cli/tui/widgets/messages/cognition_step.py +1466 -0
- soothe_cli-0.6.16/src/soothe_cli/tui/widgets/messages/cognition_step_activity.py +947 -0
- soothe_cli-0.6.16/src/soothe_cli/tui/widgets/messages/diff_message.py +110 -0
- soothe_cli-0.6.16/src/soothe_cli/tui/widgets/messages/error.py +59 -0
- soothe_cli-0.6.16/src/soothe_cli/tui/widgets/messages/skill.py +312 -0
- soothe_cli-0.6.16/src/soothe_cli/tui/widgets/messages/user.py +170 -0
- soothe_cli-0.6.14/src/soothe_cli/tui/widgets/messages.py +0 -3997
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/.gitignore +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/README.md +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/pyproject.toml +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/__init__.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/cli/__init__.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/cli/commands/__init__.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/cli/commands/cron_cmd.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/cli/commands/loop_cmd.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/cli/commands/run_cmd.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/cli/commands/status_cmd.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/cli/execution/__init__.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/cli/execution/daemon.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/cli/execution/daemon_errors.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/cli/execution/headless.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/cli/execution/headless_renderer.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/cli/execution/launcher.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/cli/main.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/config/__init__.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/config/loader.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/config/logging_setup.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/runtime/__init__.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/runtime/headless/processor.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/runtime/headless/processor_state.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/runtime/parse/_utils.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/runtime/parse/message_processing.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/runtime/parse/tool_message_format.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/runtime/parse/tool_result.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/runtime/policy/display_policy.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/runtime/policy/essential_events.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/runtime/policy/tui_trace_log.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/runtime/presentation/duration_format.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/runtime/presentation/engine.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/runtime/presentation/explore_task_display.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/runtime/presentation/renderer_base.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/runtime/presentation/renderer_protocol.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/runtime/state/file_tracker.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/runtime/state/session_stats.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/runtime/state/step_router.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/runtime/state/stream_accumulator.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/runtime/state/transcript.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/runtime/transport/session.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/runtime/turn/pipeline.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/runtime/turn/prepare.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/runtime/wire/chunk_filter.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/runtime/wire/display_text.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/runtime/wire/message_text.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/runtime/wire/messages.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/tui/__init__.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/tui/_cli_context.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/tui/_env_vars.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/tui/_version.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/tui/app/__init__.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/tui/app/_commands.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/tui/app/_history.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/tui/app/_module_init.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/tui/app/_startup.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/tui/app/_ui.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/tui/app/app.tcss +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/tui/binding.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/tui/commands/__init__.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/tui/commands/command_router.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/tui/commands/slash_commands.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/tui/commands/subagent_routing.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/tui/config.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/tui/file_change_notify.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/tui/file_change_renderers.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/tui/hooks.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/tui/input.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/tui/media_utils.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/tui/model_config.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/tui/path_utils.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/tui/project_utils.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/tui/skills/__init__.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/tui/skills/invocation.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/tui/skills/load.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/tui/tips.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/tui/tool_display.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/tui/update_check.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/tui/widgets/__init__.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/tui/widgets/_links.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/tui/widgets/autopilot_screen.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/tui/widgets/chat_input.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/tui/widgets/clipboard.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/tui/widgets/editor.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/tui/widgets/file_change_preview.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/tui/widgets/history.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/tui/widgets/loading.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/tui/widgets/mcp_viewer.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/tui/widgets/message_store.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/tui/widgets/model_selector.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/tui/widgets/notification_settings.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/tui/widgets/status.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/tui/widgets/theme_selector.py +0 -0
- {soothe_cli-0.6.14 → soothe_cli-0.6.16}/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, {})
|
{soothe_cli-0.6.14 → soothe_cli-0.6.16}/src/soothe_cli/runtime/parse/tool_call_resolution.py
RENAMED
|
@@ -64,6 +64,45 @@ def is_main_step_level_tool_call_id(tool_call_id: str) -> bool:
|
|
|
64
64
|
return not is_step_level_task_tool_id(tcid)
|
|
65
65
|
|
|
66
66
|
|
|
67
|
+
def predict_main_execute_tool_call_id(
|
|
68
|
+
step_id: str,
|
|
69
|
+
*,
|
|
70
|
+
tool_call_id: str = "",
|
|
71
|
+
tool_name: str = "",
|
|
72
|
+
chunk_index: Any = None,
|
|
73
|
+
) -> str:
|
|
74
|
+
"""Best-effort unified id for main-graph execute tools during streaming."""
|
|
75
|
+
from soothe_sdk.ux.task_namespace import (
|
|
76
|
+
_format_unified_tool_call_id,
|
|
77
|
+
_provider_tool_fragment,
|
|
78
|
+
is_unified_tool_call_id,
|
|
79
|
+
parse_unified_tool_call_id,
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
tcid = str(tool_call_id or "").strip()
|
|
83
|
+
if tcid and is_unified_tool_call_id(tcid):
|
|
84
|
+
parsed_sid, type_code, _, _ = parse_unified_tool_call_id(tcid)
|
|
85
|
+
if parsed_sid and type_code == "s":
|
|
86
|
+
return tcid
|
|
87
|
+
sid = str(step_id or "").strip()
|
|
88
|
+
if not sid:
|
|
89
|
+
return tcid
|
|
90
|
+
raw_tid = tcid
|
|
91
|
+
if not raw_tid:
|
|
92
|
+
name = str(tool_name or "").strip()
|
|
93
|
+
if name and chunk_index is not None:
|
|
94
|
+
try:
|
|
95
|
+
idx = int(chunk_index)
|
|
96
|
+
except (TypeError, ValueError):
|
|
97
|
+
idx = None
|
|
98
|
+
if idx is not None:
|
|
99
|
+
raw_tid = f"{name}:{idx}"
|
|
100
|
+
if not raw_tid:
|
|
101
|
+
return ""
|
|
102
|
+
frag = _provider_tool_fragment(raw_tid)
|
|
103
|
+
return _format_unified_tool_call_id(sid, "s", frag)
|
|
104
|
+
|
|
105
|
+
|
|
67
106
|
def is_task_level_subgraph_tool_call_id(tool_call_id: str) -> bool:
|
|
68
107
|
"""True for unified subgraph tools (``{step}:t{n}:{tool}:{seq}``), not nested ``task`` rows."""
|
|
69
108
|
from soothe_sdk.ux.task_namespace import (
|
|
@@ -87,19 +126,22 @@ def should_ingest_tool_for_step_stats(
|
|
|
87
126
|
) -> bool:
|
|
88
127
|
"""Whether the TUI should register a tool row for step-card stats.
|
|
89
128
|
|
|
90
|
-
Main
|
|
91
|
-
are known; display does not wait for streamed args.
|
|
129
|
+
Main execute-graph tools are tracked as soon as ``tool_call_id`` and ``tool_name``
|
|
130
|
+
are known; display does not wait for streamed args. Subgraph explore tools often
|
|
131
|
+
arrive with placeholder kwargs before real args.
|
|
92
132
|
"""
|
|
133
|
+
from soothe_sdk.ux.task_namespace import is_step_level_task_tool_id
|
|
134
|
+
|
|
93
135
|
name = str(tool_name or "").strip()
|
|
94
136
|
tcid = str(tool_call_id or "").strip()
|
|
95
137
|
if not name or not tcid or name == "task":
|
|
96
138
|
return False
|
|
97
139
|
if args_meaningful:
|
|
98
140
|
return True
|
|
99
|
-
if is_step_card_scope
|
|
100
|
-
return
|
|
141
|
+
if is_step_card_scope:
|
|
142
|
+
return not is_step_level_task_tool_id(tcid)
|
|
101
143
|
# Subgraph explore tools often arrive with ``{"_subgraph_tool": true}`` before real args.
|
|
102
|
-
return
|
|
144
|
+
return is_task_level_subgraph_tool_call_id(tcid)
|
|
103
145
|
|
|
104
146
|
|
|
105
147
|
def _args_from_toolish_block(block: dict[str, Any]) -> dict[str, Any]:
|
|
@@ -498,6 +540,7 @@ __all__ = [
|
|
|
498
540
|
"is_main_step_level_tool_call_id",
|
|
499
541
|
"is_step_card_tool_scope",
|
|
500
542
|
"is_task_level_subgraph_tool_call_id",
|
|
543
|
+
"predict_main_execute_tool_call_id",
|
|
501
544
|
"is_toolish_display_block",
|
|
502
545
|
"materialize_ai_blocks_with_resolved_tools",
|
|
503
546
|
"merge_tool_display_args",
|
|
@@ -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":
|
|
@@ -743,8 +743,3 @@ class _MessagesMixin:
|
|
|
743
743
|
# =========================================================================
|
|
744
744
|
# Model Switching
|
|
745
745
|
# =========================================================================
|
|
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
|
|
@@ -17,7 +17,7 @@ STEP_CARD_SHOW_TOOL_ROW_DETAILS: Final[bool] = False
|
|
|
17
17
|
# Latest per-tool invocation lines shown per scope (task branch vs main-agent branch).
|
|
18
18
|
STEP_CARD_TOOL_ACTIVITY_PREVIEW_COUNT: Final[int] = 3
|
|
19
19
|
|
|
20
|
-
#
|
|
20
|
+
# Optional manual full tool-list folding threshold (not auto-collapse).
|
|
21
21
|
STEP_TASK_CARD_COLLAPSE_LINE_THRESHOLD: Final[int] = 3
|
|
22
22
|
|
|
23
23
|
# --- Skill invocation cards (`SkillMessage` collapsed SKILL.md body) ---
|
|
@@ -3,71 +3,13 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import logging
|
|
6
|
-
from contextlib import asynccontextmanager
|
|
7
6
|
from datetime import datetime
|
|
8
|
-
from
|
|
9
|
-
from typing import TYPE_CHECKING, Any
|
|
7
|
+
from typing import Any
|
|
10
8
|
|
|
11
|
-
from soothe_sdk.client.config import SOOTHE_HOME
|
|
12
9
|
from typing_extensions import TypedDict
|
|
13
10
|
|
|
14
|
-
if TYPE_CHECKING:
|
|
15
|
-
from collections.abc import AsyncIterator
|
|
16
|
-
|
|
17
|
-
import aiosqlite
|
|
18
|
-
|
|
19
11
|
logger = logging.getLogger(__name__)
|
|
20
12
|
|
|
21
|
-
_aiosqlite_patched = False
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
def _patch_aiosqlite() -> None:
|
|
25
|
-
"""Patch aiosqlite.Connection with `is_alive()` if missing.
|
|
26
|
-
|
|
27
|
-
Required by langgraph-checkpoint>=2.1.0.
|
|
28
|
-
See: https://github.com/langchain-ai/langgraph/issues/6583
|
|
29
|
-
"""
|
|
30
|
-
global _aiosqlite_patched # noqa: PLW0603 # Module-level flag requires global statement
|
|
31
|
-
if _aiosqlite_patched:
|
|
32
|
-
return
|
|
33
|
-
|
|
34
|
-
import aiosqlite as _aiosqlite
|
|
35
|
-
|
|
36
|
-
if not hasattr(_aiosqlite.Connection, "is_alive"):
|
|
37
|
-
|
|
38
|
-
def _is_alive(self: _aiosqlite.Connection) -> bool:
|
|
39
|
-
"""Check if the connection is still alive.
|
|
40
|
-
|
|
41
|
-
Returns:
|
|
42
|
-
True if connection is alive, False otherwise.
|
|
43
|
-
"""
|
|
44
|
-
return bool(self._running and self._connection is not None)
|
|
45
|
-
|
|
46
|
-
# Dynamically adding a method to aiosqlite.Connection at runtime.
|
|
47
|
-
# Type checkers can't understand this monkey-patch, so we suppress the
|
|
48
|
-
# "attr-defined" error that would otherwise be raised.
|
|
49
|
-
_aiosqlite.Connection.is_alive = _is_alive # type: ignore[attr-defined]
|
|
50
|
-
|
|
51
|
-
_aiosqlite_patched = True
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
@asynccontextmanager
|
|
55
|
-
async def _connect() -> AsyncIterator[aiosqlite.Connection]:
|
|
56
|
-
"""Import aiosqlite, apply the compatibility patch, and connect.
|
|
57
|
-
|
|
58
|
-
Centralizes the deferred import + patch + connect sequence used by every
|
|
59
|
-
database function in this module.
|
|
60
|
-
|
|
61
|
-
Yields:
|
|
62
|
-
An open aiosqlite connection to the sessions database.
|
|
63
|
-
"""
|
|
64
|
-
import aiosqlite as _aiosqlite
|
|
65
|
-
|
|
66
|
-
_patch_aiosqlite()
|
|
67
|
-
|
|
68
|
-
async with _aiosqlite.connect(str(get_db_path()), timeout=30.0) as conn:
|
|
69
|
-
yield conn
|
|
70
|
-
|
|
71
13
|
|
|
72
14
|
class LoopInfo(TypedDict, total=False):
|
|
73
15
|
"""Loop metadata returned by `list_loops_via_daemon_rpc`."""
|
|
@@ -182,27 +124,6 @@ def format_relative_timestamp(iso_timestamp: str | None) -> str:
|
|
|
182
124
|
return f"{years}y ago"
|
|
183
125
|
|
|
184
126
|
|
|
185
|
-
_db_path: Path | None = None
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
def get_db_path() -> Path:
|
|
189
|
-
"""Get path to global database.
|
|
190
|
-
|
|
191
|
-
The result is cached after the first successful call to avoid repeated
|
|
192
|
-
filesystem operations.
|
|
193
|
-
|
|
194
|
-
Returns:
|
|
195
|
-
Path to the SQLite database file.
|
|
196
|
-
"""
|
|
197
|
-
global _db_path # noqa: PLW0603 # Module-level cache requires global statement
|
|
198
|
-
if _db_path is not None:
|
|
199
|
-
return _db_path
|
|
200
|
-
db_dir = Path(SOOTHE_HOME)
|
|
201
|
-
db_dir.mkdir(parents=True, exist_ok=True)
|
|
202
|
-
_db_path = db_dir / "sessions.db"
|
|
203
|
-
return _db_path
|
|
204
|
-
|
|
205
|
-
|
|
206
127
|
def generate_loop_id() -> str:
|
|
207
128
|
"""Generate a new loop ID as a full UUID7 string.
|
|
208
129
|
|
|
@@ -214,17 +135,6 @@ def generate_loop_id() -> str:
|
|
|
214
135
|
return str(uuid7())
|
|
215
136
|
|
|
216
137
|
|
|
217
|
-
async def _table_exists(conn: aiosqlite.Connection, table: str) -> bool:
|
|
218
|
-
"""Check if a table exists in the database.
|
|
219
|
-
|
|
220
|
-
Returns:
|
|
221
|
-
True if table exists, False otherwise.
|
|
222
|
-
"""
|
|
223
|
-
query = "SELECT 1 FROM sqlite_master WHERE type='table' AND name=?"
|
|
224
|
-
async with conn.execute(query, (table,)) as cursor:
|
|
225
|
-
return await cursor.fetchone() is not None
|
|
226
|
-
|
|
227
|
-
|
|
228
138
|
async def list_loops_via_daemon_rpc(
|
|
229
139
|
daemon_session: Any,
|
|
230
140
|
limit: int = 20,
|
|
@@ -309,97 +219,6 @@ async def list_loops_via_daemon_rpc(
|
|
|
309
219
|
return loops
|
|
310
220
|
|
|
311
221
|
|
|
312
|
-
async def get_most_recent(agent_name: str | None = None) -> str | None:
|
|
313
|
-
"""Return the most recent loop id from local SQLite, optionally filtered by agent.
|
|
314
|
-
|
|
315
|
-
Returns:
|
|
316
|
-
Loop id (``checkpoints.thread_id``), or `None` when the database is empty.
|
|
317
|
-
"""
|
|
318
|
-
async with _connect() as conn:
|
|
319
|
-
if not await _table_exists(conn, "checkpoints"):
|
|
320
|
-
return None
|
|
321
|
-
|
|
322
|
-
if agent_name:
|
|
323
|
-
query = """
|
|
324
|
-
SELECT thread_id FROM checkpoints
|
|
325
|
-
WHERE json_extract(metadata, '$.agent_name') = ?
|
|
326
|
-
ORDER BY checkpoint_id DESC
|
|
327
|
-
LIMIT 1
|
|
328
|
-
"""
|
|
329
|
-
params: tuple = (agent_name,)
|
|
330
|
-
else:
|
|
331
|
-
query = "SELECT thread_id FROM checkpoints ORDER BY checkpoint_id DESC LIMIT 1"
|
|
332
|
-
params = ()
|
|
333
|
-
|
|
334
|
-
async with conn.execute(query, params) as cursor:
|
|
335
|
-
row = await cursor.fetchone()
|
|
336
|
-
return row[0] if row else None
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
async def get_loop_agent(loop_id: str) -> str | None:
|
|
340
|
-
"""Return ``agent_name`` metadata for a loop id.
|
|
341
|
-
|
|
342
|
-
Returns:
|
|
343
|
-
Agent name from checkpoint row metadata, or `None` if not found.
|
|
344
|
-
"""
|
|
345
|
-
async with _connect() as conn:
|
|
346
|
-
if not await _table_exists(conn, "checkpoints"):
|
|
347
|
-
return None
|
|
348
|
-
|
|
349
|
-
query = """
|
|
350
|
-
SELECT json_extract(metadata, '$.agent_name')
|
|
351
|
-
FROM checkpoints
|
|
352
|
-
WHERE thread_id = ?
|
|
353
|
-
LIMIT 1
|
|
354
|
-
"""
|
|
355
|
-
async with conn.execute(query, (loop_id,)) as cursor:
|
|
356
|
-
row = await cursor.fetchone()
|
|
357
|
-
return row[0] if row else None
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
async def loop_exists(loop_id: str) -> bool:
|
|
361
|
-
"""Return True if any checkpoint row exists for this loop id.
|
|
362
|
-
|
|
363
|
-
Returns:
|
|
364
|
-
True if a checkpoint row exists, False otherwise.
|
|
365
|
-
"""
|
|
366
|
-
async with _connect() as conn:
|
|
367
|
-
if not await _table_exists(conn, "checkpoints"):
|
|
368
|
-
return False
|
|
369
|
-
|
|
370
|
-
query = "SELECT 1 FROM checkpoints WHERE thread_id = ? LIMIT 1"
|
|
371
|
-
async with conn.execute(query, (loop_id,)) as cursor:
|
|
372
|
-
row = await cursor.fetchone()
|
|
373
|
-
return row is not None
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
async def find_similar_loops(loop_id: str, limit: int = 3) -> list[str]:
|
|
377
|
-
"""Find loop ids that share the given prefix.
|
|
378
|
-
|
|
379
|
-
Args:
|
|
380
|
-
loop_id: Prefix to match against `checkpoints.thread_id`.
|
|
381
|
-
limit: Maximum number of matches.
|
|
382
|
-
|
|
383
|
-
Returns:
|
|
384
|
-
Matching loop ids (SQLite ``thread_id`` column values).
|
|
385
|
-
"""
|
|
386
|
-
async with _connect() as conn:
|
|
387
|
-
if not await _table_exists(conn, "checkpoints"):
|
|
388
|
-
return []
|
|
389
|
-
|
|
390
|
-
query = """
|
|
391
|
-
SELECT DISTINCT thread_id
|
|
392
|
-
FROM checkpoints
|
|
393
|
-
WHERE thread_id LIKE ?
|
|
394
|
-
ORDER BY thread_id
|
|
395
|
-
LIMIT ?
|
|
396
|
-
"""
|
|
397
|
-
prefix = loop_id + "%"
|
|
398
|
-
async with conn.execute(query, (prefix, limit)) as cursor:
|
|
399
|
-
rows = await cursor.fetchall()
|
|
400
|
-
return [r[0] for r in rows]
|
|
401
|
-
|
|
402
|
-
|
|
403
222
|
def get_loop_limit() -> int:
|
|
404
223
|
"""Default maximum loops to load for `/resume` when no explicit limit is set.
|
|
405
224
|
|
|
@@ -66,6 +66,7 @@ from soothe_cli.runtime.parse.tool_call_resolution import (
|
|
|
66
66
|
is_step_card_tool_scope,
|
|
67
67
|
materialize_ai_blocks_with_resolved_tools,
|
|
68
68
|
merge_tool_display_args,
|
|
69
|
+
predict_main_execute_tool_call_id,
|
|
69
70
|
resolve_stream_tool_name,
|
|
70
71
|
should_ingest_tool_for_step_stats,
|
|
71
72
|
tool_args_meaningful,
|
|
@@ -983,6 +984,16 @@ async def apply_tool_call_wire_update(
|
|
|
983
984
|
else:
|
|
984
985
|
step_w.add_tool_call(tcid, name, update_payload, raw_args=raw_args_stream)
|
|
985
986
|
adapter._tool_to_step[tcid] = step_w
|
|
987
|
+
elif name != "task":
|
|
988
|
+
update_payload = dict(display_args or {})
|
|
989
|
+
if not update_payload and raw_args_stream:
|
|
990
|
+
update_payload = {"_raw": raw_args_stream}
|
|
991
|
+
router.buffer_main_tool(
|
|
992
|
+
tcid,
|
|
993
|
+
name,
|
|
994
|
+
update_payload,
|
|
995
|
+
raw_args=raw_args_stream,
|
|
996
|
+
)
|
|
986
997
|
return True
|
|
987
998
|
|
|
988
999
|
_merge_buf, display_key = canonical_subgraph_tool_ids(ns_key, tcid, task_scope=ts)
|
|
@@ -2286,6 +2297,23 @@ async def execute_task_textual(
|
|
|
2286
2297
|
continue
|
|
2287
2298
|
|
|
2288
2299
|
lookup_id = str(buffer_id) if buffer_id is not None else ""
|
|
2300
|
+
if not lookup_id and is_step_scope and buffer_name:
|
|
2301
|
+
bound_step_id = ""
|
|
2302
|
+
if len(router.active_step_ids) == 1:
|
|
2303
|
+
bound_step_id = next(iter(router.active_step_ids))
|
|
2304
|
+
elif adapter._step_by_namespace.get(ns_key) is not None:
|
|
2305
|
+
bound_step_id = getattr(
|
|
2306
|
+
adapter._step_by_namespace[ns_key],
|
|
2307
|
+
"_step_id",
|
|
2308
|
+
"",
|
|
2309
|
+
)
|
|
2310
|
+
if bound_step_id:
|
|
2311
|
+
lookup_id = predict_main_execute_tool_call_id(
|
|
2312
|
+
bound_step_id,
|
|
2313
|
+
tool_call_id=lookup_id,
|
|
2314
|
+
tool_name=str(buffer_name),
|
|
2315
|
+
chunk_index=chunk_index,
|
|
2316
|
+
)
|
|
2289
2317
|
raw_args_stream = ""
|
|
2290
2318
|
pend_stream = (
|
|
2291
2319
|
pending_tool_calls_lc.get(lookup_id) if lookup_id else None
|
|
@@ -2821,7 +2849,12 @@ async def execute_task_textual(
|
|
|
2821
2849
|
data.get("assessment_reasoning", "")
|
|
2822
2850
|
).strip()
|
|
2823
2851
|
plan_reasoning = str(data.get("plan_reasoning", "")).strip()
|
|
2824
|
-
|
|
2852
|
+
next_action = str(data.get("next_action", "")).strip()
|
|
2853
|
+
if (
|
|
2854
|
+
not assessment_reasoning
|
|
2855
|
+
and not plan_reasoning
|
|
2856
|
+
and not next_action
|
|
2857
|
+
):
|
|
2825
2858
|
continue
|
|
2826
2859
|
pending_text = pending_text_by_namespace.get(ns_key, "")
|
|
2827
2860
|
if pending_text:
|
|
@@ -2837,7 +2870,7 @@ async def execute_task_textual(
|
|
|
2837
2870
|
pa_raw = data.get("plan_action", "")
|
|
2838
2871
|
plan_action = pa_raw if pa_raw in ("keep", "new") else ""
|
|
2839
2872
|
plan_widget = CognitionReasonMessage(
|
|
2840
|
-
next_action=
|
|
2873
|
+
next_action=next_action,
|
|
2841
2874
|
status=str(data.get("status", "")),
|
|
2842
2875
|
iteration=int(data.get("iteration", 0)),
|
|
2843
2876
|
plan_action=str(plan_action),
|
|
@@ -99,9 +99,6 @@ LC_TOOL_HOVER = "#FFCB91"
|
|
|
99
99
|
LC_COGNITION = "#56B6C2"
|
|
100
100
|
"""Agent loop step / cognition progress accent (distinct from tool amber)."""
|
|
101
101
|
|
|
102
|
-
LC_COGNITION_HOVER = "#7BC9D3"
|
|
103
|
-
"""Cognition progress hover — lighter teal for interactive feedback."""
|
|
104
|
-
|
|
105
102
|
|
|
106
103
|
# ---------------------------------------------------------------------------
|
|
107
104
|
# Unified Card Palette — Grayscale system for Goal/Plan/Step/Task cards (IG-468)
|
|
@@ -194,9 +191,6 @@ CARD_ERROR_LIGHT = "#BE185D"
|
|
|
194
191
|
LC_COGNITION = CARD_BORDER_DARK
|
|
195
192
|
"""Legacy alias — now uses unified card border color."""
|
|
196
193
|
|
|
197
|
-
LC_COGNITION_HOVER = CARD_BORDER_HOVER_DARK
|
|
198
|
-
"""Legacy alias — now uses unified card hover color."""
|
|
199
|
-
|
|
200
194
|
|
|
201
195
|
# ---------------------------------------------------------------------------
|
|
202
196
|
# Brand palette — light
|
|
@@ -255,12 +249,6 @@ LC_LIGHT_TOOL = LC_LIGHT_AMBER
|
|
|
255
249
|
LC_LIGHT_TOOL_HOVER = "#78350F"
|
|
256
250
|
"""Tool call hover (darkened for light bg contrast)."""
|
|
257
251
|
|
|
258
|
-
LC_LIGHT_COGNITION = CARD_BORDER_LIGHT
|
|
259
|
-
"""Legacy alias — now uses unified card border color."""
|
|
260
|
-
|
|
261
|
-
LC_LIGHT_COGNITION_HOVER = CARD_BORDER_HOVER_LIGHT
|
|
262
|
-
"""Legacy alias — now uses unified card hover color."""
|
|
263
|
-
|
|
264
252
|
|
|
265
253
|
# ---------------------------------------------------------------------------
|
|
266
254
|
# Semantic constants (ANSI color names for Rich console output)
|