#!/usr/bin/env bash set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # shellcheck disable=SC1091 source "${SCRIPT_DIR}/lib_ops_center.sh" ops_center_load_config "${SCRIPT_DIR}" OPS_CENTER_CURL_CONNECT_TIMEOUT="${OPS_CENTER_CURL_CONNECT_TIMEOUT:-3}" OPS_CENTER_CURL_MAX_TIME="${OPS_CENTER_CURL_MAX_TIME:-20}" OPS_CENTER_CURL_MAX_TIME_CODEX_BRIEF="${OPS_CENTER_CURL_MAX_TIME_CODEX_BRIEF:-45}" OPS_CENTER_REPORT_COMMAND_TIMEOUT_SECONDS="${OPS_CENTER_REPORT_COMMAND_TIMEOUT_SECONDS:-25}" OPS_CENTER_DOCTOR_REPORT_TIMEOUT_SECONDS="${OPS_CENTER_DOCTOR_REPORT_TIMEOUT_SECONDS:-${OPS_CENTER_REPORT_COMMAND_TIMEOUT_SECONDS}}" usage() { cat <<'EOF' usage: bash domain-api/deploy/multi-region/drive_ops_center.sh [args...] commands: help config contracts [mainland_base_url] [contract_key] env-audit [mainland_base_url] runtime-refresh-check [mainland_base_url] runtime-refresh-recover [mainland_base_url] stack-diagnosis [mainland_base_url] [summary|full] stack-check [mainland_base_url] [summary|full] (backward-compatible alias of stack-diagnosis) go-live-check [mainland_base_url] [overseas_base_url] [summary|full] go-live-recover [mainland_base_url] [overseas_base_url] go-live-export [report_dir] [mainland_base_url] [overseas_base_url] go-live-review [manifest.json|report_dir] go-live-signoff [manifest.json|report_dir|mainland_base_url] [overseas_base_url] stack-next [mainland_base_url] [preview|run] [confirm] [requested_by] release-package [skip-smoke] release-verify [archive_path] [hash_path] release-prepare release-show release-launchpad [mainland_base_url] [channel] release-preview [mainland_base_url] release-preview-smart [mainland_base_url] release-import-latest [mainland_base_url] publish-latest [mainland_base_url] [confirm] [skip-smoke] doctor-export [report_dir] [mainland_base_url] [overseas_base_url] doctor-decision [manifest_path|report_dir|mainland_base_url] [overseas_base_url] doctor [mainland_base_url] [overseas_base_url] ops-plane [mainland_base_url] inspection [mainland_base_url] participation [mainland_base_url] topology [mainland_base_url] [overseas_base_url] nodes [mainland_base_url] stack-first-gap-handover [mainland_base_url] node-bind-ssh [mainland_base_url] [ssh_port] [title] node-handover [mainland_base_url] node-onboarding [mainland_base_url] node-recover [mainland_base_url] [confirm] [requested_by] node-bootstrap-preview [mainland_base_url] [requested_by] node-bootstrap-run [mainland_base_url] [requested_by] node-acceptance-plan [mainland_base_url] [requested_by] node-acceptance-run [mainland_base_url] [requested_by] scene-node-log [mainland_base_url] [limit] [key|full] log-sync-check [mainland_base_url] log-sync-recover [mainland_base_url] [key|full] [confirm] [requested_by] agent-gap-check [mainland_base_url] agent-gap-recover [mainland_base_url] [confirm] [requested_by] agent-gap-export [report_dir] [mainland_base_url] [requested_by] node-bootstrap-plan [mainland_base_url] [issued_by] [expires_in_hours] [control_plane_base_url] [root_dir] queue-status [mainland_base_url] queue-records [mainland_base_url] [state] queue-flush [mainland_base_url] [limit] [requested_by] queue-replay [mainland_base_url] [limit] [requested_by] queue-replay-record [mainland_base_url] [requested_by] queue-discard-record [mainland_base_url] [requested_by] driver-feed [mainland_base_url] activity-stream [mainland_base_url] [key=value ...] activity-preview [mainland_base_url] [activity_key] [requested_by] driver-focus-preview [mainland_base_url] [entry_key] [requested_by] driver-focus-run [mainland_base_url] [entry_key] [primary|secondary] [confirm] [requested_by] codex-brief [mainland_base_url] codex-focus-preview [mainland_base_url] [entry_key] [requested_by] codex-focus-run [mainland_base_url] [entry_key] [confirm] [requested_by] release-hub [mainland_base_url] [args...] driver-preview [mainland_base_url] [action_payload_json|@payload.json] [requested_by] driver-resolve [mainland_base_url] [action_payload_json|@payload.json] [requested_by] driver-run [mainland_base_url] [action_payload_json|@payload.json] [confirm] [requested_by] driver [mainland_base_url] [action_payload_json|@payload.json] [requested_by] runbook-preview [mainland_base_url] [action_payload_json|@payload.json] [requested_by] runbook-run [mainland_base_url] [primary|secondary] [action_payload_json|@payload.json] [confirm] [requested_by] runbook-resolve [mainland_base_url] [primary|secondary] [action_payload_json|@payload.json] [requested_by] runbook-sequence [mainland_base_url] [primary|secondary] [action_payload_json|@payload.json] [requested_by] smart-latest [mainland_base_url] [confirm] smart-release [mainland_base_url] [confirm] agent-plan [mainland_base_url] [node_region] [node_role] [control_plane_base_url] [root_dir] agent-plan-export [report_dir] [mainland_base_url] [node_region] [node_role] [control_plane_base_url] [root_dir] agent-check [service_name] [env_file] examples: bash domain-api/deploy/multi-region/drive_ops_center.sh config bash domain-api/deploy/multi-region/drive_ops_center.sh contracts bash domain-api/deploy/multi-region/drive_ops_center.sh env-audit bash domain-api/deploy/multi-region/drive_ops_center.sh runtime-refresh-check bash domain-api/deploy/multi-region/drive_ops_center.sh runtime-refresh-recover bash domain-api/deploy/multi-region/drive_ops_center.sh stack-diagnosis bash domain-api/deploy/multi-region/drive_ops_center.sh contracts ops_stack_diagnosis_contract bash domain-api/deploy/multi-region/drive_ops_center.sh contracts http://127.0.0.1:8100 release_hub_contract bash domain-api/deploy/multi-region/drive_ops_center.sh go-live-recover bash domain-api/deploy/multi-region/drive_ops_center.sh go-live-check bash domain-api/deploy/multi-region/drive_ops_center.sh go-live-export bash domain-api/deploy/multi-region/drive_ops_center.sh go-live-review bash domain-api/deploy/multi-region/drive_ops_center.sh go-live-signoff bash domain-api/deploy/multi-region/drive_ops_center.sh go-live-signoff /path/to/go-live-bundle bash domain-api/deploy/multi-region/drive_ops_center.sh stack-next bash domain-api/deploy/multi-region/drive_ops_center.sh stack-next run confirm bash domain-api/deploy/multi-region/drive_ops_center.sh release-package bash domain-api/deploy/multi-region/drive_ops_center.sh release-show bash domain-api/deploy/multi-region/drive_ops_center.sh release-launchpad bash domain-api/deploy/multi-region/drive_ops_center.sh release-preview bash domain-api/deploy/multi-region/drive_ops_center.sh release-preview-smart worker bash domain-api/deploy/multi-region/drive_ops_center.sh release-import-latest bash domain-api/deploy/multi-region/drive_ops_center.sh publish-latest worker bash domain-api/deploy/multi-region/drive_ops_center.sh doctor-export bash domain-api/deploy/multi-region/drive_ops_center.sh doctor-decision bash domain-api/deploy/multi-region/drive_ops_center.sh doctor http://127.0.0.1:8100 http://152.53.37.118:8100 bash domain-api/deploy/multi-region/drive_ops_center.sh nodes bash domain-api/deploy/multi-region/drive_ops_center.sh stack-first-gap-handover bash domain-api/deploy/multi-region/drive_ops_center.sh node-bind-ssh mainland-worker-01 121.204.244.248 root bash domain-api/deploy/multi-region/drive_ops_center.sh node-bind-ssh http://127.0.0.1:8100 mainland-controller-01 121.204.244.188 root 22 mainland-controller bash domain-api/deploy/multi-region/drive_ops_center.sh node-handover mainland-worker-01 bash domain-api/deploy/multi-region/drive_ops_center.sh node-onboarding mainland-worker-01 bash domain-api/deploy/multi-region/drive_ops_center.sh node-recover mainland-worker-01 bash domain-api/deploy/multi-region/drive_ops_center.sh node-recover mainland-worker-01 confirm ops-center bash domain-api/deploy/multi-region/drive_ops_center.sh node-bootstrap-preview mainland-worker-01 bash domain-api/deploy/multi-region/drive_ops_center.sh node-bootstrap-run mainland-worker-01 ops-center bash domain-api/deploy/multi-region/drive_ops_center.sh node-acceptance-plan mainland-worker-01 bash domain-api/deploy/multi-region/drive_ops_center.sh node-acceptance-run mainland-worker-01 ops-center bash domain-api/deploy/multi-region/drive_ops_center.sh scene-node-log mainland-worker-01 bash domain-api/deploy/multi-region/drive_ops_center.sh scene-node-log http://127.0.0.1:8100 mainland-worker-01 120 full bash domain-api/deploy/multi-region/drive_ops_center.sh log-sync-check bash domain-api/deploy/multi-region/drive_ops_center.sh log-sync-recover bash domain-api/deploy/multi-region/drive_ops_center.sh log-sync-recover http://127.0.0.1:8100 full confirm cli/ops bash domain-api/deploy/multi-region/drive_ops_center.sh agent-gap-check bash domain-api/deploy/multi-region/drive_ops_center.sh agent-gap-recover bash domain-api/deploy/multi-region/drive_ops_center.sh agent-gap-export bash domain-api/deploy/multi-region/drive_ops_center.sh node-bootstrap-plan mainland-worker-01 bash domain-api/deploy/multi-region/drive_ops_center.sh queue-status mainland-worker-01 bash domain-api/deploy/multi-region/drive_ops_center.sh queue-records mainland-worker-01 dead_letter bash domain-api/deploy/multi-region/drive_ops_center.sh queue-flush mainland-worker-01 20 bash domain-api/deploy/multi-region/drive_ops_center.sh queue-replay mainland-worker-01 20 bash domain-api/deploy/multi-region/drive_ops_center.sh queue-replay-record mainland-worker-01 event-ops-123-2 bash domain-api/deploy/multi-region/drive_ops_center.sh queue-discard-record mainland-worker-01 event-ops-123-2 "确认已人工归档" bash domain-api/deploy/multi-region/drive_ops_center.sh driver-feed bash domain-api/deploy/multi-region/drive_ops_center.sh activity-stream bash domain-api/deploy/multi-region/drive_ops_center.sh activity-stream kind=ops_job status=running bash domain-api/deploy/multi-region/drive_ops_center.sh activity-preview bash domain-api/deploy/multi-region/drive_ops_center.sh driver-focus-preview bash domain-api/deploy/multi-region/drive_ops_center.sh driver-focus-run bash domain-api/deploy/multi-region/drive_ops_center.sh codex-brief bash domain-api/deploy/multi-region/drive_ops_center.sh codex-focus-preview bash domain-api/deploy/multi-region/drive_ops_center.sh codex-focus-run bash domain-api/deploy/multi-region/drive_ops_center.sh release-hub http://127.0.0.1:8100 latest-package bash domain-api/deploy/multi-region/drive_ops_center.sh smart-release http://127.0.0.1:8100 12 worker bash domain-api/deploy/multi-region/drive_ops_center.sh driver-preview http://127.0.0.1:8100 open_release_dialog bash domain-api/deploy/multi-region/drive_ops_center.sh driver-resolve http://127.0.0.1:8100 enable_log_sync_key bash domain-api/deploy/multi-region/drive_ops_center.sh driver-run http://127.0.0.1:8100 enable_log_sync_key bash domain-api/deploy/multi-region/drive_ops_center.sh driver http://127.0.0.1:8100 open_release_dialog bash domain-api/deploy/multi-region/drive_ops_center.sh runbook-preview release_progression bash domain-api/deploy/multi-region/drive_ops_center.sh runbook-run release_progression bash domain-api/deploy/multi-region/drive_ops_center.sh runbook-resolve release_progression bash domain-api/deploy/multi-region/drive_ops_center.sh runbook-sequence release_progression design: - this is the single overseas control-plane entrypoint - it does not execute remote shell directly - it first delegates to contract preview APIs, then to ops/release execute APIs EOF } COMMAND="${1:-help}" shift || true run_step() { local title="$1" shift echo "============================================================" echo "${title}" echo "============================================================" "$@" echo } print_json_or_raw() { local raw_text="${1:-}" python3 - "${raw_text}" <<'PY' import json import sys raw_text = str(sys.argv[1] or "") try: payload = json.loads(raw_text) except Exception: print(raw_text) else: print(json.dumps(payload, ensure_ascii=False, indent=2)) PY } request_with_status_custom() { local method="$1" local url="$2" local body="${3:-}" local connect_timeout="${4:-${OPS_CENTER_CURL_CONNECT_TIMEOUT}}" local max_time="${5:-${OPS_CENTER_CURL_MAX_TIME}}" if [[ "${method}" == "POST" ]]; then curl -sS -X POST "${url}" \ --connect-timeout "${connect_timeout}" \ --max-time "${max_time}" \ -H 'Content-Type: application/json' \ -d "${body}" \ -w $'\nHTTP_STATUS=%{http_code}' else curl -sS \ --connect-timeout "${connect_timeout}" \ --max-time "${max_time}" \ "${url}" \ -w $'\nHTTP_STATUS=%{http_code}' fi } request_with_status() { local method="$1" local url="$2" local body="${3:-}" request_with_status_custom "${method}" "${url}" "${body}" \ "${OPS_CENTER_CURL_CONNECT_TIMEOUT}" "${OPS_CENTER_CURL_MAX_TIME}" } print_config() { local requested_profile="${1:-${OPS_CENTER_ACTIVE_PROFILE}}" local saved_profile="${OPS_CENTER_ACTIVE_PROFILE}" local saved_profile_key="${OPS_CENTER_ACTIVE_PROFILE_KEY}" OPS_CENTER_ACTIVE_PROFILE="${requested_profile}" OPS_CENTER_ACTIVE_PROFILE_KEY="$(ops_center_normalize_profile_key "${OPS_CENTER_ACTIVE_PROFILE}")" python3 - <<'PY' "${OPS_CENTER_CONFIG_FILE}" \ "${OPS_CENTER_ACTIVE_PROFILE}" \ "${OPS_CENTER_PROFILE_NAMES}" \ "$(ops_center_resolve_mainland_api_base_url)" \ "$(ops_center_resolve_overseas_api_base_url)" \ "$(ops_center_resolve_control_plane_base_url)" \ "$(ops_center_resolve_requested_by)" \ "$(ops_center_resolve_issued_by)" \ "$(ops_center_resolve_expires_in_hours)" \ "$(ops_center_resolve_root_dir)" \ "$(ops_center_resolve_node_agent_service_name)" \ "$(ops_center_resolve_node_agent_env_file)" \ "${OPS_CENTER_REPORT_ROOT}" import json import sys from pathlib import Path config_path = Path(sys.argv[1]) print(json.dumps({ "config_file": str(config_path), "config_exists": config_path.exists(), "active_profile": sys.argv[2], "available_profiles": [item.strip() for item in str(sys.argv[3]).split(",") if item.strip()], "ops_mainland_api_base_url": sys.argv[4], "ops_overseas_api_base_url": sys.argv[5], "ops_default_control_plane_base_url": sys.argv[6], "ops_default_requested_by": sys.argv[7], "ops_default_issued_by": sys.argv[8], "ops_default_expires_in_hours": int(sys.argv[9] or 72), "ops_default_root_dir": sys.argv[10], "ops_node_agent_service_name": sys.argv[11], "ops_node_agent_env_file": sys.argv[12], "ops_report_root": sys.argv[13], }, ensure_ascii=False, indent=2)) PY OPS_CENTER_ACTIVE_PROFILE="${saved_profile}" OPS_CENTER_ACTIVE_PROFILE_KEY="${saved_profile_key}" } timestamp_now() { date '+%Y%m%d_%H%M%S' } ops_center_repo_root() { printf '%s' "${OPS_CENTER_PROJECT_ROOT}" } confirm_to_bool() { local raw_value="${1:-}" case "$(printf '%s' "${raw_value}" | tr '[:upper:]' '[:lower:]')" in 1|true|yes|y|confirm|confirmed|skip|skip-smoke) printf 'true' ;; *) printf 'false' ;; esac } is_confirm_token() { local raw_value="${1:-}" case "$(printf '%s' "${raw_value}" | tr '[:upper:]' '[:lower:]')" in 1|true|yes|y|confirm|confirmed) return 0 ;; *) return 1 ;; esac } is_secondary_token() { local raw_value="${1:-}" case "$(printf '%s' "${raw_value}" | tr '[:upper:]' '[:lower:]')" in secondary|second|2|true|yes|y) return 0 ;; *) return 1 ;; esac } normalize_payload_json_input() { local raw_value="${1:-}" if [[ -z "${raw_value}" ]]; then printf '{}' return 0 fi if [[ "${raw_value}" == @* ]]; then local payload_path="${raw_value#@}" if [[ ! -f "${payload_path}" ]]; then echo "payload file not found: ${payload_path}" >&2 return 1 fi cat "${payload_path}" return 0 fi printf '%s' "${raw_value}" } enrich_driver_action_payload_json() { local mainland_base_url="$1" local action_code="$2" local payload_json="$3" python3 - <<'PY' "${mainland_base_url}" "${action_code}" "${payload_json}" import json import sys import urllib.request def normalize_codes(*groups): result = [] for group in groups: for item in list(group or []): node_code = str(item or "").strip() if node_code and node_code not in result: result.append(node_code) return result base_url = str(sys.argv[1] or "").rstrip("/") action_code = str(sys.argv[2] or "").strip() payload_raw = str(sys.argv[3] or "").strip() or "{}" try: payload = json.loads(payload_raw) except Exception: payload = {} if not isinstance(payload, dict): payload = {} existing_node_codes = normalize_codes(payload.get("node_codes") or []) if existing_node_codes: print(json.dumps(payload, ensure_ascii=False)) raise SystemExit(0) supported_actions = { "open_worker_logs_participating", "run_inspection_participating", "open_worker_logs_standby", "run_inspection_standby", "open_worker_logs", "run_standard_inspection", } if action_code not in supported_actions or not base_url: print(json.dumps(payload, ensure_ascii=False)) raise SystemExit(0) overview = {} try: with urllib.request.urlopen(f"{base_url}/api/v1/ops/overview", timeout=20) as response: overview_payload = json.loads(response.read().decode("utf-8")) overview = dict((overview_payload or {}).get("data") or {}) except Exception: print(json.dumps(payload, ensure_ascii=False)) raise SystemExit(0) execution_scene = dict(overview.get("execution_scene") or {}) participation_summary = dict(execution_scene.get("participation_summary") or {}) log_sync = dict(execution_scene.get("log_sync") or {}) dispatch_active = normalize_codes(participation_summary.get("dispatch_active_node_codes") or []) recent_only = normalize_codes(participation_summary.get("recent_only_node_codes") or []) standby = normalize_codes( participation_summary.get("standby_node_codes") or [], participation_summary.get("non_participating_node_codes") or [], ) missing_logs = normalize_codes(log_sync.get("missing_participating_nodes") or []) derived_node_codes = [] if action_code in {"open_worker_logs_participating", "run_inspection_participating"}: derived_node_codes = normalize_codes(missing_logs, dispatch_active, recent_only) elif action_code in {"open_worker_logs_standby", "run_inspection_standby"}: derived_node_codes = normalize_codes(standby) elif action_code == "open_worker_logs": derived_node_codes = normalize_codes(missing_logs, dispatch_active, recent_only, standby) elif action_code == "run_standard_inspection": derived_node_codes = normalize_codes(dispatch_active, recent_only, standby) if derived_node_codes: payload["node_codes"] = derived_node_codes payload.setdefault("_auto_context", {}) payload["_auto_context"]["resolved_by"] = "drive_ops_center" payload["_auto_context"]["resolved_from"] = "ops.overview" payload["_auto_context"]["action_code"] = action_code print(json.dumps(payload, ensure_ascii=False)) PY } normalize_optional_base_url_arg() { local raw_value="${1:-}" local default_value="${2:-}" case "$(printf '%s' "${raw_value}" | tr '[:upper:]' '[:lower:]')" in "" ) printf '%s' "${default_value}" ;; -|none|null|disabled|off) printf '' ;; *) printf '%s' "${raw_value}" ;; esac } write_json_file() { local output_path="$1" local json_payload="$2" python3 - "${output_path}" "${json_payload}" <<'PY' import json import sys from pathlib import Path path = Path(sys.argv[1]) payload = json.loads(sys.argv[2]) path.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8") PY } run_and_capture_report() { local title="$1" local output_path="$2" shift 2 { echo "============================================================" echo "${title}" echo "============================================================" "$@" echo } | tee "${output_path}" } capture_report_quiet() { local title="$1" local output_path="$2" shift 2 local command_exit_code=0 echo "============================================================" echo "${title}" echo "============================================================" { echo "============================================================" echo "${title}" echo "============================================================" set +e timeout "${OPS_CENTER_DOCTOR_REPORT_TIMEOUT_SECONDS}" "$@" command_exit_code=$? set -e echo echo "command_exit_code=${command_exit_code}" } >"${output_path}" echo "saved_report=${output_path}" echo "command_exit_code=${command_exit_code}" echo } capture_report_optional() { local title="$1" local output_path="$2" shift 2 local command_exit_code=0 echo "============================================================" echo "${title}" echo "============================================================" { echo "============================================================" echo "${title}" echo "============================================================" set +e timeout "${OPS_CENTER_DOCTOR_REPORT_TIMEOUT_SECONDS}" "$@" command_exit_code=$? set -e echo echo "command_exit_code=${command_exit_code}" } >"${output_path}" 2>&1 echo "saved_report=${output_path}" echo "command_exit_code=${command_exit_code}" echo } run_doctor_report_export() { local report_dir="$1" local mainland_base_url="$2" local overseas_base_url="$3" ops_center_ensure_report_dir "${report_dir}" local -a report_files=( "00_ops_center_stack_summary.txt" "01_ops_plane.txt" "02_release_hub.txt" "03_inspection.txt" "04_participation.txt" ) if [[ -n "${overseas_base_url}" ]]; then report_files+=("05_topology.txt") fi report_files+=( "06_contracts_registry.txt" "07_driver_feed.txt" "08_activity_stream.txt" "09_codex_brief.txt" "10_stack_next_preview.txt" "11_driver_focus_preview.txt" "12_activity_preview.txt" "13_codex_focus_preview.txt" "14_first_handover_gap.txt" ) capture_report_quiet "[doctor/0] ops center stack" "${report_dir}/00_ops_center_stack_summary.txt" \ bash "${SCRIPT_DIR}/check_ops_center_stack.sh" "${mainland_base_url}" summary capture_report_quiet "[doctor/1] ops plane" "${report_dir}/01_ops_plane.txt" \ bash "${SCRIPT_DIR}/check_ops_plane.sh" "${mainland_base_url}" capture_report_quiet "[doctor/2] release hub" "${report_dir}/02_release_hub.txt" \ bash "${SCRIPT_DIR}/check_release_hub.sh" "${mainland_base_url}" capture_report_quiet "[doctor/3] inspection" "${report_dir}/03_inspection.txt" \ bash "${SCRIPT_DIR}/check_ops_inspection.sh" "${mainland_base_url}" capture_report_quiet "[doctor/4] participation" "${report_dir}/04_participation.txt" \ bash "${SCRIPT_DIR}/check_worker_participation.sh" "${mainland_base_url}" if [[ -n "${overseas_base_url}" ]]; then capture_report_quiet "[doctor/5] temp topology" "${report_dir}/05_topology.txt" \ bash "${SCRIPT_DIR}/check_temp_topology.sh" "${mainland_base_url}" "${overseas_base_url}" fi capture_report_quiet "[doctor/6] contracts registry" "${report_dir}/06_contracts_registry.txt" \ bash "${SCRIPT_DIR}/drive_ops_center.sh" contracts "${mainland_base_url}" capture_report_quiet "[doctor/7] driver feed" "${report_dir}/07_driver_feed.txt" \ bash "${SCRIPT_DIR}/drive_ops_center.sh" driver-feed "${mainland_base_url}" capture_report_quiet "[doctor/8] activity stream" "${report_dir}/08_activity_stream.txt" \ bash "${SCRIPT_DIR}/drive_ops_center.sh" activity-stream "${mainland_base_url}" capture_report_quiet "[doctor/9] codex brief" "${report_dir}/09_codex_brief.txt" \ bash "${SCRIPT_DIR}/drive_ops_center.sh" codex-brief "${mainland_base_url}" capture_report_optional "[doctor/10] stack next preview" "${report_dir}/10_stack_next_preview.txt" \ bash "${SCRIPT_DIR}/drive_ops_center.sh" stack-next "${mainland_base_url}" preview capture_report_optional "[doctor/11] driver focus preview" "${report_dir}/11_driver_focus_preview.txt" \ bash "${SCRIPT_DIR}/drive_ops_center.sh" driver-focus-preview "${mainland_base_url}" capture_report_optional "[doctor/12] activity preview" "${report_dir}/12_activity_preview.txt" \ bash "${SCRIPT_DIR}/drive_ops_center.sh" activity-preview "${mainland_base_url}" capture_report_optional "[doctor/13] codex focus preview" "${report_dir}/13_codex_focus_preview.txt" \ bash "${SCRIPT_DIR}/drive_ops_center.sh" codex-focus-preview "${mainland_base_url}" capture_report_optional "[doctor/14] first handover gap" "${report_dir}/14_first_handover_gap.txt" \ bash "${SCRIPT_DIR}/drive_ops_center.sh" stack-first-gap-handover "${mainland_base_url}" local scene_log_targets_json scene_log_targets_json="$(python3 - <<'PY' "${report_dir}/00_ops_center_stack_summary.txt" import json import re import sys from pathlib import Path target = Path(str(sys.argv[1] or "").strip()) if not target.exists(): print("[]") raise SystemExit(0) text = target.read_text(encoding="utf-8", errors="replace") decoder = json.JSONDecoder() best = None for index, ch in enumerate(text): if ch not in "{[": continue try: value, end = decoder.raw_decode(text[index:]) except Exception: continue absolute_end = index + end if best is None or absolute_end >= best[0]: best = (absolute_end, value) payload = best[1] if best else {} diagnosis = dict((payload or {}).get("diagnosis") or {}) issues = list(diagnosis.get("issues") or []) next_step = dict(diagnosis.get("next_step") or {}) operator_decision = dict(diagnosis.get("operator_decision") or {}) targets = [] seen = set() def add_focus_ref(focus_ref): if not isinstance(focus_ref, dict): return if str(focus_ref.get("kind") or "").strip() != "node_scene_log": return node_code = str(focus_ref.get("node_code") or "").strip() if not node_code or node_code in seen: return seen.add(node_code) mode = str(focus_ref.get("mode") or "key").strip().lower() if mode not in {"key", "full"}: mode = "key" try: limit = int(focus_ref.get("limit") or 80) except Exception: limit = 80 limit = max(20, min(limit, 300)) targets.append({ "node_code": node_code, "mode": mode, "limit": limit, "source": str(focus_ref.get("source") or "").strip(), }) for issue in issues: add_focus_ref((issue or {}).get("focus_ref")) add_focus_ref(next_step.get("focus_ref")) add_focus_ref(operator_decision.get("next_focus")) print(json.dumps(targets[:3], ensure_ascii=False)) PY )" while IFS=$'\t' read -r scene_node_code scene_mode scene_limit scene_source; do [[ -z "${scene_node_code}" ]] && continue local safe_node_code safe_node_code="$(printf '%s' "${scene_node_code}" | tr -c 'A-Za-z0-9._-' '_')" local scene_report_name="15_scene_node_log_${safe_node_code}.txt" report_files+=("${scene_report_name}") capture_report_optional "[doctor/15] scene node log ${scene_node_code}" "${report_dir}/${scene_report_name}" \ bash "${SCRIPT_DIR}/drive_ops_center.sh" scene-node-log "${mainland_base_url}" "${scene_node_code}" "${scene_limit:-80}" "${scene_mode:-key}" done < <( python3 - <<'PY' "${scene_log_targets_json}" import json import sys targets = json.loads(str(sys.argv[1] or "[]")) for item in targets: print( "\t".join( [ str(item.get("node_code") or "").strip(), str(item.get("mode") or "key").strip(), str(item.get("limit") or 80).strip(), str(item.get("source") or "").strip(), ] ) ) PY ) write_json_file "${report_dir}/meta.json" "$(python3 - \ "${OPS_CENTER_CONFIG_FILE}" \ "${OPS_CENTER_ACTIVE_PROFILE}" \ "${mainland_base_url}" \ "${overseas_base_url}" \ "${report_dir}" \ "${report_files[@]}" <<'PY' import json import sys from datetime import datetime files = list(sys.argv[6:]) print(json.dumps({ "generated_at": datetime.now().isoformat(timespec="seconds"), "config_file": sys.argv[1], "active_profile": sys.argv[2], "mainland_api_base_url": sys.argv[3], "overseas_api_base_url": sys.argv[4], "report_dir": sys.argv[5], "files": files, }, ensure_ascii=False)) PY )" write_json_file "${report_dir}/manifest.json" "$(python3 - \ "${OPS_CENTER_CONFIG_FILE}" \ "${OPS_CENTER_ACTIVE_PROFILE}" \ "${mainland_base_url}" \ "${overseas_base_url}" \ "${report_dir}" \ "${report_files[@]}" <<'PY' import json import re import sys from datetime import datetime from pathlib import Path config_file = str(sys.argv[1] or "").strip() active_profile = str(sys.argv[2] or "").strip() mainland_api_base_url = str(sys.argv[3] or "").strip() overseas_api_base_url = str(sys.argv[4] or "").strip() report_dir = Path(str(sys.argv[5] or "").strip()) report_files = [str(item or "").strip() for item in sys.argv[6:] if str(item or "").strip()] optional_preview_files = { "10_stack_next_preview.txt", "11_driver_focus_preview.txt", "12_activity_preview.txt", "13_codex_focus_preview.txt", } def extract_exit_code(text: str) -> int: matches = re.findall(r"(?m)^command_exit_code=(\d+)\s*$", text) if matches: try: return int(matches[-1]) except Exception: return 1 return 0 def extract_last_json_value(text: str): decoder = json.JSONDecoder() best = None for index, ch in enumerate(text): if ch not in "{[": continue try: value, end = decoder.raw_decode(text[index:]) except Exception: continue absolute_end = index + end if best is None or absolute_end >= best[0]: best = (absolute_end, value) return None if best is None else best[1] def last_nonempty_line(text: str) -> str: for line in reversed(text.splitlines()): stripped = line.strip() if stripped: return stripped[:300] return "" def collect_contract_keys(value, bucket: set) -> None: if isinstance(value, dict): for key, child in value.items(): if key in {"contract_key", "primary_contract_key"}: contract_key = str(child or "").strip() if contract_key: bucket.add(contract_key) continue if key in {"contract_keys", "related_contract_keys"} and isinstance(child, list): for item in child: contract_key = str(item or "").strip() if contract_key: bucket.add(contract_key) continue collect_contract_keys(child, bucket) elif isinstance(value, list): for item in value: collect_contract_keys(item, bucket) def summarize_report(name: str, payload, exit_code: int, text: str) -> dict: if not isinstance(payload, dict): return { "parsed_json": False, "available": exit_code == 0, "text_tail": last_nonempty_line(text), } if name == "00_ops_center_stack_summary.txt": diagnosis = dict(payload.get("diagnosis") or {}) api = dict(payload.get("api") or {}) issues = list(diagnosis.get("issues") or payload.get("issues") or []) next_step = dict(diagnosis.get("next_step") or payload.get("next_step") or {}) operator_decision = dict(diagnosis.get("operator_decision") or {}) next_actions = dict(diagnosis.get("next_actions") or {}) recommended_commands = dict(diagnosis.get("recommended_commands") or {}) stack_status = str(diagnosis.get("stack_status") or payload.get("stack_status") or payload.get("status") or "") blocked_issue = next( (dict(item or {}) for item in issues if str((item or {}).get("severity") or "") == "blocked"), {}, ) headline = "" if stack_status == "blocked": headline = str(blocked_issue.get("summary") or "").strip() if not headline: headline = str( diagnosis.get("headline") or (payload.get("link_snapshot") or {}).get("headline") or "" ).strip() return { "parsed_json": True, "available": exit_code == 0, "http_status": int(api.get("http_status", 0) or 0), "stack_status": stack_status, "surface_status": str(diagnosis.get("surface_status") or ""), "automation_status": str(diagnosis.get("automation_status") or ""), "issues_total": len(issues), "blocked_issues": sum(1 for item in issues if str((item or {}).get("severity") or "") == "blocked"), "headline": headline, "blocked_issue_detail": str(blocked_issue.get("detail") or "").strip(), "launchpad_recommended_target_node_code": str(diagnosis.get("launchpad_recommended_target_node_code") or ""), "launchpad_recommended_recovery_label": str(diagnosis.get("launchpad_recommended_recovery_label") or ""), "launchpad_recommended_recovery_summary": str(diagnosis.get("launchpad_recommended_recovery_summary") or ""), "launchpad_onboarding_bootstrap_pending_nodes": int(diagnosis.get("launchpad_onboarding_bootstrap_pending_nodes", 0) or 0), "launchpad_onboarding_acceptance_ready_nodes": int(diagnosis.get("launchpad_onboarding_acceptance_ready_nodes", 0) or 0), "next_step_action_code": str(next_step.get("action_code") or ""), "next_step_summary": str(next_step.get("summary") or next_step.get("title") or next_step.get("reason") or ""), "operator_lane": str(operator_decision.get("lane") or "").strip(), "operator_priority": str(operator_decision.get("priority") or "").strip(), "operator_reason_code": str(operator_decision.get("reason_code") or "").strip(), "operator_title": str(operator_decision.get("title") or "").strip(), "operator_summary": str(operator_decision.get("summary") or "").strip(), "operator_primary_command_key": str(operator_decision.get("primary_command_key") or "").strip(), "operator_secondary_command_key": str(operator_decision.get("secondary_command_key") or "").strip(), "operator_primary_command": str( next_actions.get("primary_command") or recommended_commands.get(str(operator_decision.get("primary_command_key") or "").strip()) or "" ).strip(), "operator_secondary_command": str( next_actions.get("secondary_command") or recommended_commands.get(str(operator_decision.get("secondary_command_key") or "").strip()) or "" ).strip(), "operator_focus_kind": str((operator_decision.get("next_focus") or {}).get("kind") or "").strip(), "operator_focus_node_code": str((operator_decision.get("next_focus") or {}).get("node_code") or "").strip(), } if name == "06_contracts_registry.txt": contracts = list(payload.get("contracts") or []) return { "parsed_json": True, "available": exit_code == 0, "http_status": int(payload.get("http_status", 0) or 0), "registry_version": str(payload.get("registry_version") or ""), "schema_version": str(payload.get("schema_version") or ""), "contracts_total": int(payload.get("contracts_total", 0) or len(contracts)), } if name == "07_driver_feed.txt": summary = dict(payload.get("summary") or {}) top_recommendation = dict(payload.get("top_recommendation") or {}) contract_navigation = dict(payload.get("contract_navigation") or {}) return { "parsed_json": True, "available": exit_code == 0, "http_status": int(payload.get("http_status", 0) or 0), "headline": str(payload.get("headline") or ""), "primary_contract_key": str(contract_navigation.get("primary_contract_key") or ""), "entry_count": len(list(payload.get("entries") or [])), "activity_focus_count": len(list(payload.get("activity_focus") or [])), "top_recommendation_key": str(top_recommendation.get("key") or ""), "top_recommendation_contract_key": str(top_recommendation.get("primary_contract_key") or ""), "do_now_total": int(summary.get("do_now_total", 0) or 0), "launchpad_recommended_target_node_code": str(summary.get("launchpad_recommended_target_node_code") or ""), "launchpad_recommended_recovery_label": str(summary.get("launchpad_recommended_recovery_label") or ""), "launchpad_recommended_recovery_summary": str(summary.get("launchpad_recommended_recovery_summary") or ""), "launchpad_onboarding_bootstrap_pending_nodes": int(summary.get("launchpad_onboarding_bootstrap_pending_nodes", 0) or 0), "launchpad_onboarding_acceptance_ready_nodes": int(summary.get("launchpad_onboarding_acceptance_ready_nodes", 0) or 0), } if name == "08_activity_stream.txt": summary = dict(payload.get("summary") or {}) contract_navigation = dict(payload.get("contract_navigation") or {}) items = list(payload.get("items") or []) first_item = dict(items[0] or {}) if items else {} return { "parsed_json": True, "available": exit_code == 0, "http_status": int(payload.get("http_status", 0) or 0), "primary_contract_key": str(contract_navigation.get("primary_contract_key") or ""), "filtered_total": int(summary.get("filtered_total", 0) or 0), "item_count": len(items), "first_item_key": str(first_item.get("activity_key") or ""), "first_item_contract_key": str(first_item.get("primary_contract_key") or ""), } if name == "09_codex_brief.txt": summary = dict(payload.get("summary") or {}) focus = dict(payload.get("focus") or {}) contract_navigation = dict(payload.get("contract_navigation") or {}) return { "parsed_json": True, "available": exit_code == 0, "http_status": int(payload.get("http_status", 0) or 0), "headline": str(payload.get("headline") or ""), "primary_contract_key": str(contract_navigation.get("primary_contract_key") or ""), "focus_entry_key": str(focus.get("entry_key") or ""), "focus_contract_key": str(focus.get("primary_contract_key") or ""), "entry_count": len(list(payload.get("entries") or [])), "activity_focus_count": len(list(payload.get("activity_focus") or [])), "blocked_total": int(summary.get("blocked_total", 0) or 0), "launchpad_recommended_target_node_code": str(summary.get("launchpad_recommended_target_node_code") or ""), "launchpad_recommended_recovery_label": str(summary.get("launchpad_recommended_recovery_label") or ""), "launchpad_recommended_recovery_summary": str(summary.get("launchpad_recommended_recovery_summary") or ""), "launchpad_onboarding_bootstrap_pending_nodes": int(summary.get("launchpad_onboarding_bootstrap_pending_nodes", 0) or 0), "launchpad_onboarding_acceptance_ready_nodes": int(summary.get("launchpad_onboarding_acceptance_ready_nodes", 0) or 0), } if name in optional_preview_files: contract = dict(payload.get("contract") or {}) gate = dict(payload.get("gate") or {}) selected = dict(payload.get("selected_entry") or {}) selected_key = ( str(selected.get("key") or "").strip() or str(selected.get("activity_key") or "").strip() or str(selected.get("action_code") or "").strip() ) return { "parsed_json": True, "available": exit_code == 0, "http_status": int(payload.get("http_status", 0) or 0), "contract_key": str(contract.get("contract_key") or ""), "contract_version": str(contract.get("contract_version") or ""), "preview_endpoint": str(contract.get("preview_endpoint") or ""), "gate_decision": str(gate.get("decision") or ""), "selected_key": selected_key, "selected_title": str(selected.get("title") or ""), } if name.startswith("15_scene_node_log_"): participation = dict(payload.get("participation") or {}) source_summary = dict(payload.get("source_summary") or {}) return { "parsed_json": True, "available": exit_code == 0, "http_status": int(payload.get("http_status", 0) or 0), "node_code": str(payload.get("node_code") or ""), "status": str(payload.get("status") or ""), "status_label": str(payload.get("status_label") or ""), "mode": str(payload.get("mode") or ""), "records_total": int(payload.get("records_total", 0) or 0), "detect_participating": bool(participation.get("detect_participating", False)), "participation_label": str(participation.get("participation_label") or ""), "source_line_count": int(source_summary.get("line_count", 0) or 0), "missing_reason_code": str(payload.get("missing_reason_code") or ""), } return { "parsed_json": True, "available": exit_code == 0, "top_level_keys": list(payload.keys())[:12], } def classify_report_failure(name: str, exists: bool, payload, summary: dict, exit_code: int, text: str) -> dict: http_status_raw = "" if isinstance(summary, dict): http_status_raw = str(summary.get("http_status") or "").strip() text_tail = last_nonempty_line(text) has_payload = isinstance(payload, (dict, list)) detail = "" reason_code = "" label = "" if not exists: reason_code = "report_missing" label = "未生成报告" detail = "导出阶段没有落盘该 report 文件。" elif exit_code == 124: reason_code = "command_timeout" label = "执行超时" detail = "报告命令超过超时阈值后被终止,建议先单独复跑对应子命令。" elif exit_code != 0: reason_code = "command_failed" label = "命令失败" detail = f"报告命令退出码为 {exit_code}。" elif http_status_raw and http_status_raw.isdigit() and not (200 <= int(http_status_raw) < 300): http_status = int(http_status_raw) if http_status == 0: reason_code = "http_unreachable" label = "接口不可达" detail = "curl 未拿到有效 HTTP 状态,通常表示超时、连接失败或被中间网络丢弃。" else: reason_code = "http_error" label = "接口异常" detail = f"目标接口返回 HTTP {http_status}。" elif not has_payload: reason_code = "result_unparsed" label = "结果不可解析" detail = "命令成功返回,但未解析出结构化 JSON 结果。" else: reason_code = "ok" label = "正常" detail = "" if text_tail and reason_code != "ok": detail = (detail + f" 最近输出: {text_tail}")[:500].strip() return { "reason_code": reason_code, "label": label, "detail": detail, "timeout": reason_code == "command_timeout", "http_status": int(http_status_raw) if http_status_raw.isdigit() else 0, "text_tail": text_tail, "parsed_json": bool(isinstance(summary, dict) and summary.get("parsed_json")), } reports = [] all_contract_keys = set() required_failures = [] optional_unavailable = [] for name in report_files: path = report_dir / name exists = path.exists() text = path.read_text(encoding="utf-8", errors="replace") if exists else "" exit_code = extract_exit_code(text) if exists else 1 parsed_payload = extract_last_json_value(text) if exists else None summary = summarize_report(name, parsed_payload, exit_code, text) if parsed_payload is not None: collect_contract_keys(parsed_payload, all_contract_keys) http_status = summary.get("http_status") if isinstance(summary, dict) else None http_ok = True if isinstance(http_status, int): http_ok = 200 <= http_status < 300 report_ok = exists and exit_code == 0 and http_ok if isinstance(summary, dict) and "available" in summary: summary["available"] = report_ok report_entry = { "name": name, "path": str(path), "exists": exists, "size_bytes": int(path.stat().st_size) if exists else 0, "optional": name in optional_preview_files, "command_exit_code": exit_code, "ok": report_ok, "summary": summary, "failure": classify_report_failure(name, exists, parsed_payload, summary, exit_code, text), } reports.append(report_entry) if name in optional_preview_files: if not report_entry["ok"]: optional_unavailable.append(name) else: if not report_entry["ok"]: required_failures.append(name) preview_contract_keys = sorted( { str((report.get("summary") or {}).get("contract_key") or "").strip() for report in reports if report.get("name") in optional_preview_files and str((report.get("summary") or {}).get("contract_key") or "").strip() } ) primary_contract_keys = sorted( { str((report.get("summary") or {}).get("primary_contract_key") or "").strip() for report in reports if str((report.get("summary") or {}).get("primary_contract_key") or "").strip() } ) reports_by_name = {str(report.get("name") or ""): report for report in reports} scene_log_reports = [ report for report in reports if str(report.get("name") or "").startswith("15_scene_node_log_") ] scene_log_report_summaries = [dict(report.get("summary") or {}) for report in scene_log_reports] scene_log_reports_total = len(scene_log_reports) scene_log_reports_ok = sum(1 for report in scene_log_reports if bool(report.get("ok"))) scene_log_status_counts = {} for summary in scene_log_report_summaries: status_key = str(summary.get("status") or "unknown").strip() or "unknown" scene_log_status_counts[status_key] = int(scene_log_status_counts.get(status_key, 0) or 0) + 1 scene_log_problem_reports = [ summary for summary in scene_log_report_summaries if str(summary.get("status") or "").strip() in {"waiting_sample", "missing_sample", "disabled", "standby", "unknown"} ] scene_log_problem_node_codes = [ str(summary.get("node_code") or "").strip() for summary in scene_log_problem_reports if str(summary.get("node_code") or "").strip() ] scene_log_healthy_reports = [ summary for summary in scene_log_report_summaries if str(summary.get("status") or "").strip() in {"healthy", "full_capture", "historical_sample"} ] required_failure_details = [ { "name": str(report.get("name") or ""), "reason_code": str((report.get("failure") or {}).get("reason_code") or ""), "label": str((report.get("failure") or {}).get("label") or ""), "detail": str((report.get("failure") or {}).get("detail") or ""), } for report in reports if not bool(report.get("optional")) and not bool(report.get("ok")) ] optional_unavailable_details = [ { "name": str(report.get("name") or ""), "reason_code": str((report.get("failure") or {}).get("reason_code") or ""), "label": str((report.get("failure") or {}).get("label") or ""), "detail": str((report.get("failure") or {}).get("detail") or ""), } for report in reports if bool(report.get("optional")) and not bool(report.get("ok")) ] def report_summary(name: str) -> dict: return dict((reports_by_name.get(name) or {}).get("summary") or {}) def report_ok(name: str) -> bool: return bool((reports_by_name.get(name) or {}).get("ok")) def report_http_status(name: str) -> int: value = report_summary(name).get("http_status") return int(value or 0) if isinstance(value, int) or str(value).isdigit() else 0 contracts_summary = report_summary("06_contracts_registry.txt") driver_feed_summary = report_summary("07_driver_feed.txt") activity_stream_summary = report_summary("08_activity_stream.txt") codex_brief_summary = report_summary("09_codex_brief.txt") stack_summary = report_summary("00_ops_center_stack_summary.txt") launchpad_recommended_target_node_code = next( ( str(source.get("launchpad_recommended_target_node_code") or "").strip() for source in [driver_feed_summary, codex_brief_summary, stack_summary] if str(source.get("launchpad_recommended_target_node_code") or "").strip() ), "", ) launchpad_recommended_recovery_label = next( ( str(source.get("launchpad_recommended_recovery_label") or "").strip() for source in [driver_feed_summary, codex_brief_summary, stack_summary] if str(source.get("launchpad_recommended_recovery_label") or "").strip() ), "", ) launchpad_recommended_recovery_summary = next( ( str(source.get("launchpad_recommended_recovery_summary") or "").strip() for source in [driver_feed_summary, codex_brief_summary, stack_summary] if str(source.get("launchpad_recommended_recovery_summary") or "").strip() ), "", ) launchpad_onboarding_bootstrap_pending_nodes = max( int(driver_feed_summary.get("launchpad_onboarding_bootstrap_pending_nodes", 0) or 0), int(codex_brief_summary.get("launchpad_onboarding_bootstrap_pending_nodes", 0) or 0), int(stack_summary.get("launchpad_onboarding_bootstrap_pending_nodes", 0) or 0), ) launchpad_onboarding_acceptance_ready_nodes = max( int(driver_feed_summary.get("launchpad_onboarding_acceptance_ready_nodes", 0) or 0), int(codex_brief_summary.get("launchpad_onboarding_acceptance_ready_nodes", 0) or 0), int(stack_summary.get("launchpad_onboarding_acceptance_ready_nodes", 0) or 0), ) stack_status = str(stack_summary.get("stack_status") or "").strip() stack_headline = str(stack_summary.get("headline") or "").strip() stack_blocked_issue_detail = str(stack_summary.get("blocked_issue_detail") or "").strip() stack_next_summary = str(stack_summary.get("next_step_summary") or "").strip() stack_next_action_code = str(stack_summary.get("next_step_action_code") or "").strip() stack_blocked_issues = int(stack_summary.get("blocked_issues", 0) or 0) stack_operator_lane = str(stack_summary.get("operator_lane") or "").strip() stack_operator_priority = str(stack_summary.get("operator_priority") or "").strip() stack_operator_reason_code = str(stack_summary.get("operator_reason_code") or "").strip() stack_operator_title = str(stack_summary.get("operator_title") or "").strip() stack_operator_summary = str(stack_summary.get("operator_summary") or "").strip() stack_operator_primary_command_key = str(stack_summary.get("operator_primary_command_key") or "").strip() stack_operator_secondary_command_key = str(stack_summary.get("operator_secondary_command_key") or "").strip() stack_operator_primary_command = str(stack_summary.get("operator_primary_command") or "").strip() stack_operator_secondary_command = str(stack_summary.get("operator_secondary_command") or "").strip() stack_operator_focus_kind = str(stack_summary.get("operator_focus_kind") or "").strip() stack_operator_focus_node_code = str(stack_summary.get("operator_focus_node_code") or "").strip() stack_primary_commands = [] for candidate in [ stack_operator_primary_command, stack_operator_secondary_command, ]: normalized_candidate = str(candidate or "").strip() if normalized_candidate and normalized_candidate not in stack_primary_commands: stack_primary_commands.append(normalized_candidate) stack_preferred_surface = "stack-summary" if stack_operator_focus_kind == "node_scene_log": stack_preferred_surface = "execution-scene" elif stack_operator_lane == "node_handover": stack_preferred_surface = "managed-nodes" elif stack_operator_lane == "release": stack_preferred_surface = "release-hub" elif stack_operator_lane == "observability": stack_preferred_surface = "execution-scene" elif stack_operator_lane in {"ops_jobs", "steady"}: stack_preferred_surface = "driver-feed" contract_surface_gaps = [] if report_ok("07_driver_feed.txt") and not str(driver_feed_summary.get("primary_contract_key") or "").strip(): contract_surface_gaps.append("driver-feed") if report_ok("08_activity_stream.txt") and not str(activity_stream_summary.get("primary_contract_key") or "").strip(): contract_surface_gaps.append("activity-stream") if report_ok("09_codex_brief.txt") and not str(codex_brief_summary.get("primary_contract_key") or "").strip(): contract_surface_gaps.append("codex-brief") surface_http_status = { "stack_summary": report_http_status("00_ops_center_stack_summary.txt"), "contracts_registry": report_http_status("06_contracts_registry.txt"), "driver_feed": report_http_status("07_driver_feed.txt"), "activity_stream": report_http_status("08_activity_stream.txt"), "codex_brief": report_http_status("09_codex_brief.txt"), "stack_next_preview": report_http_status("10_stack_next_preview.txt"), "driver_focus_preview": report_http_status("11_driver_focus_preview.txt"), "activity_preview": report_http_status("12_activity_preview.txt"), "codex_focus_preview": report_http_status("13_codex_focus_preview.txt"), } doctor_command = f"bash domain-api/deploy/multi-region/drive_ops_center.sh doctor {mainland_api_base_url}" if overseas_api_base_url: doctor_command += f" {overseas_api_base_url}" decision = { "status": "ready", "reason_code": "ops_center_ready", "headline": "海外控制面已经具备协议驱动的观察与预览能力。", "detail": "", "preferred_surface": "driver-feed", "next_action_code": "review_driver_feed", "recommended_commands": [ f"bash domain-api/deploy/multi-region/drive_ops_center.sh driver-feed {mainland_api_base_url}", f"bash domain-api/deploy/multi-region/drive_ops_center.sh activity-stream {mainland_api_base_url}", f"bash domain-api/deploy/multi-region/drive_ops_center.sh codex-brief {mainland_api_base_url}", ], "evidence": { "required_failures": required_failures, "optional_unavailable": optional_unavailable, "contract_surface_gaps": contract_surface_gaps, "surface_http_status": surface_http_status, }, } if "06_contracts_registry.txt" in required_failures: http_status = int(contracts_summary.get("http_status", 0) or 0) contracts_failure = next( ( item for item in required_failure_details if str(item.get("name") or "") == "06_contracts_registry.txt" ), {}, ) contracts_reason_code = str(contracts_failure.get("reason_code") or "").strip() contracts_failure_detail = str(contracts_failure.get("detail") or "").strip() decision = { "status": "blocked", "reason_code": contracts_reason_code or "contracts_registry_unavailable", "headline": ( "控制面 contract registry 拉取超时,海外驾驶链路当前无法确认协议面是否完整。 " if contracts_reason_code == "command_timeout" else "控制面还没暴露 ops/contracts,海外驾驶链路当前不完整。" ), "detail": contracts_failure_detail or f"06_contracts_registry.txt http_status={http_status},先恢复 contract registry,再继续 preview / execute。", "preferred_surface": "contracts", "next_action_code": "restore_contract_registry", "recommended_commands": [ f"bash domain-api/deploy/multi-region/drive_ops_center.sh contracts {mainland_api_base_url}", f"bash domain-api/deploy/multi-region/drive_ops_center.sh runtime-refresh-recover {mainland_api_base_url}", f"bash domain-api/deploy/multi-region/check_ops_center_stack.sh {mainland_api_base_url} summary", ], "evidence": { "required_failures": required_failures, "required_failure_details": required_failure_details, "optional_unavailable": optional_unavailable, "optional_unavailable_details": optional_unavailable_details, "contract_surface_gaps": contract_surface_gaps, "surface_http_status": surface_http_status, }, } elif required_failures: scene_log_required_failures = [ name for name in required_failures if str(name or "").startswith("15_scene_node_log_") ] if scene_log_required_failures and stack_primary_commands: decision = { "status": "blocked" if stack_status == "blocked" else "attention", "reason_code": stack_operator_reason_code or "scene_log_surface_unavailable", "headline": stack_operator_title or "执行现场观察链路还没拿到可用日志样本。", "detail": stack_operator_summary or " / ".join(scene_log_required_failures), "preferred_surface": stack_preferred_surface, "next_action_code": stack_operator_primary_command_key or "inspect_scene_log_reports", "recommended_commands": [ *stack_primary_commands, f"bash domain-api/deploy/multi-region/check_ops_center_stack.sh {mainland_api_base_url} summary", doctor_command, ], "evidence": { "required_failures": required_failures, "required_failure_details": required_failure_details, "optional_unavailable": optional_unavailable, "optional_unavailable_details": optional_unavailable_details, "contract_surface_gaps": contract_surface_gaps, "surface_http_status": surface_http_status, "scene_log_status_counts": scene_log_status_counts, }, } else: decision = { "status": "blocked", "reason_code": "required_surfaces_unavailable", "headline": "海外控制面仍有必需 surface 缺失,先修 surface 再谈自动驾驶。", "detail": " / ".join(required_failures), "preferred_surface": stack_preferred_surface if stack_primary_commands else "stack-summary", "next_action_code": stack_operator_primary_command_key or "repair_required_surfaces", "recommended_commands": [ *stack_primary_commands, doctor_command, f"bash domain-api/deploy/multi-region/check_ops_center_stack.sh {mainland_api_base_url} summary", ], "evidence": { "required_failures": required_failures, "required_failure_details": required_failure_details, "optional_unavailable": optional_unavailable, "optional_unavailable_details": optional_unavailable_details, "contract_surface_gaps": contract_surface_gaps, "surface_http_status": surface_http_status, }, } elif contract_surface_gaps: decision = { "status": "attention", "reason_code": "contract_navigation_incomplete", "headline": "核心观察面已在线,但 contract_navigation 还没完全补齐。", "detail": "缺少主协议标记的 surface: " + " / ".join(contract_surface_gaps), "preferred_surface": contract_surface_gaps[0], "next_action_code": "fill_contract_navigation", "recommended_commands": [ f"bash domain-api/deploy/multi-region/drive_ops_center.sh driver-feed {mainland_api_base_url}", f"bash domain-api/deploy/multi-region/drive_ops_center.sh activity-stream {mainland_api_base_url}", f"bash domain-api/deploy/multi-region/drive_ops_center.sh codex-brief {mainland_api_base_url}", ], "evidence": { "required_failures": required_failures, "required_failure_details": required_failure_details, "optional_unavailable": optional_unavailable, "optional_unavailable_details": optional_unavailable_details, "contract_surface_gaps": contract_surface_gaps, "surface_http_status": surface_http_status, }, } elif stack_status == "blocked": decision = { "status": "blocked", "reason_code": stack_operator_reason_code or "stack_diagnosis_blocked", "headline": stack_operator_title or stack_headline or "控制面总检已判定当前现场存在阻断问题,暂不应继续执行自动化动作。", "detail": stack_operator_summary or stack_blocked_issue_detail or stack_next_summary or f"stack_diagnosis.blocked_issues={stack_blocked_issues}", "preferred_surface": stack_preferred_surface, "next_action_code": stack_operator_primary_command_key or stack_next_action_code or "resolve_stack_blockers", "recommended_commands": [ *stack_primary_commands, f"bash domain-api/deploy/multi-region/drive_ops_center.sh stack-next {mainland_api_base_url}", f"bash domain-api/deploy/multi-region/check_ops_center_stack.sh {mainland_api_base_url} summary", doctor_command, ], "evidence": { "required_failures": required_failures, "required_failure_details": required_failure_details, "optional_unavailable": optional_unavailable, "optional_unavailable_details": optional_unavailable_details, "contract_surface_gaps": contract_surface_gaps, "surface_http_status": surface_http_status, }, } elif stack_status == "attention": if stack_operator_reason_code == "runtime_build_schema_stale": restart_commands = [] for candidate in [ stack_operator_primary_command, stack_operator_secondary_command, f"bash domain-api/deploy/multi-region/drive_ops_center.sh runtime-refresh-recover {mainland_api_base_url}", f"bash domain-api/deploy/multi-region/check_ops_center_stack.sh {mainland_api_base_url} summary", f"bash domain-api/deploy/multi-region/drive_ops_center.sh stack-next {mainland_api_base_url}", doctor_command, ]: normalized_candidate = str(candidate or "").strip() if normalized_candidate and normalized_candidate not in restart_commands: restart_commands.append(normalized_candidate) decision = { "status": "attention", "reason_code": "runtime_build_schema_stale", "headline": stack_operator_title or "仓库代码已更新,但运行中的控制面 API 还是旧 schema。", "detail": stack_operator_summary or stack_next_summary or "先执行 runtime-refresh-recover,让控制面 API 切到最新代码;随后重新执行 stack-diagnosis / stack-next,再继续节点接管。", "preferred_surface": "stack-summary", "next_action_code": stack_operator_primary_command_key or "api-restart", "recommended_commands": restart_commands, "evidence": { "required_failures": required_failures, "required_failure_details": required_failure_details, "optional_unavailable": optional_unavailable, "optional_unavailable_details": optional_unavailable_details, "contract_surface_gaps": contract_surface_gaps, "surface_http_status": surface_http_status, }, } else: decision = { "status": "attention", "reason_code": stack_operator_reason_code or "stack_diagnosis_attention", "headline": stack_operator_title or stack_headline or "控制面总检已发现待收口事项,建议先顺着 next_step 收口后再继续预览或执行。", "detail": stack_operator_summary or stack_next_summary or "请先处理 stack-diagnosis 提示的 attention 项。", "preferred_surface": stack_preferred_surface, "next_action_code": stack_operator_primary_command_key or stack_next_action_code or "review_stack_next", "recommended_commands": [ *stack_primary_commands, f"bash domain-api/deploy/multi-region/drive_ops_center.sh stack-next {mainland_api_base_url}", f"bash domain-api/deploy/multi-region/check_ops_center_stack.sh {mainland_api_base_url} summary", doctor_command, ], "evidence": { "required_failures": required_failures, "required_failure_details": required_failure_details, "optional_unavailable": optional_unavailable, "optional_unavailable_details": optional_unavailable_details, "contract_surface_gaps": contract_surface_gaps, "surface_http_status": surface_http_status, }, } elif optional_unavailable: focus_entry = str(codex_brief_summary.get("focus_entry_key") or "").strip() decision = { "status": "attention", "reason_code": "preview_unavailable", "headline": "核心观察面已可用,但当前 preview 入口还没形成可执行焦点。", "detail": "当前不可用 preview: " + " / ".join(optional_unavailable), "preferred_surface": "codex-brief" if focus_entry else "driver-feed", "next_action_code": "inspect_focus_surfaces", "recommended_commands": [ f"bash domain-api/deploy/multi-region/drive_ops_center.sh driver-feed {mainland_api_base_url}", f"bash domain-api/deploy/multi-region/drive_ops_center.sh activity-stream {mainland_api_base_url}", f"bash domain-api/deploy/multi-region/drive_ops_center.sh codex-brief {mainland_api_base_url}", ], "evidence": { "required_failures": required_failures, "required_failure_details": required_failure_details, "optional_unavailable": optional_unavailable, "optional_unavailable_details": optional_unavailable_details, "contract_surface_gaps": contract_surface_gaps, "surface_http_status": surface_http_status, }, } elif scene_log_reports_total > 0 and scene_log_reports_ok == 0: first_scene_log = dict(scene_log_report_summaries[0] or {}) if scene_log_report_summaries else {} scene_log_node_code = str(first_scene_log.get("node_code") or "").strip() scene_log_mode = str(first_scene_log.get("mode") or "key").strip() or "key" decision = { "status": "attention", "reason_code": "scene_log_reports_unavailable", "headline": "总检已经识别出关键节点现场,但节点现场日志附件还没有成功形成可用样本。", "detail": ( f"scene_log_reports_total={scene_log_reports_total},全部附件当前不可用;" f"缺口节点 {', '.join(scene_log_problem_node_codes) or scene_log_node_code or '-'}" ), "preferred_surface": "execution-scene", "next_action_code": "inspect_scene_log_reports", "recommended_commands": [ *( [ f"bash domain-api/deploy/multi-region/drive_ops_center.sh scene-node-log {mainland_api_base_url} {scene_log_node_code} 120 {scene_log_mode}" ] if scene_log_node_code else [] ), f"bash domain-api/deploy/multi-region/check_ops_center_stack.sh {mainland_api_base_url} summary", doctor_command, ], "evidence": { "required_failures": required_failures, "required_failure_details": required_failure_details, "optional_unavailable": optional_unavailable, "optional_unavailable_details": optional_unavailable_details, "contract_surface_gaps": contract_surface_gaps, "surface_http_status": surface_http_status, "scene_log_status_counts": scene_log_status_counts, }, } elif scene_log_problem_reports: first_scene_log = dict(scene_log_problem_reports[0] or {}) scene_log_node_code = str(first_scene_log.get("node_code") or "").strip() scene_log_mode = str(first_scene_log.get("mode") or "key").strip() or "key" decision = { "status": "attention", "reason_code": "scene_log_reports_need_attention", "headline": "总检已经带回节点现场日志附件,但仍有参与节点的现场样本没有完全收口。", "detail": ( f"问题节点 {', '.join(scene_log_problem_node_codes) or scene_log_node_code or '-'};" f" 当前状态分布 {scene_log_status_counts}" ), "preferred_surface": "execution-scene", "next_action_code": "inspect_scene_log_reports", "recommended_commands": [ *( [ f"bash domain-api/deploy/multi-region/drive_ops_center.sh scene-node-log {mainland_api_base_url} {scene_log_node_code} 120 {scene_log_mode}" ] if scene_log_node_code else [] ), f"bash domain-api/deploy/multi-region/drive_ops_center.sh stack-next {mainland_api_base_url}", doctor_command, ], "evidence": { "required_failures": required_failures, "required_failure_details": required_failure_details, "optional_unavailable": optional_unavailable, "optional_unavailable_details": optional_unavailable_details, "contract_surface_gaps": contract_surface_gaps, "surface_http_status": surface_http_status, "scene_log_status_counts": scene_log_status_counts, }, } else: codex_headline = str(codex_brief_summary.get("headline") or "").strip() driver_headline = str(driver_feed_summary.get("headline") or "").strip() decision["headline"] = stack_operator_title or codex_headline or driver_headline or decision["headline"] decision["detail"] = stack_operator_summary or stack_next_summary or "先看 driver-feed,再按 manifest 已准备好的 preview surface 继续收敛。" decision["preferred_surface"] = ( stack_preferred_surface if stack_operator_primary_command_key or stack_operator_focus_kind else ("codex-brief" if str(codex_brief_summary.get("focus_entry_key") or "").strip() else "driver-feed") ) decision["next_action_code"] = stack_operator_primary_command_key or stack_next_action_code or "preview_primary_focus" decision["recommended_commands"] = [ *stack_primary_commands, f"bash domain-api/deploy/multi-region/drive_ops_center.sh stack-next {mainland_api_base_url}", f"bash domain-api/deploy/multi-region/drive_ops_center.sh driver-focus-preview {mainland_api_base_url}", f"bash domain-api/deploy/multi-region/drive_ops_center.sh codex-focus-preview {mainland_api_base_url}", ] if isinstance(decision.get("evidence"), dict): decision["evidence"].update({ "launchpad_recommended_target_node_code": launchpad_recommended_target_node_code, "launchpad_recommended_recovery_label": launchpad_recommended_recovery_label, "launchpad_recommended_recovery_summary": launchpad_recommended_recovery_summary, "launchpad_onboarding_bootstrap_pending_nodes": launchpad_onboarding_bootstrap_pending_nodes, "launchpad_onboarding_acceptance_ready_nodes": launchpad_onboarding_acceptance_ready_nodes, }) manifest = { "generated_at": datetime.now().isoformat(timespec="seconds"), "config_file": config_file, "active_profile": active_profile, "mainland_api_base_url": mainland_api_base_url, "overseas_api_base_url": overseas_api_base_url, "report_dir": str(report_dir), "reports_total": len(reports), "reports": reports, "summary": { "ok": len(required_failures) == 0, "required_failures": required_failures, "required_failure_details": required_failure_details, "optional_unavailable": optional_unavailable, "optional_unavailable_details": optional_unavailable_details, "scene_log_reports_total": scene_log_reports_total, "scene_log_reports_ok": scene_log_reports_ok, "scene_log_status_counts": scene_log_status_counts, "scene_log_reports": [ { "name": str(report.get("name") or ""), "node_code": str((report.get("summary") or {}).get("node_code") or ""), "status": str((report.get("summary") or {}).get("status") or ""), "status_label": str((report.get("summary") or {}).get("status_label") or ""), "mode": str((report.get("summary") or {}).get("mode") or ""), "records_total": int((report.get("summary") or {}).get("records_total", 0) or 0), "ok": bool(report.get("ok")), } for report in scene_log_reports ], "available_preview_reports": sum( 1 for report in reports if report.get("name") in optional_preview_files and report.get("ok") ), "discovered_contract_keys": sorted(all_contract_keys), "primary_contract_keys": primary_contract_keys, "preview_contract_keys": preview_contract_keys, "contract_surface_gaps": contract_surface_gaps, "surface_http_status": surface_http_status, "launchpad_recommended_target_node_code": launchpad_recommended_target_node_code, "launchpad_recommended_recovery_label": launchpad_recommended_recovery_label, "launchpad_recommended_recovery_summary": launchpad_recommended_recovery_summary, "launchpad_onboarding_bootstrap_pending_nodes": launchpad_onboarding_bootstrap_pending_nodes, "launchpad_onboarding_acceptance_ready_nodes": launchpad_onboarding_acceptance_ready_nodes, }, "decision": decision, } print(json.dumps(manifest, ensure_ascii=False)) PY )" cat >"${report_dir}/README.txt" </dev/null print_doctor_manifest_summary "${report_dir}/manifest.json" } export_agent_plan() { local report_dir="$1" local mainland_base_url="$2" local node_code="$3" local node_region="$4" local node_role="$5" local control_plane_base_url="$6" local root_dir="$7" local timestamp timestamp="$(timestamp_now)" local safe_node_code safe_node_code="$(printf '%s' "${node_code}" | tr '/:' '__')" ops_center_ensure_report_dir "${report_dir}" local output_path="${report_dir}/agent-plan-${safe_node_code}-${timestamp}.txt" ISSUED_BY="$(ops_center_resolve_issued_by)" \ EXPIRES_IN_HOURS="$(ops_center_resolve_expires_in_hours)" \ bash "${SCRIPT_DIR}/build_node_agent_bootstrap_plan.sh" \ "${mainland_base_url}" \ "${node_code}" \ "${node_region}" \ "${node_role}" \ "${control_plane_base_url}" \ "${root_dir}" | tee "${output_path}" write_json_file "${output_path}.meta.json" "$(python3 - \ "${node_code}" "${node_region}" "${node_role}" "${mainland_base_url}" "${control_plane_base_url}" "${root_dir}" "${output_path}" <<'PY' import json import sys from datetime import datetime print(json.dumps({ "generated_at": datetime.now().isoformat(timespec="seconds"), "node_code": sys.argv[1], "node_region": sys.argv[2], "node_role": sys.argv[3], "mainland_api_base_url": sys.argv[4], "control_plane_base_url": sys.argv[5], "root_dir": sys.argv[6], "output_path": sys.argv[7], }, ensure_ascii=False)) PY )" python3 - <<'PY' "${output_path}" "${output_path}.meta.json" import json import sys print(json.dumps({ "ok": True, "output_path": sys.argv[1], "meta_path": sys.argv[2], }, ensure_ascii=False, indent=2)) PY } run_publish_latest() { local mainland_base_url="$1" local mode="$2" local confirm_flag="$3" local skip_smoke_flag="$4" local repo_root repo_root="$(ops_center_repo_root)" local skip_smoke skip_smoke="$(confirm_to_bool "${skip_smoke_flag}")" if [[ "${mode}" != "worker" && "${mode}" != "control" ]]; then echo "mode must be worker or control" >&2 usage exit 1 fi echo "============================================================" echo "[publish/1] package latest" echo "============================================================" if [[ "${skip_smoke}" == "true" ]]; then DOMAINCHECK_SKIP_SMOKE_TEST=1 bash "${repo_root}/package_domain_release.sh" else bash "${repo_root}/package_domain_release.sh" fi echo echo "============================================================" echo "[publish/2] preview smart rollout from latest package" echo "============================================================" bash "${SCRIPT_DIR}/drive_release_hub.sh" "${mainland_base_url}" preview-latest-smart "${mode}" echo echo "============================================================" echo "[publish/3] smart rollout from latest package" echo "============================================================" bash "${SCRIPT_DIR}/drive_release_hub.sh" "${mainland_base_url}" smart-latest "${mode}" "${confirm_flag}" } show_ops_contracts() { local mainland_base_url="$1" local contract_key="${2:-}" local raw_http_response if [[ -n "${contract_key}" ]]; then raw_http_response="$(request_with_status "GET" "${mainland_base_url}/api/v1/ops/contracts/${contract_key}")" else raw_http_response="$(request_with_status "GET" "${mainland_base_url}/api/v1/ops/contracts")" fi local http_status="${raw_http_response##*$'\n'HTTP_STATUS=}" local response_json="${raw_http_response%$'\n'HTTP_STATUS=*}" echo "[1/2] raw response" printf '%s\n' "http_status=${http_status}" print_json_or_raw "${response_json}" echo echo "[2/2] condensed summary" python3 - <<'PY' "${response_json}" "${http_status}" "${contract_key}" import json import sys payload = json.loads(str(sys.argv[1] or "{}")) http_status = int(sys.argv[2] or 0) contract_key = str(sys.argv[3] or "").strip() data = payload.get("data") or {} if contract_key: contract = data.get("contract") or {} related_contracts = list(data.get("related_contracts") or []) print(json.dumps({ "http_status": http_status, "requested_contract_key": contract_key, "registry_version": str(data.get("registry_version") or ""), "schema_version": str(data.get("schema_version") or ""), "docs_root": str(data.get("docs_root") or ""), "detail_endpoint": str(data.get("detail_endpoint") or ""), "contract": { "key": str(contract.get("key") or ""), "title": str(contract.get("title") or ""), "status": str(contract.get("status") or ""), "version": str(contract.get("version") or ""), "primary_endpoint": str(contract.get("primary_endpoint") or ""), "schema_doc_path": str(contract.get("schema_doc_path") or ""), "summary": str(contract.get("summary") or ""), "consumers": list(contract.get("consumers") or []), "service_keys": list(contract.get("service_keys") or []), "related_contract_keys": list(contract.get("related_contract_keys") or data.get("related_contract_keys") or []), "discovery_endpoints": list(contract.get("discovery_endpoints") or []), }, "related_contracts": [ { "key": str(item.get("key") or ""), "title": str(item.get("title") or ""), "version": str(item.get("version") or ""), "primary_endpoint": str(item.get("primary_endpoint") or ""), } for item in related_contracts ], }, ensure_ascii=False, indent=2)) else: contracts = list(data.get("contracts") or []) print(json.dumps({ "http_status": http_status, "registry_version": str(data.get("registry_version") or ""), "schema_version": str(data.get("schema_version") or ""), "contracts_total": int(data.get("contracts_total", 0) or 0), "docs_root": str(data.get("docs_root") or ""), "discovery_entrypoints": list(data.get("discovery_entrypoints") or []), "contracts": [ { "key": str(item.get("key") or ""), "version": str(item.get("version") or ""), "primary_endpoint": str(item.get("primary_endpoint") or ""), "schema_doc_path": str(item.get("schema_doc_path") or ""), "related_contract_keys": list(item.get("related_contract_keys") or []), } for item in contracts ], }, ensure_ascii=False, indent=2)) PY } show_activity_stream() { local mainland_base_url="$1" shift || true local request_url request_url="$(python3 - <<'PY' "${mainland_base_url}" "$@" import sys from urllib.parse import urlencode base_url = str(sys.argv[1] or "").rstrip("/") tokens = [str(item or "").strip() for item in sys.argv[2:] if str(item or "").strip()] params = {} allowed = {"limit", "kind", "status", "execution_mode", "query"} for token in tokens: if "=" in token: key, value = token.split("=", 1) key = str(key or "").strip() value = str(value or "").strip() if key in allowed and value: params[key] = value elif "query" not in params: params["query"] = token query_string = urlencode(params) endpoint = f"{base_url}/api/v1/ops/activity-stream" print(f"{endpoint}?{query_string}" if query_string else endpoint) PY )" local raw_http_response raw_http_response="$(request_with_status "GET" "${request_url}")" local http_status="${raw_http_response##*$'\n'HTTP_STATUS=}" local response_json="${raw_http_response%$'\n'HTTP_STATUS=*}" echo "[1/2] raw response" printf '%s\n' "http_status=${http_status}" printf '%s\n' "request_url=${request_url}" print_json_or_raw "${response_json}" echo echo "[2/2] condensed summary" python3 - <<'PY' "${response_json}" "${http_status}" "${request_url}" import json import sys payload = json.loads(str(sys.argv[1] or "{}")) http_status = int(sys.argv[2] or 0) request_url = str(sys.argv[3] or "").strip() data = payload.get("data") or {} summary = data.get("summary") or {} items = list(data.get("items") or []) contract_navigation = data.get("contract_navigation") or {} print(json.dumps({ "http_status": http_status, "request_url": request_url, "contract_navigation": { "primary_contract_key": str(contract_navigation.get("primary_contract_key") or ""), "contract_keys": list(contract_navigation.get("contract_keys") or []), "detail_endpoint_pattern": str(contract_navigation.get("detail_endpoint_pattern") or ""), }, "summary": { "status": str(summary.get("status") or ""), "status_label": str(summary.get("status_label") or ""), "summary_text": str(summary.get("summary_text") or ""), "total": int(summary.get("total", 0) or 0), "filtered_total": int(summary.get("filtered_total", 0) or 0), "unfiltered_total": int(summary.get("unfiltered_total", 0) or 0), "latest_at": str(summary.get("latest_at") or ""), "filters": dict(summary.get("filters") or {}), "kind_counts": dict(summary.get("kind_counts") or {}), "status_counts": dict(summary.get("status_counts") or {}), }, "items": [ { "activity_key": str(item.get("activity_key") or ""), "kind": str(item.get("kind") or ""), "title": str(item.get("title") or ""), "status": str(item.get("status") or ""), "status_label": str(item.get("status_label") or ""), "execution_mode": str(item.get("execution_mode") or ""), "occurred_at": str(item.get("occurred_at") or ""), "primary_contract_key": str((item.get("contract_navigation") or {}).get("primary_contract_key") or ""), "contract_keys": list((item.get("contract_navigation") or {}).get("contract_keys") or []), "focus_ref": item.get("focus_ref") or {}, "ui_intent": item.get("ui_intent") or {}, } for item in items ], }, ensure_ascii=False, indent=2)) PY } build_activity_preview_bundle() { local activity_response_json="$1" local activity_key="$2" local requested_by="$3" local source_label="$4" python3 - <<'PY' "${activity_response_json}" "${activity_key}" "${requested_by}" "${source_label}" import json import sys payload = json.loads(str(sys.argv[1] or "{}")) requested_activity_key = str(sys.argv[2] or "").strip() requested_by = str(sys.argv[3] or "").strip() or "cli/ops-center/activity-preview" source_label = str(sys.argv[4] or "").strip() or "cli/activity-preview" data = payload.get("data") or {} items = [dict(item or {}) for item in list(data.get("items") or []) if dict(item or {})] target_item = {} if requested_activity_key: target_item = next((item for item in items if str(item.get("activity_key") or "").strip() == requested_activity_key), {}) if not target_item: target_item = next((item for item in items if str((item.get("ui_intent") or {}).get("kind") or "").strip()), {}) if not target_item: raise SystemExit("activity-stream 当前没有可预览的活动项") ui_intent = dict(target_item.get("ui_intent") or {}) if not str(ui_intent.get("kind") or "").strip(): raise SystemExit("目标活动项没有 ui_intent,当前不能生成 preview") kind = str(target_item.get("kind") or "").strip() if kind == "playbook_run": primary_label = "查看编排" elif kind == "rollout": primary_label = "查看任务" elif kind == "runbook_sequence": primary_label = "查看路径" elif kind in {"execution_scene", "log_sync"}: primary_label = "查看现场" elif str(ui_intent.get("kind") or "").strip() == "job_detail": primary_label = "看详情" else: primary_label = "看事件" focus_ref = dict(target_item.get("focus_ref") or {}) request_payload = { "source_label": source_label, "title": str(target_item.get("title") or "").strip() or str(target_item.get("activity_key") or "").strip() or "activity-preview", "summary": str(target_item.get("summary_text") or target_item.get("summary") or "").strip(), "reason": str(target_item.get("summary_text") or target_item.get("summary") or "").strip() or "建议先进入该活动项对应的工作区。", "meta_text": str(target_item.get("meta_text") or "").strip(), "executor_kind": "driver_action", "focus_ref": focus_ref, "primary_focus_ref": focus_ref, "primary_label": primary_label, "primary_action_code": "focus_activity_item", "primary_action_payload": { "activity_key": str(target_item.get("activity_key") or "").strip(), "kind": kind, "status": str(target_item.get("status") or "").strip(), "ui_intent": ui_intent, "focus_ref": focus_ref, }, "include_secondary": False, "requested_by": requested_by, } print(json.dumps({ "selected_entry": { "activity_key": str(target_item.get("activity_key") or "").strip(), "kind": kind, "title": str(target_item.get("title") or "").strip(), "status": str(target_item.get("status") or "").strip(), "status_label": str(target_item.get("status_label") or "").strip(), "summary": str(target_item.get("summary_text") or target_item.get("summary") or "").strip(), "focus_ref": focus_ref, "ui_intent": ui_intent, "contract_navigation": dict(target_item.get("contract_navigation") or {}), }, "request": request_payload, }, ensure_ascii=False)) PY } preview_activity_item() { local mainland_base_url="$1" local activity_key="$2" local requested_by="$3" local raw_activity_response raw_activity_response="$(request_with_status "GET" "${mainland_base_url}/api/v1/ops/activity-stream?limit=50")" local activity_http_status="${raw_activity_response##*$'\n'HTTP_STATUS=}" local activity_response_json="${raw_activity_response%$'\n'HTTP_STATUS=*}" if [[ "${activity_http_status}" -lt 200 || "${activity_http_status}" -ge 300 ]]; then echo "[1/3] activity stream raw response" printf '%s\n' "http_status=${activity_http_status}" print_json_or_raw "${activity_response_json}" exit 1 fi local bundle_json bundle_json="$(build_activity_preview_bundle "${activity_response_json}" "${activity_key}" "${requested_by}" "cli/activity-preview")" local selected_entry_json selected_entry_json="$(python3 - <<'PY' "${bundle_json}" import json import sys payload = json.loads(str(sys.argv[1] or "{}")) print(json.dumps(payload.get("selected_entry") or {}, ensure_ascii=False)) PY )" local request_json request_json="$(python3 - <<'PY' "${bundle_json}" import json import sys payload = json.loads(str(sys.argv[1] or "{}")) print(json.dumps(payload.get("request") or {}, ensure_ascii=False)) PY )" echo "[1/4] selected activity" print_json_or_raw "${selected_entry_json}" echo echo echo "[2/4] request" print_json_or_raw "${request_json}" echo echo local raw_http_response raw_http_response="$(request_with_status "POST" "${mainland_base_url}/api/v1/ops/driver-actions/resolve" "${request_json}")" local http_status="${raw_http_response##*$'\n'HTTP_STATUS=}" local response_json="${raw_http_response%$'\n'HTTP_STATUS=*}" echo "[3/4] raw response" printf '%s\n' "http_status=${http_status}" print_json_or_raw "${response_json}" echo echo echo "[4/4] condensed summary" python3 - <<'PY' "${response_json}" "${http_status}" "${selected_entry_json}" import json import sys payload = json.loads(str(sys.argv[1] or "{}")) http_status = int(sys.argv[2] or 0) selected = json.loads(str(sys.argv[3] or "{}")) data = payload.get("data") or {} preview = data.get("preview") or {} primary = preview.get("primary") or {} gate = data.get("gate") or {} print(json.dumps({ "http_status": http_status, "message": str(payload.get("message") or ""), "selected_entry": selected, "contract": { "contract_key": str(preview.get("contract_key") or ""), "contract_version": str(preview.get("contract_version") or ""), "contract_primary_endpoint": str(preview.get("contract_primary_endpoint") or ""), "preview_endpoint": str(preview.get("preview_endpoint") or ""), "schema_doc_path": str(preview.get("contract_schema_doc_path") or ""), }, "execution_chain": str(preview.get("execution_chain") or ""), "focus_ref": data.get("focus_ref") or {}, "gate": { "decision": str(gate.get("decision") or ""), "decision_label": str(gate.get("decision_label") or ""), "recommendation": str(gate.get("recommendation") or ""), "recommendation_label": str(gate.get("recommendation_label") or ""), "will_execute": bool(gate.get("will_execute", False)), "confirm_required": bool(gate.get("confirm_required", False)), "target_api": str(gate.get("target_api") or ""), "focus_ref": gate.get("focus_ref") or {}, }, "primary": { "label": str(primary.get("label") or ""), "action_code": str(primary.get("action_code") or ""), "target_api": str(primary.get("target_api") or ""), "recommendation_label": str(primary.get("recommendation_label") or ""), "focus_ref": primary.get("focus_ref") or {}, "request_payload": primary.get("request_payload") or {}, }, }, ensure_ascii=False, indent=2)) PY if [[ "${http_status}" -lt 200 || "${http_status}" -ge 300 ]]; then exit 1 fi } show_delivery_queue_status() { local mainland_base_url="$1" local node_code="$2" if [[ -z "${node_code}" ]]; then echo "node_code is required" >&2 exit 1 fi local raw_http_response raw_http_response="$(request_with_status "GET" "${mainland_base_url}/api/v1/ops/nodes/${node_code}/delivery-queue")" local http_status="${raw_http_response##*$'\n'HTTP_STATUS=}" local response_json="${raw_http_response%$'\n'HTTP_STATUS=*}" echo "[1/2] raw response" printf '%s\n' "http_status=${http_status}" print_json_or_raw "${response_json}" echo echo "[2/2] condensed summary" python3 - <<'PY' "${response_json}" "${http_status}" import json import sys payload = json.loads(str(sys.argv[1] or "{}")) http_status = int(sys.argv[2] or 0) data = payload.get("data") or {} summary = data.get("summary") or {} node = data.get("node") or {} records_capability = data.get("records_capability") or {} print(json.dumps({ "http_status": http_status, "node": { "node_code": str(node.get("node_code") or ""), "region": str(node.get("region") or ""), "role": str(node.get("role") or ""), "agent_state_label": str(node.get("agent_state_label") or ""), "remote_access_label": str(node.get("remote_access_label") or ""), }, "summary": { "state": str(summary.get("state") or ""), "label": str(summary.get("label") or ""), "reason": str(summary.get("reason") or ""), "pending_count": int(summary.get("pending_count", 0) or 0), "dead_letter_count": int(summary.get("dead_letter_count", 0) or 0), "last_flush_at": str(summary.get("last_flush_at") or ""), "oldest_pending_at": str(summary.get("oldest_pending_at") or ""), "oldest_dead_letter_at": str(summary.get("oldest_dead_letter_at") or ""), }, "records_capability": { "record_visibility": str(records_capability.get("record_visibility") or ""), "record_source": str(records_capability.get("record_source") or ""), "full_records_available": bool(records_capability.get("full_records_available", False)), }, "recommended_commands": { "records": ( f"bash domain-api/deploy/multi-region/drive_ops_center.sh delivery-queue-records http://127.0.0.1:8100 {str(node.get('node_code') or '')}" if str(node.get("node_code") or "").strip() else "" ), "flush": ( f"bash domain-api/deploy/multi-region/drive_ops_center.sh delivery-queue-flush http://127.0.0.1:8100 {str(node.get('node_code') or '')}" if str(node.get("node_code") or "").strip() else "" ), "replay": ( f"bash domain-api/deploy/multi-region/drive_ops_center.sh delivery-queue-replay http://127.0.0.1:8100 {str(node.get('node_code') or '')}" if str(node.get("node_code") or "").strip() else "" ), }, "head_records": data.get("head_records") or {}, }, ensure_ascii=False, indent=2)) PY } show_node_handover() { local mainland_base_url="$1" local node_code="$2" if [[ -z "${node_code}" ]]; then echo "node_code is required" >&2 exit 1 fi local raw_http_response raw_http_response="$(request_with_status "GET" "${mainland_base_url}/api/v1/ops/nodes/${node_code}/handover")" local http_status="${raw_http_response##*$'\n'HTTP_STATUS=}" local response_json="${raw_http_response%$'\n'HTTP_STATUS=*}" local effective_http_status="${http_status}" local fallback_json="" if [[ "${http_status}" == "404" ]]; then local stack_http_response stack_http_status stack_response_json stack_http_response="$(request_with_status "GET" "${mainland_base_url}/api/v1/ops/stack-diagnosis?base_url=${mainland_base_url}")" stack_http_status="${stack_http_response##*$'\n'HTTP_STATUS=}" stack_response_json="${stack_http_response%$'\n'HTTP_STATUS=*}" if [[ "${stack_http_status}" -ge 200 && "${stack_http_status}" -lt 300 ]]; then fallback_json="$(python3 - <<'PY' "${stack_response_json}" "${node_code}" "${http_status}" import json import sys payload = json.loads(str(sys.argv[1] or "{}")) target_node_code = str(sys.argv[2] or "").strip() backend_http_status = int(sys.argv[3] or 404) data = payload.get("data") or {} managed_nodes = data.get("managed_nodes") or {} problem_nodes = [dict(item or {}) for item in list(managed_nodes.get("problem_nodes") or []) if isinstance(item, dict)] node = next( (item for item in problem_nodes if str(item.get("node_code") or "").strip() == target_node_code), {}, ) first_gap = dict(managed_nodes.get("first_handover_gap") or {}) if not node and str(first_gap.get("node_code") or "").strip() == target_node_code: node = dict(first_gap.get("node") or {}) if not node: node = {"node_code": target_node_code} agent_state = str(node.get("agent_state") or "").strip() remote_access_state = str(node.get("remote_access_state") or "").strip() summary = "当前节点存在接管缺口,建议先生成 bootstrap 方案并检查 Node Agent 接入。" stage_code = "unknown" stage_label = "待判断" stage_summary = summary next_step = { "code": "managed_node_handover", "label": "先看节点接管详情", "summary": "建议先确认节点为什么还没进入 remote-agent 就绪状态。", } if agent_state == "pending_bootstrap": stage_code = "pending_bootstrap" stage_label = "待执行接入" stage_summary = "已签发有效 Token,但目标节点尚未执行 bootstrap 片段。" next_step = { "code": "execute_bootstrap_plan", "label": "去目标机执行接入片段", "summary": "把 bootstrap env 和一键落地片段放到目标机执行,然后观察 register / heartbeat 是否建立。", } elif agent_state == "runtime_only": stage_code = "runtime_only" stage_label = "仅运行态在线" stage_summary = "控制面能看到 runtime 心跳,但 Node Agent 尚未接管。" next_step = { "code": "issue_bootstrap_plan", "label": "生成接入方案", "summary": "下一步应生成 bootstrap env、脚本和一键落地命令,再在目标节点启动 domaincheck-node-agent。", } elif agent_state == "stale": stage_code = "stale" stage_label = "心跳过期" stage_summary = "Node Agent 曾接入过,但心跳已过期,需先恢复服务或重新接入。" next_step = { "code": "recover_agent", "label": "优先恢复 Agent", "summary": "先检查 domaincheck-node-agent 服务、网络连通性和控制面地址,再决定是否重新签发接入方案。", } elif remote_access_state == "agent_pending": stage_code = "agent_pending" stage_label = "待接入" stage_summary = "当前 remote-agent 仍未就绪,先补齐 bootstrap 接入链路。" result = { "http_status": 200, "backend_http_status": backend_http_status, "fallback_used": True, "fallback_source": "stack_diagnosis", "compatibility_warning": "当前运行中的 API 尚未暴露 /api/v1/ops/nodes/{node_code}/handover,已降级为 stack-diagnosis 兼容视图。", "node_code": target_node_code, "summary": stage_summary, "stage": { "code": stage_code, "label": stage_label, "summary": stage_summary, "next_step": next_step, }, "readiness": { "is_managed": True, "is_enabled": True, "has_ssh_access": False, "is_agent_online": agent_state in {"online", "online_busy"}, "is_remote_access_ready": False, }, "ssh_profile": { "host": "", "port": 22, "user": "", "label": "", "reason": "兼容视图未携带 SSH 资料,请到托管节点详情页补看。", "ssh_command_hint": "", }, "cluster": { "status": "", "current_load": 0, "last_heartbeat_at": "", }, "blocking_items": [stage_summary] if stage_code in {"pending_bootstrap", "runtime_only", "stale", "agent_pending"} else [], "attention_items": [ "当前是兼容降级视图,如需完整 SSH / Token / delivery queue 详情,请升级控制面 API 到包含 handover 路由的版本。" ], "bootstrap_defaults": { "control_plane_base_url": "", "root_dir": "/opt/domaincheck", "node_region": "", "node_role": "", }, "endpoints": { "detail": f"/api/v1/ops/nodes/{target_node_code}/handover", "bootstrap_plan": f"/api/v1/ops/nodes/{target_node_code}/handover/bootstrap-plan", }, } print(json.dumps(result, ensure_ascii=False)) PY )" if [[ -n "${fallback_json}" ]]; then effective_http_status="200" fi fi fi echo "[1/2] raw response" printf '%s\n' "http_status=${http_status}" print_json_or_raw "${response_json}" if [[ -n "${fallback_json}" ]]; then echo printf '%s\n' "fallback=stack_diagnosis" print_json_or_raw "${fallback_json}" fi echo echo "[2/2] condensed summary" python3 - <<'PY' "${response_json}" "${effective_http_status}" "${fallback_json}" import json import pathlib import sys payload = json.loads(str(sys.argv[1] or "{}")) http_status = int(sys.argv[2] or 0) fallback_json = str(sys.argv[3] or "").strip() if fallback_json: data = json.loads(fallback_json) else: data = payload.get("data") or {} stage = data.get("stage") or {} readiness = data.get("readiness") or {} ssh_profile = data.get("ssh_profile") or {} cluster = data.get("cluster") or {} bootstrap_defaults = data.get("bootstrap_defaults") or {} node_code = str(data.get("node_code") or "").strip() next_step = stage.get("next_step") or {} print(json.dumps({ "http_status": http_status, "backend_http_status": int(data.get("backend_http_status", 0) or 0) if fallback_json else http_status, "fallback_used": bool(data.get("fallback_used", False)), "fallback_source": str(data.get("fallback_source") or ""), "compatibility_warning": str(data.get("compatibility_warning") or ""), "node_code": node_code, "summary": str(data.get("summary") or ""), "stage": { "code": str(stage.get("code") or ""), "label": str(stage.get("label") or ""), "summary": str(stage.get("summary") or ""), "next_step": next_step, }, "readiness": { "is_managed": bool(readiness.get("is_managed", False)), "is_enabled": bool(readiness.get("is_enabled", False)), "has_ssh_access": bool(readiness.get("has_ssh_access", False)), "is_agent_online": bool(readiness.get("is_agent_online", False)), "is_remote_access_ready": bool(readiness.get("is_remote_access_ready", False)), }, "ssh_profile": { "host": str(ssh_profile.get("host") or ""), "port": int(ssh_profile.get("port", 22) or 22), "user": str(ssh_profile.get("user") or ""), "label": str(ssh_profile.get("label") or ""), "reason": str(ssh_profile.get("reason") or ""), "ssh_command_hint": str(ssh_profile.get("ssh_command_hint") or ""), }, "cluster": { "status": str(cluster.get("status") or ""), "current_load": int(cluster.get("current_load", 0) or 0), "last_heartbeat_at": str(cluster.get("last_heartbeat_at") or ""), }, "blocking_items": list(data.get("blocking_items") or []), "attention_items": list(data.get("attention_items") or []), "bootstrap_defaults": { "control_plane_base_url": str(bootstrap_defaults.get("control_plane_base_url") or ""), "root_dir": str(bootstrap_defaults.get("root_dir") or ""), "node_region": str(bootstrap_defaults.get("node_region") or ""), "node_role": str(bootstrap_defaults.get("node_role") or ""), }, "endpoints": data.get("endpoints") or {}, "recommended_commands": { "bootstrap_plan": ( f"bash domain-api/deploy/multi-region/drive_ops_center.sh node-bootstrap-plan http://127.0.0.1:8100 {node_code}" if node_code else "" ), "delivery_queue": ( f"bash domain-api/deploy/multi-region/drive_ops_center.sh delivery-queue http://127.0.0.1:8100 {node_code}" if node_code else "" ), "node_agent_check": "bash domain-api/deploy/multi-region/check_node_agent.sh", "ssh_hint": str(ssh_profile.get("ssh_command_hint") or ""), }, }, ensure_ascii=False, indent=2)) PY } show_ops_nodes() { local mainland_base_url="$1" local raw_http_response raw_http_response="$(request_with_status "GET" "${mainland_base_url}/api/v1/ops/nodes")" local http_status="${raw_http_response##*$'\n'HTTP_STATUS=}" local response_json="${raw_http_response%$'\n'HTTP_STATUS=*}" echo "[1/2] raw response" printf '%s\n' "http_status=${http_status}" print_json_or_raw "${response_json}" echo echo "[2/2] condensed summary" python3 - <<'PY' "${response_json}" "${http_status}" import json import sys payload = json.loads(str(sys.argv[1] or "{}")) http_status = int(sys.argv[2] or 0) data = payload.get("data") or {} summary = data.get("summary") or {} nodes = [dict(item or {}) for item in list(data.get("nodes") or []) if isinstance(item, dict)] rows = [] for node in nodes: rows.append({ "node_code": str(node.get("node_code") or "").strip(), "region": str(node.get("region") or "").strip(), "role": str(node.get("role") or "").strip(), "agent_state": str(node.get("agent_state") or "").strip(), "remote_access_state": str(node.get("remote_access_state") or "").strip(), "ssh_access_label": str(node.get("ssh_access_label") or "").strip(), "ssh_entry": ( f"{str(node.get('ssh_user') or '').strip()}@{str(node.get('ssh_host') or '').strip()}:{int(node.get('ssh_port') or 22)}" if str(node.get("ssh_host") or "").strip() and str(node.get("ssh_user") or "").strip() else "" ), "participation_state": str(node.get("participation_state") or "").strip(), "cluster_status": str(node.get("cluster_status") or "").strip(), "current_load": int(node.get("cluster_current_load", 0) or 0), }) print(json.dumps({ "http_status": http_status, "summary": { "total": int(summary.get("total", len(nodes)) or 0), "managed_enabled": int(summary.get("managed_enabled", 0) or 0), "agent_ready": int(summary.get("agent_ready", 0) or 0), "ssh_ready": int(summary.get("ssh_ready", 0) or 0), "remote_access_ready": int(summary.get("remote_access_ready", 0) or 0), "participating": int(summary.get("participating", 0) or 0), "queue_dead_letter_nodes": int(summary.get("queue_dead_letter_nodes", 0) or 0), "remote_access_state_counts": dict(summary.get("remote_access_state_counts") or {}), }, "nodes": rows, }, ensure_ascii=False, indent=2)) PY } bind_node_ssh_profile() { local mainland_base_url="$1" local node_code="$2" local ssh_host="$3" local ssh_user="$4" local ssh_port="${5:-22}" local title="${6:-}" if [[ -z "${node_code}" || -z "${ssh_host}" || -z "${ssh_user}" ]]; then echo "usage: node-bind-ssh [mainland_base_url] [ssh_port] [title]" >&2 exit 1 fi local request_json request_json="$(python3 - <<'PY' "${node_code}" "${ssh_host}" "${ssh_user}" "${ssh_port}" "${title}" import json import sys node_code = str(sys.argv[1] or "").strip() ssh_host = str(sys.argv[2] or "").strip() ssh_user = str(sys.argv[3] or "").strip() ssh_port = int(sys.argv[4] or 22) title = str(sys.argv[5] or "").strip() payload = { "node_code": node_code, "ssh_host": ssh_host, "ssh_user": ssh_user, "ssh_port": ssh_port, "auth_mode": "key", "metadata": { "ssh_bound_via": "drive_ops_center.sh/node-bind-ssh", }, } if title: payload["title"] = title print(json.dumps(payload, ensure_ascii=False)) PY )" local raw_http_response raw_http_response="$(request_with_status "POST" "${mainland_base_url}/api/v1/ops/nodes" "${request_json}")" local http_status="${raw_http_response##*$'\n'HTTP_STATUS=}" local response_json="${raw_http_response%$'\n'HTTP_STATUS=*}" echo "[1/3] save managed node ssh profile" printf '%s\n' "http_status=${http_status}" print_json_or_raw "${response_json}" echo echo "[2/3] node handover after binding" show_node_handover "${mainland_base_url}" "${node_code}" echo echo "[3/3] condensed ssh binding" python3 - <<'PY' "${response_json}" "${http_status}" "${node_code}" "${ssh_host}" "${ssh_user}" "${ssh_port}" "${mainland_base_url}" import json import sys payload = json.loads(str(sys.argv[1] or "{}")) http_status = int(sys.argv[2] or 0) node_code = str(sys.argv[3] or "").strip() ssh_host = str(sys.argv[4] or "").strip() ssh_user = str(sys.argv[5] or "").strip() ssh_port = int(sys.argv[6] or 22) base_url = str(sys.argv[7] or "http://127.0.0.1:8100").strip() or "http://127.0.0.1:8100" data = payload.get("data") or {} node = dict(data.get("node") or {}) print(json.dumps({ "http_status": http_status, "message": str(payload.get("message") or ""), "node_code": node_code, "ssh_entry": f"{ssh_user}@{ssh_host}:{ssh_port}", "saved_title": str(node.get("title") or "").strip(), "saved_region": str(node.get("region") or "").strip(), "saved_role": str(node.get("role") or "").strip(), "next_recommended_commands": { "nodes": f"bash domain-api/deploy/multi-region/drive_ops_center.sh nodes {base_url}", "handover": f"bash domain-api/deploy/multi-region/drive_ops_center.sh node-handover {base_url} {node_code}", "bootstrap_plan": f"bash domain-api/deploy/multi-region/drive_ops_center.sh node-bootstrap-plan {base_url} {node_code}", }, }, ensure_ascii=False, indent=2)) PY } show_node_scene_log() { local mainland_base_url="$1" local node_code="$2" local limit="${3:-80}" local mode="${4:-}" if [[ -z "${node_code}" ]]; then echo "node_code is required" >&2 exit 1 fi local request_url request_url="$(python3 - <<'PY' "${mainland_base_url}" "${node_code}" "${limit}" "${mode}" import sys from urllib.parse import quote, urlencode base_url = str(sys.argv[1] or "").rstrip("/") node_code = str(sys.argv[2] or "").strip() limit = max(1, min(int(sys.argv[3] or 80), 400)) mode = str(sys.argv[4] or "").strip().lower() params = {"limit": limit} if mode in {"key", "full"}: params["mode"] = mode endpoint = f"{base_url}/api/v1/ops/nodes/{quote(node_code, safe='')}/scene-log" query_string = urlencode(params) print(f"{endpoint}?{query_string}" if query_string else endpoint) PY )" local raw_http_response raw_http_response="$(request_with_status "GET" "${request_url}")" local http_status="${raw_http_response##*$'\n'HTTP_STATUS=}" local response_json="${raw_http_response%$'\n'HTTP_STATUS=*}" local effective_http_status="${http_status}" local fallback_json="" if [[ "${http_status}" == "404" ]]; then local runtime_http_response runtime_http_status runtime_response_json runtime_http_response="$(request_with_status "GET" "${mainland_base_url}/api/v1/runtime/status")" runtime_http_status="${runtime_http_response##*$'\n'HTTP_STATUS=}" runtime_response_json="${runtime_http_response%$'\n'HTTP_STATUS=*}" if [[ "${runtime_http_status}" -ge 200 && "${runtime_http_status}" -lt 300 ]]; then fallback_json="$(python3 - <<'PY' "${runtime_response_json}" "${node_code}" "${limit}" "${mode}" "${http_status}" import json import re import sys payload = json.loads(str(sys.argv[1] or "{}")) node_code = str(sys.argv[2] or "").strip() limit = max(1, min(int(sys.argv[3] or 80), 400)) requested_mode = str(sys.argv[4] or "").strip().lower() backend_http_status = int(sys.argv[5] or 404) data = dict(payload.get("data") or {}) detect = dict(data.get("detect") or {}) cluster = dict(data.get("cluster") or {}) log_sync = dict(detect.get("log_sync") or {}) mode = "full" if requested_mode == "full" else ("full" if str(log_sync.get("mode") or "").strip().lower() == "full" else "key") cluster_nodes = [dict(item or {}) for item in list(cluster.get("nodes") or []) if isinstance(item, dict)] cluster_map = {str(item.get("node_code") or "").strip(): item for item in cluster_nodes if str(item.get("node_code") or "").strip()} participating_nodes = [dict(item or {}) for item in list(detect.get("participating_nodes") or []) if isinstance(item, dict)] standby_nodes = [dict(item or {}) for item in list(detect.get("non_participating_nodes") or []) if isinstance(item, dict)] participating_map = {str(item.get("node_code") or "").strip(): item for item in participating_nodes if str(item.get("node_code") or "").strip()} standby_map = {str(item.get("node_code") or "").strip(): item for item in standby_nodes if str(item.get("node_code") or "").strip()} preview_lines = [str(item or "").strip() for item in list(log_sync.get("preview_lines") or []) if str(item or "").strip()] pattern = re.compile(r"^\[(?P[^\]]+)\]\s+\[(?P[^\]]+)\]\s+(?P.+)$") node_preview_lines = [] for line in preview_lines: match = pattern.match(line) if not match: continue if str(match.group("node_code") or "").strip() != node_code: continue node_preview_lines.append({ "created_at": str(match.group("created_at") or "").strip(), "node_code": node_code, "message": str(match.group("message") or "").strip(), "line": line, "mode": "key", }) node_preview_lines = node_preview_lines[-limit:] source_summary_map = { str(item.get("node_code") or "").strip(): dict(item or {}) for item in list(log_sync.get("source_node_summaries") or []) if isinstance(item, dict) and str(item.get("node_code") or "").strip() } missing_summary_map = { str(item.get("node_code") or "").strip(): dict(item or {}) for item in list(log_sync.get("missing_participating_node_summaries") or []) if isinstance(item, dict) and str(item.get("node_code") or "").strip() } covered_summary_map = { str(item.get("node_code") or "").strip(): dict(item or {}) for item in list(log_sync.get("covered_participating_node_summaries") or []) if isinstance(item, dict) and str(item.get("node_code") or "").strip() } node_meta = dict(participating_map.get(node_code) or standby_map.get(node_code) or {}) cluster_meta = dict(cluster_map.get(node_code) or {}) source_summary = dict(source_summary_map.get(node_code) or covered_summary_map.get(node_code) or {}) missing_summary = dict(missing_summary_map.get(node_code) or {}) derived_line_count = len(node_preview_lines) derived_last_at = str((node_preview_lines[-1] if node_preview_lines else {}).get("created_at") or "").strip() derived_last_line = str((node_preview_lines[-1] if node_preview_lines else {}).get("line") or "").strip() source_summary = { "node_code": node_code, "line_count": max(int(source_summary.get("line_count", 0) or 0), derived_line_count), "key_line_count": max(int(source_summary.get("key_line_count", 0) or 0), derived_line_count), "full_line_count": int(source_summary.get("full_line_count", 0) or 0), "last_at": derived_last_at or str(source_summary.get("last_at") or "").strip(), "last_line": derived_last_line or str(source_summary.get("last_line") or "").strip(), } log_sync_enabled = bool(log_sync.get("enabled", False)) detect_participating = node_code in participating_map standby_visible = node_code in standby_map record_available = bool(source_summary.get("line_count", 0) or node_preview_lines) if record_available: if detect_participating: status = "full_capture" if mode == "full" else "healthy" else: status = "historical_sample" elif missing_summary: missing_reason_code = str(missing_summary.get("missing_reason_code") or "").strip() if missing_reason_code == "disabled": status = "disabled" elif missing_reason_code == "waiting_sample": status = "waiting_sample" else: status = "missing_sample" elif detect_participating: status = "waiting_sample" if log_sync_enabled else "disabled" elif standby_visible: status = "standby" else: status = "unknown" status_labels = { "healthy": ("关键覆盖", "success"), "full_capture": ("全量观察", "success"), "historical_sample": ("历史样本", "success"), "waiting_sample": ("等待样本", "warning"), "missing_sample": ("缺少样本", "warning"), "disabled": ("已关闭", "info"), "standby": ("在线待命", "info"), "unknown": ("未发现", "info"), } status_label, status_type = status_labels.get(status, ("待确认", "info")) if record_available and detect_participating: summary = f"节点当前正在参与检测,已通过兼容降级视图识别到 {int(source_summary.get('line_count', 0) or 0)} 条现场日志样本。" elif record_available: summary = f"节点已通过兼容降级视图识别到 {int(source_summary.get('line_count', 0) or 0)} 条现场日志样本。" elif missing_summary: summary = str(missing_summary.get("missing_reason") or "").strip() or "当前还没有收到该节点的现场日志样本。" elif detect_participating and log_sync_enabled: summary = "节点正在参与检测,但当前现场日志样本仍未形成。" elif detect_participating: summary = "节点正在参与检测,但远端日志回传当前关闭。" elif standby_visible: summary = "节点当前在线但未参与本轮检测,暂时没有新的现场日志样本。" else: summary = "当前没有找到该节点的现场日志记录。" latest_record = dict(node_preview_lines[-1] or {}) if node_preview_lines else {} missing_reason_code = str(missing_summary.get("missing_reason_code") or "").strip() missing_reason = str(missing_summary.get("missing_reason") or "").strip() result = { "http_status": 200, "backend_http_status": backend_http_status, "fallback_used": True, "fallback_source": "runtime_status", "fallback_reason": "running_api_missing_scene_log_route", "compatibility_warning": "当前运行中的 API 尚未暴露 /api/v1/ops/nodes/{node_code}/scene-log,已降级为 runtime/status 兼容视图。", "node_code": node_code, "status": status, "status_label": status_label, "status_type": status_type, "mode": mode, "mode_label": "全量回传" if mode == "full" else "关键回传", "summary": summary, "node": { "region": str(node_meta.get("region") or cluster_meta.get("region") or missing_summary.get("region") or "").strip(), "role": str(node_meta.get("role") or cluster_meta.get("role") or missing_summary.get("role") or "").strip(), "status": str(node_meta.get("status") or cluster_meta.get("status") or "").strip(), "current_load": int(node_meta.get("current_load", cluster_meta.get("current_load", 0)) or 0), "last_heartbeat_at": str(node_meta.get("last_heartbeat_at") or cluster_meta.get("last_heartbeat_at") or "").strip(), }, "participation": { "detect_participating": detect_participating, "participation_label": str(node_meta.get("participation_label") or "").strip(), "participation_bucket": str(node_meta.get("participation_bucket") or "").strip(), "participation_reason": str(node_meta.get("participation_reason") or "").strip(), }, "source_summary": { "line_count": int(source_summary.get("line_count", 0) or 0), "key_line_count": int(source_summary.get("key_line_count", 0) or 0), "full_line_count": int(source_summary.get("full_line_count", 0) or 0), "last_at": str(source_summary.get("last_at") or "").strip(), "last_line": str(source_summary.get("last_line") or "").strip(), }, "missing_reason_code": missing_reason_code, "missing_reason": missing_reason, "records_total": int(source_summary.get("line_count", 0) or 0), "records_visible": len(node_preview_lines), "records_truncated": False, "latest_record": latest_record, "preview_lines": [str(item.get("line") or "").strip() for item in node_preview_lines[-8:]], } print(json.dumps(result, ensure_ascii=False)) PY )" if [[ -n "${fallback_json}" ]]; then effective_http_status="200" fi fi fi echo "[1/2] raw response" printf '%s\n' "http_status=${http_status}" printf '%s\n' "request_url=${request_url}" print_json_or_raw "${response_json}" if [[ -n "${fallback_json}" ]]; then echo printf '%s\n' "fallback=runtime_status" print_json_or_raw "${fallback_json}" fi echo echo "[2/2] condensed summary" python3 - <<'PY' "${response_json}" "${effective_http_status}" "${fallback_json}" import json import pathlib import sys payload = json.loads(str(sys.argv[1] or "{}")) http_status = int(sys.argv[2] or 0) fallback_json = str(sys.argv[3] or "").strip() if fallback_json: data = json.loads(fallback_json) node = data.get("node") or {} participation = data.get("participation") or {} source_summary = data.get("source_summary") or {} latest_record = data.get("latest_record") or {} preview_lines = list(data.get("preview_lines") or []) else: data = payload.get("data") or {} node = data.get("node") or {} participation = data.get("participation") or {} source_summary = data.get("source_summary") or {} records = [dict(item or {}) for item in list(data.get("records") or []) if dict(item or {})] latest_record = data.get("latest_record") or {} preview_lines = [str(item.get("line") or "") for item in records[-8:]] print(json.dumps({ "http_status": http_status, "backend_http_status": int(data.get("backend_http_status", 0) or 0) if fallback_json else http_status, "fallback_used": bool(data.get("fallback_used", False)), "fallback_source": str(data.get("fallback_source") or ""), "compatibility_warning": str(data.get("compatibility_warning") or ""), "node_code": str(data.get("node_code") or ""), "status": str(data.get("status") or ""), "status_label": str(data.get("status_label") or ""), "mode": str(data.get("mode") or ""), "mode_label": str(data.get("mode_label") or ""), "summary": str(data.get("summary") or ""), "node": { "region": str(node.get("region") or ""), "role": str(node.get("role") or ""), "status": str(node.get("status") or ""), "current_load": int(node.get("current_load", 0) or 0), "last_heartbeat_at": str(node.get("last_heartbeat_at") or ""), }, "participation": { "detect_participating": bool(participation.get("detect_participating", False)), "participation_label": str(participation.get("participation_label") or ""), "participation_bucket": str(participation.get("participation_bucket") or ""), "participation_reason": str(participation.get("participation_reason") or ""), }, "source_summary": { "line_count": int(source_summary.get("line_count", 0) or 0), "key_line_count": int(source_summary.get("key_line_count", 0) or 0), "full_line_count": int(source_summary.get("full_line_count", 0) or 0), "last_at": str(source_summary.get("last_at") or ""), "last_line": str(source_summary.get("last_line") or ""), }, "missing_reason_code": str(data.get("missing_reason_code") or ""), "missing_reason": str(data.get("missing_reason") or ""), "records_total": int(data.get("records_total", 0) or 0), "records_visible": int(data.get("records_visible", 0) or 0), "records_truncated": bool(data.get("records_truncated", False)), "latest_record": latest_record, "preview_lines": [str(item or "") for item in preview_lines], }, ensure_ascii=False, indent=2)) PY } build_node_handover_bootstrap_plan() { local mainland_base_url="$1" local node_code="$2" local issued_by="$3" local expires_in_hours="$4" local control_plane_base_url="$5" local root_dir="$6" if [[ -z "${node_code}" ]]; then echo "node_code is required" >&2 exit 1 fi local request_json request_json="$(python3 - <<'PY' "${issued_by}" "${expires_in_hours}" "${control_plane_base_url}" "${root_dir}" import json import sys issued_by = str(sys.argv[1] or "cli/ops-center/node-bootstrap-plan").strip() or "cli/ops-center/node-bootstrap-plan" expires_in_hours = max(1, min(int(sys.argv[2] or 72), 24 * 30)) control_plane_base_url = str(sys.argv[3] or "").strip() root_dir = str(sys.argv[4] or "/opt/domaincheck").strip() or "/opt/domaincheck" print(json.dumps({ "issued_by": issued_by, "expires_in_hours": expires_in_hours, "control_plane_base_url": control_plane_base_url, "root_dir": root_dir, "metadata": { "issued_from": "cli-node-handover-bootstrap-plan", }, }, ensure_ascii=False)) PY )" local raw_http_response raw_http_response="$(request_with_status "POST" "${mainland_base_url}/api/v1/ops/nodes/${node_code}/handover/bootstrap-plan" "${request_json}")" local http_status="${raw_http_response##*$'\n'HTTP_STATUS=}" local response_json="${raw_http_response%$'\n'HTTP_STATUS=*}" local effective_http_status="${http_status}" local fallback_json="" if [[ "${http_status}" == "404" ]]; then local generic_request_json generic_http_response generic_http_status generic_response_json generic_request_json="$(python3 - <<'PY' "${node_code}" "${issued_by}" "${expires_in_hours}" "${control_plane_base_url}" "${root_dir}" import json import sys node_code = str(sys.argv[1] or "").strip() issued_by = str(sys.argv[2] or "cli/ops-center/node-bootstrap-plan").strip() or "cli/ops-center/node-bootstrap-plan" expires_in_hours = max(1, min(int(sys.argv[3] or 72), 24 * 30)) control_plane_base_url = str(sys.argv[4] or "").strip() root_dir = str(sys.argv[5] or "/opt/domaincheck").strip() or "/opt/domaincheck" normalized = node_code.lower() node_region = "overseas" if normalized.startswith("overseas-") else "mainland" node_role = "control" if ("controller" in normalized or "-control" in normalized) else "worker" print(json.dumps({ "node_code": node_code, "node_region": node_region, "node_role": node_role, "issued_by": issued_by, "expires_in_hours": expires_in_hours, "control_plane_base_url": control_plane_base_url, "root_dir": root_dir, "metadata": { "issued_from": "cli-node-handover-bootstrap-plan-fallback", "fallback_used": True, }, }, ensure_ascii=False)) PY )" generic_http_response="$(request_with_status "POST" "${mainland_base_url}/api/v1/ops/agent/bootstrap-plan" "${generic_request_json}")" generic_http_status="${generic_http_response##*$'\n'HTTP_STATUS=}" generic_response_json="${generic_http_response%$'\n'HTTP_STATUS=*}" if [[ "${generic_http_status}" -ge 200 && "${generic_http_status}" -lt 300 ]]; then fallback_json="$(python3 - <<'PY' "${generic_response_json}" "${http_status}" "${node_code}" import json import sys payload = json.loads(str(sys.argv[1] or "{}")) backend_http_status = int(sys.argv[2] or 404) node_code = str(sys.argv[3] or "").strip() data = payload.get("data") or {} bootstrap_plan = dict(data.get("bootstrap_plan") or {}) print(json.dumps({ "http_status": 200, "backend_http_status": backend_http_status, "fallback_used": True, "fallback_source": "ops_agent_bootstrap_plan", "compatibility_warning": "当前运行中的 API 尚未暴露节点级 /handover/bootstrap-plan,已降级为通用 /ops/agent/bootstrap-plan。", "message": str(payload.get("message") or ""), "node_code": node_code, "token_preview": str(data.get("token_preview") or ""), "expires_at": str(data.get("expires_at") or ""), "control_plane_base_url": str(data.get("control_plane_base_url") or ""), "bootstrap_plan": bootstrap_plan, "handover_stage": { "code": "bootstrap_plan_issued", "label": "已生成接入方案", "summary": "当前已通过通用 bootstrap-plan 接口生成接入方案,可直接去目标节点执行。", }, }, ensure_ascii=False)) PY )" effective_http_status="200" fi fi echo "[1/2] raw response" printf '%s\n' "http_status=${http_status}" print_json_or_raw "${response_json}" if [[ -n "${fallback_json}" ]]; then echo printf '%s\n' "fallback=ops_agent_bootstrap_plan" print_json_or_raw "${fallback_json}" fi echo echo "[2/2] condensed summary" python3 - <<'PY' "${response_json}" "${effective_http_status}" "${fallback_json}" import json import pathlib import sys payload = json.loads(str(sys.argv[1] or "{}")) http_status = int(sys.argv[2] or 0) fallback_json = str(sys.argv[3] or "").strip() if fallback_json: data = json.loads(fallback_json) plan = data.get("bootstrap_plan") or {} stage = data.get("handover_stage") or {} node_code = str(data.get("node_code") or "").strip() else: data = payload.get("data") or {} plan = data.get("bootstrap_plan") or {} handover = data.get("handover") or {} stage = handover.get("stage") or {} node_code = str(data.get("node_code") or handover.get("node_code") or "").strip() workspace_root = pathlib.Path.cwd() ops_agent_service_path = workspace_root / "domain-api" / "app" / "services" / "ops_agent_service.py" bootstrap_script_path = workspace_root / "domain-api" / "deploy" / "multi-region" / "bootstrap_node_agent.sh" ops_agent_service_text = "" bootstrap_script_text = "" try: ops_agent_service_text = ops_agent_service_path.read_text(encoding="utf-8", errors="replace") except Exception: ops_agent_service_text = "" try: bootstrap_script_text = bootstrap_script_path.read_text(encoding="utf-8", errors="replace") except Exception: bootstrap_script_text = "" repo_supports_install_command_block = ( "install_command_block" in ops_agent_service_text and "_candidate_node_agent_install_paths" in ops_agent_service_text ) repo_supports_multi_layout_bootstrap = "resolve_project_root" in bootstrap_script_text install_command_block_present = bool(str(plan.get("install_command_block") or "").strip()) runtime_may_need_restart = bool(repo_supports_install_command_block and not install_command_block_present) runtime_restart_hint = ( "仓库代码已支持 install_command_block,但本次接口响应里仍未返回,当前运行中的控制面 API 可能还没重启到最新代码。" if runtime_may_need_restart else "" ) print(json.dumps({ "http_status": http_status, "backend_http_status": int(data.get("backend_http_status", 0) or 0) if fallback_json else http_status, "fallback_used": bool(data.get("fallback_used", False)), "fallback_source": str(data.get("fallback_source") or ""), "compatibility_warning": str(data.get("compatibility_warning") or ""), "runtime_restart_hint": runtime_restart_hint, "message": str(payload.get("message") or ""), "node_code": node_code, "token_preview": str(data.get("token_preview") or ""), "expires_at": str(data.get("expires_at") or ""), "control_plane_base_url": str(data.get("control_plane_base_url") or ""), "bootstrap_plan": { "env_file": str(plan.get("env_file") or ""), "service_name": str(plan.get("service_name") or ""), "bootstrap_script_name": str(plan.get("bootstrap_script_name") or ""), "bootstrap_script_path": str(plan.get("bootstrap_script_path") or ""), "install_command_block_present": install_command_block_present, "bootstrap_run_script_block_present": bool(str(plan.get("bootstrap_run_script_block") or "").strip()), }, "repo_capability": { "supports_install_command_block": repo_supports_install_command_block, "supports_multi_layout_bootstrap": repo_supports_multi_layout_bootstrap, "runtime_may_need_restart": runtime_may_need_restart, }, "handover_stage": { "code": str(stage.get("code") or ""), "label": str(stage.get("label") or ""), "summary": str(stage.get("summary") or ""), }, "recommended_commands": { "node_handover": ( f"bash domain-api/deploy/multi-region/drive_ops_center.sh node-handover http://127.0.0.1:8100 {node_code}" if node_code else "" ), "delivery_queue": ( f"bash domain-api/deploy/multi-region/drive_ops_center.sh delivery-queue http://127.0.0.1:8100 {node_code}" if node_code else "" ), "restart_api_if_runtime_stale": ( "bash domain-api/deploy/multi-region/drive_ops_center.sh runtime-refresh-recover http://127.0.0.1:8100" if runtime_may_need_restart else "" ), }, }, ensure_ascii=False, indent=2)) PY } show_stack_first_gap_handover() { local mainland_base_url="$1" local raw_http_response raw_http_response="$(request_with_status "GET" "${mainland_base_url}/api/v1/ops/stack-diagnosis?base_url=${mainland_base_url}")" local http_status="${raw_http_response##*$'\n'HTTP_STATUS=}" local response_json="${raw_http_response%$'\n'HTTP_STATUS=*}" local first_gap_node_code first_gap_node_code="$(python3 - <<'PY' "${response_json}" import json import sys payload = json.loads(str(sys.argv[1] or "{}")) data = payload.get("data") or {} managed_nodes = data.get("managed_nodes") or {} first_gap = managed_nodes.get("first_handover_gap") or {} problem_nodes = [dict(item or {}) for item in list(managed_nodes.get("problem_nodes") or []) if isinstance(item, dict)] node_code = str( first_gap.get("node_code") or managed_nodes.get("first_handover_gap_node_code") or ((problem_nodes[0] or {}).get("node_code") if problem_nodes else "") or "" ).strip() print(node_code) PY )" if [[ -z "${first_gap_node_code}" ]]; then python3 - <<'PY' "${http_status}" "${response_json}" import json import sys http_status = int(sys.argv[1] or 0) payload = json.loads(str(sys.argv[2] or "{}")) data = payload.get("data") or {} managed_nodes = data.get("managed_nodes") or {} print(json.dumps({ "http_status": http_status, "message": "当前 stack diagnosis 没有首个 handover gap 节点。", "managed_nodes": { "managed_enabled": int(managed_nodes.get("managed_enabled", 0) or 0), "remote_access_ready": int(managed_nodes.get("remote_access_ready", 0) or 0), "problem_nodes": list(managed_nodes.get("problem_nodes") or [])[:5], }, }, ensure_ascii=False, indent=2)) PY return 0 fi show_node_handover "${mainland_base_url}" "${first_gap_node_code}" } show_node_onboarding() { local mainland_base_url="$1" local node_code="$2" if [[ -z "${node_code}" ]]; then echo "node_code is required" >&2 exit 1 fi local raw_http_response raw_http_response="$(request_with_status "GET" "${mainland_base_url}/api/v1/ops/nodes/${node_code}/onboarding")" local http_status="${raw_http_response##*$'\n'HTTP_STATUS=}" local response_json="${raw_http_response%$'\n'HTTP_STATUS=*}" local effective_http_status="${http_status}" local fallback_json="" if [[ "${http_status}" == "404" ]]; then local handover_http_response handover_http_status handover_response_json handover_http_response="$(request_with_status "GET" "${mainland_base_url}/api/v1/ops/nodes/${node_code}/handover")" handover_http_status="${handover_http_response##*$'\n'HTTP_STATUS=}" handover_response_json="${handover_http_response%$'\n'HTTP_STATUS=*}" if [[ "${handover_http_status}" == "404" ]]; then local stack_http_response stack_http_status stack_response_json stack_http_response="$(request_with_status "GET" "${mainland_base_url}/api/v1/ops/stack-diagnosis?base_url=${mainland_base_url}")" stack_http_status="${stack_http_response##*$'\n'HTTP_STATUS=}" stack_response_json="${stack_http_response%$'\n'HTTP_STATUS=*}" if [[ "${stack_http_status}" -ge 200 && "${stack_http_status}" -lt 300 ]]; then handover_response_json="$(python3 - <<'PY' "${stack_response_json}" "${node_code}" import json import sys payload = json.loads(str(sys.argv[1] or "{}")) target_node_code = str(sys.argv[2] or "").strip() data = payload.get("data") or {} managed_nodes = data.get("managed_nodes") or {} problem_nodes = [dict(item or {}) for item in list(managed_nodes.get("problem_nodes") or []) if isinstance(item, dict)] first_gap = dict(managed_nodes.get("first_handover_gap") or {}) node = next((item for item in problem_nodes if str(item.get("node_code") or "").strip() == target_node_code), {}) if not node and str(first_gap.get("node_code") or "").strip() == target_node_code: node = dict(first_gap.get("node") or {}) if not node: node = {"node_code": target_node_code} agent_state = str(node.get("agent_state") or "").strip() remote_access_state = str(node.get("remote_access_state") or "").strip() stage_code = "unknown" stage_label = "待判断" stage_summary = "当前节点存在接管缺口,建议先生成 bootstrap 方案并检查 Node Agent 接入。" if agent_state == "pending_bootstrap": stage_code = "pending_bootstrap" stage_label = "待执行接入" stage_summary = "已签发有效 Token,但目标节点尚未执行 bootstrap 片段。" elif agent_state == "runtime_only": stage_code = "runtime_only" stage_label = "仅运行态在线" stage_summary = "控制面能看到 runtime 心跳,但 Node Agent 尚未接管。" elif agent_state == "stale": stage_code = "stale" stage_label = "心跳过期" stage_summary = "Node Agent 曾接入过,但心跳已过期,需先恢复服务或重新接入。" elif remote_access_state == "agent_pending": stage_code = "agent_pending" stage_label = "待接入" stage_summary = "当前 remote-agent 仍未就绪,先补齐 bootstrap 接入链路。" print(json.dumps({ "data": { "node_code": target_node_code, "summary": stage_summary, "stage": { "code": stage_code, "label": stage_label, "summary": stage_summary, }, "readiness": { "is_managed": True, "is_enabled": True, "has_ssh_access": False, "is_agent_online": agent_state in {"online", "online_busy"}, "is_remote_access_ready": False, }, } }, ensure_ascii=False)) PY )" handover_http_status="200" fi fi if [[ "${handover_http_status}" -ge 200 && "${handover_http_status}" -lt 300 ]]; then fallback_json="$(python3 - <<'PY' "${handover_response_json}" "${node_code}" "${http_status}" import json import sys payload = json.loads(str(sys.argv[1] or "{}")) node_code = str(sys.argv[2] or "").strip() backend_http_status = int(sys.argv[3] or 404) data = payload.get("data") or {} readiness = data.get("readiness") or {} stage = data.get("stage") or {} is_agent_online = bool(readiness.get("is_agent_online", False)) has_ssh_access = bool(readiness.get("has_ssh_access", False)) is_remote_access_ready = bool(readiness.get("is_remote_access_ready", False)) acceptance_execution_mode = "" acceptance_status_code = "blocked" acceptance_status_label = "暂不可验收" acceptance_summary = "当前还没进入标准接管就绪状态,暂不建议直接跑 onboarding.acceptance。" if is_agent_online: acceptance_execution_mode = "remote-agent" acceptance_status_code = "ready" acceptance_status_label = "可远端验收" acceptance_summary = "Node Agent 已在线,可以直接跑 onboarding.acceptance 做标准接管验收。" elif has_ssh_access: acceptance_execution_mode = "ssh" acceptance_status_code = "ssh_only" acceptance_status_label = "可走 SSH 验收" acceptance_summary = "当前 Agent 还没在线,但 SSH 已备好,可先走 SSH 模式做接管验收。" onboarding_stage_code = str(stage.get("code") or "").strip() or "unknown" onboarding_stage_label = str(stage.get("label") or "").strip() or "待判断" onboarding_summary = str(data.get("summary") or stage.get("summary") or "").strip() if acceptance_status_code in {"ready", "ssh_only"} and onboarding_stage_code == "ready": onboarding_stage_code = "acceptance_ready" onboarding_stage_label = "待接管验收" onboarding_summary = acceptance_summary actions = [] if onboarding_stage_code in {"pending_bootstrap", "runtime_only", "profile_incomplete", "ssh_ready", "token_issue", "stale"}: actions.append({ "key": "bootstrap_plan", "label": "生成接入方案", "summary": "先生成并执行 Node Agent bootstrap 方案,再观察 register / heartbeat 是否建立。", }) if acceptance_status_code in {"ready", "ssh_only"}: actions.append({ "key": "run_acceptance", "label": "执行接管验收", "summary": acceptance_summary, }) recovery_decision = { "action": "noop", "label": "当前无需额外恢复动作", "summary": onboarding_summary or "当前节点暂无需要执行的接管恢复动作。", "command_hint": "", "needs_confirmation": True, "window": "none", "stage_code": onboarding_stage_code, "acceptance_status_code": acceptance_status_code, } if onboarding_stage_code == "acceptance_ready" or is_agent_online: recovery_decision = { "action": "run_acceptance", "label": "执行接管验收", "summary": acceptance_summary, "command_hint": ( f"bash domain-api/deploy/multi-region/drive_ops_center.sh node-acceptance-run http://127.0.0.1:8100 {node_code} cli" if node_code else "" ), "needs_confirmation": True, "window": "acceptance", "stage_code": onboarding_stage_code, "acceptance_status_code": acceptance_status_code, } elif onboarding_stage_code in {"pending_bootstrap", "runtime_only", "profile_incomplete", "ssh_ready", "token_issue", "stale", "agent_pending", "unknown", ""}: recovery_decision = { "action": "bootstrap_run", "label": "签发接入工单", "summary": "当前节点仍处于接入阶段,建议先签发 onboarding.bootstrap 并等待 register / heartbeat 建立。", "command_hint": ( f"bash domain-api/deploy/multi-region/drive_ops_center.sh node-bootstrap-run http://127.0.0.1:8100 {node_code} cli" if node_code else "" ), "needs_confirmation": True, "window": "bootstrap", "stage_code": onboarding_stage_code, "acceptance_status_code": acceptance_status_code, } print(json.dumps({ "http_status": 200, "backend_http_status": backend_http_status, "fallback_used": True, "fallback_source": "node_handover", "compatibility_warning": "当前运行中的 API 尚未暴露 /api/v1/ops/nodes/{node_code}/onboarding,已降级为 handover 兼容视图。", "node_code": node_code, "summary": onboarding_summary, "onboarding_stage": { "code": onboarding_stage_code, "label": onboarding_stage_label, "summary": onboarding_summary, }, "handover": data, "acceptance": { "status_code": acceptance_status_code, "status_label": acceptance_status_label, "summary": acceptance_summary, "execution_mode": acceptance_execution_mode, "is_agent_online": is_agent_online, "has_ssh_access": has_ssh_access, "is_remote_access_ready": is_remote_access_ready, "playbook": { "playbook_key": "onboarding.acceptance", "target_node_codes": [node_code] if node_code else [], "execution_mode": acceptance_execution_mode or "remote-agent", "auto_approve": True, }, }, "recommended_actions": actions, "recovery_decision": recovery_decision, }, ensure_ascii=False)) PY )" effective_http_status="200" fi fi echo "[1/2] raw response" printf '%s\n' "http_status=${http_status}" print_json_or_raw "${response_json}" if [[ -n "${fallback_json}" ]]; then echo printf '%s\n' "fallback=node_handover" print_json_or_raw "${fallback_json}" fi echo echo "[2/2] condensed summary" python3 - <<'PY' "${response_json}" "${effective_http_status}" "${fallback_json}" import json import sys payload = json.loads(str(sys.argv[1] or "{}")) http_status = int(sys.argv[2] or 0) fallback_json = str(sys.argv[3] or "").strip() data = json.loads(fallback_json) if fallback_json else (payload.get("data") or {}) onboarding_stage = data.get("onboarding_stage") or {} acceptance = data.get("acceptance") or {} recovery_decision = data.get("recovery_decision") or {} playbook = acceptance.get("playbook") or {} node_code = str(data.get("node_code") or "").strip() print(json.dumps({ "http_status": http_status, "backend_http_status": int(data.get("backend_http_status", 0) or 0) if fallback_json else http_status, "fallback_used": bool(data.get("fallback_used", False)), "fallback_source": str(data.get("fallback_source") or ""), "compatibility_warning": str(data.get("compatibility_warning") or ""), "node_code": node_code, "summary": str(data.get("summary") or ""), "onboarding_stage": { "code": str(onboarding_stage.get("code") or ""), "label": str(onboarding_stage.get("label") or ""), "summary": str(onboarding_stage.get("summary") or ""), }, "acceptance": { "status_code": str(acceptance.get("status_code") or ""), "status_label": str(acceptance.get("status_label") or ""), "summary": str(acceptance.get("summary") or ""), "execution_mode": str(acceptance.get("execution_mode") or ""), "is_agent_online": bool(acceptance.get("is_agent_online", False)), "has_ssh_access": bool(acceptance.get("has_ssh_access", False)), "is_remote_access_ready": bool(acceptance.get("is_remote_access_ready", False)), "playbook": playbook, }, "recommended_actions": list(data.get("recommended_actions") or []), "recovery_decision": { "action": str(recovery_decision.get("action") or ""), "label": str(recovery_decision.get("label") or ""), "summary": str(recovery_decision.get("summary") or ""), "command_hint": str(recovery_decision.get("command_hint") or ""), "needs_confirmation": bool(recovery_decision.get("needs_confirmation", False)), "window": str(recovery_decision.get("window") or ""), "stage_code": str(recovery_decision.get("stage_code") or ""), "acceptance_status_code": str(recovery_decision.get("acceptance_status_code") or ""), }, "recommended_commands": { "node_handover": ( f"bash domain-api/deploy/multi-region/drive_ops_center.sh node-handover http://127.0.0.1:8100 {node_code}" if node_code else "" ), "node_bootstrap_preview": ( f"bash domain-api/deploy/multi-region/drive_ops_center.sh node-bootstrap-preview http://127.0.0.1:8100 {node_code}" if node_code else "" ), "node_bootstrap_run": ( f"bash domain-api/deploy/multi-region/drive_ops_center.sh node-bootstrap-run http://127.0.0.1:8100 {node_code} cli" if node_code else "" ), "node_bootstrap_plan": ( f"bash domain-api/deploy/multi-region/drive_ops_center.sh node-bootstrap-plan http://127.0.0.1:8100 {node_code}" if node_code else "" ), "node_acceptance_plan": ( f"bash domain-api/deploy/multi-region/drive_ops_center.sh node-acceptance-plan http://127.0.0.1:8100 {node_code}" if node_code else "" ), "node_acceptance_run": ( f"bash domain-api/deploy/multi-region/drive_ops_center.sh node-acceptance-run http://127.0.0.1:8100 {node_code} cli" if node_code else "" ), "agent_gap_check": "bash domain-api/deploy/multi-region/drive_ops_center.sh agent-gap-check http://127.0.0.1:8100", }, }, ensure_ascii=False, indent=2)) PY } recover_node_onboarding() { local mainland_base_url="$1" local node_code="$2" local confirm_flag="${3:-}" local requested_by="${4:-cli}" if [[ -z "${node_code}" ]]; then echo "node_code is required" >&2 exit 1 fi local recovery_endpoint if [[ "${confirm_flag}" == "confirm" ]]; then recovery_endpoint="${mainland_base_url}/api/v1/ops/nodes/${node_code}/onboarding/recovery/execute" else recovery_endpoint="${mainland_base_url}/api/v1/ops/nodes/${node_code}/onboarding/recovery/preview" fi local recovery_request_json recovery_request_json="$(python3 - <<'PY' "${requested_by}" import json import sys print(json.dumps({"requested_by": str(sys.argv[1] or "cli").strip() or "cli"}, ensure_ascii=False)) PY )" local raw_http_response local source_mode="recovery_endpoint" raw_http_response="$(request_with_status "POST" "${recovery_endpoint}" "${recovery_request_json}")" local http_status="${raw_http_response##*$'\n'HTTP_STATUS=}" local response_json="${raw_http_response%$'\n'HTTP_STATUS=*}" if [[ "${http_status}" == "404" ]]; then source_mode="onboarding" raw_http_response="$(request_with_status "GET" "${mainland_base_url}/api/v1/ops/nodes/${node_code}/onboarding")" http_status="${raw_http_response##*$'\n'HTTP_STATUS=}" response_json="${raw_http_response%$'\n'HTTP_STATUS=*}" fi if [[ "${http_status}" == "404" ]]; then source_mode="handover" raw_http_response="$(request_with_status "GET" "${mainland_base_url}/api/v1/ops/nodes/${node_code}/handover")" http_status="${raw_http_response##*$'\n'HTTP_STATUS=}" response_json="${raw_http_response%$'\n'HTTP_STATUS=*}" if [[ "${http_status}" == "404" ]]; then source_mode="stack_diagnosis" raw_http_response="$(request_with_status "GET" "${mainland_base_url}/api/v1/ops/stack-diagnosis?base_url=${mainland_base_url}")" http_status="${raw_http_response##*$'\n'HTTP_STATUS=}" response_json="${raw_http_response%$'\n'HTTP_STATUS=*}" if [[ "${http_status}" -ge 200 && "${http_status}" -lt 300 ]]; then response_json="$(python3 - <<'PY' "${response_json}" "${node_code}" import json import sys payload = json.loads(str(sys.argv[1] or "{}")) target_node_code = str(sys.argv[2] or "").strip() data = payload.get("data") or {} managed_nodes = data.get("managed_nodes") or {} problem_nodes = [dict(item or {}) for item in list(managed_nodes.get("problem_nodes") or []) if isinstance(item, dict)] node = next((item for item in problem_nodes if str(item.get("node_code") or "").strip() == target_node_code), {}) first_gap = dict(managed_nodes.get("first_handover_gap") or {}) if not node and str(first_gap.get("node_code") or "").strip() == target_node_code: node = dict(first_gap.get("node") or {}) agent_state = str(node.get("agent_state") or "").strip() stage_code = "unknown" stage_summary = "当前节点存在接管缺口,建议先生成 bootstrap 方案并检查 Node Agent 接入。" if agent_state == "pending_bootstrap": stage_code = "pending_bootstrap" stage_summary = "已签发有效 Token,但目标节点尚未执行 bootstrap 片段。" elif agent_state == "runtime_only": stage_code = "runtime_only" stage_summary = "控制面能看到 runtime 心跳,但 Node Agent 尚未接管。" elif agent_state == "stale": stage_code = "stale" stage_summary = "Node Agent 曾接入过,但心跳已过期,需先恢复服务或重新接入。" elif str(node.get("remote_access_state") or "").strip() == "agent_pending": stage_code = "agent_pending" stage_summary = "当前 remote-agent 仍未就绪,先补齐 bootstrap 接入链路。" print(json.dumps({ "data": { "node_code": target_node_code, "summary": stage_summary, "onboarding_stage": { "code": stage_code, "summary": stage_summary, }, "acceptance": { "status_code": "blocked", "summary": "当前还没进入标准接管就绪状态,暂不建议直接跑 onboarding.acceptance。", }, } }, ensure_ascii=False)) PY )" http_status="200" fi fi fi python3 - <<'PY' "${response_json}" "${node_code}" "${mainland_base_url}" "${confirm_flag}" "${requested_by}" "${http_status}" "${source_mode}" import json import sys payload = json.loads(str(sys.argv[1] or "{}")) node_code = str(sys.argv[2] or "").strip() base_url = str(sys.argv[3] or "").strip() confirm_flag = str(sys.argv[4] or "").strip() requested_by = str(sys.argv[5] or "cli").strip() or "cli" backend_http_status = int(sys.argv[6] or 0) source_mode = str(sys.argv[7] or "").strip() data = payload.get("data") or {} if str(data.get("recovery_action") or "").strip(): recovery_decision = (data.get("recovery_decision") or {}) selected_preview = (data.get("selected_preview") or {}) selected_run = (data.get("selected_run") or {}) selected_payload = selected_run or selected_preview playbook_preview = (selected_payload.get("playbook_preview") or {}) playbook_run = (selected_payload.get("playbook_run") or {}) follow_up = (selected_payload.get("follow_up") or {}) result = { "node_code": node_code, "source_mode": source_mode, "backend_http_status": backend_http_status, "stage_code": str((recovery_decision.get("stage_code") or "")).strip(), "acceptance_status_code": str((recovery_decision.get("acceptance_status_code") or "")).strip(), "recovery_action": str(data.get("recovery_action") or "").strip(), "selected_action": "node_acceptance_run" if str(data.get("recovery_action") or "").strip() == "run_acceptance" else "node_bootstrap_run", "summary": str((recovery_decision.get("summary") or payload.get("message") or "")).strip(), "command": str((recovery_decision.get("command_hint") or "")).strip(), "execution_mode": str((selected_payload.get("execution_mode") or "")).strip(), "playbook_preview_key": str(((playbook_preview.get("playbook") or {}).get("key") or "")).strip(), "playbook_run_code": str((playbook_run.get("run_code") or "")).strip(), "follow_up_next_stage_code": str((follow_up.get("next_stage_code") or "")).strip(), "follow_up_summary": str((follow_up.get("summary") or "")).strip(), "follow_up_commands": list(follow_up.get("recommended_commands") or []), "confirm_required": True, "confirmed": confirm_flag == "confirm", "handled_by_recovery_endpoint": source_mode == "recovery_endpoint", } print(json.dumps(result, ensure_ascii=False, indent=2)) raise SystemExit(0) stage = data.get("onboarding_stage") or data.get("stage") or {} acceptance = data.get("acceptance") or {} recovery = data.get("recovery_decision") or {} stage_code = str(stage.get("code") or "").strip() acceptance_status_code = str(acceptance.get("status_code") or "").strip() stage_summary = str(stage.get("summary") or data.get("summary") or "").strip() if not stage_code: handover_stage = data.get("stage") or {} stage_code = str(handover_stage.get("code") or "").strip() stage_summary = str(handover_stage.get("summary") or stage_summary).strip() action = "noop" reason = stage_summary or "当前节点暂无可执行恢复动作。" command = "" recovery_action = str(recovery.get("action") or "").strip() recovery_command_hint = str(recovery.get("command_hint") or "").strip() recovery_summary = str(recovery.get("summary") or "").strip() if recovery_action == "run_acceptance": action = "node_acceptance_run" reason = recovery_summary or "当前节点已进入验收窗口,建议直接跑 onboarding.acceptance。" command = recovery_command_hint or f"bash domain-api/deploy/multi-region/drive_ops_center.sh node-acceptance-run {base_url} {node_code} {requested_by}" elif recovery_action == "bootstrap_run": action = "node_bootstrap_run" reason = recovery_summary or "当前节点仍处于接入阶段,建议先签发 onboarding.bootstrap。" command = recovery_command_hint or f"bash domain-api/deploy/multi-region/drive_ops_center.sh node-bootstrap-run {base_url} {node_code} {requested_by}" elif acceptance_status_code in {"ready", "ssh_only"} or stage_code == "acceptance_ready": action = "node_acceptance_run" reason = "当前节点已进入验收窗口,建议直接跑 onboarding.acceptance。" command = f"bash domain-api/deploy/multi-region/drive_ops_center.sh node-acceptance-run {base_url} {node_code} {requested_by}" elif stage_code in {"pending_bootstrap", "runtime_only", "profile_incomplete", "ssh_ready", "token_issue", "stale", "agent_pending", "unknown", ""}: action = "node_bootstrap_run" reason = "当前节点仍处于接入阶段,建议先签发 onboarding.bootstrap。" command = f"bash domain-api/deploy/multi-region/drive_ops_center.sh node-bootstrap-run {base_url} {node_code} {requested_by}" result = { "node_code": node_code, "source_mode": source_mode, "backend_http_status": backend_http_status, "stage_code": stage_code, "acceptance_status_code": acceptance_status_code, "recovery_action": recovery_action, "selected_action": action, "summary": reason, "command": command, "confirm_required": True, "confirmed": confirm_flag == "confirm", } print(json.dumps(result, ensure_ascii=False, indent=2)) PY if [[ "${confirm_flag}" != "confirm" ]]; then return 0 fi if [[ "${source_mode}" == "recovery_endpoint" ]]; then return 0 fi local selected_action selected_action="$(python3 - <<'PY' "${response_json}" import json import sys payload = json.loads(str(sys.argv[1] or "{}")) data = payload.get("data") or {} stage = data.get("onboarding_stage") or data.get("stage") or {} acceptance = data.get("acceptance") or {} recovery = data.get("recovery_decision") or {} stage_code = str(stage.get("code") or "").strip() acceptance_status_code = str(acceptance.get("status_code") or "").strip() recovery_action = str(recovery.get("action") or "").strip() if recovery_action == "run_acceptance": print("node_acceptance_run") elif recovery_action == "bootstrap_run": print("node_bootstrap_run") elif acceptance_status_code in {"ready", "ssh_only"} or stage_code == "acceptance_ready": print("node_acceptance_run") elif stage_code in {"pending_bootstrap", "runtime_only", "profile_incomplete", "ssh_ready", "token_issue", "stale", "agent_pending", "unknown", ""}: print("node_bootstrap_run") else: print("") PY )" case "${selected_action}" in node_bootstrap_run) run_node_bootstrap "${mainland_base_url}" "${node_code}" "${requested_by}" ;; node_acceptance_run) run_node_acceptance "${mainland_base_url}" "${node_code}" "${requested_by}" ;; *) printf '%s\n' '{"message":"当前节点没有可自动执行的恢复动作。"}' ;; esac } show_node_acceptance_plan() { local mainland_base_url="$1" local node_code="$2" local requested_by="${3:-cli}" if [[ -z "${node_code}" ]]; then echo "node_code is required" >&2 exit 1 fi local request_json request_json="$(python3 - <<'PY' "${requested_by}" import json import sys print(json.dumps({"requested_by": str(sys.argv[1] or "cli").strip() or "cli"}, ensure_ascii=False)) PY )" local raw_http_response raw_http_response="$(request_with_status "POST" "${mainland_base_url}/api/v1/ops/nodes/${node_code}/onboarding/acceptance/preview" "${request_json}")" local http_status="${raw_http_response##*$'\n'HTTP_STATUS=}" local response_json="${raw_http_response%$'\n'HTTP_STATUS=*}" if [[ "${http_status}" == "404" ]]; then request_json="$(python3 - <<'PY' "${node_code}" "${requested_by}" import json import sys node_code = str(sys.argv[1] or "").strip() requested_by = str(sys.argv[2] or "cli").strip() or "cli" print(json.dumps({ "playbook_key": "onboarding.acceptance", "node_codes": [node_code] if node_code else [], "execution_mode": "remote-agent", "auto_approve": True, "requested_by": requested_by, }, ensure_ascii=False)) PY )" raw_http_response="$(request_with_status "POST" "${mainland_base_url}/api/v1/ops/playbooks/preview" "${request_json}")" response_json="${raw_http_response%$'\n'HTTP_STATUS=*}" fi printf '%s\n' "${response_json}" } show_node_bootstrap_preview() { local mainland_base_url="$1" local node_code="$2" local requested_by="${3:-cli}" if [[ -z "${node_code}" ]]; then echo "node_code is required" >&2 exit 1 fi local request_json request_json="$(python3 - <<'PY' "${requested_by}" import json import sys print(json.dumps({"requested_by": str(sys.argv[1] or "cli").strip() or "cli"}, ensure_ascii=False)) PY )" local raw_http_response raw_http_response="$(request_with_status "POST" "${mainland_base_url}/api/v1/ops/nodes/${node_code}/onboarding/bootstrap/preview" "${request_json}")" local http_status="${raw_http_response##*$'\n'HTTP_STATUS=}" local response_json="${raw_http_response%$'\n'HTTP_STATUS=*}" if [[ "${http_status}" == "404" ]]; then request_json="$(python3 - <<'PY' "${node_code}" "${requested_by}" import json import sys node_code = str(sys.argv[1] or "").strip() requested_by = str(sys.argv[2] or "cli").strip() or "cli" print(json.dumps({ "playbook_key": "onboarding.bootstrap", "node_codes": [node_code] if node_code else [], "execution_mode": "control-plane", "auto_approve": True, "requested_by": requested_by, }, ensure_ascii=False)) PY )" raw_http_response="$(request_with_status "POST" "${mainland_base_url}/api/v1/ops/playbooks/preview" "${request_json}")" response_json="${raw_http_response%$'\n'HTTP_STATUS=*}" fi printf '%s\n' "${response_json}" } run_node_bootstrap() { local mainland_base_url="$1" local node_code="$2" local requested_by="${3:-cli}" if [[ -z "${node_code}" ]]; then echo "node_code is required" >&2 exit 1 fi local request_json request_json="$(python3 - <<'PY' "${requested_by}" import json import sys print(json.dumps({"requested_by": str(sys.argv[1] or "cli").strip() or "cli"}, ensure_ascii=False)) PY )" local raw_http_response raw_http_response="$(request_with_status "POST" "${mainland_base_url}/api/v1/ops/nodes/${node_code}/onboarding/bootstrap/execute" "${request_json}")" local http_status="${raw_http_response##*$'\n'HTTP_STATUS=}" local response_json="${raw_http_response%$'\n'HTTP_STATUS=*}" if [[ "${http_status}" == "404" ]]; then request_json="$(python3 - <<'PY' "${node_code}" "${requested_by}" import json import sys node_code = str(sys.argv[1] or "").strip() requested_by = str(sys.argv[2] or "cli").strip() or "cli" print(json.dumps({ "playbook_key": "onboarding.bootstrap", "node_codes": [node_code] if node_code else [], "execution_mode": "control-plane", "auto_approve": True, "requested_by": requested_by, }, ensure_ascii=False)) PY )" raw_http_response="$(request_with_status "POST" "${mainland_base_url}/api/v1/ops/playbooks/execute" "${request_json}")" response_json="${raw_http_response%$'\n'HTTP_STATUS=*}" fi printf '%s\n' "${response_json}" echo echo "[follow-up]" python3 - <<'PY' "${response_json}" "${node_code}" import json import sys payload = json.loads(str(sys.argv[1] or "{}")) node_code = str(sys.argv[2] or "").strip() data = payload.get("data") or {} follow_up = dict(data.get("follow_up") or {}) playbook_run = dict(data.get("playbook_run") or {}) follow_up_summary = str(follow_up.get("summary") or "").strip() or str(playbook_run.get("focus_summary") or "").strip() print(json.dumps({ "node_code": node_code, "playbook_run_code": str(playbook_run.get("run_code") or "").strip(), "follow_up_next_stage_code": str(follow_up.get("next_stage_code") or "").strip(), "follow_up_summary": follow_up_summary, "follow_up_commands": list(follow_up.get("recommended_commands") or []), }, ensure_ascii=False, indent=2)) PY } run_node_acceptance() { local mainland_base_url="$1" local node_code="$2" local requested_by="${3:-cli}" if [[ -z "${node_code}" ]]; then echo "node_code is required" >&2 exit 1 fi local request_json request_json="$(python3 - <<'PY' "${requested_by}" import json import sys print(json.dumps({"requested_by": str(sys.argv[1] or "cli").strip() or "cli"}, ensure_ascii=False)) PY )" local raw_http_response raw_http_response="$(request_with_status "POST" "${mainland_base_url}/api/v1/ops/nodes/${node_code}/onboarding/acceptance/execute" "${request_json}")" local http_status="${raw_http_response##*$'\n'HTTP_STATUS=}" local response_json="${raw_http_response%$'\n'HTTP_STATUS=*}" if [[ "${http_status}" == "404" ]]; then request_json="$(python3 - <<'PY' "${node_code}" "${requested_by}" import json import sys node_code = str(sys.argv[1] or "").strip() requested_by = str(sys.argv[2] or "cli").strip() or "cli" print(json.dumps({ "playbook_key": "onboarding.acceptance", "node_codes": [node_code] if node_code else [], "execution_mode": "remote-agent", "auto_approve": True, "requested_by": requested_by, }, ensure_ascii=False)) PY )" raw_http_response="$(request_with_status "POST" "${mainland_base_url}/api/v1/ops/playbooks/execute" "${request_json}")" response_json="${raw_http_response%$'\n'HTTP_STATUS=*}" fi printf '%s\n' "${response_json}" echo echo "[follow-up]" python3 - <<'PY' "${response_json}" "${node_code}" import json import sys payload = json.loads(str(sys.argv[1] or "{}")) node_code = str(sys.argv[2] or "").strip() data = payload.get("data") or {} follow_up = dict(data.get("follow_up") or {}) playbook_run = dict(data.get("playbook_run") or {}) follow_up_summary = str(follow_up.get("summary") or "").strip() or str(playbook_run.get("focus_summary") or "").strip() print(json.dumps({ "node_code": node_code, "playbook_run_code": str(playbook_run.get("run_code") or "").strip(), "follow_up_next_stage_code": str(follow_up.get("next_stage_code") or "").strip(), "follow_up_summary": follow_up_summary, "follow_up_commands": list(follow_up.get("recommended_commands") or []), }, ensure_ascii=False, indent=2)) PY } show_delivery_queue_records() { local mainland_base_url="$1" local node_code="$2" local state="${3:-}" if [[ -z "${node_code}" ]]; then echo "node_code is required" >&2 exit 1 fi local url="${mainland_base_url}/api/v1/ops/nodes/${node_code}/delivery-queue/records" if [[ -n "${state}" ]]; then url="${url}?state=${state}" fi local raw_http_response raw_http_response="$(request_with_status "GET" "${url}")" local http_status="${raw_http_response##*$'\n'HTTP_STATUS=}" local response_json="${raw_http_response%$'\n'HTTP_STATUS=*}" echo "[1/2] raw response" printf '%s\n' "http_status=${http_status}" print_json_or_raw "${response_json}" echo echo "[2/2] condensed summary" python3 - <<'PY' "${response_json}" "${http_status}" import json import sys payload = json.loads(str(sys.argv[1] or "{}")) http_status = int(sys.argv[2] or 0) data = payload.get("data") or {} summary = data.get("summary") or {} records = list(data.get("records") or []) node_code = str(data.get("node_code") or "").strip() print(json.dumps({ "http_status": http_status, "node_code": node_code, "summary": { "total": int(summary.get("total", 0) or 0), "available_total": int(summary.get("available_total", 0) or 0), "record_visibility": str(summary.get("record_visibility") or ""), "available_state_counts": dict(summary.get("available_state_counts") or {}), }, "recommended_commands": { "queue_status": ( f"bash domain-api/deploy/multi-region/drive_ops_center.sh delivery-queue http://127.0.0.1:8100 {node_code}" if node_code else "" ), "replay": ( f"bash domain-api/deploy/multi-region/drive_ops_center.sh delivery-queue-replay http://127.0.0.1:8100 {node_code}" if node_code else "" ), }, "records": [ { "record_id": str(item.get("record_id") or item.get("client_request_id") or item.get("record_key") or ""), "state": str(item.get("state") or ""), "request_kind": str(item.get("request_kind") or ""), "created_at": str(item.get("created_at") or ""), "dead_letter_at": str(item.get("dead_letter_at") or ""), "label": str(item.get("label") or ""), "record_source": str(item.get("record_source") or ""), } for item in records ], }, ensure_ascii=False, indent=2)) PY } run_delivery_queue_action() { local mainland_base_url="$1" local node_code="$2" local action_path="$3" local body_json="$4" if [[ -z "${node_code}" ]]; then echo "node_code is required" >&2 exit 1 fi local raw_http_response raw_http_response="$(request_with_status "POST" "${mainland_base_url}/api/v1/ops/nodes/${node_code}${action_path}" "${body_json}")" local http_status="${raw_http_response##*$'\n'HTTP_STATUS=}" local response_json="${raw_http_response%$'\n'HTTP_STATUS=*}" printf '%s\n' "http_status=${http_status}" print_json_or_raw "${response_json}" } preview_runbook_sequence() { local mainland_base_url="$1" local sequence_key="$2" local action_payload_json="$3" local requested_by="$4" if [[ "${action_payload_json}" == @* ]]; then local action_payload_path="${action_payload_json#@}" if [[ ! -f "${action_payload_path}" ]]; then echo "payload file not found: ${action_payload_path}" >&2 exit 1 fi action_payload_json="$(cat "${action_payload_path}")" fi local request_json request_json="$(python3 - <<'PY' "${sequence_key}" "${action_payload_json}" "${requested_by}" import json import sys sequence_key = str(sys.argv[1] or "").strip() payload_raw = str(sys.argv[2] or "").strip() or "{}" requested_by = str(sys.argv[3] or "").strip() or "cli/ops-center/runbook-preview" try: action_payload = json.loads(payload_raw) except Exception as exc: raise SystemExit(f"invalid action payload json: {exc}") if not isinstance(action_payload, dict): raise SystemExit("action payload must be a json object") request_payload = { "source_label": "cli/runbook-preview", "title": sequence_key, "executor_kind": "runbook_sequence", "sequence_key": sequence_key, "requested_by": requested_by, "include_secondary": True, "primary_action_payload": action_payload, "secondary_action_payload": action_payload, } print(json.dumps(request_payload, ensure_ascii=False)) PY )" echo "[1/3] runbook preview request" print_json_or_raw "${request_json}" echo echo local raw_http_response raw_http_response="$(request_with_status "POST" "${mainland_base_url}/api/v1/ops/driver-actions/preview" "${request_json}")" local http_status="${raw_http_response##*$'\n'HTTP_STATUS=}" local response_json="${raw_http_response%$'\n'HTTP_STATUS=*}" echo "[2/3] raw response" printf '%s\n' "http_status=${http_status}" print_json_or_raw "${response_json}" echo echo echo "[3/3] condensed summary" python3 - <<'PY' "${response_json}" "${http_status}" import json import sys payload = json.loads(str(sys.argv[1] or "{}")) http_status = int(sys.argv[2] or 0) data = payload.get("data") or {} primary = data.get("primary") or {} secondary = data.get("secondary") or {} print(json.dumps({ "http_status": http_status, "message": str(payload.get("message") or ""), "sequence_key": str(data.get("sequence_key") or ""), "execution_chain": str(data.get("execution_chain") or ""), "focus_ref": data.get("focus_ref") or {}, "primary_focus_ref": data.get("primary_focus_ref") or {}, "secondary_focus_ref": data.get("secondary_focus_ref") or {}, "primary": { "label": str(primary.get("label") or ""), "action_code": str(primary.get("action_code") or ""), "target_api": str(primary.get("target_api") or ""), "recommendation_label": str(primary.get("recommendation_label") or ""), "focus_ref": primary.get("focus_ref") or {}, "request_payload": primary.get("request_payload") or {}, }, "secondary": ( { "label": str(secondary.get("label") or ""), "action_code": str(secondary.get("action_code") or ""), "target_api": str(secondary.get("target_api") or ""), "recommendation_label": str(secondary.get("recommendation_label") or ""), "focus_ref": secondary.get("focus_ref") or {}, "request_payload": secondary.get("request_payload") or {}, } if secondary else {} ), }, ensure_ascii=False, indent=2)) PY if [[ "${http_status}" -lt 200 || "${http_status}" -ge 300 ]]; then exit 1 fi } build_codex_focus_bundle() { local codex_response_json="$1" local entry_key="$2" local requested_by="$3" local source_label="$4" python3 - <<'PY' "${codex_response_json}" "${entry_key}" "${requested_by}" "${source_label}" import json import sys payload = json.loads(str(sys.argv[1] or "{}")) requested_entry_key = str(sys.argv[2] or "").strip() requested_by = str(sys.argv[3] or "").strip() or "cli/ops-center/codex-focus-preview" source_label = str(sys.argv[4] or "").strip() or "cli/codex-focus-preview" data = payload.get("data") or {} focus = data.get("focus") or {} entries = [dict(item or {}) for item in list(data.get("entries") or []) if dict(item or {})] activity_focus = [dict(item or {}) for item in list(data.get("activity_focus") or []) if dict(item or {})] def build_activity_focus_entry(item): normalized = dict(item or {}) focus_action_code = str(normalized.get("focus_action_code") or "").strip() if not str(normalized.get("key") or "").strip() or not focus_action_code: return {} focus_ref = dict(normalized.get("focus_ref") or {}) target_node_codes = list(normalized.get("target_node_codes") or []) focus_action_payload = dict(normalized.get("focus_action_payload") or {}) summary = str(normalized.get("summary") or "").strip() reason = summary or "该焦点属于运行态观察项,建议进入对应工作区查看上下文。" return { "key": str(normalized.get("key") or "").strip(), "title": str(normalized.get("title") or "").strip(), "summary": summary, "reason": reason, "meta_text": str(normalized.get("meta_text") or "").strip(), "executor_kind": "driver_action", "primary_action_code": focus_action_code, "secondary_action_code": "", "focus_ref": focus_ref, "primary_focus_ref": focus_ref, "secondary_focus_ref": {}, "primary_label": "进入工作区", "secondary_label": "", "primary_execution": { "label": "进入工作区", "node_codes": target_node_codes, "action_payload": focus_action_payload, }, "secondary_execution": {}, "autopilot": { "recommendation": "open_ui", "automation_level": "ui_only", "status_text": str(normalized.get("status_label") or normalized.get("status") or "").strip(), "executor_mode_hint": "ui-intent", "target_api": "/api/v1/ops/driver-actions/execute", "target_method": "POST", "reason": reason, "confirm_required": False, "ui_only": True, "blocked": False, }, "observation_only": True, "activity_key": str(normalized.get("activity_key") or "").strip(), "activity_kind": str(normalized.get("activity_kind") or "").strip(), "ui_intent_kind": str(normalized.get("ui_intent_kind") or "").strip(), "contract_navigation": dict(normalized.get("contract_navigation") or {}), } activity_focus_entries = [entry for entry in (build_activity_focus_entry(item) for item in activity_focus) if entry] target_entry_key = requested_entry_key or str(focus.get("entry_key") or "").strip() target_entry = {} if target_entry_key: target_entry = next((item for item in entries if str(item.get("key") or "").strip() == target_entry_key), {}) if not target_entry and target_entry_key: target_entry = next((item for item in activity_focus_entries if str(item.get("key") or "").strip() == target_entry_key), {}) if not target_entry and entries: target_entry = dict(entries[0] or {}) target_entry_key = str(target_entry.get("key") or "").strip() if not target_entry and activity_focus_entries: target_entry = dict(activity_focus_entries[0] or {}) target_entry_key = str(target_entry.get("key") or "").strip() if not target_entry: raise SystemExit("codex brief 当前没有可预览的 entry") autopilot = dict(target_entry.get("autopilot") or {}) primary_execution = dict(target_entry.get("primary_execution") or {}) secondary_execution = dict(target_entry.get("secondary_execution") or {}) request_payload = { "source_label": source_label, "title": str(target_entry.get("title") or "").strip() or target_entry_key or "codex-focus", "summary": str(target_entry.get("summary") or "").strip(), "reason": str(target_entry.get("reason") or "").strip(), "meta_text": str(target_entry.get("meta_text") or "").strip(), "executor_kind": str(target_entry.get("executor_kind") or "driver_action").strip() or "driver_action", "sequence_key": str(target_entry.get("sequence_key") or "").strip(), "focus_ref": dict(target_entry.get("focus_ref") or {}), "primary_focus_ref": dict(target_entry.get("primary_focus_ref") or {}), "secondary_focus_ref": dict(target_entry.get("secondary_focus_ref") or {}), "primary_label": str(primary_execution.get("label") or target_entry.get("primary_label") or "").strip(), "secondary_label": str(secondary_execution.get("label") or target_entry.get("secondary_label") or "").strip(), "primary_action_code": str(target_entry.get("primary_action_code") or "").strip(), "secondary_action_code": str(target_entry.get("secondary_action_code") or "").strip(), "primary_node_codes": list(primary_execution.get("node_codes") or target_entry.get("node_codes") or []), "secondary_node_codes": list(secondary_execution.get("node_codes") or target_entry.get("node_codes") or []), "primary_action_payload": dict(primary_execution.get("action_payload") or {}), "secondary_action_payload": dict(secondary_execution.get("action_payload") or {}), "include_secondary": True, "requested_by": requested_by, } print(json.dumps({ "selected_entry": { "key": target_entry_key, "title": str(target_entry.get("title") or "").strip(), "executor_kind": str(target_entry.get("executor_kind") or "").strip(), "recommendation": str(autopilot.get("recommendation") or "").strip(), "automation_level": str(autopilot.get("automation_level") or "").strip(), "status_text": str(autopilot.get("status_text") or "").strip(), "executor_mode_hint": str(autopilot.get("executor_mode_hint") or "").strip(), "target_api": str(autopilot.get("target_api") or "").strip(), "target_method": str(autopilot.get("target_method") or "").strip(), "reason": str(autopilot.get("reason") or "").strip(), "confirm_required": bool(autopilot.get("confirm_required", False)), "ui_only": bool(autopilot.get("ui_only", False)), "blocked": bool(autopilot.get("blocked", False)), "primary_action_code": str(target_entry.get("primary_action_code") or "").strip(), "sequence_key": str(target_entry.get("sequence_key") or "").strip(), "focus_ref": dict(target_entry.get("focus_ref") or {}), "primary_focus_ref": dict(target_entry.get("primary_focus_ref") or {}), "secondary_focus_ref": dict(target_entry.get("secondary_focus_ref") or {}), "observation_only": bool(target_entry.get("observation_only", False)), "activity_key": str(target_entry.get("activity_key") or ""), "activity_kind": str(target_entry.get("activity_kind") or ""), "ui_intent_kind": str(target_entry.get("ui_intent_kind") or ""), "contract_navigation": dict(target_entry.get("contract_navigation") or {}), }, "preview_request": request_payload, }, ensure_ascii=False)) PY } preview_codex_focus_entry() { local mainland_base_url="$1" local entry_key="$2" local requested_by="$3" local request_json request_json="$(python3 - <<'PY' "${entry_key}" "${requested_by}" import json import sys print(json.dumps({ "entry_key": str(sys.argv[1] or "").strip(), "requested_by": str(sys.argv[2] or "").strip() or "cli/ops-center/codex-focus-preview", "source_label": "cli/codex-focus-preview", "include_secondary": True, }, ensure_ascii=False)) PY )" echo "[1/3] request" print_json_or_raw "${request_json}" echo echo local raw_http_response raw_http_response="$(request_with_status "POST" "${mainland_base_url}/api/v1/ops/codex-actions/resolve" "${request_json}")" local http_status="${raw_http_response##*$'\n'HTTP_STATUS=}" local response_json="${raw_http_response%$'\n'HTTP_STATUS=*}" echo "[2/3] raw response" printf '%s\n' "http_status=${http_status}" print_json_or_raw "${response_json}" echo echo echo "[3/3] condensed summary" python3 - <<'PY' "${response_json}" "${http_status}" import json import sys payload = json.loads(str(sys.argv[1] or "{}")) http_status = int(sys.argv[2] or 0) data = payload.get("data") or {} selected = data.get("selected_entry") or {} preview = data.get("preview") or {} primary = preview.get("primary") or {} secondary = preview.get("secondary") or {} gate = data.get("gate") or {} print(json.dumps({ "http_status": http_status, "message": str(payload.get("message") or ""), "entry_key": str(data.get("entry_key") or ""), "title": str(selected.get("title") or ""), "executor_kind": str(preview.get("executor_kind") or selected.get("executor_kind") or ""), "contract": { "contract_key": str(preview.get("contract_key") or ""), "contract_version": str(preview.get("contract_version") or ""), "contract_primary_endpoint": str(preview.get("contract_primary_endpoint") or ""), "preview_endpoint": str(preview.get("preview_endpoint") or ""), "schema_doc_path": str(preview.get("contract_schema_doc_path") or ""), }, "execution_chain": str(preview.get("execution_chain") or ""), "focus_ref": data.get("focus_ref") or {}, "gate": { "decision": str(gate.get("decision") or ""), "decision_label": str(gate.get("decision_label") or ""), "recommendation": str(gate.get("recommendation") or ""), "recommendation_label": str(gate.get("recommendation_label") or ""), "will_execute": bool(gate.get("will_execute", False)), "confirm_required": bool(gate.get("confirm_required", False)), "target_api": str(gate.get("target_api") or ""), "focus_ref": gate.get("focus_ref") or {}, }, "selected_entry": { "key": str(selected.get("key") or ""), "contract_navigation": selected.get("contract_navigation") or {}, "focus_ref": selected.get("focus_ref") or {}, "primary_focus_ref": selected.get("primary_focus_ref") or {}, "secondary_focus_ref": selected.get("secondary_focus_ref") or {}, }, "primary": { "label": str(primary.get("label") or ""), "action_code": str(primary.get("action_code") or ""), "target_api": str(primary.get("target_api") or ""), "recommendation_label": str(primary.get("recommendation_label") or ""), "focus_ref": primary.get("focus_ref") or {}, }, "secondary": ( { "label": str(secondary.get("label") or ""), "action_code": str(secondary.get("action_code") or ""), "target_api": str(secondary.get("target_api") or ""), "recommendation_label": str(secondary.get("recommendation_label") or ""), "focus_ref": secondary.get("focus_ref") or {}, } if secondary else {} ), }, ensure_ascii=False, indent=2)) PY if [[ "${http_status}" -lt 200 || "${http_status}" -ge 300 ]]; then exit 1 fi } run_codex_focus_entry() { local mainland_base_url="$1" local entry_key="$2" local confirm_flag="$3" local requested_by="$4" local confirm_risky confirm_risky="$(confirm_to_bool "${confirm_flag}")" local request_json request_json="$(python3 - <<'PY' "${entry_key}" "${requested_by}" "${confirm_risky}" import json import sys print(json.dumps({ "entry_key": str(sys.argv[1] or "").strip(), "requested_by": str(sys.argv[2] or "").strip() or "cli/ops-center/codex-focus-run", "source_label": "cli/codex-focus-run", "include_secondary": True, "confirm": str(sys.argv[3] or "").strip().lower() == "true", }, ensure_ascii=False)) PY )" echo "[1/3] request" print_json_or_raw "${request_json}" echo echo local execute_http_response execute_http_response="$(request_with_status "POST" "${mainland_base_url}/api/v1/ops/codex-actions/execute" "${request_json}")" local execute_http_status="${execute_http_response##*$'\n'HTTP_STATUS=}" local execute_response_json="${execute_http_response%$'\n'HTTP_STATUS=*}" echo "[2/3] raw response" printf '%s\n' "http_status=${execute_http_status}" print_json_or_raw "${execute_response_json}" echo echo echo "[3/3] execution result" python3 - <<'PY' "${execute_response_json}" "${execute_http_status}" import json import sys payload = json.loads(str(sys.argv[1] or "{}")) http_status = int(sys.argv[2] or 0) data = payload.get("data") or {} gate = data.get("gate") or {} execution_result = data.get("execution_result") or {} print(json.dumps({ "http_status": http_status, "entry_key": str(data.get("entry_key") or ""), "code": int((0 if payload.get("code", 1) in (0, "0", False) else payload.get("code", 1)) or 0), "message": str(payload.get("message") or ""), "executed": bool(data.get("executed", False)), "focus_ref": data.get("focus_ref") or {}, "decision": str(gate.get("decision") or ""), "decision_label": str(gate.get("decision_label") or ""), "recommendation": str(gate.get("recommendation") or ""), "target_api": str(gate.get("target_api") or ""), "target_method": str(gate.get("target_method") or ""), "gate_focus_ref": gate.get("focus_ref") or {}, "handled": bool(execution_result.get("handled", False)), "mode": str(execution_result.get("mode") or ""), "action_code": str(gate.get("action_code") or execution_result.get("action_code") or execution_result.get("driver_action_code") or ""), "sequence_key": str(gate.get("sequence_key") or execution_result.get("sequence_key") or ""), }, ensure_ascii=False, indent=2)) PY if [[ "${execute_http_status}" -lt 200 || "${execute_http_status}" -ge 300 ]]; then exit 1 fi } build_driver_focus_bundle() { local driver_feed_response_json="$1" local entry_key="$2" local requested_by="$3" local source_label="$4" local secondary_flag="$5" local confirm_flag="$6" python3 - <<'PY' "${driver_feed_response_json}" "${entry_key}" "${requested_by}" "${source_label}" "${secondary_flag}" "${confirm_flag}" import json import sys payload = json.loads(str(sys.argv[1] or "{}")) requested_entry_key = str(sys.argv[2] or "").strip() requested_by = str(sys.argv[3] or "").strip() or "cli/ops-center/driver-focus-preview" source_label = str(sys.argv[4] or "").strip() or "cli/driver-focus-preview" secondary_flag = str(sys.argv[5] or "primary").strip().lower() confirm_flag = str(sys.argv[6] or "").strip().lower() data = payload.get("data") or {} top_recommendation = dict(data.get("top_recommendation") or {}) entries = [dict(item or {}) for item in list(data.get("entries") or []) if dict(item or {})] activity_focus = [dict(item or {}) for item in list(data.get("activity_focus") or []) if dict(item or {})] secondary = secondary_flag in {"secondary", "second", "2", "true", "yes", "y"} confirm = confirm_flag in {"1", "true", "yes", "y", "confirm", "confirmed"} def build_activity_focus_entry(item): normalized = dict(item or {}) focus_action_code = str(normalized.get("focus_action_code") or "").strip() if not str(normalized.get("key") or "").strip() or not focus_action_code: return {} focus_ref = dict(normalized.get("focus_ref") or {}) target_node_codes = list(normalized.get("target_node_codes") or []) focus_action_payload = dict(normalized.get("focus_action_payload") or {}) summary = str(normalized.get("summary") or "").strip() reason = summary or "该焦点属于运行态观察项,建议进入对应工作区查看上下文。" return { "key": str(normalized.get("key") or "").strip(), "kind": "activity_focus", "lane": "activity_focus", "title": str(normalized.get("title") or "").strip(), "summary": summary, "reason": reason, "meta_text": str(normalized.get("meta_text") or "").strip(), "status": str(normalized.get("status") or "").strip(), "executor_kind": "driver_action", "sequence_key": "", "primary_label": "进入工作区", "secondary_label": "", "primary_action_code": focus_action_code, "secondary_action_code": "", "node_codes": target_node_codes, "primary_node_codes": target_node_codes, "secondary_node_codes": [], "focus_ref": focus_ref, "primary_focus_ref": focus_ref, "secondary_focus_ref": {}, "primary_action_payload": focus_action_payload, "secondary_action_payload": {}, "observation_only": True, "activity_key": str(normalized.get("activity_key") or "").strip(), "activity_kind": str(normalized.get("activity_kind") or "").strip(), "ui_intent_kind": str(normalized.get("ui_intent_kind") or "").strip(), "contract_navigation": dict(normalized.get("contract_navigation") or {}), } activity_focus_entries = [entry for entry in (build_activity_focus_entry(item) for item in activity_focus) if entry] target_entry = {} target_entry_key = requested_entry_key if requested_entry_key: target_entry = next((item for item in entries if str(item.get("key") or "").strip() == requested_entry_key), {}) if not target_entry and requested_entry_key: target_entry = next((item for item in activity_focus_entries if str(item.get("key") or "").strip() == requested_entry_key), {}) if not target_entry and requested_entry_key and str(top_recommendation.get("key") or "").strip() == requested_entry_key: target_entry = dict(top_recommendation or {}) if not target_entry: target_entry = dict(top_recommendation or {}) target_entry_key = str(target_entry.get("key") or "").strip() if not target_entry and entries: target_entry = dict(entries[0] or {}) target_entry_key = str(target_entry.get("key") or "").strip() if not target_entry and activity_focus_entries: target_entry = dict(activity_focus_entries[0] or {}) target_entry_key = str(target_entry.get("key") or "").strip() if not target_entry: raise SystemExit("driver feed 当前没有可执行的焦点动作") request_payload = { "source_label": source_label, "title": str(target_entry.get("title") or "").strip() or target_entry_key or "driver-focus", "summary": str(target_entry.get("summary") or "").strip(), "reason": str(target_entry.get("reason") or "").strip(), "meta_text": str(target_entry.get("meta_text") or "").strip(), "executor_kind": str(target_entry.get("executor_kind") or "driver_action").strip() or "driver_action", "sequence_key": str(target_entry.get("sequence_key") or "").strip(), "focus_ref": dict(target_entry.get("focus_ref") or {}), "primary_focus_ref": dict(target_entry.get("primary_focus_ref") or {}), "secondary_focus_ref": dict(target_entry.get("secondary_focus_ref") or {}), "primary_label": str(target_entry.get("primary_label") or "").strip(), "secondary_label": str(target_entry.get("secondary_label") or "").strip(), "primary_action_code": str(target_entry.get("primary_action_code") or "").strip(), "secondary_action_code": str(target_entry.get("secondary_action_code") or "").strip(), "primary_node_codes": list(target_entry.get("primary_node_codes") or target_entry.get("node_codes") or []), "secondary_node_codes": list(target_entry.get("secondary_node_codes") or target_entry.get("node_codes") or []), "primary_action_payload": dict(target_entry.get("primary_action_payload") or {}), "secondary_action_payload": dict(target_entry.get("secondary_action_payload") or {}), "include_secondary": True, "requested_by": requested_by, "secondary": secondary, "confirm": confirm, } print(json.dumps({ "selected_entry": { "key": target_entry_key, "kind": str(target_entry.get("kind") or "").strip(), "lane": str(target_entry.get("lane") or "").strip(), "title": str(target_entry.get("title") or "").strip(), "status": str(target_entry.get("status") or "").strip(), "executor_kind": str(target_entry.get("executor_kind") or "").strip(), "sequence_key": str(target_entry.get("sequence_key") or "").strip(), "primary_action_code": str(target_entry.get("primary_action_code") or "").strip(), "secondary_action_code": str(target_entry.get("secondary_action_code") or "").strip(), "focus_ref": dict(target_entry.get("focus_ref") or {}), "primary_focus_ref": dict(target_entry.get("primary_focus_ref") or {}), "secondary_focus_ref": dict(target_entry.get("secondary_focus_ref") or {}), "observation_only": bool(target_entry.get("observation_only", False)), "activity_key": str(target_entry.get("activity_key") or ""), "activity_kind": str(target_entry.get("activity_kind") or ""), "ui_intent_kind": str(target_entry.get("ui_intent_kind") or ""), "contract_navigation": dict(target_entry.get("contract_navigation") or {}), }, "request": request_payload, }, ensure_ascii=False)) PY } preview_driver_focus_entry() { local mainland_base_url="$1" local entry_key="$2" local requested_by="$3" local raw_driver_feed_response raw_driver_feed_response="$(request_with_status "GET" "${mainland_base_url}/api/v1/ops/driver-feed")" local driver_feed_http_status="${raw_driver_feed_response##*$'\n'HTTP_STATUS=}" local driver_feed_response_json="${raw_driver_feed_response%$'\n'HTTP_STATUS=*}" if [[ "${driver_feed_http_status}" -lt 200 || "${driver_feed_http_status}" -ge 300 ]]; then echo "[1/3] driver feed raw response" printf '%s\n' "http_status=${driver_feed_http_status}" print_json_or_raw "${driver_feed_response_json}" exit 1 fi local bundle_json bundle_json="$(build_driver_focus_bundle "${driver_feed_response_json}" "${entry_key}" "${requested_by}" "cli/driver-focus-preview" "primary" "false")" local selected_entry_json selected_entry_json="$(python3 - <<'PY' "${bundle_json}" import json import sys payload = json.loads(str(sys.argv[1] or "{}")) print(json.dumps(payload.get("selected_entry") or {}, ensure_ascii=False)) PY )" local request_json request_json="$(python3 - <<'PY' "${bundle_json}" import json import sys payload = json.loads(str(sys.argv[1] or "{}")) print(json.dumps(payload.get("request") or {}, ensure_ascii=False)) PY )" echo "[1/4] selected focus" print_json_or_raw "${selected_entry_json}" echo echo echo "[2/4] request" print_json_or_raw "${request_json}" echo echo local raw_http_response raw_http_response="$(request_with_status "POST" "${mainland_base_url}/api/v1/ops/driver-actions/resolve" "${request_json}")" local http_status="${raw_http_response##*$'\n'HTTP_STATUS=}" local response_json="${raw_http_response%$'\n'HTTP_STATUS=*}" echo "[3/4] raw response" printf '%s\n' "http_status=${http_status}" print_json_or_raw "${response_json}" echo echo echo "[4/4] condensed summary" python3 - <<'PY' "${response_json}" "${http_status}" "${selected_entry_json}" import json import sys payload = json.loads(str(sys.argv[1] or "{}")) http_status = int(sys.argv[2] or 0) selected = json.loads(str(sys.argv[3] or "{}")) data = payload.get("data") or {} preview = data.get("preview") or {} primary = preview.get("primary") or {} secondary = preview.get("secondary") or {} gate = data.get("gate") or {} print(json.dumps({ "http_status": http_status, "message": str(payload.get("message") or ""), "selected_entry": selected, "selected_section": str(data.get("selected_section") or ""), "contract": { "contract_key": str(preview.get("contract_key") or ""), "contract_version": str(preview.get("contract_version") or ""), "contract_primary_endpoint": str(preview.get("contract_primary_endpoint") or ""), "preview_endpoint": str(preview.get("preview_endpoint") or ""), "schema_doc_path": str(preview.get("contract_schema_doc_path") or ""), }, "execution_chain": str(preview.get("execution_chain") or ""), "focus_ref": data.get("focus_ref") or {}, "gate": { "decision": str(gate.get("decision") or ""), "decision_label": str(gate.get("decision_label") or ""), "recommendation": str(gate.get("recommendation") or ""), "recommendation_label": str(gate.get("recommendation_label") or ""), "will_execute": bool(gate.get("will_execute", False)), "confirm_required": bool(gate.get("confirm_required", False)), "target_api": str(gate.get("target_api") or ""), "focus_ref": gate.get("focus_ref") or {}, }, "primary": { "label": str(primary.get("label") or ""), "action_code": str(primary.get("action_code") or ""), "target_api": str(primary.get("target_api") or ""), "recommendation_label": str(primary.get("recommendation_label") or ""), "focus_ref": primary.get("focus_ref") or {}, "request_payload": primary.get("request_payload") or {}, }, "secondary": ( { "label": str(secondary.get("label") or ""), "action_code": str(secondary.get("action_code") or ""), "target_api": str(secondary.get("target_api") or ""), "recommendation_label": str(secondary.get("recommendation_label") or ""), "focus_ref": secondary.get("focus_ref") or {}, "request_payload": secondary.get("request_payload") or {}, } if secondary else {} ), }, ensure_ascii=False, indent=2)) PY if [[ "${http_status}" -lt 200 || "${http_status}" -ge 300 ]]; then exit 1 fi } run_driver_focus_entry() { local mainland_base_url="$1" local entry_key="$2" local secondary_flag="$3" local confirm_flag="$4" local requested_by="$5" local raw_driver_feed_response raw_driver_feed_response="$(request_with_status "GET" "${mainland_base_url}/api/v1/ops/driver-feed")" local driver_feed_http_status="${raw_driver_feed_response##*$'\n'HTTP_STATUS=}" local driver_feed_response_json="${raw_driver_feed_response%$'\n'HTTP_STATUS=*}" if [[ "${driver_feed_http_status}" -lt 200 || "${driver_feed_http_status}" -ge 300 ]]; then echo "[1/3] driver feed raw response" printf '%s\n' "http_status=${driver_feed_http_status}" print_json_or_raw "${driver_feed_response_json}" exit 1 fi local bundle_json bundle_json="$(build_driver_focus_bundle "${driver_feed_response_json}" "${entry_key}" "${requested_by}" "cli/driver-focus-run" "${secondary_flag}" "${confirm_flag}")" local selected_entry_json selected_entry_json="$(python3 - <<'PY' "${bundle_json}" import json import sys payload = json.loads(str(sys.argv[1] or "{}")) print(json.dumps(payload.get("selected_entry") or {}, ensure_ascii=False)) PY )" local request_json request_json="$(python3 - <<'PY' "${bundle_json}" import json import sys payload = json.loads(str(sys.argv[1] or "{}")) print(json.dumps(payload.get("request") or {}, ensure_ascii=False)) PY )" echo "[1/4] selected focus" print_json_or_raw "${selected_entry_json}" echo echo echo "[2/4] request" print_json_or_raw "${request_json}" echo echo local raw_http_response raw_http_response="$(request_with_status "POST" "${mainland_base_url}/api/v1/ops/driver-actions/execute-resolved" "${request_json}")" local http_status="${raw_http_response##*$'\n'HTTP_STATUS=}" local response_json="${raw_http_response%$'\n'HTTP_STATUS=*}" echo "[3/4] raw response" printf '%s\n' "http_status=${http_status}" print_json_or_raw "${response_json}" echo echo echo "[4/4] execution result" python3 - <<'PY' "${response_json}" "${http_status}" "${selected_entry_json}" import json import sys payload = json.loads(str(sys.argv[1] or "{}")) http_status = int(sys.argv[2] or 0) selected = json.loads(str(sys.argv[3] or "{}")) data = payload.get("data") or {} gate = data.get("gate") or {} execution_result = data.get("execution_result") or {} raw_code = payload.get("code", 1) if raw_code in (0, "0", False): normalized_code = 0 else: normalized_code = int(raw_code or 1) print(json.dumps({ "http_status": http_status, "code": normalized_code, "message": str(payload.get("message") or ""), "selected_entry": selected, "executed": bool(data.get("executed", False)), "selected_section": str(data.get("selected_section") or ""), "focus_ref": data.get("focus_ref") or {}, "decision": str(gate.get("decision") or ""), "decision_label": str(gate.get("decision_label") or ""), "recommendation": str(gate.get("recommendation") or ""), "target_api": str(gate.get("target_api") or ""), "target_method": str(gate.get("target_method") or ""), "gate_focus_ref": gate.get("focus_ref") or {}, "action_code": str(gate.get("action_code") or execution_result.get("action_code") or execution_result.get("driver_action_code") or ""), "sequence_key": str(gate.get("sequence_key") or execution_result.get("sequence_key") or ""), "handled": bool(execution_result.get("handled", False)), "mode": str(execution_result.get("mode") or ""), }, ensure_ascii=False, indent=2)) PY if [[ "${http_status}" -lt 200 || "${http_status}" -ge 300 ]]; then exit 1 fi } build_stack_next_bundle() { local stack_diagnosis_response_json="$1" local requested_by="$2" local source_label="$3" local confirm_flag="$4" python3 - <<'PY' "${stack_diagnosis_response_json}" "${requested_by}" "${source_label}" "${confirm_flag}" import json import sys payload = json.loads(str(sys.argv[1] or "{}")) requested_by = str(sys.argv[2] or "").strip() or "cli/ops-center/stack-next" source_label = str(sys.argv[3] or "").strip() or "cli/stack-next" confirm_flag = str(sys.argv[4] or "").strip().lower() data = payload.get("data") or {} diagnosis = dict(data.get("diagnosis") or {}) operator_decision = dict(diagnosis.get("operator_decision") or {}) next_actions = dict(diagnosis.get("next_actions") or {}) recommended_commands = dict(diagnosis.get("recommended_commands") or {}) next_step = dict(diagnosis.get("next_step") or {}) recommended_actions = [dict(item or {}) for item in list(diagnosis.get("recommended_actions") or []) if dict(item or {})] recommended_action_codes = { str(item.get("action_code") or "").strip() for item in recommended_actions if str(item.get("action_code") or "").strip() } overview = dict(data.get("overview") or {}) managed_nodes = dict(data.get("managed_nodes") or {}) release_hub = dict(data.get("release_hub") or {}) issues = [dict(item or {}) for item in list(diagnosis.get("issues") or []) if dict(item or {})] def normalize_node_codes(value): result = [] seen = set() if not isinstance(value, list): return result for item in value: if isinstance(item, dict): node_code = str(item.get("node_code") or "").strip() else: node_code = str(item or "").strip() if not node_code or node_code in seen: continue seen.add(node_code) result.append(node_code) return result def scene_node_log_command(node_code, limit=120, mode="key"): node_code = str(node_code or "").strip() if not node_code: return "" return ( "bash domain-api/deploy/multi-region/drive_ops_center.sh " f"scene-node-log http://127.0.0.1:8100 {node_code} {int(limit or 120)} {str(mode or 'key').strip() or 'key'}" ) if not operator_decision or not next_actions or not recommended_commands: log_sync = dict((overview.get("log_sync") or {})) problem_nodes = [dict(item or {}) for item in list(managed_nodes.get("problem_nodes") or []) if dict(item or {})] launchpad_focus_ref = dict(release_hub.get("focus_ref") or release_hub.get("default_rollout_gate_focus_ref") or {}) default_rollout_gate_status = str(release_hub.get("default_rollout_gate_status") or "").strip() first_problem_node_code = str((problem_nodes[0] if problem_nodes else {}).get("node_code") or "").strip() missing_sample_node_codes = normalize_node_codes(log_sync.get("missing_sample_node_codes") or []) scene_log_target_node_code = missing_sample_node_codes[0] if missing_sample_node_codes else first_problem_node_code synthesized_recommended_commands = { "api-restart": "bash domain-api/deploy/multi-region/drive_ops_center.sh runtime-refresh-recover http://127.0.0.1:8100", "stack-diagnosis": "bash domain-api/deploy/multi-region/drive_ops_center.sh stack-diagnosis http://127.0.0.1:8100 summary", "stack-check": "bash domain-api/deploy/multi-region/drive_ops_center.sh stack-diagnosis http://127.0.0.1:8100 summary", "managed-node-bootstrap-plan": ( "bash domain-api/deploy/multi-region/drive_ops_center.sh " f"node-bootstrap-plan http://127.0.0.1:8100 {first_problem_node_code}" if first_problem_node_code else "" ), "scene_log": scene_node_log_command(scene_log_target_node_code, 120, "key"), "driver_focus_preview": "bash domain-api/deploy/multi-region/drive_ops_center.sh driver-focus-preview http://127.0.0.1:8100", "doctor": "bash domain-api/deploy/multi-region/drive_ops_center.sh doctor http://127.0.0.1:8100", "node_handover": ( f"bash domain-api/deploy/multi-region/drive_ops_center.sh node-handover http://127.0.0.1:8100 {first_problem_node_code}" if first_problem_node_code else "" ), "release_launchpad": "bash domain-api/deploy/multi-region/drive_ops_center.sh release-launchpad http://127.0.0.1:8100", } synthesized_operator_decision = {} if any(str(item.get("code") or "").strip() in {"remote_log_sync_disabled", "remote_log_sync_waiting_sample"} for item in issues): synthesized_operator_decision = { "lane": "observability", "priority": "high", "reason_code": "scene_log_attention", "title": "先看执行现场日志", "summary": "当前总检优先建议先补现场可见性,再决定是否继续接管或推进发布。", "next_focus": { "kind": "node_scene_log", "node_code": scene_log_target_node_code, "mode": "key", "limit": 120, } if scene_log_target_node_code else {}, "primary_command_key": "scene_log", "secondary_command_key": "driver_focus_preview", } elif any(str(item.get("code") or "").strip() == "runtime_build_schema_stale" for item in issues): synthesized_operator_decision = { "lane": "runtime_recovery", "priority": "high", "reason_code": "runtime_build_schema_stale", "title": "先重启控制面 API,让运行时切到最新 schema", "summary": "当前属于仓库代码已更新,但运行中的 API 仍旧停留在旧 schema 的场景。先重启 domaincheck-api,再重新看 stack-diagnosis 与 node-bootstrap-plan。", "next_focus": { "kind": "runtime_build_info", "node_code": "", }, "primary_command_key": "api-restart", "secondary_command_key": "managed-node-bootstrap-plan" if first_problem_node_code else "stack-diagnosis", } elif any(str(item.get("code") or "").strip() == "managed_nodes_agent_pending" for item in issues): synthesized_operator_decision = { "lane": "node_handover", "priority": "high", "reason_code": "managed_nodes_agent_pending", "title": "先补接管缺口", "summary": "当前真正阻断是 remote-agent / 接管未就绪,应先把节点接入链路打通。", "next_focus": {}, "primary_command_key": "node_handover", "secondary_command_key": "doctor", } elif default_rollout_gate_status == "blocked": synthesized_operator_decision = { "lane": "release", "priority": "medium", "reason_code": "release_rollout_gate_blocked", "title": "先看发布门禁", "summary": str(release_hub.get("default_rollout_gate_summary") or "").strip(), "next_focus": launchpad_focus_ref, "primary_command_key": "release_launchpad", "secondary_command_key": "doctor", } if synthesized_operator_decision: operator_decision = synthesized_operator_decision if synthesized_recommended_commands: merged_recommended_commands = dict(synthesized_recommended_commands) merged_recommended_commands.update({ str(key or "").strip(): str(value or "").strip() for key, value in recommended_commands.items() if str(key or "").strip() and str(value or "").strip() }) recommended_commands = merged_recommended_commands if operator_decision: primary_key = str(operator_decision.get("primary_command_key") or "").strip() secondary_key = str(operator_decision.get("secondary_command_key") or "").strip() next_actions = { "primary_command_key": primary_key, "primary_command": str(recommended_commands.get(primary_key) or "").strip(), "secondary_command_key": secondary_key, "secondary_command": str(recommended_commands.get(secondary_key) or "").strip(), } primary_command_key = str(operator_decision.get("primary_command_key") or "").strip() primary_command = str( next_actions.get("primary_command") or recommended_commands.get(primary_command_key) or "" ).strip() operator_focus_ref = dict(operator_decision.get("next_focus") or {}) action_code = "" selection_mode = "driver_action" if primary_command_key and primary_command_key in recommended_action_codes: action_code = primary_command_key elif primary_command_key and primary_command: selection_mode = "command_only" elif str(next_step.get("action_code") or "").strip(): action_code = str(next_step.get("action_code") or "").strip() elif primary_command: selection_mode = "command_only" else: raise SystemExit("stack diagnosis 当前既没有可执行 driver action,也没有可下钻命令") matched_action = next( (item for item in recommended_actions if str(item.get("action_code") or "").strip() == action_code), {}, ) focus_ref = dict(operator_focus_ref or next_step.get("focus_ref") or matched_action.get("focus_ref") or {}) confirm = confirm_flag in {"1", "true", "yes", "y", "confirm", "confirmed"} request_payload = { "source_label": source_label, "title": str(matched_action.get("title") or action_code).strip() or action_code, "summary": str(next_step.get("reason") or matched_action.get("reason") or "").strip(), "reason": str(next_step.get("reason") or matched_action.get("reason") or "").strip(), "meta_text": str(next_step.get("source") or matched_action.get("source") or "stack-diagnosis").strip(), "executor_kind": "driver_action", "focus_ref": focus_ref, "primary_focus_ref": focus_ref, "primary_label": str(matched_action.get("title") or action_code).strip() or action_code, "primary_action_code": action_code, "primary_action_payload": {}, "include_secondary": True, "requested_by": requested_by, "confirm": confirm, } if selection_mode == "command_only": print(json.dumps({ "http_status": 200, "mode": selection_mode, "message": "当前主决策优先指向命令型下钻动作,不进入 driver-actions/resolve。", "contract": { "contract_key": str((diagnosis.get("contract_navigation") or {}).get("primary_contract_key") or "ops_stack_diagnosis_contract").strip(), "contract_version": str(diagnosis.get("contract_version") or "").strip(), "contract_primary_endpoint": "/api/v1/ops/stack-diagnosis", "preview_endpoint": "", "schema_doc_path": "docs/schemas/ops_stack_diagnosis_contract.md", }, "selected_entry": { "action_code": primary_command_key, "source": str(operator_decision.get("reason_code") or next_step.get("source") or "stack-diagnosis").strip(), "title": str(operator_decision.get("title") or primary_command_key or "stack-next").strip(), "reason": str(operator_decision.get("summary") or next_step.get("reason") or "").strip(), "command": primary_command, "focus_ref": focus_ref, "contract_navigation": dict( diagnosis.get("contract_navigation") or next_step.get("contract_navigation") or {} ), }, "gate": { "decision": "command_only", "decision_label": "命令下钻", "recommendation": "open_ui", "recommendation_label": "先下钻", "will_execute": False, "confirm_required": False, "target_api": "", "focus_ref": focus_ref, }, "execution_chain": "stack-diagnosis -> command-only guidance", "focus_ref": focus_ref, "request": {}, }, ensure_ascii=False)) raise SystemExit(0) print(json.dumps({ "mode": selection_mode, "selected_entry": { "action_code": action_code, "source": str(next_step.get("source") or matched_action.get("source") or "stack-diagnosis").strip(), "title": str(matched_action.get("title") or action_code).strip() or action_code, "reason": str(next_step.get("reason") or matched_action.get("reason") or "").strip(), "command": str(next_step.get("command") or matched_action.get("command") or "").strip(), "focus_ref": focus_ref, "contract_navigation": dict(next_step.get("contract_navigation") or matched_action.get("contract_navigation") or {}), }, "request": request_payload, }, ensure_ascii=False)) PY } print_stack_diagnosis_failure_and_hint() { local mainland_base_url="$1" local stack_http_status="$2" local stack_response_json="$3" echo "[1/3] stack diagnosis raw response" printf '%s\n' "http_status=${stack_http_status}" print_json_or_raw "${stack_response_json}" echo echo echo "[2/3] compatibility hint" python3 - <<'PY' "${mainland_base_url}" "${stack_http_status}" "${stack_response_json}" import json import sys base_url = str(sys.argv[1] or "").strip() http_status = int(sys.argv[2] or 0) raw_text = str(sys.argv[3] or "") detail = "" try: payload = json.loads(raw_text) except Exception: payload = {} else: detail = str(payload.get("detail") or payload.get("message") or "").strip() message = "stack-diagnosis 请求失败,请先检查控制面 API 连通性。" commands = [ f"curl -sS {base_url}/api/v1/ops/stack-diagnosis", ] if http_status == 404: message = "当前控制面 API 还未暴露 /api/v1/ops/stack-diagnosis,通常表示运行中的后端版本还没升级或升级后未重启。" commands = [ "git status", f"bash domain-api/deploy/multi-region/drive_ops_center.sh runtime-refresh-recover {base_url}", f"curl -sS {base_url}/api/v1/ops/stack-diagnosis", ] print(json.dumps({ "http_status": http_status, "detail": detail, "message": message, "recommended_commands": commands, }, ensure_ascii=False, indent=2)) PY echo echo echo "[3/3] next action" if [[ "${stack_http_status}" == "404" ]]; then printf '%s\n' "先把控制面后端升级到包含 /api/v1/ops/stack-diagnosis 的版本,并重启 domaincheck-api;随后再重试 stack-next。" else printf '%s\n' "先修复控制面 API / 网络问题,再重试 stack-next。" fi } preview_stack_next_entry() { local mainland_base_url="$1" local requested_by="$2" local raw_stack_response raw_stack_response="$(request_with_status "GET" "${mainland_base_url}/api/v1/ops/stack-diagnosis?base_url=${mainland_base_url}")" local stack_http_status="${raw_stack_response##*$'\n'HTTP_STATUS=}" local stack_response_json="${raw_stack_response%$'\n'HTTP_STATUS=*}" if [[ "${stack_http_status}" -lt 200 || "${stack_http_status}" -ge 300 ]]; then print_stack_diagnosis_failure_and_hint "${mainland_base_url}" "${stack_http_status}" "${stack_response_json}" exit 1 fi local bundle_json bundle_json="$(build_stack_next_bundle "${stack_response_json}" "${requested_by}" "cli/stack-next-preview" "false")" local bundle_mode bundle_mode="$(python3 - <<'PY' "${bundle_json}" import json import sys payload = json.loads(str(sys.argv[1] or "{}")) print(str(payload.get("mode") or "driver_action").strip()) PY )" local selected_entry_json selected_entry_json="$(python3 - <<'PY' "${bundle_json}" import json import sys payload = json.loads(str(sys.argv[1] or "{}")) print(json.dumps(payload.get("selected_entry") or {}, ensure_ascii=False)) PY )" if [[ "${bundle_mode}" == "command_only" ]]; then echo "[1/2] selected next step" print_json_or_raw "${selected_entry_json}" echo echo echo "[2/2] command-only guidance" python3 - <<'PY' "${selected_entry_json}" import json import sys selected = json.loads(str(sys.argv[1] or "{}")) print(json.dumps({ "http_status": 200, "mode": "command_only", "message": "当前主决策优先指向命令型下钻动作,不进入 driver-actions/resolve。", "contract": { "contract_key": str((selected.get("contract_navigation") or {}).get("primary_contract_key") or "ops_stack_diagnosis_contract"), "contract_version": "", "contract_primary_endpoint": "/api/v1/ops/stack-diagnosis", "preview_endpoint": "", "schema_doc_path": "docs/schemas/ops_stack_diagnosis_contract.md", }, "selected_entry": selected, "gate": { "decision": "command_only", "decision_label": "命令下钻", "recommendation": "open_ui", "recommendation_label": "先下钻", "will_execute": False, "confirm_required": False, "target_api": "", "focus_ref": selected.get("focus_ref") or {}, }, "execution_chain": "stack-diagnosis -> command-only guidance", "focus_ref": selected.get("focus_ref") or {}, "recommended_next_command": str(selected.get("command") or ""), }, ensure_ascii=False, indent=2)) PY return 0 fi local request_json request_json="$(python3 - <<'PY' "${bundle_json}" import json import sys payload = json.loads(str(sys.argv[1] or "{}")) print(json.dumps(payload.get("request") or {}, ensure_ascii=False)) PY )" echo "[1/4] selected next step" print_json_or_raw "${selected_entry_json}" echo echo echo "[2/4] request" print_json_or_raw "${request_json}" echo echo local raw_http_response raw_http_response="$(request_with_status "POST" "${mainland_base_url}/api/v1/ops/driver-actions/resolve" "${request_json}")" local http_status="${raw_http_response##*$'\n'HTTP_STATUS=}" local response_json="${raw_http_response%$'\n'HTTP_STATUS=*}" echo "[3/4] raw response" printf '%s\n' "http_status=${http_status}" print_json_or_raw "${response_json}" echo echo echo "[4/4] condensed summary" python3 - <<'PY' "${response_json}" "${http_status}" "${selected_entry_json}" import json import sys payload = json.loads(str(sys.argv[1] or "{}")) http_status = int(sys.argv[2] or 0) selected = json.loads(str(sys.argv[3] or "{}")) data = payload.get("data") or {} preview = data.get("preview") or {} primary = preview.get("primary") or {} secondary = preview.get("secondary") or {} gate = data.get("gate") or {} print(json.dumps({ "http_status": http_status, "message": str(payload.get("message") or ""), "selected_entry": selected, "selected_section": str(data.get("selected_section") or ""), "contract": { "contract_key": str(preview.get("contract_key") or ""), "contract_version": str(preview.get("contract_version") or ""), "contract_primary_endpoint": str(preview.get("contract_primary_endpoint") or ""), "preview_endpoint": str(preview.get("preview_endpoint") or ""), "schema_doc_path": str(preview.get("contract_schema_doc_path") or ""), }, "execution_chain": str(preview.get("execution_chain") or ""), "focus_ref": data.get("focus_ref") or {}, "gate": { "decision": str(gate.get("decision") or ""), "decision_label": str(gate.get("decision_label") or ""), "recommendation": str(gate.get("recommendation") or ""), "recommendation_label": str(gate.get("recommendation_label") or ""), "will_execute": bool(gate.get("will_execute", False)), "confirm_required": bool(gate.get("confirm_required", False)), "target_api": str(gate.get("target_api") or ""), "focus_ref": gate.get("focus_ref") or {}, }, "primary": { "label": str(primary.get("label") or ""), "action_code": str(primary.get("action_code") or ""), "target_api": str(primary.get("target_api") or ""), "recommendation_label": str(primary.get("recommendation_label") or ""), "focus_ref": primary.get("focus_ref") or {}, "request_payload": primary.get("request_payload") or {}, }, "secondary": ( { "label": str(secondary.get("label") or ""), "action_code": str(secondary.get("action_code") or ""), "target_api": str(secondary.get("target_api") or ""), "recommendation_label": str(secondary.get("recommendation_label") or ""), "focus_ref": secondary.get("focus_ref") or {}, "request_payload": secondary.get("request_payload") or {}, } if secondary else {} ), }, ensure_ascii=False, indent=2)) PY if [[ "${http_status}" -lt 200 || "${http_status}" -ge 300 ]]; then exit 1 fi } run_stack_next_entry() { local mainland_base_url="$1" local confirm_flag="$2" local requested_by="$3" local raw_stack_response raw_stack_response="$(request_with_status "GET" "${mainland_base_url}/api/v1/ops/stack-diagnosis?base_url=${mainland_base_url}")" local stack_http_status="${raw_stack_response##*$'\n'HTTP_STATUS=}" local stack_response_json="${raw_stack_response%$'\n'HTTP_STATUS=*}" if [[ "${stack_http_status}" -lt 200 || "${stack_http_status}" -ge 300 ]]; then print_stack_diagnosis_failure_and_hint "${mainland_base_url}" "${stack_http_status}" "${stack_response_json}" exit 1 fi local bundle_json bundle_json="$(build_stack_next_bundle "${stack_response_json}" "${requested_by}" "cli/stack-next-run" "${confirm_flag}")" local bundle_mode bundle_mode="$(python3 - <<'PY' "${bundle_json}" import json import sys payload = json.loads(str(sys.argv[1] or "{}")) print(str(payload.get("mode") or "driver_action").strip()) PY )" local selected_entry_json selected_entry_json="$(python3 - <<'PY' "${bundle_json}" import json import sys payload = json.loads(str(sys.argv[1] or "{}")) print(json.dumps(payload.get("selected_entry") or {}, ensure_ascii=False)) PY )" if [[ "${bundle_mode}" == "command_only" ]]; then echo "[1/2] selected next step" print_json_or_raw "${selected_entry_json}" echo echo echo "[2/2] command-only guidance" python3 - <<'PY' "${selected_entry_json}" import json import sys selected = json.loads(str(sys.argv[1] or "{}")) print(json.dumps({ "http_status": 200, "mode": "command_only", "executed": False, "message": "当前主决策属于命令型下钻动作,stack-next run 不会代替你执行本地 shell。", "contract": { "contract_key": str((selected.get("contract_navigation") or {}).get("primary_contract_key") or "ops_stack_diagnosis_contract"), "contract_version": "", "contract_primary_endpoint": "/api/v1/ops/stack-diagnosis", "preview_endpoint": "", "schema_doc_path": "docs/schemas/ops_stack_diagnosis_contract.md", }, "selected_entry": selected, "gate": { "decision": "command_only", "decision_label": "命令下钻", "recommendation": "open_ui", "recommendation_label": "先下钻", "will_execute": False, "confirm_required": False, "target_api": "", "focus_ref": selected.get("focus_ref") or {}, }, "execution_chain": "stack-diagnosis -> command-only guidance", "focus_ref": selected.get("focus_ref") or {}, "recommended_next_command": str(selected.get("command") or ""), }, ensure_ascii=False, indent=2)) PY return 0 fi local request_json request_json="$(python3 - <<'PY' "${bundle_json}" import json import sys payload = json.loads(str(sys.argv[1] or "{}")) print(json.dumps(payload.get("request") or {}, ensure_ascii=False)) PY )" echo "[1/4] selected next step" print_json_or_raw "${selected_entry_json}" echo echo echo "[2/4] request" print_json_or_raw "${request_json}" echo echo local raw_http_response raw_http_response="$(request_with_status "POST" "${mainland_base_url}/api/v1/ops/driver-actions/execute-resolved" "${request_json}")" local http_status="${raw_http_response##*$'\n'HTTP_STATUS=}" local response_json="${raw_http_response%$'\n'HTTP_STATUS=*}" echo "[3/4] raw response" printf '%s\n' "http_status=${http_status}" print_json_or_raw "${response_json}" echo echo echo "[4/4] execution result" python3 - <<'PY' "${response_json}" "${http_status}" "${selected_entry_json}" import json import sys payload = json.loads(str(sys.argv[1] or "{}")) http_status = int(sys.argv[2] or 0) selected = json.loads(str(sys.argv[3] or "{}")) data = payload.get("data") or {} gate = data.get("gate") or {} execution_result = data.get("execution_result") or {} raw_code = payload.get("code", 1) if raw_code in (0, "0", False): normalized_code = 0 else: normalized_code = int(raw_code or 1) print(json.dumps({ "http_status": http_status, "code": normalized_code, "message": str(payload.get("message") or ""), "selected_entry": selected, "executed": bool(data.get("executed", False)), "selected_section": str(data.get("selected_section") or ""), "focus_ref": data.get("focus_ref") or {}, "decision": str(gate.get("decision") or ""), "decision_label": str(gate.get("decision_label") or ""), "recommendation": str(gate.get("recommendation") or ""), "target_api": str(gate.get("target_api") or ""), "target_method": str(gate.get("target_method") or ""), "gate_focus_ref": gate.get("focus_ref") or {}, "action_code": str(gate.get("action_code") or execution_result.get("action_code") or execution_result.get("driver_action_code") or ""), "sequence_key": str(gate.get("sequence_key") or execution_result.get("sequence_key") or ""), "handled": bool(execution_result.get("handled", False)), "mode": str(execution_result.get("mode") or ""), }, ensure_ascii=False, indent=2)) PY if [[ "${http_status}" -lt 200 || "${http_status}" -ge 300 ]]; then exit 1 fi } resolve_driver_action_entry() { local mainland_base_url="$1" local action_code="$2" local action_payload_json="$3" local requested_by="$4" local source_label="$5" action_payload_json="$(normalize_payload_json_input "${action_payload_json}")" action_payload_json="$(enrich_driver_action_payload_json "${mainland_base_url}" "${action_code}" "${action_payload_json}")" local request_json request_json="$(python3 - <<'PY' "${action_code}" "${action_payload_json}" "${requested_by}" "${source_label}" import json import sys action_code = str(sys.argv[1] or "").strip() payload_raw = str(sys.argv[2] or "").strip() or "{}" requested_by = str(sys.argv[3] or "").strip() or "cli/ops-center/driver-resolve" source_label = str(sys.argv[4] or "").strip() or "cli/driver-resolve" try: action_payload = json.loads(payload_raw) except Exception as exc: raise SystemExit(f"invalid action payload json: {exc}") if not isinstance(action_payload, dict): raise SystemExit("action payload must be a json object") node_codes = [] seen = set() for item in list(action_payload.get("node_codes") or []): value = str(item or "").strip() if not value or value in seen: continue seen.add(value) node_codes.append(value) print(json.dumps({ "source_label": source_label, "title": action_code, "executor_kind": "driver_action", "primary_label": action_code, "primary_action_code": action_code, "primary_node_codes": node_codes, "primary_action_payload": action_payload, "include_secondary": True, "requested_by": requested_by, }, ensure_ascii=False)) PY )" echo "[1/3] request" print_json_or_raw "${request_json}" echo echo local raw_http_response raw_http_response="$(request_with_status "POST" "${mainland_base_url}/api/v1/ops/driver-actions/resolve" "${request_json}")" local http_status="${raw_http_response##*$'\n'HTTP_STATUS=}" local response_json="${raw_http_response%$'\n'HTTP_STATUS=*}" echo "[2/3] raw response" printf '%s\n' "http_status=${http_status}" print_json_or_raw "${response_json}" echo echo echo "[3/3] condensed summary" python3 - <<'PY' "${response_json}" "${http_status}" import json import sys payload = json.loads(str(sys.argv[1] or "{}")) http_status = int(sys.argv[2] or 0) data = payload.get("data") or {} preview = data.get("preview") or {} primary = preview.get("primary") or {} secondary = preview.get("secondary") or {} gate = data.get("gate") or {} print(json.dumps({ "http_status": http_status, "message": str(payload.get("message") or ""), "selected_section": str(data.get("selected_section") or ""), "execution_chain": str(preview.get("execution_chain") or ""), "focus_ref": data.get("focus_ref") or {}, "gate": { "decision": str(gate.get("decision") or ""), "decision_label": str(gate.get("decision_label") or ""), "recommendation": str(gate.get("recommendation") or ""), "recommendation_label": str(gate.get("recommendation_label") or ""), "will_execute": bool(gate.get("will_execute", False)), "confirm_required": bool(gate.get("confirm_required", False)), "target_api": str(gate.get("target_api") or ""), "focus_ref": gate.get("focus_ref") or {}, }, "primary": { "label": str(primary.get("label") or ""), "action_code": str(primary.get("action_code") or ""), "target_api": str(primary.get("target_api") or ""), "recommendation_label": str(primary.get("recommendation_label") or ""), "focus_ref": primary.get("focus_ref") or {}, "request_payload": primary.get("request_payload") or {}, }, "secondary": ( { "label": str(secondary.get("label") or ""), "action_code": str(secondary.get("action_code") or ""), "target_api": str(secondary.get("target_api") or ""), "recommendation_label": str(secondary.get("recommendation_label") or ""), "focus_ref": secondary.get("focus_ref") or {}, "request_payload": secondary.get("request_payload") or {}, } if secondary else {} ), }, ensure_ascii=False, indent=2)) PY if [[ "${http_status}" -lt 200 || "${http_status}" -ge 300 ]]; then exit 1 fi } run_driver_action_entry() { local mainland_base_url="$1" local action_code="$2" local action_payload_json="$3" local confirm_flag="$4" local requested_by="$5" local source_label="$6" local confirm_value confirm_value="$(confirm_to_bool "${confirm_flag}")" action_payload_json="$(normalize_payload_json_input "${action_payload_json}")" action_payload_json="$(enrich_driver_action_payload_json "${mainland_base_url}" "${action_code}" "${action_payload_json}")" local request_json request_json="$(python3 - <<'PY' "${action_code}" "${action_payload_json}" "${requested_by}" "${source_label}" "${confirm_value}" import json import sys action_code = str(sys.argv[1] or "").strip() payload_raw = str(sys.argv[2] or "").strip() or "{}" requested_by = str(sys.argv[3] or "").strip() or "cli/ops-center/driver-run" source_label = str(sys.argv[4] or "").strip() or "cli/driver-run" confirm = str(sys.argv[5] or "").strip().lower() == "true" try: action_payload = json.loads(payload_raw) except Exception as exc: raise SystemExit(f"invalid action payload json: {exc}") if not isinstance(action_payload, dict): raise SystemExit("action payload must be a json object") node_codes = [] seen = set() for item in list(action_payload.get("node_codes") or []): value = str(item or "").strip() if not value or value in seen: continue seen.add(value) node_codes.append(value) print(json.dumps({ "source_label": source_label, "title": action_code, "executor_kind": "driver_action", "primary_label": action_code, "primary_action_code": action_code, "primary_node_codes": node_codes, "primary_action_payload": action_payload, "include_secondary": True, "requested_by": requested_by, "confirm": confirm, }, ensure_ascii=False)) PY )" echo "[1/3] request" print_json_or_raw "${request_json}" echo echo local raw_http_response raw_http_response="$(request_with_status "POST" "${mainland_base_url}/api/v1/ops/driver-actions/execute-resolved" "${request_json}")" local http_status="${raw_http_response##*$'\n'HTTP_STATUS=}" local response_json="${raw_http_response%$'\n'HTTP_STATUS=*}" echo "[2/3] raw response" printf '%s\n' "http_status=${http_status}" print_json_or_raw "${response_json}" echo echo echo "[3/3] execution result" python3 - <<'PY' "${response_json}" "${http_status}" import json import sys payload = json.loads(str(sys.argv[1] or "{}")) http_status = int(sys.argv[2] or 0) data = payload.get("data") or {} gate = data.get("gate") or {} execution_result = data.get("execution_result") or {} raw_code = payload.get("code", 1) if raw_code in (0, "0", False): normalized_code = 0 else: normalized_code = int(raw_code or 1) print(json.dumps({ "http_status": http_status, "code": normalized_code, "message": str(payload.get("message") or ""), "executed": bool(data.get("executed", False)), "selected_section": str(data.get("selected_section") or ""), "focus_ref": data.get("focus_ref") or {}, "decision": str(gate.get("decision") or ""), "decision_label": str(gate.get("decision_label") or ""), "recommendation": str(gate.get("recommendation") or ""), "target_api": str(gate.get("target_api") or ""), "target_method": str(gate.get("target_method") or ""), "gate_focus_ref": gate.get("focus_ref") or {}, "action_code": str(gate.get("action_code") or execution_result.get("action_code") or execution_result.get("driver_action_code") or ""), "handled": bool(execution_result.get("handled", False)), "mode": str(execution_result.get("mode") or ""), }, ensure_ascii=False, indent=2)) PY if [[ "${http_status}" -lt 200 || "${http_status}" -ge 300 ]]; then exit 1 fi if [[ "${action_code}" == "enable_log_sync_key" || "${action_code}" == "enable_log_sync_full" || "${action_code}" == "disable_log_sync" ]]; then echo echo "[4/4] log sync post-check" print_log_sync_post_check "${mainland_base_url}" fi } print_log_sync_post_check() { local mainland_base_url="$1" local raw_http_response raw_http_response="$(request_with_status "GET" "${mainland_base_url}/api/v1/ops/overview")" local http_status="${raw_http_response##*$'\n'HTTP_STATUS=}" local response_json="${raw_http_response%$'\n'HTTP_STATUS=*}" python3 - <<'PY' "${response_json}" "${http_status}" import json import sys payload = json.loads(str(sys.argv[1] or "{}")) http_status = int(sys.argv[2] or 0) data = payload.get("data") or {} execution_scene = data.get("execution_scene") or {} log_sync = execution_scene.get("log_sync") or {} participation_summary = execution_scene.get("participation_summary") or {} participating_nodes = int( log_sync.get("participating_nodes_total", log_sync.get("participating_node_count", 0)) or participation_summary.get("participating_nodes", 0) or 0 ) covered_nodes = int( log_sync.get("covered_participating_nodes", log_sync.get("covered_participating_node_count", 0)) or 0 ) line_count = int(log_sync.get("line_count", 0) or 0) source_node_count = int(log_sync.get("source_node_count", 0) or 0) missing_nodes = list(log_sync.get("missing_participating_nodes") or []) mode = str(log_sync.get("mode") or "key").strip() or "key" enabled = bool(log_sync.get("enabled", False)) last_at = str(log_sync.get("last_at") or "").strip() preview_lines = [ str(item or "").strip() for item in list(log_sync.get("preview_lines") or []) if str(item or "").strip() ][:5] status = "standby" summary = "当前没有参与检测节点,远端日志回传处于待命状态。" if participating_nodes > 0 and not enabled: status = "disabled" summary = "当前已有参与检测节点,但远端日志回传仍关闭。" elif participating_nodes > 0 and (line_count <= 0 or source_node_count <= 0 or missing_nodes): status = "waiting_sample" summary = "远端日志回传已经开启,但样本或覆盖仍未形成完整闭环。" elif participating_nodes > 0: status = "ready" summary = "远端日志回传已经形成样本,可以继续下钻节点现场日志。" result = { "http_status": http_status, "code": int(payload.get("code", 0) or 0), "message": str(payload.get("message") or ""), "status": status, "summary": summary, "log_sync": { "enabled": enabled, "mode": mode, "participating_nodes": participating_nodes, "covered_participating_nodes": covered_nodes, "line_count": line_count, "source_node_count": source_node_count, "missing_participating_nodes": missing_nodes, "last_at": last_at, "preview_lines": preview_lines, }, } if http_status < 200 or http_status >= 300: result["warning"] = "post-check 未能成功读取 /api/v1/ops/overview,请确认当前 API 版本是否已包含 ops overview。" print(json.dumps(result, ensure_ascii=False, indent=2)) PY } run_log_sync_recover_flow() { local mainland_base_url="$1" local desired_mode_raw="$2" local confirm_flag="$3" local requested_by="$4" local desired_mode desired_mode="$(printf '%s' "${desired_mode_raw:-key}" | tr '[:upper:]' '[:lower:]')" if [[ "${desired_mode}" != "full" ]]; then desired_mode="key" fi local action_code="enable_log_sync_key" if [[ "${desired_mode}" == "full" ]]; then action_code="enable_log_sync_full" fi echo "[1/2] execute log sync recovery action" run_driver_action_entry "${mainland_base_url}" "${action_code}" '{}' "${confirm_flag}" "${requested_by}" "cli/log-sync-recover" echo echo "[2/2] recovery guidance" build_log_sync_guidance_json "${mainland_base_url}" } build_log_sync_guidance_json() { local mainland_base_url="$1" local raw_http_response raw_http_response="$(request_with_status "GET" "${mainland_base_url}/api/v1/ops/overview")" local http_status="${raw_http_response##*$'\n'HTTP_STATUS=}" local response_json="${raw_http_response%$'\n'HTTP_STATUS=*}" python3 - <<'PY' "${response_json}" "${http_status}" "${mainland_base_url}" import json import sys payload = json.loads(str(sys.argv[1] or "{}")) http_status = int(sys.argv[2] or 0) base_url = str(sys.argv[3] or "").strip() data = payload.get("data") or {} execution_scene = data.get("execution_scene") or {} log_sync = execution_scene.get("log_sync") or {} participation_summary = execution_scene.get("participation_summary") or {} state = str(log_sync.get("state") or "").strip() or "unknown" description = str(log_sync.get("description") or "").strip() enabled = bool(log_sync.get("enabled", False)) mode = str(log_sync.get("mode") or "key").strip() or "key" source_nodes = [str(item or "").strip() for item in list(log_sync.get("source_nodes") or []) if str(item or "").strip()] missing_nodes = [str(item or "").strip() for item in list(log_sync.get("missing_participating_nodes") or []) if str(item or "").strip()] covered_nodes = [str(item or "").strip() for item in list(log_sync.get("covered_participating_nodes") or []) if str(item or "").strip()] preview_lines = [str(item or "").strip() for item in list(log_sync.get("preview_lines") or []) if str(item or "").strip()][:5] participating_nodes = int( log_sync.get("participating_node_count", participation_summary.get("participating_nodes", 0)) or participation_summary.get("participating_nodes", 0) or 0 ) covered_count = int(log_sync.get("covered_participating_node_count", len(covered_nodes)) or len(covered_nodes)) line_count = int(log_sync.get("line_count", 0) or 0) recommended_commands = [] primary_target = "" if source_nodes: primary_target = source_nodes[0] elif covered_nodes: primary_target = covered_nodes[0] elif missing_nodes: primary_target = missing_nodes[0] if primary_target: recommended_commands.append( f"bash domain-api/deploy/multi-region/drive_ops_center.sh scene-node-log {base_url} {primary_target} 120 {mode}" ) if missing_nodes: recommended_commands.extend( f"bash domain-api/deploy/multi-region/drive_ops_center.sh scene-node-log {base_url} {node_code} 120 {mode}" for node_code in missing_nodes[:2] if node_code != primary_target ) recommended_commands.append( f"bash domain-api/deploy/multi-region/drive_ops_center.sh log-sync-check {base_url}" ) recommended_commands.append( f"bash domain-api/deploy/multi-region/drive_ops_center.sh doctor {base_url}" ) next_step = "继续观察" if state in {"disabled", "unknown"}: next_step = "开关仍未真正生效,先确认当前 API 是否已加载最新 runtime_settings,并复查执行链。" elif state in {"waiting_sample", "partial_coverage"}: next_step = "开关已经生效,但样本或覆盖还不完整,优先下钻参与节点现场日志。" elif state in {"healthy", "full_capture"}: next_step = "远端日志回传已进入可用状态,可以继续从海外控制面盯现场。" elif state == "idle": next_step = "当前没有参与检测节点,日志回传已就绪,等任务进入执行即可。" print(json.dumps({ "http_status": http_status, "code": int(payload.get("code", 0) or 0), "message": str(payload.get("message") or ""), "status": state, "summary": description, "next_step": next_step, "log_sync": { "enabled": enabled, "mode": mode, "participating_nodes": participating_nodes, "covered_participating_nodes": covered_count, "line_count": line_count, "source_nodes": source_nodes, "missing_participating_nodes": missing_nodes, "preview_lines": preview_lines, }, "recommended_commands": recommended_commands[:5], }, ensure_ascii=False, indent=2)) PY } run_runtime_refresh_recover() { local mainland_base_url="$1" local audit_output audit_output="$(bash "${SCRIPT_DIR}/check_env_drift.sh" "${mainland_base_url}")" echo "[1/2] env audit" printf '%s\n' "${audit_output}" echo echo echo "[2/2] runtime refresh guidance" python3 - <<'PY' "${audit_output}" "${mainland_base_url}" import json import sys raw_text = str(sys.argv[1] or "") base_url = str(sys.argv[2] or "").strip() marker = "[8/8] condensed env audit summary" candidate_text = raw_text.split(marker, 1)[1].strip() if marker in raw_text else raw_text.strip() payload = {} if candidate_text: try: payload = json.loads(candidate_text) except Exception: payload = {} runtime = dict(payload.get("runtime") or {}) recommended_actions = [ str(item).strip() for item in list(payload.get("recommended_actions") or []) if str(item).strip() ] recommended_commands = [] for candidate in recommended_actions: if candidate not in recommended_commands: recommended_commands.append(candidate) for candidate in [ f"bash domain-api/deploy/multi-region/drive_ops_center.sh stack-diagnosis {base_url} summary", f"bash domain-api/deploy/multi-region/drive_ops_center.sh stack-next {base_url}", f"bash domain-api/deploy/multi-region/drive_ops_center.sh doctor-decision {base_url} -", ]: normalized_candidate = str(candidate or "").strip() if normalized_candidate and normalized_candidate not in recommended_commands: recommended_commands.append(normalized_candidate) status = str(payload.get("status") or "").strip() or "unknown" headline = str(payload.get("headline") or "").strip() runtime_may_need_restart = bool(runtime.get("runtime_may_need_restart", False)) repo_capability_drift = bool(runtime.get("repo_capability_drift", False)) repo_commit_drift = bool(runtime.get("repo_commit_drift", False)) summary = "当前没有识别到明显的运行时版本漂移。" if runtime_may_need_restart: summary = "已经识别到运行时版本漂移,先让 domaincheck-api 切到最新代码,再继续节点接管与上线复核。" elif status == "blocked": summary = "当前环境本身仍有阻断项,先修环境,再谈运行时刷新。" elif status == "attention": summary = "当前环境存在注意项,但不一定都是运行时版本问题。" print(json.dumps({ "status": status, "headline": headline, "summary": summary, "runtime_refresh": { "runtime_may_need_restart": runtime_may_need_restart, "repo_capability_drift": repo_capability_drift, "repo_commit_drift": repo_commit_drift, "build_commit_sha": str(runtime.get("build_commit_sha") or "").strip(), "build_commit_ref": str(runtime.get("build_commit_ref") or "").strip(), "route_surface_declares_bootstrap_plan": bool(runtime.get("route_surface_declares_bootstrap_plan", False)), "route_surface_missing_keys": [ str(item).strip() for item in list(runtime.get("route_surface_missing_keys") or []) if str(item).strip() ], }, "recommended_commands": recommended_commands[:6], }, ensure_ascii=False, indent=2)) PY } run_go_live_recover() { local mainland_base_url="$1" local overseas_base_url="$2" echo "[1/5] runtime refresh recover" run_runtime_refresh_recover "${mainland_base_url}" echo echo echo "[2/5] stack check summary" bash "${SCRIPT_DIR}/check_ops_center_stack.sh" "${mainland_base_url}" "summary" echo echo echo "[3/5] stack next preview" preview_stack_next_entry "${mainland_base_url}" "$(ops_center_resolve_requested_by)" echo echo echo "[4/5] go live check summary" bash "${SCRIPT_DIR}/check_go_live.sh" "${mainland_base_url}" "${overseas_base_url}" "summary" echo echo echo "[5/5] doctor decision" run_doctor_decision "${mainland_base_url}" "${overseas_base_url}" } show_agent_gap_check() { local mainland_base_url="$1" local raw_http_response raw_http_response="$(request_with_status "GET" "${mainland_base_url}/api/v1/ops/stack-diagnosis?base_url=${mainland_base_url}")" local http_status="${raw_http_response##*$'\n'HTTP_STATUS=}" local response_json="${raw_http_response%$'\n'HTTP_STATUS=*}" python3 - <<'PY' "${response_json}" "${http_status}" "${mainland_base_url}" import json import sys payload = json.loads(str(sys.argv[1] or "{}")) http_status = int(sys.argv[2] or 0) base_url = str(sys.argv[3] or "").strip() data = payload.get("data") or {} managed_nodes = data.get("managed_nodes") or {} problem_nodes = [dict(item or {}) for item in list(managed_nodes.get("problem_nodes") or []) if isinstance(item, dict)] first_gap = dict(managed_nodes.get("first_handover_gap") or {}) managed_enabled = int(managed_nodes.get("managed_enabled", 0) or 0) remote_access_ready = int(managed_nodes.get("remote_access_ready", 0) or 0) first_node_code = str( first_gap.get("node_code") or managed_nodes.get("first_handover_gap_node_code") or ((problem_nodes[0] or {}).get("node_code") if problem_nodes else "") or "" ).strip() summary = "当前没有启用托管节点。" if problem_nodes and managed_enabled > 0 and remote_access_ready <= 0: summary = "当前已有托管节点,但 remote-agent 仍未就绪,优先补齐节点接管链路。" elif problem_nodes and managed_enabled > 0 and remote_access_ready < managed_enabled: summary = ( f"当前仍有 {len(problem_nodes)} 台托管节点未进入 remote-agent 就绪状态," "优先补齐剩余节点接管。" ) elif managed_enabled > 0: summary = "当前托管节点均已进入 remote-agent 就绪状态。" recommended_commands = [] if first_node_code: recommended_commands.extend([ f"bash domain-api/deploy/multi-region/drive_ops_center.sh node-recover {base_url} {first_node_code}", f"bash domain-api/deploy/multi-region/drive_ops_center.sh node-onboarding {base_url} {first_node_code}", f"bash domain-api/deploy/multi-region/drive_ops_center.sh node-handover {base_url} {first_node_code}", f"bash domain-api/deploy/multi-region/drive_ops_center.sh node-bootstrap-plan {base_url} {first_node_code}", ]) recommended_commands.extend([ f"bash domain-api/deploy/multi-region/drive_ops_center.sh agent-gap-export /tmp/domaincheck-agent-gap {base_url}", f"bash domain-api/deploy/multi-region/drive_ops_center.sh stack-first-gap-handover {base_url}", f"bash domain-api/deploy/multi-region/drive_ops_center.sh doctor {base_url}", ]) print(json.dumps({ "http_status": http_status, "code": int(payload.get("code", 0) or 0), "message": str(payload.get("message") or ""), "summary": summary, "managed_nodes": { "managed_enabled": managed_enabled, "remote_access_ready": remote_access_ready, "first_gap_node_code": first_node_code, "problem_nodes": [ { "node_code": str(item.get("node_code") or "").strip(), "agent_state": str(item.get("agent_state") or "").strip(), "remote_access_state": str(item.get("remote_access_state") or "").strip(), "delivery_queue_state": str(item.get("delivery_queue_state") or "").strip(), "participation_state": str(item.get("participation_state") or "").strip(), } for item in problem_nodes[:5] ], }, "recommended_commands": recommended_commands[:5], }, ensure_ascii=False, indent=2)) PY } run_agent_gap_recover_flow() { local mainland_base_url="$1" local confirm_flag="$2" local requested_by="$3" local normalized_confirm normalized_confirm="$(confirm_to_bool "${confirm_flag}")" echo "[1/4] stack first gap handover" show_stack_first_gap_handover "${mainland_base_url}" echo echo "[2/4] first node bootstrap plan" local first_gap_node_code first_gap_node_code="$(python3 - <<'PY' "${mainland_base_url}" "${OPS_CENTER_CURL_CONNECT_TIMEOUT}" "${OPS_CENTER_CURL_MAX_TIME}" import json import subprocess import sys base_url = str(sys.argv[1] or "").strip() connect_timeout = str(sys.argv[2] or "3").strip() max_time = str(sys.argv[3] or "10").strip() proc = subprocess.run( [ "curl", "-sS", "--connect-timeout", connect_timeout, "--max-time", max_time, f"{base_url}/api/v1/ops/stack-diagnosis?base_url={base_url}", ], capture_output=True, text=True, check=False, ) raw = (proc.stdout or "").strip() try: payload = json.loads(raw) except Exception: print("") raise SystemExit(0) data = payload.get("data") or {} managed_nodes = data.get("managed_nodes") or {} problem_nodes = [dict(item or {}) for item in list(managed_nodes.get("problem_nodes") or []) if isinstance(item, dict)] first_gap = dict(managed_nodes.get("first_handover_gap") or {}) node_code = str( first_gap.get("node_code") or managed_nodes.get("first_handover_gap_node_code") or ((problem_nodes[0] or {}).get("node_code") if problem_nodes else "") or "" ).strip() print(node_code) PY )" if [[ -n "${first_gap_node_code}" ]]; then build_node_handover_bootstrap_plan \ "${mainland_base_url}" \ "${first_gap_node_code}" \ "${requested_by}" \ "72" \ "${mainland_base_url}" \ "/opt/domaincheck" else echo '{"message":"当前没有可生成 bootstrap-plan 的缺口节点。"}' fi echo echo "[3/4] node onboarding view" if [[ -n "${first_gap_node_code}" ]]; then show_node_onboarding "${mainland_base_url}" "${first_gap_node_code}" else echo '{"message":"当前没有可查看 onboarding 的缺口节点。"}' fi echo echo "[4/4] first node recovery decision" if [[ -n "${first_gap_node_code}" ]]; then bash "${SCRIPT_DIR}/drive_ops_center.sh" node-recover "${mainland_base_url}" "${first_gap_node_code}" "" "${requested_by}" echo echo "[follow-up] gap recovery guidance" show_agent_gap_check "${mainland_base_url}" else echo '{"message":"当前没有可生成恢复决策的缺口节点。"}' echo echo "[follow-up] gap recovery guidance" show_agent_gap_check "${mainland_base_url}" fi if [[ "${normalized_confirm}" == "true" ]]; then echo echo '{"confirm":"true","note":"当前 agent-gap-recover 仍是结构化恢复流,不会直接 SSH 落地远端 bootstrap。"}' fi } export_agent_gap_bundle() { local report_dir="$1" local mainland_base_url="$2" local requested_by="$3" ops_center_ensure_report_dir "${report_dir}" local stack_json_path="${report_dir}/00_stack_first_gap_handover.json" local check_json_path="${report_dir}/01_agent_gap_check.json" local onboarding_json_path="${report_dir}/02_node_onboarding.json" local bootstrap_plan_txt_path="${report_dir}/03_node_bootstrap_plan.txt" local bootstrap_preview_json_path="${report_dir}/04_node_bootstrap_preview.json" local acceptance_plan_json_path="${report_dir}/05_node_acceptance_plan.json" local manifest_path="${report_dir}/manifest.json" local nodes_dir="${report_dir}/nodes" bash "${SCRIPT_DIR}/drive_ops_center.sh" stack-first-gap-handover "${mainland_base_url}" >"${stack_json_path}" bash "${SCRIPT_DIR}/drive_ops_center.sh" agent-gap-check "${mainland_base_url}" >"${check_json_path}" local first_gap_node_code local gap_nodes_json gap_nodes_json="$(python3 - <<'PY' "${check_json_path}" import json import sys from pathlib import Path text = Path(sys.argv[1]).read_text(encoding="utf-8") decoder = json.JSONDecoder() best = None for index, ch in enumerate(text): if ch not in "{[": continue try: value, end = decoder.raw_decode(text[index:]) except Exception: continue absolute_end = index + end if best is None or absolute_end >= best[0]: best = (absolute_end, value) payload = best[1] if best else {} managed = payload.get("managed_nodes") or {} problem_nodes = [dict(item or {}) for item in list(managed.get("problem_nodes") or []) if isinstance(item, dict)] first_gap_node_code = str(managed.get("first_gap_node_code") or "").strip() node_codes = [] for item in problem_nodes: node_code = str(item.get("node_code") or "").strip() if node_code and node_code not in node_codes: node_codes.append(node_code) if first_gap_node_code and first_gap_node_code not in node_codes: node_codes.insert(0, first_gap_node_code) print(json.dumps({ "first_gap_node_code": first_gap_node_code, "gap_node_codes": node_codes, }, ensure_ascii=False)) PY )" first_gap_node_code="$(python3 - <<'PY' "${gap_nodes_json}" import json import sys payload = json.loads(str(sys.argv[1] or "{}")) print(str(payload.get("first_gap_node_code") or "").strip()) PY )" mapfile -t gap_node_codes < <(python3 - <<'PY' "${gap_nodes_json}" import json import sys payload = json.loads(str(sys.argv[1] or "{}")) for item in list(payload.get("gap_node_codes") or []): node_code = str(item or "").strip() if node_code: print(node_code) PY ) if [[ -n "${first_gap_node_code}" ]]; then bash "${SCRIPT_DIR}/drive_ops_center.sh" node-onboarding "${mainland_base_url}" "${first_gap_node_code}" >"${onboarding_json_path}" bash "${SCRIPT_DIR}/drive_ops_center.sh" node-bootstrap-plan "${mainland_base_url}" "${first_gap_node_code}" "${requested_by}" "72" "${mainland_base_url}" "/opt/domaincheck" >"${bootstrap_plan_txt_path}" bash "${SCRIPT_DIR}/drive_ops_center.sh" node-bootstrap-preview "${mainland_base_url}" "${first_gap_node_code}" "${requested_by}" >"${bootstrap_preview_json_path}" bash "${SCRIPT_DIR}/drive_ops_center.sh" node-acceptance-plan "${mainland_base_url}" "${first_gap_node_code}" "${requested_by}" >"${acceptance_plan_json_path}" || true fi mkdir -p "${nodes_dir}" local node_code safe_node_code node_dir for node_code in "${gap_node_codes[@]}"; do [[ -n "${node_code}" ]] || continue safe_node_code="$(printf '%s' "${node_code}" | tr '/:' '__')" safe_node_code="${safe_node_code// /_}" node_dir="${nodes_dir}/${safe_node_code}" mkdir -p "${node_dir}" bash "${SCRIPT_DIR}/drive_ops_center.sh" node-handover "${mainland_base_url}" "${node_code}" >"${node_dir}/00_node_handover.json" bash "${SCRIPT_DIR}/drive_ops_center.sh" node-onboarding "${mainland_base_url}" "${node_code}" >"${node_dir}/01_node_onboarding.json" bash "${SCRIPT_DIR}/drive_ops_center.sh" node-bootstrap-plan "${mainland_base_url}" "${node_code}" "${requested_by}" "72" "${mainland_base_url}" "/opt/domaincheck" >"${node_dir}/02_node_bootstrap_plan.txt" bash "${SCRIPT_DIR}/drive_ops_center.sh" node-bootstrap-preview "${mainland_base_url}" "${node_code}" "${requested_by}" >"${node_dir}/03_node_bootstrap_preview.json" bash "${SCRIPT_DIR}/drive_ops_center.sh" node-acceptance-plan "${mainland_base_url}" "${node_code}" "${requested_by}" >"${node_dir}/04_node_acceptance_plan.json" || true done python3 - <<'PY' \ "${manifest_path}" \ "${report_dir}" \ "${mainland_base_url}" \ "${requested_by}" \ "${first_gap_node_code}" \ "${gap_nodes_json}" \ "${nodes_dir}" \ "${stack_json_path}" \ "${check_json_path}" \ "${onboarding_json_path}" \ "${bootstrap_plan_txt_path}" \ "${bootstrap_preview_json_path}" \ "${acceptance_plan_json_path}" import json import sys from pathlib import Path manifest_path = Path(sys.argv[1]) report_dir = Path(sys.argv[2]) base_url = sys.argv[3] requested_by = sys.argv[4] first_gap_node_code = sys.argv[5] gap_nodes_payload = json.loads(str(sys.argv[6] or "{}")) nodes_dir = Path(sys.argv[7]) paths = { "stack_first_gap_handover": Path(sys.argv[8]), "agent_gap_check": Path(sys.argv[9]), "node_onboarding": Path(sys.argv[10]), "node_bootstrap_plan": Path(sys.argv[11]), "node_bootstrap_preview": Path(sys.argv[12]), "node_acceptance_plan": Path(sys.argv[13]), } artifacts = [] for key, path in paths.items(): artifacts.append({ "key": key, "path": str(path), "exists": path.exists(), "size": path.stat().st_size if path.exists() else 0, }) node_bundles = [] for node_code in list(gap_nodes_payload.get("gap_node_codes") or []): normalized_node_code = str(node_code or "").strip() if not normalized_node_code: continue normalized_dir_name = normalized_node_code.replace("/", "_").replace(":", "_").replace(" ", "_") node_dir = nodes_dir / normalized_dir_name bundle_paths = { "node_handover": node_dir / "00_node_handover.json", "node_onboarding": node_dir / "01_node_onboarding.json", "node_bootstrap_plan": node_dir / "02_node_bootstrap_plan.txt", "node_bootstrap_preview": node_dir / "03_node_bootstrap_preview.json", "node_acceptance_plan": node_dir / "04_node_acceptance_plan.json", } node_bundles.append({ "node_code": normalized_node_code, "bundle_dir": str(node_dir), "artifacts": [ { "key": key, "path": str(path), "exists": path.exists(), "size": path.stat().st_size if path.exists() else 0, } for key, path in bundle_paths.items() ], }) payload = { "report_dir": str(report_dir), "mainland_base_url": base_url, "requested_by": requested_by, "first_gap_node_code": first_gap_node_code, "gap_node_codes": list(gap_nodes_payload.get("gap_node_codes") or []), "gap_node_total": len(list(gap_nodes_payload.get("gap_node_codes") or [])), "artifacts": artifacts, "node_bundles": node_bundles, "summary": { "ok": bool(node_bundles), "headline": ( f"已为 {len(node_bundles)} 台接管缺口节点导出接管材料;首个缺口节点为 {first_gap_node_code}。" if node_bundles and first_gap_node_code else ( f"已为 {len(node_bundles)} 台接管缺口节点导出接管材料。" if node_bundles else "当前没有可导出的接管缺口节点。" ) ), }, } manifest_path.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8") print(json.dumps(payload, ensure_ascii=False, indent=2)) PY } run_resolved_runbook_sequence() { local mainland_base_url="$1" local sequence_key="$2" local secondary_flag="$3" local action_payload_json="$4" local confirm_flag="$5" local requested_by="$6" local source_label="$7" local confirm_value confirm_value="$(confirm_to_bool "${confirm_flag}")" action_payload_json="$(normalize_payload_json_input "${action_payload_json}")" local request_json request_json="$(python3 - <<'PY' "${sequence_key}" "${secondary_flag}" "${action_payload_json}" "${requested_by}" "${source_label}" "${confirm_value}" import json import sys sequence_key = str(sys.argv[1] or "").strip() secondary_flag = str(sys.argv[2] or "primary").strip().lower() payload_raw = str(sys.argv[3] or "").strip() or "{}" requested_by = str(sys.argv[4] or "").strip() or "cli/ops-center/runbook-run" source_label = str(sys.argv[5] or "").strip() or "cli/runbook-run" confirm = str(sys.argv[6] or "").strip().lower() == "true" try: action_payload = json.loads(payload_raw) except Exception as exc: raise SystemExit(f"invalid action payload json: {exc}") if not isinstance(action_payload, dict): raise SystemExit("action payload must be a json object") secondary = secondary_flag in {"secondary", "second", "2", "true", "yes", "y"} print(json.dumps({ "source_label": source_label, "title": sequence_key, "executor_kind": "runbook_sequence", "sequence_key": sequence_key, "include_secondary": True, "requested_by": requested_by, "secondary": secondary, "confirm": confirm, "primary_action_payload": action_payload, "secondary_action_payload": action_payload, }, ensure_ascii=False)) PY )" echo "[1/3] request" print_json_or_raw "${request_json}" echo echo local raw_http_response raw_http_response="$(request_with_status "POST" "${mainland_base_url}/api/v1/ops/driver-actions/execute-resolved" "${request_json}")" local http_status="${raw_http_response##*$'\n'HTTP_STATUS=}" local response_json="${raw_http_response%$'\n'HTTP_STATUS=*}" echo "[2/3] raw response" printf '%s\n' "http_status=${http_status}" print_json_or_raw "${response_json}" echo echo echo "[3/3] execution result" python3 - <<'PY' "${response_json}" "${http_status}" import json import sys payload = json.loads(str(sys.argv[1] or "{}")) http_status = int(sys.argv[2] or 0) data = payload.get("data") or {} gate = data.get("gate") or {} execution_result = data.get("execution_result") or {} raw_code = payload.get("code", 1) if raw_code in (0, "0", False): normalized_code = 0 else: normalized_code = int(raw_code or 1) print(json.dumps({ "http_status": http_status, "code": normalized_code, "message": str(payload.get("message") or ""), "executed": bool(data.get("executed", False)), "selected_section": str(data.get("selected_section") or ""), "decision": str(gate.get("decision") or ""), "decision_label": str(gate.get("decision_label") or ""), "recommendation": str(gate.get("recommendation") or ""), "sequence_key": str(gate.get("sequence_key") or execution_result.get("sequence_key") or ""), "action_code": str(gate.get("action_code") or execution_result.get("action_code") or execution_result.get("driver_action_code") or ""), "handled": bool(execution_result.get("handled", False)), "mode": str(execution_result.get("mode") or ""), }, ensure_ascii=False, indent=2)) PY if [[ "${http_status}" -lt 200 || "${http_status}" -ge 300 ]]; then exit 1 fi } case "${COMMAND}" in help|-h|--help) usage ;; config) print_config "${1:-${OPS_CENTER_ACTIVE_PROFILE}}" ;; contracts) MAINLAND_BASE_URL="${1:-$(ops_center_resolve_mainland_api_base_url)}" CONTRACT_KEY="${2:-}" if [[ "${MAINLAND_BASE_URL}" != http://* && "${MAINLAND_BASE_URL}" != https://* ]]; then CONTRACT_KEY="${MAINLAND_BASE_URL}" MAINLAND_BASE_URL="$(ops_center_resolve_mainland_api_base_url)" fi show_ops_contracts "${MAINLAND_BASE_URL}" "${CONTRACT_KEY}" ;; env-audit) MAINLAND_BASE_URL="${1:-$(ops_center_resolve_mainland_api_base_url)}" exec bash "${SCRIPT_DIR}/check_env_drift.sh" "${MAINLAND_BASE_URL}" ;; runtime-refresh-check) MAINLAND_BASE_URL="${1:-$(ops_center_resolve_mainland_api_base_url)}" exec bash "${SCRIPT_DIR}/check_env_drift.sh" "${MAINLAND_BASE_URL}" ;; runtime-refresh-recover) MAINLAND_BASE_URL="${1:-$(ops_center_resolve_mainland_api_base_url)}" run_runtime_refresh_recover "${MAINLAND_BASE_URL}" ;; stack-diagnosis|stack-check) MAINLAND_BASE_URL="${1:-$(ops_center_resolve_mainland_api_base_url)}" VIEW_MODE="${2:-summary}" if [[ "${MAINLAND_BASE_URL}" != http://* && "${MAINLAND_BASE_URL}" != https://* ]]; then VIEW_MODE="${MAINLAND_BASE_URL}" MAINLAND_BASE_URL="$(ops_center_resolve_mainland_api_base_url)" fi exec bash "${SCRIPT_DIR}/check_ops_center_stack.sh" "${MAINLAND_BASE_URL}" "${VIEW_MODE}" ;; go-live-check) MAINLAND_BASE_URL="${1:-$(ops_center_resolve_mainland_api_base_url)}" OVERSEAS_BASE_URL="${2:-$(ops_center_resolve_overseas_api_base_url)}" VIEW_MODE="${3:-summary}" if [[ "${MAINLAND_BASE_URL}" != http://* && "${MAINLAND_BASE_URL}" != https://* ]]; then VIEW_MODE="${OVERSEAS_BASE_URL:-summary}" OVERSEAS_BASE_URL="${MAINLAND_BASE_URL}" MAINLAND_BASE_URL="$(ops_center_resolve_mainland_api_base_url)" fi exec bash "${SCRIPT_DIR}/check_go_live.sh" "${MAINLAND_BASE_URL}" "${OVERSEAS_BASE_URL}" "${VIEW_MODE}" ;; go-live-recover) MAINLAND_BASE_URL="${1:-$(ops_center_resolve_mainland_api_base_url)}" OVERSEAS_BASE_URL="$(normalize_optional_base_url_arg "${2:-}" "$(ops_center_resolve_overseas_api_base_url)")" if [[ "${MAINLAND_BASE_URL}" != http://* && "${MAINLAND_BASE_URL}" != https://* ]]; then OVERSEAS_BASE_URL="$(normalize_optional_base_url_arg "${MAINLAND_BASE_URL}" "$(ops_center_resolve_overseas_api_base_url)")" MAINLAND_BASE_URL="$(ops_center_resolve_mainland_api_base_url)" fi run_go_live_recover "${MAINLAND_BASE_URL}" "${OVERSEAS_BASE_URL}" ;; go-live-export) REPORT_DIR="${1:-${OPS_CENTER_REPORT_ROOT}/go-live-bundles/go-live-bundle-$(timestamp_now)}" MAINLAND_BASE_URL="${2:-$(ops_center_resolve_mainland_api_base_url)}" OVERSEAS_BASE_URL="$(normalize_optional_base_url_arg "${3:-}" "$(ops_center_resolve_overseas_api_base_url)")" if [[ "${REPORT_DIR}" == http://* || "${REPORT_DIR}" == https://* ]]; then OVERSEAS_BASE_URL="$(normalize_optional_base_url_arg "${MAINLAND_BASE_URL:-}" "$(ops_center_resolve_overseas_api_base_url)")" MAINLAND_BASE_URL="${REPORT_DIR}" REPORT_DIR="${OPS_CENTER_REPORT_ROOT}/go-live-bundles/go-live-bundle-$(timestamp_now)}" fi exec bash "${SCRIPT_DIR}/export_go_live_bundle.sh" "${REPORT_DIR}" "${MAINLAND_BASE_URL}" "${OVERSEAS_BASE_URL}" ;; go-live-review) TARGET_PATH="${1:-${OPS_CENTER_REPORT_ROOT}/go-live-bundles}" if [[ -d "${TARGET_PATH}" && ! -f "${TARGET_PATH}/manifest.json" ]]; then TARGET_PATH="$(find "${TARGET_PATH}" -mindepth 1 -maxdepth 1 -type d | sort | tail -n 1)" fi exec bash "${SCRIPT_DIR}/review_go_live_bundle.sh" "${TARGET_PATH}" ;; go-live-signoff) FIRST_ARG="${1:-}" SECOND_ARG="${2:-}" if [[ -n "${FIRST_ARG}" && -d "${FIRST_ARG}" && ! -f "${FIRST_ARG}/manifest.json" ]]; then FIRST_ARG="$(find "${FIRST_ARG}" -mindepth 1 -maxdepth 1 -type d | sort | tail -n 1)" fi exec bash "${SCRIPT_DIR}/check_go_live_signoff.sh" "${FIRST_ARG}" "${SECOND_ARG}" ;; stack-next) MAINLAND_BASE_URL="${1:-$(ops_center_resolve_mainland_api_base_url)}" STACK_MODE="${2:-preview}" CONFIRM_FLAG="${3:-}" REQUESTED_BY="${4:-$(ops_center_resolve_requested_by)}" if [[ "${MAINLAND_BASE_URL}" != http://* && "${MAINLAND_BASE_URL}" != https://* ]]; then REQUESTED_BY="${CONFIRM_FLAG:-$(ops_center_resolve_requested_by)}" CONFIRM_FLAG="${STACK_MODE:-}" STACK_MODE="${MAINLAND_BASE_URL}" MAINLAND_BASE_URL="$(ops_center_resolve_mainland_api_base_url)" fi if is_confirm_token "${STACK_MODE}" && [[ -z "${CONFIRM_FLAG}" ]]; then CONFIRM_FLAG="${STACK_MODE}" STACK_MODE="run" fi case "$(printf '%s' "${STACK_MODE}" | tr '[:upper:]' '[:lower:]')" in preview|show|resolve|"") preview_stack_next_entry "${MAINLAND_BASE_URL}" "${REQUESTED_BY}" ;; run|execute) run_stack_next_entry "${MAINLAND_BASE_URL}" "${CONFIRM_FLAG}" "${REQUESTED_BY}" ;; *) echo "stack-next mode must be preview or run" >&2 exit 1 ;; esac ;; release-package) REPO_ROOT="$(ops_center_repo_root)" SKIP_SMOKE="$(confirm_to_bool "${1:-}")" if [[ "${SKIP_SMOKE}" == "true" ]]; then DOMAINCHECK_SKIP_SMOKE_TEST=1 exec bash "${REPO_ROOT}/package_domain_release.sh" fi exec bash "${REPO_ROOT}/package_domain_release.sh" ;; release-verify) REPO_ROOT="$(ops_center_repo_root)" exec bash "${REPO_ROOT}/verify_domain_release.sh" "${1:-}" "${2:-}" ;; release-prepare) REPO_ROOT="$(ops_center_repo_root)" exec bash "${REPO_ROOT}/prepare_final_release.sh" ;; release-show) REPO_ROOT="$(ops_center_repo_root)" exec bash "${REPO_ROOT}/show_latest_release.sh" ;; release-launchpad) MAINLAND_BASE_URL="${1:-$(ops_center_resolve_mainland_api_base_url)}" CHANNEL="${2:-stable}" exec bash "${SCRIPT_DIR}/drive_release_hub.sh" "${MAINLAND_BASE_URL}" launchpad "${CHANNEL}" ;; release-preview) MAINLAND_BASE_URL="${1:-$(ops_center_resolve_mainland_api_base_url)}" exec bash "${SCRIPT_DIR}/drive_release_hub.sh" "${MAINLAND_BASE_URL}" preview-latest ;; release-preview-smart) MAINLAND_BASE_URL="${1:-$(ops_center_resolve_mainland_api_base_url)}" MODE="${2:-}" if [[ -z "${MODE}" && ( "${MAINLAND_BASE_URL}" == "worker" || "${MAINLAND_BASE_URL}" == "control" ) ]]; then MODE="${MAINLAND_BASE_URL}" MAINLAND_BASE_URL="$(ops_center_resolve_mainland_api_base_url)" fi if [[ "${MODE}" != "worker" && "${MODE}" != "control" ]]; then echo "mode must be worker or control" >&2 usage exit 1 fi exec bash "${SCRIPT_DIR}/drive_release_hub.sh" "${MAINLAND_BASE_URL}" preview-latest-smart "${MODE}" ;; release-import-latest) MAINLAND_BASE_URL="${1:-$(ops_center_resolve_mainland_api_base_url)}" exec bash "${SCRIPT_DIR}/drive_release_hub.sh" "${MAINLAND_BASE_URL}" create-latest-release ;; publish-latest) MAINLAND_BASE_URL="${1:-$(ops_center_resolve_mainland_api_base_url)}" MODE="${2:-}" CONFIRM_FLAG="${3:-}" SKIP_SMOKE_FLAG="${4:-}" if [[ -z "${MODE}" && ( "${MAINLAND_BASE_URL}" == "worker" || "${MAINLAND_BASE_URL}" == "control" ) ]]; then MODE="${MAINLAND_BASE_URL}" MAINLAND_BASE_URL="$(ops_center_resolve_mainland_api_base_url)" CONFIRM_FLAG="${2:-}" SKIP_SMOKE_FLAG="${3:-}" fi run_publish_latest "${MAINLAND_BASE_URL}" "${MODE}" "${CONFIRM_FLAG}" "${SKIP_SMOKE_FLAG}" ;; doctor-export) REPORT_DIR="${1:-${OPS_CENTER_REPORT_ROOT}/doctor-$(timestamp_now)}" MAINLAND_BASE_URL="${2:-$(ops_center_resolve_mainland_api_base_url)}" OVERSEAS_BASE_URL="$(normalize_optional_base_url_arg "${3:-}" "$(ops_center_resolve_overseas_api_base_url)")" run_doctor_report_export "${REPORT_DIR}" "${MAINLAND_BASE_URL}" "${OVERSEAS_BASE_URL}" ;; doctor-decision) FIRST_ARG="${1:-}" SECOND_ARG="${2:-}" run_doctor_decision "${FIRST_ARG}" "${SECOND_ARG}" ;; doctor) MAINLAND_BASE_URL="${1:-$(ops_center_resolve_mainland_api_base_url)}" OVERSEAS_BASE_URL="$(normalize_optional_base_url_arg "${2:-}" "$(ops_center_resolve_overseas_api_base_url)")" run_step "[doctor/0] ops center stack" bash "${SCRIPT_DIR}/check_ops_center_stack.sh" "${MAINLAND_BASE_URL}" summary run_step "[doctor/1] ops plane" bash "${SCRIPT_DIR}/check_ops_plane.sh" "${MAINLAND_BASE_URL}" run_step "[doctor/2] release hub" bash "${SCRIPT_DIR}/check_release_hub.sh" "${MAINLAND_BASE_URL}" run_step "[doctor/3] inspection" bash "${SCRIPT_DIR}/check_ops_inspection.sh" "${MAINLAND_BASE_URL}" run_step "[doctor/4] participation" bash "${SCRIPT_DIR}/check_worker_participation.sh" "${MAINLAND_BASE_URL}" if [[ -n "${OVERSEAS_BASE_URL}" ]]; then run_step "[doctor/5] temp topology" bash "${SCRIPT_DIR}/check_temp_topology.sh" "${MAINLAND_BASE_URL}" "${OVERSEAS_BASE_URL}" fi ;; ops-plane) MAINLAND_BASE_URL="${1:-$(ops_center_resolve_mainland_api_base_url)}" exec bash "${SCRIPT_DIR}/check_ops_plane.sh" "${MAINLAND_BASE_URL}" ;; inspection) MAINLAND_BASE_URL="${1:-$(ops_center_resolve_mainland_api_base_url)}" exec bash "${SCRIPT_DIR}/check_ops_inspection.sh" "${MAINLAND_BASE_URL}" ;; participation) MAINLAND_BASE_URL="${1:-$(ops_center_resolve_mainland_api_base_url)}" exec bash "${SCRIPT_DIR}/check_worker_participation.sh" "${MAINLAND_BASE_URL}" ;; topology) MAINLAND_BASE_URL="${1:-$(ops_center_resolve_mainland_api_base_url)}" OVERSEAS_BASE_URL="${2:-$(ops_center_resolve_overseas_api_base_url)}" exec bash "${SCRIPT_DIR}/check_temp_topology.sh" "${MAINLAND_BASE_URL}" "${OVERSEAS_BASE_URL}" ;; nodes) MAINLAND_BASE_URL="${1:-$(ops_center_resolve_mainland_api_base_url)}" show_ops_nodes "${MAINLAND_BASE_URL}" ;; stack-first-gap-handover) MAINLAND_BASE_URL="${1:-$(ops_center_resolve_mainland_api_base_url)}" if [[ "${MAINLAND_BASE_URL}" != http://* && "${MAINLAND_BASE_URL}" != https://* ]]; then MAINLAND_BASE_URL="$(ops_center_resolve_mainland_api_base_url)" fi show_stack_first_gap_handover "${MAINLAND_BASE_URL}" ;; node-bind-ssh) ARG1="${1:-}" ARG2="${2:-}" ARG3="${3:-}" ARG4="${4:-}" ARG5="${5:-}" ARG6="${6:-}" if [[ -n "${ARG1}" && ( "${ARG1}" == http://* || "${ARG1}" == https://* ) ]]; then MAINLAND_BASE_URL="${ARG1}" NODE_CODE="${ARG2:-}" SSH_HOST="${ARG3:-}" SSH_USER="${ARG4:-}" SSH_PORT="${ARG5:-22}" TITLE="${ARG6:-}" else MAINLAND_BASE_URL="$(ops_center_resolve_mainland_api_base_url)" NODE_CODE="${ARG1:-}" SSH_HOST="${ARG2:-}" SSH_USER="${ARG3:-}" SSH_PORT="${ARG4:-22}" TITLE="${ARG5:-}" fi bind_node_ssh_profile "${MAINLAND_BASE_URL}" "${NODE_CODE}" "${SSH_HOST}" "${SSH_USER}" "${SSH_PORT}" "${TITLE}" ;; node-handover) MAINLAND_BASE_URL="${1:-$(ops_center_resolve_mainland_api_base_url)}" NODE_CODE="${2:-}" if [[ "${MAINLAND_BASE_URL}" != http://* && "${MAINLAND_BASE_URL}" != https://* ]]; then NODE_CODE="${MAINLAND_BASE_URL}" MAINLAND_BASE_URL="$(ops_center_resolve_mainland_api_base_url)" fi show_node_handover "${MAINLAND_BASE_URL}" "${NODE_CODE}" ;; node-onboarding) MAINLAND_BASE_URL="${1:-$(ops_center_resolve_mainland_api_base_url)}" NODE_CODE="${2:-}" if [[ "${MAINLAND_BASE_URL}" != http://* && "${MAINLAND_BASE_URL}" != https://* ]]; then NODE_CODE="${MAINLAND_BASE_URL}" MAINLAND_BASE_URL="$(ops_center_resolve_mainland_api_base_url)" fi show_node_onboarding "${MAINLAND_BASE_URL}" "${NODE_CODE}" ;; node-recover) MAINLAND_BASE_URL="${1:-$(ops_center_resolve_mainland_api_base_url)}" NODE_CODE="${2:-}" CONFIRM_FLAG="${3:-}" REQUESTED_BY="${4:-cli}" if [[ "${MAINLAND_BASE_URL}" != http://* && "${MAINLAND_BASE_URL}" != https://* ]]; then REQUESTED_BY="${CONFIRM_FLAG:-cli}" CONFIRM_FLAG="${NODE_CODE:-}" NODE_CODE="${MAINLAND_BASE_URL}" MAINLAND_BASE_URL="$(ops_center_resolve_mainland_api_base_url)" fi recover_node_onboarding "${MAINLAND_BASE_URL}" "${NODE_CODE}" "${CONFIRM_FLAG}" "${REQUESTED_BY}" ;; node-bootstrap-preview) MAINLAND_BASE_URL="${1:-$(ops_center_resolve_mainland_api_base_url)}" NODE_CODE="${2:-}" REQUESTED_BY="${3:-cli}" if [[ "${MAINLAND_BASE_URL}" != http://* && "${MAINLAND_BASE_URL}" != https://* ]]; then REQUESTED_BY="${NODE_CODE:-cli}" NODE_CODE="${MAINLAND_BASE_URL}" MAINLAND_BASE_URL="$(ops_center_resolve_mainland_api_base_url)" fi show_node_bootstrap_preview "${MAINLAND_BASE_URL}" "${NODE_CODE}" "${REQUESTED_BY}" ;; node-bootstrap-run) MAINLAND_BASE_URL="${1:-$(ops_center_resolve_mainland_api_base_url)}" NODE_CODE="${2:-}" REQUESTED_BY="${3:-cli}" if [[ "${MAINLAND_BASE_URL}" != http://* && "${MAINLAND_BASE_URL}" != https://* ]]; then REQUESTED_BY="${NODE_CODE:-cli}" NODE_CODE="${MAINLAND_BASE_URL}" MAINLAND_BASE_URL="$(ops_center_resolve_mainland_api_base_url)" fi run_node_bootstrap "${MAINLAND_BASE_URL}" "${NODE_CODE}" "${REQUESTED_BY}" ;; node-acceptance-plan) MAINLAND_BASE_URL="${1:-$(ops_center_resolve_mainland_api_base_url)}" NODE_CODE="${2:-}" REQUESTED_BY="${3:-cli}" if [[ "${MAINLAND_BASE_URL}" != http://* && "${MAINLAND_BASE_URL}" != https://* ]]; then REQUESTED_BY="${NODE_CODE:-cli}" NODE_CODE="${MAINLAND_BASE_URL}" MAINLAND_BASE_URL="$(ops_center_resolve_mainland_api_base_url)" fi show_node_acceptance_plan "${MAINLAND_BASE_URL}" "${NODE_CODE}" "${REQUESTED_BY}" ;; node-acceptance-run) MAINLAND_BASE_URL="${1:-$(ops_center_resolve_mainland_api_base_url)}" NODE_CODE="${2:-}" REQUESTED_BY="${3:-cli}" if [[ "${MAINLAND_BASE_URL}" != http://* && "${MAINLAND_BASE_URL}" != https://* ]]; then REQUESTED_BY="${NODE_CODE:-cli}" NODE_CODE="${MAINLAND_BASE_URL}" MAINLAND_BASE_URL="$(ops_center_resolve_mainland_api_base_url)" fi run_node_acceptance "${MAINLAND_BASE_URL}" "${NODE_CODE}" "${REQUESTED_BY}" ;; scene-node-log) MAINLAND_BASE_URL="${1:-$(ops_center_resolve_mainland_api_base_url)}" NODE_CODE="${2:-}" LIMIT="${3:-80}" MODE="${4:-}" if [[ "${MAINLAND_BASE_URL}" != http://* && "${MAINLAND_BASE_URL}" != https://* ]]; then MODE="${LIMIT:-}" LIMIT="${NODE_CODE:-80}" NODE_CODE="${MAINLAND_BASE_URL}" MAINLAND_BASE_URL="$(ops_center_resolve_mainland_api_base_url)" fi show_node_scene_log "${MAINLAND_BASE_URL}" "${NODE_CODE}" "${LIMIT}" "${MODE}" ;; log-sync-check) MAINLAND_BASE_URL="${1:-$(ops_center_resolve_mainland_api_base_url)}" print_log_sync_post_check "${MAINLAND_BASE_URL}" ;; log-sync-recover) MAINLAND_BASE_URL="${1:-$(ops_center_resolve_mainland_api_base_url)}" MODE_ARG="${2:-key}" CONFIRM_FLAG="${3:-}" REQUESTED_BY="${4:-$(ops_center_resolve_requested_by)}" if [[ "${MAINLAND_BASE_URL}" != http://* && "${MAINLAND_BASE_URL}" != https://* ]]; then REQUESTED_BY="${CONFIRM_FLAG:-$(ops_center_resolve_requested_by)}" CONFIRM_FLAG="${MODE_ARG:-}" MODE_ARG="${MAINLAND_BASE_URL:-key}" MAINLAND_BASE_URL="$(ops_center_resolve_mainland_api_base_url)" fi run_log_sync_recover_flow "${MAINLAND_BASE_URL}" "${MODE_ARG}" "${CONFIRM_FLAG}" "${REQUESTED_BY}" ;; agent-gap-check) MAINLAND_BASE_URL="${1:-$(ops_center_resolve_mainland_api_base_url)}" show_agent_gap_check "${MAINLAND_BASE_URL}" ;; agent-gap-recover) MAINLAND_BASE_URL="${1:-$(ops_center_resolve_mainland_api_base_url)}" CONFIRM_FLAG="${2:-}" REQUESTED_BY="${3:-$(ops_center_resolve_requested_by)}" if [[ "${MAINLAND_BASE_URL}" != http://* && "${MAINLAND_BASE_URL}" != https://* ]]; then REQUESTED_BY="${CONFIRM_FLAG:-$(ops_center_resolve_requested_by)}" CONFIRM_FLAG="${MAINLAND_BASE_URL:-}" MAINLAND_BASE_URL="$(ops_center_resolve_mainland_api_base_url)" fi run_agent_gap_recover_flow "${MAINLAND_BASE_URL}" "${CONFIRM_FLAG}" "${REQUESTED_BY}" ;; agent-gap-export) ARG1="${1:-}" ARG2="${2:-}" ARG3="${3:-$(ops_center_resolve_requested_by)}" if [[ -n "${ARG1}" && "${ARG1}" != http://* && "${ARG1}" != https://* ]]; then REPORT_DIR="${ARG1}" MAINLAND_BASE_URL="${ARG2:-$(ops_center_resolve_mainland_api_base_url)}" REQUESTED_BY="${ARG3:-$(ops_center_resolve_requested_by)}" else REPORT_DIR="${OPS_CENTER_REPORT_ROOT}/agent-gap-export-$(timestamp_now)" MAINLAND_BASE_URL="${ARG1:-$(ops_center_resolve_mainland_api_base_url)}" REQUESTED_BY="${ARG2:-$(ops_center_resolve_requested_by)}" fi export_agent_gap_bundle "${REPORT_DIR}" "${MAINLAND_BASE_URL}" "${REQUESTED_BY}" ;; node-bootstrap-plan) ARG1="${1:-}" ARG2="${2:-}" ARG3="${3:-}" ARG4="${4:-}" ARG5="${5:-}" ARG6="${6:-}" if [[ -n "${ARG1}" && ( "${ARG1}" == http://* || "${ARG1}" == https://* ) ]]; then MAINLAND_BASE_URL="${ARG1}" NODE_CODE="${ARG2:-}" ISSUED_BY="${ARG3:-cli/ops-center/node-bootstrap-plan}" EXPIRES_IN_HOURS="${ARG4:-72}" CONTROL_PLANE_BASE_URL="${ARG5:-${MAINLAND_BASE_URL}}" ROOT_DIR="${ARG6:-/opt/domaincheck}" else MAINLAND_BASE_URL="$(ops_center_resolve_mainland_api_base_url)" NODE_CODE="${ARG1:-}" ISSUED_BY="${ARG2:-cli/ops-center/node-bootstrap-plan}" EXPIRES_IN_HOURS="${ARG3:-72}" CONTROL_PLANE_BASE_URL="${ARG4:-${MAINLAND_BASE_URL}}" ROOT_DIR="${ARG5:-/opt/domaincheck}" fi build_node_handover_bootstrap_plan \ "${MAINLAND_BASE_URL}" \ "${NODE_CODE}" \ "${ISSUED_BY}" \ "${EXPIRES_IN_HOURS}" \ "${CONTROL_PLANE_BASE_URL}" \ "${ROOT_DIR}" ;; queue-status) MAINLAND_BASE_URL="${1:-$(ops_center_resolve_mainland_api_base_url)}" NODE_CODE="${2:-}" if [[ "${MAINLAND_BASE_URL}" != http://* && "${MAINLAND_BASE_URL}" != https://* ]]; then NODE_CODE="${MAINLAND_BASE_URL}" MAINLAND_BASE_URL="$(ops_center_resolve_mainland_api_base_url)" fi show_delivery_queue_status "${MAINLAND_BASE_URL}" "${NODE_CODE}" ;; queue-records) MAINLAND_BASE_URL="${1:-$(ops_center_resolve_mainland_api_base_url)}" NODE_CODE="${2:-}" STATE_FILTER="${3:-}" if [[ "${MAINLAND_BASE_URL}" != http://* && "${MAINLAND_BASE_URL}" != https://* ]]; then STATE_FILTER="${NODE_CODE:-}" NODE_CODE="${MAINLAND_BASE_URL}" MAINLAND_BASE_URL="$(ops_center_resolve_mainland_api_base_url)" fi show_delivery_queue_records "${MAINLAND_BASE_URL}" "${NODE_CODE}" "${STATE_FILTER}" ;; queue-flush) MAINLAND_BASE_URL="${1:-$(ops_center_resolve_mainland_api_base_url)}" NODE_CODE="${2:-}" LIMIT="${3:-20}" REQUESTED_BY="${4:-$(ops_center_resolve_requested_by)}" if [[ "${MAINLAND_BASE_URL}" != http://* && "${MAINLAND_BASE_URL}" != https://* ]]; then REQUESTED_BY="${LIMIT:-$(ops_center_resolve_requested_by)}" LIMIT="${NODE_CODE:-20}" NODE_CODE="${MAINLAND_BASE_URL}" MAINLAND_BASE_URL="$(ops_center_resolve_mainland_api_base_url)" fi BODY_JSON="$(python3 - <<'PY' "${LIMIT}" "${REQUESTED_BY}" import json import sys print(json.dumps({ "limit": max(1, min(int(sys.argv[1] or 20), 200)), "requested_by": str(sys.argv[2] or "cli/ops-center/queue-flush"), }, ensure_ascii=False)) PY )" run_delivery_queue_action "${MAINLAND_BASE_URL}" "${NODE_CODE}" "/delivery-queue/flush" "${BODY_JSON}" ;; queue-replay) MAINLAND_BASE_URL="${1:-$(ops_center_resolve_mainland_api_base_url)}" NODE_CODE="${2:-}" LIMIT="${3:-20}" REQUESTED_BY="${4:-$(ops_center_resolve_requested_by)}" if [[ "${MAINLAND_BASE_URL}" != http://* && "${MAINLAND_BASE_URL}" != https://* ]]; then REQUESTED_BY="${LIMIT:-$(ops_center_resolve_requested_by)}" LIMIT="${NODE_CODE:-20}" NODE_CODE="${MAINLAND_BASE_URL}" MAINLAND_BASE_URL="$(ops_center_resolve_mainland_api_base_url)" fi BODY_JSON="$(python3 - <<'PY' "${LIMIT}" "${REQUESTED_BY}" import json import sys print(json.dumps({ "limit": max(1, min(int(sys.argv[1] or 20), 200)), "flush_after_replay": True, "reason": "cli quick replay from drive_ops_center.sh", "requested_by": str(sys.argv[2] or "cli/ops-center/queue-replay"), }, ensure_ascii=False)) PY )" run_delivery_queue_action "${MAINLAND_BASE_URL}" "${NODE_CODE}" "/delivery-queue/replay" "${BODY_JSON}" ;; queue-replay-record) MAINLAND_BASE_URL="${1:-$(ops_center_resolve_mainland_api_base_url)}" NODE_CODE="${2:-}" RECORD_ID="${3:-}" REQUESTED_BY="${4:-$(ops_center_resolve_requested_by)}" if [[ "${MAINLAND_BASE_URL}" != http://* && "${MAINLAND_BASE_URL}" != https://* ]]; then REQUESTED_BY="${RECORD_ID:-$(ops_center_resolve_requested_by)}" RECORD_ID="${NODE_CODE:-}" NODE_CODE="${MAINLAND_BASE_URL}" MAINLAND_BASE_URL="$(ops_center_resolve_mainland_api_base_url)" fi if [[ -z "${RECORD_ID}" ]]; then echo "record_id is required" >&2 exit 1 fi BODY_JSON="$(python3 - <<'PY' "${REQUESTED_BY}" import json import sys print(json.dumps({ "limit": 1, "flush_after_replay": True, "reason": "cli replay visible head record", "requested_by": str(sys.argv[1] or "cli/ops-center/queue-replay-record"), }, ensure_ascii=False)) PY )" run_delivery_queue_action "${MAINLAND_BASE_URL}" "${NODE_CODE}" "/delivery-queue/records/${RECORD_ID}/replay" "${BODY_JSON}" ;; queue-discard-record) MAINLAND_BASE_URL="${1:-$(ops_center_resolve_mainland_api_base_url)}" NODE_CODE="${2:-}" RECORD_ID="${3:-}" DISCARD_REASON="${4:-}" REQUESTED_BY="${5:-$(ops_center_resolve_requested_by)}" if [[ "${MAINLAND_BASE_URL}" != http://* && "${MAINLAND_BASE_URL}" != https://* ]]; then REQUESTED_BY="${DISCARD_REASON:-$(ops_center_resolve_requested_by)}" DISCARD_REASON="${RECORD_ID:-}" RECORD_ID="${NODE_CODE:-}" NODE_CODE="${MAINLAND_BASE_URL}" MAINLAND_BASE_URL="$(ops_center_resolve_mainland_api_base_url)" fi if [[ -z "${RECORD_ID}" || -z "${DISCARD_REASON}" ]]; then echo "record_id and reason are required" >&2 exit 1 fi BODY_JSON="$(python3 - <<'PY' "${DISCARD_REASON}" "${REQUESTED_BY}" import json import sys print(json.dumps({ "limit": 1, "reason": str(sys.argv[1] or "").strip(), "discarded_by": "cli", "requested_by": str(sys.argv[2] or "cli/ops-center/queue-discard-record"), }, ensure_ascii=False)) PY )" run_delivery_queue_action "${MAINLAND_BASE_URL}" "${NODE_CODE}" "/delivery-queue/records/${RECORD_ID}/discard" "${BODY_JSON}" ;; activity-stream) if [[ $# -gt 0 && ( "$1" == http://* || "$1" == https://* ) ]]; then MAINLAND_BASE_URL="$1" shift || true else MAINLAND_BASE_URL="$(ops_center_resolve_mainland_api_base_url)" fi show_activity_stream "${MAINLAND_BASE_URL}" "$@" ;; activity-preview) MAINLAND_BASE_URL="${1:-$(ops_center_resolve_mainland_api_base_url)}" ACTIVITY_KEY="${2:-}" REQUESTED_BY="${3:-$(ops_center_resolve_requested_by)}" if [[ "${MAINLAND_BASE_URL}" != http://* && "${MAINLAND_BASE_URL}" != https://* ]]; then ACTIVITY_KEY="${MAINLAND_BASE_URL}" MAINLAND_BASE_URL="$(ops_center_resolve_mainland_api_base_url)" REQUESTED_BY="${2:-$(ops_center_resolve_requested_by)}" fi preview_activity_item "${MAINLAND_BASE_URL}" "${ACTIVITY_KEY}" "${REQUESTED_BY}" ;; driver-feed) MAINLAND_BASE_URL="${1:-$(ops_center_resolve_mainland_api_base_url)}" RAW_HTTP_RESPONSE="$(request_with_status "GET" "${MAINLAND_BASE_URL}/api/v1/ops/driver-feed")" HTTP_STATUS="${RAW_HTTP_RESPONSE##*$'\n'HTTP_STATUS=}" RESPONSE_JSON="${RAW_HTTP_RESPONSE%$'\n'HTTP_STATUS=*}" RESPONSE_JSON_FILE="$(mktemp)" printf '%s' "${RESPONSE_JSON}" > "${RESPONSE_JSON_FILE}" echo "[1/2] raw response" printf '%s\n' "http_status=${HTTP_STATUS}" python3 - <<'PY' "${RESPONSE_JSON_FILE}" import json import sys with open(sys.argv[1], "r", encoding="utf-8") as fh: payload = json.loads(fh.read() or "{}") print(json.dumps(payload, ensure_ascii=False, indent=2)) PY echo echo "[2/2] condensed summary" python3 - <<'PY' "${RESPONSE_JSON_FILE}" "${HTTP_STATUS}" import json import sys with open(sys.argv[1], "r", encoding="utf-8") as fh: payload = json.loads(fh.read() or "{}") http_status = int(sys.argv[2] or 0) data = payload.get("data") or {} summary = data.get("summary") or {} entries = list(data.get("entries") or []) activity_focus = list(data.get("activity_focus") or []) top_recommendation = data.get("top_recommendation") or {} scene_log_observation = data.get("scene_log_observation") or {} contract_navigation = data.get("contract_navigation") or {} print(json.dumps({ "http_status": http_status, "headline": str(data.get("headline") or ""), "generated_at": str(data.get("generated_at") or ""), "contract_navigation": { "primary_contract_key": str(contract_navigation.get("primary_contract_key") or ""), "contract_keys": list(contract_navigation.get("contract_keys") or []), "detail_endpoint_pattern": str(contract_navigation.get("detail_endpoint_pattern") or ""), }, "top_recommendation": { "key": str(top_recommendation.get("key") or ""), "kind": str(top_recommendation.get("kind") or ""), "lane": str(top_recommendation.get("lane") or ""), "title": str(top_recommendation.get("title") or ""), "primary_contract_key": str((top_recommendation.get("contract_navigation") or {}).get("primary_contract_key") or ""), "contract_keys": list((top_recommendation.get("contract_navigation") or {}).get("contract_keys") or []), }, "summary": { "total": int(summary.get("total", 0) or 0), "do_now_total": int(summary.get("do_now_total", 0) or 0), "standard_path_total": int(summary.get("standard_path_total", 0) or 0), "publish_ready": bool(summary.get("publish_ready", False)), "publish_status": str(summary.get("publish_status") or ""), "publish_status_label": str(summary.get("publish_status_label") or ""), "launchpad_recommended_target_node_code": str(summary.get("launchpad_recommended_target_node_code") or ""), "launchpad_recommended_recovery_label": str(summary.get("launchpad_recommended_recovery_label") or ""), "launchpad_recommended_recovery_summary": str(summary.get("launchpad_recommended_recovery_summary") or ""), "launchpad_onboarding_bootstrap_pending_nodes": int(summary.get("launchpad_onboarding_bootstrap_pending_nodes", 0) or 0), "launchpad_onboarding_acceptance_ready_nodes": int(summary.get("launchpad_onboarding_acceptance_ready_nodes", 0) or 0), "scene_log_status": str(summary.get("scene_log_status") or ""), "kind_counts": dict(summary.get("kind_counts") or {}), }, "scene_log_observation": { "status": str(scene_log_observation.get("status") or ""), "status_label": str(scene_log_observation.get("status_label") or ""), "summary": str(scene_log_observation.get("summary") or ""), "target_node_code": str(scene_log_observation.get("target_node_code") or ""), "participating_node_count": int(scene_log_observation.get("participating_node_count", 0) or 0), "covered_participating_node_count": int(scene_log_observation.get("covered_participating_node_count", 0) or 0), "line_count": int(scene_log_observation.get("line_count", 0) or 0), "focus_ref": scene_log_observation.get("focus_ref") or {}, "primary_contract_key": str((scene_log_observation.get("contract_navigation") or {}).get("primary_contract_key") or ""), "contract_keys": list((scene_log_observation.get("contract_navigation") or {}).get("contract_keys") or []), }, "entries": [ { "key": str(item.get("key") or ""), "kind": str(item.get("kind") or ""), "lane": str(item.get("lane") or ""), "title": str(item.get("title") or ""), "status": str(item.get("status") or ""), "primary_label": str(item.get("primary_label") or ""), "secondary_label": str(item.get("secondary_label") or ""), "primary_contract_key": str((item.get("contract_navigation") or {}).get("primary_contract_key") or ""), "contract_keys": list((item.get("contract_navigation") or {}).get("contract_keys") or []), } for item in entries ], "activity_focus": [ { "key": str(item.get("key") or ""), "title": str(item.get("title") or ""), "status": str(item.get("status") or ""), "primary_contract_key": str((item.get("contract_navigation") or {}).get("primary_contract_key") or ""), "contract_keys": list((item.get("contract_navigation") or {}).get("contract_keys") or []), } for item in activity_focus ], }, ensure_ascii=False, indent=2)) PY rm -f "${RESPONSE_JSON_FILE}" ;; driver-focus-preview) MAINLAND_BASE_URL="${1:-$(ops_center_resolve_mainland_api_base_url)}" ENTRY_KEY="${2:-}" REQUESTED_BY="${3:-$(ops_center_resolve_requested_by)}" if [[ "${MAINLAND_BASE_URL}" != http://* && "${MAINLAND_BASE_URL}" != https://* ]]; then ENTRY_KEY="${MAINLAND_BASE_URL}" MAINLAND_BASE_URL="$(ops_center_resolve_mainland_api_base_url)" REQUESTED_BY="${2:-$(ops_center_resolve_requested_by)}" fi preview_driver_focus_entry "${MAINLAND_BASE_URL}" "${ENTRY_KEY}" "${REQUESTED_BY}" ;; driver-focus-run) MAINLAND_BASE_URL="${1:-$(ops_center_resolve_mainland_api_base_url)}" ENTRY_KEY="${2:-}" SECONDARY_FLAG="${3:-primary}" CONFIRM_FLAG="${4:-}" REQUESTED_BY="${5:-$(ops_center_resolve_requested_by)}" if [[ "${MAINLAND_BASE_URL}" != http://* && "${MAINLAND_BASE_URL}" != https://* ]]; then ENTRY_KEY="${MAINLAND_BASE_URL}" MAINLAND_BASE_URL="$(ops_center_resolve_mainland_api_base_url)" SECONDARY_FLAG="${2:-primary}" CONFIRM_FLAG="${3:-}" REQUESTED_BY="${4:-$(ops_center_resolve_requested_by)}" fi if is_secondary_token "${ENTRY_KEY}"; then REQUESTED_BY="${CONFIRM_FLAG:-$(ops_center_resolve_requested_by)}" CONFIRM_FLAG="${SECONDARY_FLAG:-}" SECONDARY_FLAG="${ENTRY_KEY}" ENTRY_KEY="" fi if is_confirm_token "${SECONDARY_FLAG}" && [[ -z "${CONFIRM_FLAG}" ]]; then CONFIRM_FLAG="${SECONDARY_FLAG}" SECONDARY_FLAG="primary" fi run_driver_focus_entry "${MAINLAND_BASE_URL}" "${ENTRY_KEY}" "${SECONDARY_FLAG}" "${CONFIRM_FLAG}" "${REQUESTED_BY}" ;; codex-brief) MAINLAND_BASE_URL="${1:-$(ops_center_resolve_mainland_api_base_url)}" RAW_HTTP_RESPONSE="$( request_with_status_custom \ "GET" \ "${MAINLAND_BASE_URL}/api/v1/ops/codex-brief" \ "" \ "${OPS_CENTER_CURL_CONNECT_TIMEOUT}" \ "${OPS_CENTER_CURL_MAX_TIME_CODEX_BRIEF}" )" HTTP_STATUS="${RAW_HTTP_RESPONSE##*$'\n'HTTP_STATUS=}" RESPONSE_JSON="${RAW_HTTP_RESPONSE%$'\n'HTTP_STATUS=*}" RESPONSE_JSON_FILE="$(mktemp)" printf '%s' "${RESPONSE_JSON}" > "${RESPONSE_JSON_FILE}" echo "[1/2] raw response" printf '%s\n' "http_status=${HTTP_STATUS}" python3 - <<'PY' "${RESPONSE_JSON_FILE}" import json import sys with open(sys.argv[1], "r", encoding="utf-8") as fh: payload = json.loads(fh.read() or "{}") print(json.dumps(payload, ensure_ascii=False, indent=2)) PY echo echo "[2/2] condensed summary" python3 - <<'PY' "${RESPONSE_JSON_FILE}" "${HTTP_STATUS}" import json import sys with open(sys.argv[1], "r", encoding="utf-8") as fh: payload = json.loads(fh.read() or "{}") http_status = int(sys.argv[2] or 0) data = payload.get("data") or {} summary = data.get("summary") or {} focus = data.get("focus") or {} entries = list(data.get("entries") or []) activity_focus = list(data.get("activity_focus") or []) scene_log_observation = data.get("scene_log_observation") or {} contract_navigation = data.get("contract_navigation") or {} print(json.dumps({ "http_status": http_status, "headline": str(data.get("headline") or ""), "generated_at": str(data.get("generated_at") or ""), "contract_navigation": { "primary_contract_key": str(contract_navigation.get("primary_contract_key") or ""), "contract_keys": list(contract_navigation.get("contract_keys") or []), "detail_endpoint_pattern": str(contract_navigation.get("detail_endpoint_pattern") or ""), }, "focus": { "entry_key": str(focus.get("entry_key") or ""), "title": str(focus.get("title") or ""), "recommendation": str(focus.get("recommendation") or ""), "status_text": str(focus.get("status_text") or ""), "target_method": str(focus.get("target_method") or ""), "target_api": str(focus.get("target_api") or ""), "focus_ref": focus.get("focus_ref") or {}, "primary_focus_ref": focus.get("primary_focus_ref") or {}, "secondary_focus_ref": focus.get("secondary_focus_ref") or {}, "primary_contract_key": str((focus.get("contract_navigation") or {}).get("primary_contract_key") or ""), "contract_keys": list((focus.get("contract_navigation") or {}).get("contract_keys") or []), }, "summary": { "total": int(summary.get("total", 0) or 0), "go_live_status": str(summary.get("go_live_status") or ""), "publish_ready": bool(summary.get("publish_ready", False)), "publish_status": str(summary.get("publish_status") or ""), "publish_status_label": str(summary.get("publish_status_label") or ""), "launchpad_recommended_target_node_code": str(summary.get("launchpad_recommended_target_node_code") or ""), "launchpad_recommended_recovery_label": str(summary.get("launchpad_recommended_recovery_label") or ""), "launchpad_recommended_recovery_summary": str(summary.get("launchpad_recommended_recovery_summary") or ""), "launchpad_onboarding_bootstrap_pending_nodes": int(summary.get("launchpad_onboarding_bootstrap_pending_nodes", 0) or 0), "launchpad_onboarding_acceptance_ready_nodes": int(summary.get("launchpad_onboarding_acceptance_ready_nodes", 0) or 0), "auto_execute_total": int(summary.get("auto_execute_total", 0) or 0), "confirm_then_execute_total": int(summary.get("confirm_then_execute_total", 0) or 0), "resolve_first_total": int(summary.get("resolve_first_total", 0) or 0), "open_ui_total": int(summary.get("open_ui_total", 0) or 0), "blocked_total": int(summary.get("blocked_total", 0) or 0), "automation_level_counts": dict(summary.get("automation_level_counts") or {}), }, "scene_log_observation": { "status": str(scene_log_observation.get("status") or ""), "status_label": str(scene_log_observation.get("status_label") or ""), "summary": str(scene_log_observation.get("summary") or ""), "target_node_code": str(scene_log_observation.get("target_node_code") or ""), "participating_node_count": int(scene_log_observation.get("participating_node_count", 0) or 0), "covered_participating_node_count": int(scene_log_observation.get("covered_participating_node_count", 0) or 0), "line_count": int(scene_log_observation.get("line_count", 0) or 0), "focus_ref": scene_log_observation.get("focus_ref") or {}, "primary_contract_key": str((scene_log_observation.get("contract_navigation") or {}).get("primary_contract_key") or ""), "contract_keys": list((scene_log_observation.get("contract_navigation") or {}).get("contract_keys") or []), }, "entries": [ { "key": str(item.get("key") or ""), "kind": str(item.get("kind") or ""), "title": str(item.get("title") or ""), "executor_kind": str(item.get("executor_kind") or ""), "recommendation": str((item.get("autopilot") or {}).get("recommendation") or ""), "automation_level": str((item.get("autopilot") or {}).get("automation_level") or ""), "executor_mode_hint": str((item.get("autopilot") or {}).get("executor_mode_hint") or ""), "primary_action_code": str(item.get("primary_action_code") or ""), "focus_ref": item.get("focus_ref") or {}, "primary_focus_ref": item.get("primary_focus_ref") or {}, "secondary_focus_ref": item.get("secondary_focus_ref") or {}, "primary_contract_key": str((item.get("contract_navigation") or {}).get("primary_contract_key") or ""), "contract_keys": list((item.get("contract_navigation") or {}).get("contract_keys") or []), } for item in entries ], "activity_focus": [ { "key": str(item.get("key") or ""), "title": str(item.get("title") or ""), "status": str(item.get("status") or ""), "primary_contract_key": str((item.get("contract_navigation") or {}).get("primary_contract_key") or ""), "contract_keys": list((item.get("contract_navigation") or {}).get("contract_keys") or []), } for item in activity_focus ], }, ensure_ascii=False, indent=2)) PY rm -f "${RESPONSE_JSON_FILE}" ;; codex-focus-preview) MAINLAND_BASE_URL="${1:-$(ops_center_resolve_mainland_api_base_url)}" ENTRY_KEY="${2:-}" REQUESTED_BY="${3:-$(ops_center_resolve_requested_by)}" if [[ "${MAINLAND_BASE_URL}" != http://* && "${MAINLAND_BASE_URL}" != https://* ]]; then ENTRY_KEY="${MAINLAND_BASE_URL}" MAINLAND_BASE_URL="$(ops_center_resolve_mainland_api_base_url)" REQUESTED_BY="${2:-$(ops_center_resolve_requested_by)}" fi preview_codex_focus_entry "${MAINLAND_BASE_URL}" "${ENTRY_KEY}" "${REQUESTED_BY}" ;; codex-focus-run) MAINLAND_BASE_URL="${1:-$(ops_center_resolve_mainland_api_base_url)}" ENTRY_KEY="${2:-}" CONFIRM_FLAG="${3:-}" REQUESTED_BY="${4:-$(ops_center_resolve_requested_by)}" if [[ "${MAINLAND_BASE_URL}" != http://* && "${MAINLAND_BASE_URL}" != https://* ]]; then ENTRY_KEY="${MAINLAND_BASE_URL}" MAINLAND_BASE_URL="$(ops_center_resolve_mainland_api_base_url)" CONFIRM_FLAG="${2:-}" REQUESTED_BY="${3:-$(ops_center_resolve_requested_by)}" fi run_codex_focus_entry "${MAINLAND_BASE_URL}" "${ENTRY_KEY}" "${CONFIRM_FLAG}" "${REQUESTED_BY}" ;; release-hub) MAINLAND_BASE_URL="${1:-$(ops_center_resolve_mainland_api_base_url)}" shift || true exec bash "${SCRIPT_DIR}/drive_release_hub.sh" "${MAINLAND_BASE_URL}" "$@" ;; driver-preview) MAINLAND_BASE_URL="${1:-$(ops_center_resolve_mainland_api_base_url)}" ACTION_CODE="${2:-}" ACTION_PAYLOAD_JSON="${3:-}" REQUESTED_BY="${4:-$(ops_center_resolve_requested_by)}" if [[ -z "${ACTION_CODE}" && "${MAINLAND_BASE_URL}" != http://* && "${MAINLAND_BASE_URL}" != https://* ]]; then ACTION_CODE="${MAINLAND_BASE_URL}" MAINLAND_BASE_URL="$(ops_center_resolve_mainland_api_base_url)" ACTION_PAYLOAD_JSON="${2:-}" REQUESTED_BY="${3:-$(ops_center_resolve_requested_by)}" fi if [[ -z "${ACTION_PAYLOAD_JSON}" ]]; then ACTION_PAYLOAD_JSON='{}' fi if [[ -z "${ACTION_CODE}" ]]; then usage exit 1 fi exec bash "${SCRIPT_DIR}/drive_ops_action.sh" "${MAINLAND_BASE_URL}" preview "${ACTION_CODE}" "${ACTION_PAYLOAD_JSON}" "${REQUESTED_BY}" ;; driver-resolve) MAINLAND_BASE_URL="${1:-$(ops_center_resolve_mainland_api_base_url)}" ACTION_CODE="${2:-}" ACTION_PAYLOAD_JSON="${3:-}" REQUESTED_BY="${4:-$(ops_center_resolve_requested_by)}" if [[ -z "${ACTION_CODE}" && "${MAINLAND_BASE_URL}" != http://* && "${MAINLAND_BASE_URL}" != https://* ]]; then ACTION_CODE="${MAINLAND_BASE_URL}" MAINLAND_BASE_URL="$(ops_center_resolve_mainland_api_base_url)" ACTION_PAYLOAD_JSON="${2:-}" REQUESTED_BY="${3:-$(ops_center_resolve_requested_by)}" fi if [[ -z "${ACTION_CODE}" ]]; then usage exit 1 fi if [[ -z "${ACTION_PAYLOAD_JSON}" ]]; then ACTION_PAYLOAD_JSON='{}' fi resolve_driver_action_entry "${MAINLAND_BASE_URL}" "${ACTION_CODE}" "${ACTION_PAYLOAD_JSON}" "${REQUESTED_BY}" "cli/driver-resolve" ;; driver-run) MAINLAND_BASE_URL="${1:-$(ops_center_resolve_mainland_api_base_url)}" ACTION_CODE="${2:-}" ACTION_PAYLOAD_JSON="${3:-}" CONFIRM_FLAG="${4:-}" REQUESTED_BY="${5:-$(ops_center_resolve_requested_by)}" if [[ -z "${ACTION_CODE}" && "${MAINLAND_BASE_URL}" != http://* && "${MAINLAND_BASE_URL}" != https://* ]]; then ACTION_CODE="${MAINLAND_BASE_URL}" MAINLAND_BASE_URL="$(ops_center_resolve_mainland_api_base_url)" ACTION_PAYLOAD_JSON="${2:-}" CONFIRM_FLAG="${3:-}" REQUESTED_BY="${4:-$(ops_center_resolve_requested_by)}" fi if is_confirm_token "${ACTION_PAYLOAD_JSON}" && [[ -z "${CONFIRM_FLAG}" ]]; then CONFIRM_FLAG="${ACTION_PAYLOAD_JSON}" ACTION_PAYLOAD_JSON='{}' fi if [[ -z "${ACTION_CODE}" ]]; then usage exit 1 fi if [[ -z "${ACTION_PAYLOAD_JSON}" ]]; then ACTION_PAYLOAD_JSON='{}' fi run_driver_action_entry "${MAINLAND_BASE_URL}" "${ACTION_CODE}" "${ACTION_PAYLOAD_JSON}" "${CONFIRM_FLAG}" "${REQUESTED_BY}" "cli/driver-run" ;; driver) MAINLAND_BASE_URL="${1:-$(ops_center_resolve_mainland_api_base_url)}" ACTION_CODE="${2:-}" ACTION_PAYLOAD_JSON="${3:-}" REQUESTED_BY="${4:-$(ops_center_resolve_requested_by)}" if [[ -z "${ACTION_CODE}" && "${MAINLAND_BASE_URL}" != http://* && "${MAINLAND_BASE_URL}" != https://* ]]; then ACTION_CODE="${MAINLAND_BASE_URL}" MAINLAND_BASE_URL="$(ops_center_resolve_mainland_api_base_url)" ACTION_PAYLOAD_JSON="${2:-}" REQUESTED_BY="${3:-$(ops_center_resolve_requested_by)}" fi if [[ -z "${ACTION_PAYLOAD_JSON}" ]]; then ACTION_PAYLOAD_JSON='{}' fi if [[ -z "${ACTION_CODE}" ]]; then usage exit 1 fi exec bash "${SCRIPT_DIR}/drive_ops_action.sh" "${MAINLAND_BASE_URL}" "${ACTION_CODE}" "${ACTION_PAYLOAD_JSON}" "${REQUESTED_BY}" ;; runbook-preview) MAINLAND_BASE_URL="${1:-$(ops_center_resolve_mainland_api_base_url)}" SEQUENCE_KEY="${2:-}" ACTION_PAYLOAD_JSON="${3:-}" REQUESTED_BY="${4:-$(ops_center_resolve_requested_by)}" if [[ -z "${SEQUENCE_KEY}" && "${MAINLAND_BASE_URL}" != http://* && "${MAINLAND_BASE_URL}" != https://* ]]; then SEQUENCE_KEY="${MAINLAND_BASE_URL}" MAINLAND_BASE_URL="$(ops_center_resolve_mainland_api_base_url)" ACTION_PAYLOAD_JSON="${2:-}" REQUESTED_BY="${3:-$(ops_center_resolve_requested_by)}" fi if [[ -z "${ACTION_PAYLOAD_JSON}" ]]; then ACTION_PAYLOAD_JSON='{}' fi if [[ -z "${SEQUENCE_KEY}" ]]; then echo "sequence_key is required" >&2 usage exit 1 fi preview_runbook_sequence "${MAINLAND_BASE_URL}" "${SEQUENCE_KEY}" "${ACTION_PAYLOAD_JSON}" "${REQUESTED_BY}" ;; runbook-run) MAINLAND_BASE_URL="${1:-$(ops_center_resolve_mainland_api_base_url)}" SEQUENCE_KEY="${2:-}" SECONDARY_FLAG="${3:-primary}" ACTION_PAYLOAD_JSON="${4:-}" CONFIRM_FLAG="${5:-}" REQUESTED_BY="${6:-$(ops_center_resolve_requested_by)}" if [[ -z "${SEQUENCE_KEY}" && "${MAINLAND_BASE_URL}" != http://* && "${MAINLAND_BASE_URL}" != https://* ]]; then SEQUENCE_KEY="${MAINLAND_BASE_URL}" MAINLAND_BASE_URL="$(ops_center_resolve_mainland_api_base_url)" SECONDARY_FLAG="${2:-primary}" ACTION_PAYLOAD_JSON="${3:-}" CONFIRM_FLAG="${4:-}" REQUESTED_BY="${5:-$(ops_center_resolve_requested_by)}" fi if is_confirm_token "${ACTION_PAYLOAD_JSON}" && [[ -z "${CONFIRM_FLAG}" ]]; then CONFIRM_FLAG="${ACTION_PAYLOAD_JSON}" ACTION_PAYLOAD_JSON='{}' fi if [[ -z "${SEQUENCE_KEY}" ]]; then echo "sequence_key is required" >&2 usage exit 1 fi if [[ -z "${ACTION_PAYLOAD_JSON}" ]]; then ACTION_PAYLOAD_JSON='{}' fi run_resolved_runbook_sequence "${MAINLAND_BASE_URL}" "${SEQUENCE_KEY}" "${SECONDARY_FLAG}" "${ACTION_PAYLOAD_JSON}" "${CONFIRM_FLAG}" "${REQUESTED_BY}" "cli/runbook-run" ;; runbook-resolve) MAINLAND_BASE_URL="${1:-$(ops_center_resolve_mainland_api_base_url)}" SEQUENCE_KEY="${2:-}" SECONDARY_FLAG="${3:-primary}" ACTION_PAYLOAD_JSON="${4:-}" REQUESTED_BY="${5:-$(ops_center_resolve_requested_by)}" if [[ -z "${SEQUENCE_KEY}" && "${MAINLAND_BASE_URL}" != http://* && "${MAINLAND_BASE_URL}" != https://* ]]; then SEQUENCE_KEY="${MAINLAND_BASE_URL}" MAINLAND_BASE_URL="$(ops_center_resolve_mainland_api_base_url)" SECONDARY_FLAG="${2:-primary}" ACTION_PAYLOAD_JSON="${3:-}" REQUESTED_BY="${4:-$(ops_center_resolve_requested_by)}" fi if [[ -z "${ACTION_PAYLOAD_JSON}" ]]; then ACTION_PAYLOAD_JSON='{}' fi if [[ -z "${SEQUENCE_KEY}" ]]; then echo "sequence_key is required" >&2 usage exit 1 fi if [[ "${ACTION_PAYLOAD_JSON}" == @* ]]; then ACTION_PAYLOAD_PATH="${ACTION_PAYLOAD_JSON#@}" if [[ ! -f "${ACTION_PAYLOAD_PATH}" ]]; then echo "payload file not found: ${ACTION_PAYLOAD_PATH}" >&2 exit 1 fi ACTION_PAYLOAD_JSON="$(cat "${ACTION_PAYLOAD_PATH}")" fi REQUEST_JSON="$(python3 - <<'PY' "${SECONDARY_FLAG}" "${ACTION_PAYLOAD_JSON}" "${REQUESTED_BY}" import json import sys secondary_flag = str(sys.argv[1] or "primary").strip().lower() payload_raw = str(sys.argv[2] or "").strip() or "{}" requested_by = str(sys.argv[3] or "").strip() or "cli/drive_ops_center" try: action_payload = json.loads(payload_raw) except Exception as exc: raise SystemExit(f"invalid action payload json: {exc}") if not isinstance(action_payload, dict): raise SystemExit("action payload must be a json object") request_payload = { "secondary": secondary_flag in {"secondary", "second", "2", "true", "yes", "y"}, "action_payload": action_payload, "requested_by": requested_by, } print(json.dumps(request_payload, ensure_ascii=False)) PY )" echo "[1/3] runbook resolve request" python3 - <<'PY' "${MAINLAND_BASE_URL}" "${SEQUENCE_KEY}" "${REQUEST_JSON}" import json import sys print(json.dumps({ "base_url": str(sys.argv[1] or ""), "sequence_key": str(sys.argv[2] or ""), "request": json.loads(str(sys.argv[3] or "{}")), }, ensure_ascii=False, indent=2)) PY echo echo RAW_HTTP_RESPONSE="$(request_with_status "POST" "${MAINLAND_BASE_URL}/api/v1/ops/runbook/sequences/${SEQUENCE_KEY}/resolve" "${REQUEST_JSON}")" HTTP_STATUS="${RAW_HTTP_RESPONSE##*$'\n'HTTP_STATUS=}" RESPONSE_JSON="${RAW_HTTP_RESPONSE%$'\n'HTTP_STATUS=*}" echo "[2/3] raw response" printf '%s\n' "http_status=${HTTP_STATUS}" python3 - <<'PY' "${RESPONSE_JSON}" import json import sys payload = json.loads(str(sys.argv[1] or "{}")) print(json.dumps(payload, ensure_ascii=False, indent=2)) PY echo echo echo "[3/3] condensed summary" python3 - <<'PY' "${RESPONSE_JSON}" "${HTTP_STATUS}" import json import sys payload = json.loads(str(sys.argv[1] or "{}")) http_status = int(sys.argv[2] or 0) code = payload.get("code", 1) if code in (None, ""): code = 0 if http_status == 200 else 1 data = payload.get("data") or {} print(json.dumps({ "http_status": http_status, "code": int(code or 0), "message": str(payload.get("message") or ""), "sequence_key": str(data.get("sequence_key") or ""), "driver_action_code": str(data.get("driver_action_code") or ""), "driver_action_label": str(data.get("driver_action_label") or ""), "driver_node_codes": list(data.get("driver_node_codes") or []), "driver_requested_by": str(data.get("driver_requested_by") or ""), "resolved_at": str(data.get("resolved_at") or ""), "sequence_focus_ref": ((data.get("sequence") or {}).get("focus_ref") or {}), "action_focus_ref": ((data.get("sequence") or {}).get("action_focus_ref") or {}), }, ensure_ascii=False, indent=2)) PY ;; runbook-sequence) MAINLAND_BASE_URL="${1:-$(ops_center_resolve_mainland_api_base_url)}" SEQUENCE_KEY="${2:-}" SECONDARY_FLAG="${3:-primary}" ACTION_PAYLOAD_JSON="${4:-}" REQUESTED_BY="${5:-$(ops_center_resolve_requested_by)}" if [[ -z "${SEQUENCE_KEY}" && "${MAINLAND_BASE_URL}" != http://* && "${MAINLAND_BASE_URL}" != https://* ]]; then SEQUENCE_KEY="${MAINLAND_BASE_URL}" MAINLAND_BASE_URL="$(ops_center_resolve_mainland_api_base_url)" SECONDARY_FLAG="${2:-primary}" ACTION_PAYLOAD_JSON="${3:-}" REQUESTED_BY="${4:-$(ops_center_resolve_requested_by)}" fi if [[ -z "${ACTION_PAYLOAD_JSON}" ]]; then ACTION_PAYLOAD_JSON='{}' fi if [[ -z "${SEQUENCE_KEY}" ]]; then echo "sequence_key is required" >&2 usage exit 1 fi if [[ "${ACTION_PAYLOAD_JSON}" == @* ]]; then ACTION_PAYLOAD_PATH="${ACTION_PAYLOAD_JSON#@}" if [[ ! -f "${ACTION_PAYLOAD_PATH}" ]]; then echo "payload file not found: ${ACTION_PAYLOAD_PATH}" >&2 exit 1 fi ACTION_PAYLOAD_JSON="$(cat "${ACTION_PAYLOAD_PATH}")" fi REQUEST_JSON="$(python3 - <<'PY' "${SECONDARY_FLAG}" "${ACTION_PAYLOAD_JSON}" "${REQUESTED_BY}" import json import sys secondary_flag = str(sys.argv[1] or "primary").strip().lower() payload_raw = str(sys.argv[2] or "").strip() or "{}" requested_by = str(sys.argv[3] or "").strip() or "cli/drive_ops_center" try: action_payload = json.loads(payload_raw) except Exception as exc: raise SystemExit(f"invalid action payload json: {exc}") if not isinstance(action_payload, dict): raise SystemExit("action payload must be a json object") request_payload = { "secondary": secondary_flag in {"secondary", "second", "2", "true", "yes", "y"}, "action_payload": action_payload, "requested_by": requested_by, } print(json.dumps(request_payload, ensure_ascii=False)) PY )" echo "[1/3] runbook sequence request" python3 - <<'PY' "${MAINLAND_BASE_URL}" "${SEQUENCE_KEY}" "${REQUEST_JSON}" import json import sys print(json.dumps({ "base_url": str(sys.argv[1] or ""), "sequence_key": str(sys.argv[2] or ""), "request": json.loads(str(sys.argv[3] or "{}")), }, ensure_ascii=False, indent=2)) PY echo echo RAW_HTTP_RESPONSE="$(request_with_status "POST" "${MAINLAND_BASE_URL}/api/v1/ops/runbook/sequences/${SEQUENCE_KEY}/execute" "${REQUEST_JSON}")" HTTP_STATUS="${RAW_HTTP_RESPONSE##*$'\n'HTTP_STATUS=}" RESPONSE_JSON="${RAW_HTTP_RESPONSE%$'\n'HTTP_STATUS=*}" echo "[2/3] raw response" printf '%s\n' "http_status=${HTTP_STATUS}" python3 - <<'PY' "${RESPONSE_JSON}" import json import sys payload = json.loads(str(sys.argv[1] or "{}")) print(json.dumps(payload, ensure_ascii=False, indent=2)) PY echo echo echo "[3/3] condensed summary" python3 - <<'PY' "${RESPONSE_JSON}" "${HTTP_STATUS}" import json import sys payload = json.loads(str(sys.argv[1] or "{}")) http_status = int(sys.argv[2] or 0) data = payload.get("data") or {} raw_code = payload.get("code", 1) if raw_code in (0, "0", False): normalized_code = 0 else: normalized_code = int(raw_code or 1) summary = { "http_status": http_status, "code": normalized_code, "message": str(payload.get("message") or ""), "sequence_key": str(data.get("sequence_key") or ""), "driver_action_code": str(data.get("driver_action_code") or ""), "handled": bool(data.get("handled", False)), "mode": str(data.get("mode") or ""), "sequence": data.get("sequence") or {}, } print(json.dumps(summary, ensure_ascii=False, indent=2)) PY if [[ "${HTTP_STATUS}" -lt 200 || "${HTTP_STATUS}" -ge 300 ]]; then exit 1 fi ;; smart-latest) MAINLAND_BASE_URL="${1:-$(ops_center_resolve_mainland_api_base_url)}" MODE="${2:-}" CONFIRM_FLAG="${3:-}" if [[ "${MODE}" != "worker" && "${MODE}" != "control" ]]; then echo "mode must be worker or control" >&2 usage exit 1 fi exec bash "${SCRIPT_DIR}/drive_release_hub.sh" "${MAINLAND_BASE_URL}" smart-latest "${MODE}" "${CONFIRM_FLAG}" ;; smart-release) MAINLAND_BASE_URL="${1:-$(ops_center_resolve_mainland_api_base_url)}" RELEASE_ID="${2:-}" MODE="${3:-}" CONFIRM_FLAG="${4:-}" if [[ -z "${RELEASE_ID}" ]]; then echo "release_id is required" >&2 usage exit 1 fi if [[ "${MODE}" != "worker" && "${MODE}" != "control" ]]; then echo "mode must be worker or control" >&2 usage exit 1 fi exec bash "${SCRIPT_DIR}/drive_release_hub.sh" "${MAINLAND_BASE_URL}" smart-release "${RELEASE_ID}" "${MODE}" "${CONFIRM_FLAG}" ;; agent-plan) MAINLAND_BASE_URL="${1:-$(ops_center_resolve_mainland_api_base_url)}" NODE_CODE="${2:-}" NODE_REGION="${3:-mainland}" NODE_ROLE="${4:-worker}" CONTROL_PLANE_BASE_URL="${5:-$(ops_center_resolve_control_plane_base_url)}" if [[ -z "${CONTROL_PLANE_BASE_URL}" ]]; then CONTROL_PLANE_BASE_URL="$(ops_center_resolve_overseas_api_base_url)" fi ROOT_DIR="${6:-$(ops_center_resolve_root_dir)}" if [[ -z "${NODE_CODE}" ]]; then echo "node_code is required" >&2 usage exit 1 fi ISSUED_BY="$(ops_center_resolve_issued_by)" \ EXPIRES_IN_HOURS="$(ops_center_resolve_expires_in_hours)" \ exec bash "${SCRIPT_DIR}/build_node_agent_bootstrap_plan.sh" \ "${MAINLAND_BASE_URL}" \ "${NODE_CODE}" \ "${NODE_REGION}" \ "${NODE_ROLE}" \ "${CONTROL_PLANE_BASE_URL}" \ "${ROOT_DIR}" ;; agent-plan-export) REPORT_DIR="${1:-${OPS_CENTER_REPORT_ROOT}/agent-plans}" MAINLAND_BASE_URL="${2:-$(ops_center_resolve_mainland_api_base_url)}" NODE_CODE="${3:-}" NODE_REGION="${4:-mainland}" NODE_ROLE="${5:-worker}" CONTROL_PLANE_BASE_URL="${6:-$(ops_center_resolve_control_plane_base_url)}" if [[ -z "${CONTROL_PLANE_BASE_URL}" ]]; then CONTROL_PLANE_BASE_URL="$(ops_center_resolve_overseas_api_base_url)" fi ROOT_DIR="${7:-$(ops_center_resolve_root_dir)}" if [[ -z "${NODE_CODE}" ]]; then echo "node_code is required" >&2 usage exit 1 fi export_agent_plan "${REPORT_DIR}" "${MAINLAND_BASE_URL}" "${NODE_CODE}" "${NODE_REGION}" "${NODE_ROLE}" "${CONTROL_PLANE_BASE_URL}" "${ROOT_DIR}" ;; agent-check) SERVICE_NAME="${1:-$(ops_center_resolve_node_agent_service_name)}" ENV_FILE="${2:-$(ops_center_resolve_node_agent_env_file)}" SERVICE_NAME="${SERVICE_NAME}" ENV_FILE="${ENV_FILE}" exec bash "${SCRIPT_DIR}/check_node_agent.sh" ;; *) echo "unknown command: ${COMMAND}" >&2 usage exit 1 ;; esac