From 23459e9e37215be7d50b78e20ef0088e1774562f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=27=C3=A9lectron=20rare?= <108685187+electron-rare@users.noreply.github.com> Date: Thu, 26 Mar 2026 09:57:58 +0100 Subject: [PATCH] feat(cockpit): integrate Mascarade v2 features + Langfuse health mascarade_runtime_health.sh: - Voice pipeline, eval harness, machine profile, Langfuse, KiCad Seeed MCP checks - New capabilities object in cockpit-v1 artifact mascarade_dispatch_mesh.sh: - MACHINE_AWARE routing strategy - github-copilot + litellm providers - LiteLLM universal models (6 models) - resolve_strategy() + apply_machine_aware_routing() langfuse_health.sh (new): - Standalone Langfuse health checker - cockpit-v1 contract JSON output runtime_ai_gateway.sh: - Langfuse as source component - mascarade_capabilities surface - Updated summary with Langfuse status Co-Authored-By: Claude Opus 4.6 (1M context) --- tools/cockpit/langfuse_health.sh | 173 ++++++++++++++++++++++ tools/cockpit/mascarade_dispatch_mesh.sh | 49 ++++++ tools/cockpit/mascarade_runtime_health.sh | 95 +++++++++++- tools/cockpit/runtime_ai_gateway.sh | 86 ++++++++++- 4 files changed, 397 insertions(+), 6 deletions(-) create mode 100755 tools/cockpit/langfuse_health.sh diff --git a/tools/cockpit/langfuse_health.sh b/tools/cockpit/langfuse_health.sh new file mode 100755 index 0000000..df07af4 --- /dev/null +++ b/tools/cockpit/langfuse_health.sh @@ -0,0 +1,173 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +LANGFUSE_URL="${LANGFUSE_URL:-https://langfuse.saillant.cc}" +ARTIFACT_DIR="${ROOT_DIR}/artifacts/ops/langfuse_health" +OUTPUT_MODE="text" +TIMEOUT_SEC=10 + +usage() { + cat <<'EOF' +Usage: langfuse_health.sh [options] + +Options: + --url Langfuse instance URL (default: https://langfuse.saillant.cc) + --artifact-dir Artifact directory (default: artifacts/ops/langfuse_health) + --json Emit cockpit-v1 JSON to stdout + --help Show this help +EOF +} + +timestamp_utc() { + date -u +"%Y-%m-%dT%H:%M:%SZ" +} + +timestamp_slug() { + date -u +"%Y%m%dT%H%M%SZ" +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --url) + [[ $# -ge 2 ]] || { echo "Missing value for --url" >&2; exit 2; } + LANGFUSE_URL="$2" + shift 2 + ;; + --artifact-dir) + [[ $# -ge 2 ]] || { echo "Missing value for --artifact-dir" >&2; exit 2; } + ARTIFACT_DIR="$2" + shift 2 + ;; + --json) + OUTPUT_MODE="json" + shift + ;; + --help|-h) + usage + exit 0 + ;; + *) + echo "Unknown option: $1" >&2 + usage >&2 + exit 2 + ;; + esac +done + +mkdir -p "$ARTIFACT_DIR" + +RUN_ID="$(timestamp_slug)" +RUN_JSON="$ARTIFACT_DIR/$RUN_ID.json" +RUN_AT="$(timestamp_utc)" + +HEALTH_STATUS="unknown" +HEALTH_BODY="" +DEGRADED_REASONS=() +NEXT_STEPS=() + +# Check Langfuse public health endpoint +HEALTH_BODY="$(curl -s --max-time "$TIMEOUT_SEC" "${LANGFUSE_URL}/api/public/health" 2>/dev/null || true)" + +if printf "%s" "$HEALTH_BODY" | grep -qi '"status"\s*:\s*"OK"'; then + HEALTH_STATUS="ok" +elif [[ -n "$HEALTH_BODY" ]] && printf "%s" "$HEALTH_BODY" | grep -q '{'; then + HEALTH_STATUS="degraded" + DEGRADED_REASONS+=("Langfuse health endpoint responded but status is not OK") + NEXT_STEPS+=("Inspect Langfuse instance at ${LANGFUSE_URL} for service issues.") +else + HEALTH_STATUS="unreachable" + DEGRADED_REASONS+=("Langfuse instance at ${LANGFUSE_URL} is unreachable") + NEXT_STEPS+=("Verify Langfuse is running and accessible at ${LANGFUSE_URL}.") +fi + +# Build the cockpit-v1 contract JSON +python3 - \ + "$RUN_AT" \ + "$LANGFUSE_URL" \ + "$HEALTH_STATUS" \ + "$HEALTH_BODY" \ + "$RUN_JSON" \ + --reasons \ + "${DEGRADED_REASONS[@]}" \ + --next-steps \ + "${NEXT_STEPS[@]}" \ + <<'PY' >"$RUN_JSON" +import json +import sys + +run_at = sys.argv[1] +langfuse_url = sys.argv[2] +health_status = sys.argv[3] +health_body = sys.argv[4] +run_json = sys.argv[5] +tail = sys.argv[6:] + +def split_sections(values): + reasons = [] + next_steps = [] + mode = None + for value in values: + if value == "--reasons": + mode = "reasons" + continue + if value == "--next-steps": + mode = "next_steps" + continue + if mode == "reasons": + reasons.append(value) + elif mode == "next_steps": + next_steps.append(value) + return reasons, next_steps + +degraded_reasons, next_steps = split_sections(tail) + +# Parse health body if valid JSON +health_data = {} +try: + health_data = json.loads(health_body) if health_body else {} +except Exception: + pass + +contract_status = "ok" if health_status == "ok" else ("degraded" if health_status == "degraded" else "error") + +payload = { + "contract_version": "cockpit-v1", + "component": "langfuse-health", + "action": "health-check", + "status": health_status, + "contract_status": contract_status, + "checked_at": run_at, + "langfuse_url": langfuse_url, + "owner": "Runtime-Companion", + "health_response": health_data, + "langfuse_status": health_data.get("status", "unknown"), + "langfuse_version": health_data.get("version", "unknown"), + "artifacts": [run_json], + "degraded_reasons": degraded_reasons, + "next_steps": next_steps, + "json_file": run_json, +} + +print(json.dumps(payload, ensure_ascii=False, indent=2)) +PY + +# Copy to latest +cp "$RUN_JSON" "$ARTIFACT_DIR/latest.json" + +if [[ "$OUTPUT_MODE" == "json" ]]; then + cat "$RUN_JSON" +else + cat < family default > cheapest.""" + override = profile_overrides.get(profile_name, {}) + strategy = override.get("strategy") + if not strategy: + strategy = family_defaults.get(family_name, {}).get("strategy") + if not strategy: + strategy = dispatch.get("default_strategy", "cheapest") + return strategy + +def apply_machine_aware_routing(host_order, target_map): + """MACHINE_AWARE strategy: prefer hosts with machine_profile available.""" + scored = [] + for host_id in host_order: + meta = target_map.get(host_id, {}) + has_profile = bool(meta.get("machine_profile") or meta.get("gpu") or meta.get("accelerator")) + priority = meta.get("priority", 99) + scored.append((0 if has_profile else 1, priority, host_id)) + scored.sort() + return [item[2] for item in scored] + family = resolve_family(profile, family_override) +strategy = resolve_strategy(profile, family) override = profile_overrides.get(profile, {}) host_order = override.get("host_order") or family_defaults.get(family, {}).get("host_order") or default_order host_order = [host for host in host_order if host in target_map] + [host for host in default_order if host not in host_order and host in target_map] +if strategy == "machine-aware": + host_order = apply_machine_aware_routing(host_order, target_map) + selected_target = host_order[0] if host_order else None selected_meta = target_map.get(selected_target, {}) @@ -132,6 +174,7 @@ summary = { "action": action, "profile": profile or None, "family": family, + "strategy": strategy, "dispatch_file": sys.argv[1], "registry_file": sys.argv[2], "default_host_order": default_order, @@ -141,6 +184,9 @@ summary = { "selected_priority": selected_meta.get("priority"), "selected_placement": selected_meta.get("placement"), "notes": family_defaults.get(family, {}).get("notes"), + "known_providers": KNOWN_PROVIDERS, + "routing_strategies": ROUTING_STRATEGIES, + "litellm_universal_models": LITELLM_UNIVERSAL_MODELS, } if json_output: @@ -149,8 +195,11 @@ else: print("Mascarade dispatch mesh") print(f"- action: {summary['action']}") print(f"- family: {summary['family']}") + print(f"- strategy: {summary['strategy']}") if summary["profile"]: print(f"- profile: {summary['profile']}") print(f"- route: {' -> '.join(summary['resolved_host_order'])}") print(f"- selected: {summary['selected_target']} ({summary['selected_host']})") + print(f"- providers: {', '.join(summary['known_providers'])}") + print(f"- litellm models: {len(summary['litellm_universal_models'])}") PY diff --git a/tools/cockpit/mascarade_runtime_health.sh b/tools/cockpit/mascarade_runtime_health.sh index 088b2d9..35733d3 100755 --- a/tools/cockpit/mascarade_runtime_health.sh +++ b/tools/cockpit/mascarade_runtime_health.sh @@ -122,6 +122,11 @@ AGENT_SMOKE_MODEL="" ROUTING_STATUS="unknown" MEMORY_STATUS="unknown" TRUST_LEVEL="inferred" +VOICE_PIPELINE_STATUS="unknown" +EVAL_HARNESS_STATUS="unknown" +MACHINE_PROFILE="" +LANGFUSE_STATUS="unknown" +KICAD_SEEED_TOOL_COUNT=0 RESUME_REF="kill-life:mascarade-runtime-health:${RUN_ID}:${HOST}:${AGENT}" STATUS="ok" CONTRACT_STATUS="ready" @@ -172,6 +177,36 @@ if curl -fsS --max-time 5 http://127.0.0.1:11434/api/tags >/dev/null 2>&1; then else echo "ollama_tags=ko" fi +voice_resp="$(curl -s --max-time 5 http://127.0.0.1:3100/voice/pipeline 2>/dev/null || true)" +if printf "%s" "$voice_resp" | grep -qi '"enabled"'; then + echo "voice_pipeline=ok" + voice_enabled="$(printf "%s" "$voice_resp" | python3 -c "import sys,json; print(json.load(sys.stdin).get(\"enabled\",False))" 2>/dev/null || echo "false")" + echo "voice_pipeline_enabled=$voice_enabled" +else + echo "voice_pipeline=ko" +fi +eval_resp="$(curl -s -o /dev/null -w "%{http_code}" --max-time 5 http://127.0.0.1:3100/v1/eval/runs 2>/dev/null || true)" +if [[ "$eval_resp" == "200" || "$eval_resp" == "401" ]]; then + echo "eval_harness=ok" +else + echo "eval_harness=ko" +fi +machine_profile="$(curl -s --max-time 5 http://127.0.0.1:3100/api/machine-profile 2>/dev/null || true)" +if printf "%s" "$machine_profile" | grep -q '{'; then + echo "machine_profile=ok" + echo "machine_profile_data=$machine_profile" +else + echo "machine_profile=ko" +fi +langfuse_resp="$(curl -s --max-time 5 http://127.0.0.1:3100/api/langfuse/status 2>/dev/null || true)" +if printf "%s" "$langfuse_resp" | grep -qi '"active"\|"enabled"\|"ok"'; then + echo "langfuse=ok" +else + echo "langfuse=ko" +fi +kicad_tools="$(curl -s --max-time 5 http://127.0.0.1:3100/api/mcp/tools 2>/dev/null || true)" +kicad_count="$(printf "%s" "$kicad_tools" | python3 -c "import sys,json; tools=json.load(sys.stdin); print(len([t for t in tools if \"kicad\" in t.get(\"name\",\"\").lower() or \"seeed\" in t.get(\"name\",\"\").lower()]))" 2>/dev/null || echo "0")" +echo "kicad_seeed_tool_count=$kicad_count" ' if ssh -o BatchMode=yes -o ConnectTimeout="$SSH_CONNECT_TIMEOUT" "$HOST" "bash -s" >"$REMOTE_CAPTURE" 2>>"$RUN_LOG" <<<"$REMOTE_SCRIPT"; then @@ -212,6 +247,36 @@ if ssh -o BatchMode=yes -o ConnectTimeout="$SSH_CONNECT_TIMEOUT" "$HOST" "bash - append_reason "le runtime Ollama ne répond pas sur $HOST" append_next_step "Contrôler mascarade-ollama-runtime et l'endpoint 127.0.0.1:11434." ;; + voice_pipeline=ok) + VOICE_PIPELINE_STATUS="ok" + ;; + voice_pipeline=ko) + VOICE_PIPELINE_STATUS="ko" + ;; + eval_harness=ok) + EVAL_HARNESS_STATUS="ok" + ;; + eval_harness=ko) + EVAL_HARNESS_STATUS="ko" + ;; + machine_profile=ok) + MACHINE_PROFILE="available" + ;; + machine_profile=ko) + MACHINE_PROFILE="unavailable" + ;; + machine_profile_data=*) + MACHINE_PROFILE="${line#machine_profile_data=}" + ;; + langfuse=ok) + LANGFUSE_STATUS="ok" + ;; + langfuse=ko) + LANGFUSE_STATUS="ko" + ;; + kicad_seeed_tool_count=*) + KICAD_SEEED_TOOL_COUNT="${line#kicad_seeed_tool_count=}" + ;; esac done <"$REMOTE_CAPTURE" else @@ -422,6 +487,11 @@ json_from_py \ "$MEMORY_STATUS" \ "$TRUST_LEVEL" \ "$RESUME_REF" \ + "$VOICE_PIPELINE_STATUS" \ + "$EVAL_HARNESS_STATUS" \ + "$MACHINE_PROFILE" \ + "$LANGFUSE_STATUS" \ + "$KICAD_SEEED_TOOL_COUNT" \ "${ARTIFACTS[@]}" \ --reasons \ "${DEGRADED_REASONS[@]}" \ @@ -432,7 +502,7 @@ import json import sys from pathlib import Path -run_at, host, agent, status, contract_status, ssh_ok, docker_status, api_agents_status, ollama_tags_status, agent_smoke_status, agent_smoke_provider, agent_smoke_model, run_log, run_json, api_container, core_container, ollama_container, routing_capture, routing_status, memory_capture, memory_status, trust_level, resume_ref, *tail = sys.argv[1:] +run_at, host, agent, status, contract_status, ssh_ok, docker_status, api_agents_status, ollama_tags_status, agent_smoke_status, agent_smoke_provider, agent_smoke_model, run_log, run_json, api_container, core_container, ollama_container, routing_capture, routing_status, memory_capture, memory_status, trust_level, resume_ref, voice_pipeline_status, eval_harness_status, machine_profile, langfuse_status, kicad_seeed_tool_count, *tail = sys.argv[1:] def split_sections(values): artifacts = [] @@ -509,6 +579,25 @@ payload = { "mascarade-core": core_container, "mascarade-ollama-runtime": ollama_container, }, + "voice_pipeline": { + "status": voice_pipeline_status, + }, + "eval_harness": { + "status": eval_harness_status, + }, + "langfuse": { + "status": langfuse_status, + }, + "kicad_seeed_mcp": { + "tool_count": int(kicad_seeed_tool_count) if kicad_seeed_tool_count.isdigit() else 0, + }, + }, + "machine_profile": json.loads(machine_profile) if machine_profile.startswith("{") else {"status": machine_profile or "unknown"}, + "capabilities": { + "voice_pipeline": voice_pipeline_status == "ok", + "eval_harness": eval_harness_status == "ok", + "langfuse_tracing": langfuse_status == "ok", + "kicad_seeed_mcp": int(kicad_seeed_tool_count) > 0 if kicad_seeed_tool_count.isdigit() else False, }, "artifacts": artifacts, "degraded_reasons": degraded_reasons, @@ -531,6 +620,10 @@ host: $HOST status: $STATUS agent smoke: $AGENT_SMOKE_STATUS (${AGENT_SMOKE_PROVIDER:-unknown}/${AGENT_SMOKE_MODEL:-unknown}) containers: api=${CONTAINERS[mascarade-api]} core=${CONTAINERS[mascarade-core]} ollama=${CONTAINERS[mascarade-ollama-runtime]} +voice_pipeline: $VOICE_PIPELINE_STATUS +eval_harness: $EVAL_HARNESS_STATUS +langfuse: $LANGFUSE_STATUS +kicad_seeed_tools: $KICAD_SEEED_TOOL_COUNT artifacts: - $RUN_LOG - $RUN_JSON diff --git a/tools/cockpit/runtime_ai_gateway.sh b/tools/cockpit/runtime_ai_gateway.sh index cc5b5f3..918f8aa 100755 --- a/tools/cockpit/runtime_ai_gateway.sh +++ b/tools/cockpit/runtime_ai_gateway.sh @@ -21,9 +21,12 @@ MESH_REPORT="" INTELLIGENCE_SCRIPT="${RUNTIME_GATEWAY_INTELLIGENCE_SCRIPT:-${ROOT_DIR}/tools/cockpit/intelligence_tui.sh}" MESH_SCRIPT="${RUNTIME_GATEWAY_MESH_SCRIPT:-${ROOT_DIR}/tools/cockpit/mesh_health_check.sh}" MASCARADE_SCRIPT="${RUNTIME_GATEWAY_MASCARADE_SCRIPT:-${ROOT_DIR}/tools/cockpit/mascarade_runtime_health.sh}" +LANGFUSE_SCRIPT="${RUNTIME_GATEWAY_LANGFUSE_SCRIPT:-${ROOT_DIR}/tools/cockpit/langfuse_health.sh}" +LANGFUSE_REPORT="${ROOT_DIR}/artifacts/ops/langfuse_health/latest.json" INTELLIGENCE_TIMEOUT_SEC="${RUNTIME_GATEWAY_INTELLIGENCE_TIMEOUT_SEC:-15}" MESH_TIMEOUT_SEC="${RUNTIME_GATEWAY_MESH_TIMEOUT_SEC:-15}" MASCARADE_TIMEOUT_SEC="${RUNTIME_GATEWAY_MASCARADE_TIMEOUT_SEC:-15}" +LANGFUSE_TIMEOUT_SEC="${RUNTIME_GATEWAY_LANGFUSE_TIMEOUT_SEC:-10}" usage() { cat <<'EOF' @@ -118,6 +121,14 @@ def fallback(reason: str, detail: str) -> dict[str, object]: "host": "unknown", } ) + elif label == "langfuse": + payload.update( + { + "langfuse_status": "unknown", + "langfuse_version": "unknown", + "langfuse_url": "unknown", + } + ) return payload @@ -183,10 +194,16 @@ refresh_sources() { run_refresh_probe mascarade "${MASCARADE_TIMEOUT_SEC}" "${mascarade_out}" "${LOAD_PROFILE}" \ bash "${MASCARADE_SCRIPT}" --json MASCARADE_REPORT="${mascarade_out}" + + local langfuse_out="${ARTIFACT_DIR}/langfuse-${STAMP}.json" + log_line "INFO" "refreshing Langfuse health" + run_refresh_probe langfuse "${LANGFUSE_TIMEOUT_SEC}" "${langfuse_out}" "${LOAD_PROFILE}" \ + bash "${LANGFUSE_SCRIPT}" --json + LANGFUSE_REPORT="${langfuse_out}" } emit_status_json() { - python3 - "${ROOT_DIR}" "${INTELLIGENCE_REPORT}" "${MESH_REPORT}" "${MASCARADE_REPORT}" "${RUN_LOG}" <<'PY' + python3 - "${ROOT_DIR}" "${INTELLIGENCE_REPORT}" "${MESH_REPORT}" "${MASCARADE_REPORT}" "${LANGFUSE_REPORT}" "${RUN_LOG}" <<'PY' from __future__ import annotations import json @@ -198,7 +215,8 @@ root = Path(sys.argv[1]) intelligence_path = Path(sys.argv[2]) if sys.argv[2] else None mesh_path = Path(sys.argv[3]) if sys.argv[3] else None mascarade_path = Path(sys.argv[4]) if sys.argv[4] else None -run_log = Path(sys.argv[5]) +langfuse_path = Path(sys.argv[5]) if sys.argv[5] else None +run_log = Path(sys.argv[6]) def read_json(path: Path | None) -> dict[str, object] | None: @@ -416,6 +434,45 @@ def build_web_platform_surface(ia_payload: dict[str, object] | None) -> tuple[di return surface, next_steps +def build_langfuse_surface(payload: dict[str, object] | None, path: Path | None) -> tuple[dict[str, object], list[str]]: + status = normalize((payload or {}).get("status")) + langfuse_status = (payload or {}).get("langfuse_status", "unknown") + langfuse_version = (payload or {}).get("langfuse_version", "unknown") + langfuse_url = (payload or {}).get("langfuse_url", "unknown") + degraded = listify((payload or {}).get("degraded_reasons")) + if status != "ready" and not degraded: + degraded = [f"langfuse-{status}"] + next_steps = collect_next_steps(payload) + if status != "ready" and not next_steps: + next_steps = ["Check Langfuse instance health and verify tracing connectivity."] + surface = { + "status": status, + "summary_short": compact( + f"Langfuse {status}; version={langfuse_version}; url={langfuse_url}.", + 220, + ), + "evidence": path_evidence("langfuse", path), + "degraded_reasons": degraded, + "upstreams": ["tools/cockpit/langfuse_health.sh"], + "langfuse_status": langfuse_status, + "langfuse_version": langfuse_version, + "langfuse_url": langfuse_url, + "path": relative_path(path), + } + return surface, next_steps + + +def build_mascarade_capabilities(mascarade_payload: dict[str, object] | None) -> dict[str, object]: + """Extract Mascarade capabilities from the runtime health payload.""" + caps = (mascarade_payload or {}).get("capabilities", {}) + return { + "voice_pipeline": bool(caps.get("voice_pipeline")), + "eval_harness": bool(caps.get("eval_harness")), + "langfuse_tracing": bool(caps.get("langfuse_tracing")), + "kicad_seeed_mcp": bool(caps.get("kicad_seeed_mcp")), + } + + def build_firmware_cad_surface(root: Path) -> tuple[dict[str, object], dict[str, object], list[str]]: firmware_main = root / "firmware" / "src" / "main.cpp" firmware_platformio = root / "firmware" / "platformio.ini" @@ -569,12 +626,15 @@ def build_firmware_cad_surface(root: Path) -> tuple[dict[str, object], dict[str, intelligence = read_json(intelligence_path) mesh = read_json(mesh_path) mascarade = read_json(mascarade_path) +langfuse = read_json(langfuse_path) runtime_surface, runtime_next = build_runtime_surface(mascarade, mascarade_path) mcp_surface, mcp_next = build_mcp_surface(mesh, mesh_path) ia_surface, ia_next = build_ia_surface(intelligence, intelligence_path) firmware_cad_surface, firmware_cad_summary, firmware_cad_next = build_firmware_cad_surface(root) web_platform_surface, web_platform_next = build_web_platform_surface(intelligence) +langfuse_surface, langfuse_next = build_langfuse_surface(langfuse, langfuse_path) +mascarade_capabilities = build_mascarade_capabilities(mascarade) source_states = [runtime_surface["status"], mcp_surface["status"], ia_surface["status"], web_platform_surface["status"]] if any(state == "blocked" for state in source_states): @@ -592,6 +652,7 @@ for label, path, surface, extra_next in ( ("runtime", mascarade_path, runtime_surface, runtime_next), ("mcp", mesh_path, mcp_surface, mcp_next), ("ia", intelligence_path, ia_surface, ia_next), + ("langfuse", langfuse_path, langfuse_surface, langfuse_next), ): if path and path.exists(): rel = relative_path(path) @@ -629,8 +690,9 @@ summary_short = compact( f"mcp={mcp_surface['status']} ({mcp_surface['summary_short']}) | " f"ia={ia_surface['status']} ({ia_surface['summary_short']}) | " f"firmware_cad={firmware_cad_surface['status']} ({firmware_cad_surface['summary_short']}) | " - f"web_platform={web_platform_surface['status']} ({web_platform_surface['summary_short']})", - 420, + f"web_platform={web_platform_surface['status']} ({web_platform_surface['summary_short']}) | " + f"langfuse={langfuse_surface['status']} ({langfuse_surface['summary_short']})", + 500, ) payload = { @@ -647,6 +709,7 @@ payload = { "tools/cockpit/intelligence_tui.sh", "tools/cockpit/mesh_health_check.sh", "tools/cockpit/mascarade_runtime_health.sh", + "tools/cockpit/langfuse_health.sh", "docs/AI_WORKFLOWS.md", ], "summary_short": summary_short, @@ -654,7 +717,7 @@ payload = { "degraded_reasons": degraded_reasons, "next_steps": next_steps[:5], "goal": "Publier une sante canonique runtime/MCP/IA exploitable par cockpit, docs et extensions.", - "state": f"runtime={runtime_surface['status']} mcp={mcp_surface['status']} ia={ia_surface['status']} web_platform={web_platform_surface['status']}", + "state": f"runtime={runtime_surface['status']} mcp={mcp_surface['status']} ia={ia_surface['status']} web_platform={web_platform_surface['status']} langfuse={langfuse_surface['status']}", "blockers": degraded_reasons, "next": next_steps[:3], "owner": "Runtime-Companion/MCP-Health", @@ -667,7 +730,9 @@ payload = { "ia": ia_surface, "firmware_cad": firmware_cad_surface, "web_platform": web_platform_surface, + "langfuse": langfuse_surface, }, + "mascarade_capabilities": mascarade_capabilities, "sources": { "intelligence": { "path": relative_path(intelligence_path), @@ -702,6 +767,13 @@ payload = { "queue_depth": web_platform_surface.get("queue_depth"), "probes": web_platform_surface.get("probes", {}), }, + "langfuse": { + "path": relative_path(langfuse_path), + "status": langfuse_surface["status"], + "langfuse_status": langfuse_surface.get("langfuse_status", "unknown"), + "langfuse_version": langfuse_surface.get("langfuse_version", "unknown"), + "langfuse_url": langfuse_surface.get("langfuse_url", "unknown"), + }, }, "summary_short_artifacts": { "firmware_cad": { @@ -839,6 +911,10 @@ while [ "$#" -gt 0 ]; do MASCARADE_REPORT="${2:-}" shift 2 ;; + --langfuse-report) + LANGFUSE_REPORT="${2:-}" + shift 2 + ;; -h|--help) usage exit 0