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) <noreply@anthropic.com>
This commit is contained in:
Executable
+173
@@ -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-url> Langfuse instance URL (default: https://langfuse.saillant.cc)
|
||||
--artifact-dir <path> 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 <<EOF
|
||||
Langfuse health
|
||||
url: $LANGFUSE_URL
|
||||
status: $HEALTH_STATUS
|
||||
checked_at: $RUN_AT
|
||||
artifacts:
|
||||
- $RUN_JSON
|
||||
EOF
|
||||
if [[ ${#DEGRADED_REASONS[@]} -gt 0 ]]; then
|
||||
printf 'degraded reasons:\n'
|
||||
printf ' - %s\n' "${DEGRADED_REASONS[@]}"
|
||||
fi
|
||||
fi
|
||||
@@ -104,6 +104,23 @@ profile_overrides = dispatch.get("profile_overrides", {})
|
||||
family_defaults = dispatch.get("family_defaults", {})
|
||||
keyword_families = dispatch.get("keyword_families", {})
|
||||
|
||||
ROUTING_STRATEGIES = [
|
||||
"cheapest", "fastest", "best", "specific",
|
||||
"round-robin", "machine-aware",
|
||||
]
|
||||
|
||||
KNOWN_PROVIDERS = [
|
||||
"openai", "anthropic", "mistral", "google", "ollama",
|
||||
"groq", "deepseek", "openrouter", "together", "fireworks",
|
||||
"github-copilot", "litellm",
|
||||
]
|
||||
|
||||
LITELLM_UNIVERSAL_MODELS = [
|
||||
"litellm/gpt-4o", "litellm/claude-sonnet-4-20250514",
|
||||
"litellm/gemini-2.5-pro", "litellm/mistral-large-latest",
|
||||
"litellm/deepseek-chat", "litellm/llama-3.3-70b",
|
||||
]
|
||||
|
||||
def infer_family(profile_name):
|
||||
profile_name = (profile_name or "").lower()
|
||||
for family_name, keywords in keyword_families.items():
|
||||
@@ -119,11 +136,36 @@ def resolve_family(profile_name, family_name):
|
||||
return profile_overrides[profile_name]["family"]
|
||||
return infer_family(profile_name)
|
||||
|
||||
def resolve_strategy(profile_name, family_name):
|
||||
"""Resolve routing strategy: profile override > 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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user