Files
wehub-resource-sync 4b6817381b
CI (OpenClaw E2E) / openclaw test (push) Has been cancelled
CI / coverage-report (push) Has been cancelled
CI / test-kubernetes (push) Has been cancelled
CI / should-run-thorough (push) Has been cancelled
CI / test-thorough (cloudwatch-demo) (push) Has been cancelled
CI / test-thorough (flink-ecs) (push) Has been cancelled
CI / test-thorough (upstream-lambda) (push) Has been cancelled
CI / test-thorough (prefect-ecs-fargate) (push) Has been cancelled
Release / build-binaries (zip, opensre.exe, onefile, windows-latest, windows-x64) (push) Has been cancelled
Benchmark image — build + push to ECR (any adapter) / build + push (push) Has been cancelled
CI / quality (ubuntu-latest) (push) Has been cancelled
CI / test (tools-runtime) (push) Has been cancelled
CI / test (e2e-general) (push) Has been cancelled
CI / test (cli-runtime) (push) Has been cancelled
CI / test (e2e-provider-and-openclaw) (push) Has been cancelled
CI / test (integrations-and-misc) (push) Has been cancelled
Release / verify (push) Has been cancelled
Release / build-python-dist (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-15-intel, darwin-x64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-latest, darwin-arm64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04, linux-x64) (push) Has been cancelled
Release / publish-release (push) Has been cancelled
Release / publish-main-release (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-checks (no-LLM) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-live shard ${{ matrix.shard_index }} (push) Has been cancelled
Release / prepare (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04-arm, linux-arm64) (push) Has been cancelled
Synthetic Deterministic Tests / Synthetic offline (deterministic) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:10:45 +08:00

180 lines
6.0 KiB
Python

"""Format terminal-turn outcomes for prompt-log and PostHog analytics."""
from __future__ import annotations
_ANALYTICS_OUTPUT_MAX_CHARS = 8_000
# Slash commands whose handlers attach to the real TTY (wizards, pickers). Analytics
# should record structured success/failure, not full interactive transcripts.
_INTERACTIVE_WIZARD_SLASH_ROOTS: frozenset[str] = frozenset(
{
"/onboard",
"/auth",
"/login",
"/integrations",
"/mcp",
}
)
_INTERACTIVE_WIZARD_SLASH_PATHS: frozenset[str] = frozenset(
{
"/integrations setup",
"/integrations remove",
"/mcp connect",
"/mcp disconnect",
"/auth login",
"/auth logout",
}
)
# Slash commands where console capture is noisy or redundant. Substantive output for
# investigations is stored on the companion ``alert`` history row instead.
_SUMMARY_ONLY_SLASH_ROOTS: frozenset[str] = frozenset(
{
"/",
"/help",
"/?",
"/investigate",
}
)
def slash_command_is_interactive_wizard(command_line: str) -> bool:
"""True when ``command_line`` names a multi-step TTY wizard or picker."""
stripped = command_line.strip()
if not stripped.startswith("/"):
return False
parts = stripped.split()
root = parts[0].lower()
if root in _INTERACTIVE_WIZARD_SLASH_ROOTS and len(parts) == 1:
return True
if len(parts) >= 2:
path = f"{root} {parts[1].lower()}"
if path in _INTERACTIVE_WIZARD_SLASH_PATHS:
return True
return False
def slash_command_is_summary_only(command_line: str) -> bool:
"""True when analytics should omit captured console text for this slash command."""
stripped = command_line.strip()
if not stripped.startswith("/"):
return False
parts = stripped.split()
root = parts[0].lower()
if root in _SUMMARY_ONLY_SLASH_ROOTS:
return True
return slash_command_is_interactive_wizard(command_line)
def truncate_analytics_text(text: str, *, max_chars: int = _ANALYTICS_OUTPUT_MAX_CHARS) -> str:
cleaned = text.strip()
if len(cleaned) <= max_chars:
return cleaned
return f"{cleaned[: max_chars - 20].rstrip()}… [truncated]"
def format_wizard_cli_outcome(args: list[str], *, exit_code: int | None) -> str:
"""Structured outcome for delegated interactive CLI wizards (e.g. ``/onboard``)."""
command = " ".join(["opensre", *args]).strip()
if exit_code is None:
return f"{command}: interactive wizard cancelled"
if exit_code == 0:
return f"{command}: interactive wizard completed successfully"
return f"{command}: interactive wizard failed (exit {exit_code})"
def _investigation_report_excerpt(final_state: dict[str, object]) -> str:
sections: list[str] = []
root = final_state.get("root_cause")
if isinstance(root, str) and root.strip():
sections.append(f"Root cause: {root.strip()}")
for key in ("problem_md", "slack_message"):
body = final_state.get(key)
if isinstance(body, str) and body.strip():
sections.append(body.strip())
break
return "\n\n".join(sections)
def format_investigation_outcome(
target: str,
*,
final_state: dict[str, object] | None = None,
background: bool = False,
error_message: str = "",
status: str | None = None,
) -> str:
"""Human-readable investigation outcome body for analytics."""
label = target.strip() or "investigation"
if background:
return f"investigation started in background: {label}"
if status == "cancelled":
return f"investigation_cancelled ({label}): aborted by user"
if status == "failed" or (final_state is None and error_message):
reason = error_message.strip() or "investigation failed"
return truncate_analytics_text(f"investigation_failed ({label}):\n{reason}")
if final_state is None:
return f"investigation_failed ({label}): investigation did not complete"
excerpt = _investigation_report_excerpt(final_state)
if excerpt:
return truncate_analytics_text(f"investigation completed ({label}):\n{excerpt}")
return f"investigation completed: {label}"
def format_investigation_terminal_outcome(
command_line: str,
*,
target: str,
ok: bool,
final_state: dict[str, object] | None = None,
background: bool = False,
error_message: str = "",
status: str | None = None,
) -> str:
"""Two-line terminal analytics payload for ``/investigate`` turns."""
if background:
return format_investigation_outcome(target, background=True)
resolved_status = status or ("succeeded" if ok and final_state is not None else "failed")
if resolved_status == "completed":
resolved_status = "succeeded"
slash_status = {
"succeeded": "succeeded",
"failed": "failed",
"cancelled": "cancelled",
}.get(resolved_status, "failed")
prefix = f"slash {command_line.strip()} ({slash_status})"
body = format_investigation_outcome(
target,
final_state=final_state,
error_message=error_message,
status="cancelled"
if resolved_status == "cancelled"
else ("failed" if resolved_status == "failed" else None),
)
return truncate_analytics_text(f"{prefix}\n{body}")
def format_terminal_turn_outcome(
command_line: str,
*,
kind: str,
ok: bool,
captured_output: str = "",
outcome_hint: str | None = None,
include_captured_on_summary_only: bool = False,
) -> str:
"""Build the analytics payload for one handled terminal turn."""
if outcome_hint and outcome_hint.strip():
return truncate_analytics_text(outcome_hint.strip())
status = "succeeded" if ok else "failed"
prefix = f"{kind} {command_line.strip()} ({status})"
summary_only = kind == "slash" and slash_command_is_summary_only(command_line)
if summary_only and not include_captured_on_summary_only:
return prefix
if captured_output:
return truncate_analytics_text(f"{prefix}\n{captured_output}")
return prefix