d
This commit is contained in:
@@ -12,6 +12,9 @@ from app.core.db import get_db
|
||||
from app.services.ops_command_service import build_bash_command
|
||||
from app.services.ops_job_service import create_ops_job, ensure_ops_schema, get_ops_job
|
||||
from app.services.ops_template_service import build_ops_template_payload, get_ops_action_template
|
||||
from app.services.runtime_settings_service import get_runtime_settings
|
||||
from app.services.sensitive_words_service import get_sensitive_words_payload
|
||||
from app.services.settings_service import get_settings_payload
|
||||
|
||||
|
||||
_AGENT_SCHEMA_SQL = """
|
||||
@@ -117,6 +120,48 @@ def _format_time(value: object) -> str:
|
||||
return ""
|
||||
|
||||
|
||||
def _prefer_non_loopback_identity(primary: object, fallback: object) -> str:
|
||||
primary_value = str(primary or "").strip()
|
||||
fallback_value = str(fallback or "").strip()
|
||||
invalid_values = {"", "localhost", "localhost.localdomain", "127.0.0.1", "::1"}
|
||||
if primary_value and primary_value.lower() not in invalid_values:
|
||||
return primary_value
|
||||
return fallback_value
|
||||
|
||||
|
||||
def _merge_detect_runtime_snapshot(cluster_metadata: dict, metadata: dict, current_load: int) -> dict:
|
||||
active_threads = int(cluster_metadata.get("active_threads", metadata.get("active_threads", 0)) or 0)
|
||||
max_threads = int(cluster_metadata.get("max_threads", metadata.get("max_threads", 0)) or 0)
|
||||
inferred_worker_online = bool(
|
||||
cluster_metadata.get("worker_online", metadata.get("worker_online", False))
|
||||
or cluster_metadata.get("service_running", metadata.get("service_running", False))
|
||||
or current_load > 0
|
||||
or active_threads > 0
|
||||
)
|
||||
inferred_detect_participating = bool(
|
||||
cluster_metadata.get("detect_participating", metadata.get("detect_participating", False))
|
||||
or current_load > 0
|
||||
or active_threads > 0
|
||||
)
|
||||
return {
|
||||
"worker_online": inferred_worker_online,
|
||||
"detect_participating": inferred_detect_participating,
|
||||
"active_threads": active_threads,
|
||||
"max_threads": max_threads,
|
||||
"current_load": current_load,
|
||||
"phase_label": str(cluster_metadata.get("phase_label", metadata.get("phase_label", "")) or "").strip(),
|
||||
"phase_detail": str(cluster_metadata.get("phase_detail", metadata.get("phase_detail", "")) or "").strip(),
|
||||
"recent_warning": str(cluster_metadata.get("recent_warning", metadata.get("recent_warning", "")) or "").strip(),
|
||||
"proxy_runtime_label": str(
|
||||
cluster_metadata.get("proxy_runtime_label", metadata.get("proxy_runtime_label", "")) or ""
|
||||
).strip(),
|
||||
"proxy_runtime_reason": str(
|
||||
cluster_metadata.get("proxy_runtime_reason", metadata.get("proxy_runtime_reason", "")) or ""
|
||||
).strip(),
|
||||
"updated_at": str(cluster_metadata.get("updated_at", metadata.get("updated_at", "")) or "").strip(),
|
||||
}
|
||||
|
||||
|
||||
def _ops_job_event_level_label(level: object) -> str:
|
||||
normalized_level = str(level or "").strip().lower()
|
||||
mapping = {
|
||||
@@ -588,6 +633,7 @@ def list_managed_nodes_with_agent_state(*, participation_payload: dict | None =
|
||||
seen_node_codes.add(node_code)
|
||||
metadata = dict(node.get("metadata") or {})
|
||||
cluster_node = cluster_map.get(node_code, {})
|
||||
cluster_metadata = dict(cluster_node.get("metadata") or {})
|
||||
latest_token = latest_tokens.get(node_code, {})
|
||||
latest_job = latest_jobs.get(node_code, {})
|
||||
participation_row = dict(detect_participation_map.get(node_code) or {})
|
||||
@@ -607,6 +653,15 @@ def list_managed_nodes_with_agent_state(*, participation_payload: dict | None =
|
||||
current_load=current_load,
|
||||
)
|
||||
capabilities = list(metadata.get("capabilities") or [])
|
||||
detect_runtime = _merge_detect_runtime_snapshot(cluster_metadata, metadata, current_load)
|
||||
active_job_snapshot = {
|
||||
"job_code": str(cluster_metadata.get("active_job_code", metadata.get("active_job_code", "")) or "").strip(),
|
||||
"status": str(cluster_metadata.get("active_job_status", metadata.get("active_job_status", "")) or "").strip(),
|
||||
"items_total": int(cluster_metadata.get("job_items_total", metadata.get("job_items_total", 0)) or 0),
|
||||
"items_claimed": int(cluster_metadata.get("job_items_claimed", metadata.get("job_items_claimed", 0)) or 0),
|
||||
"items_running": int(cluster_metadata.get("job_items_running", metadata.get("job_items_running", 0)) or 0),
|
||||
"items_completed": int(cluster_metadata.get("job_items_completed", metadata.get("job_items_completed", 0)) or 0),
|
||||
}
|
||||
merged_node = {
|
||||
**node,
|
||||
"is_managed": True,
|
||||
@@ -620,10 +675,14 @@ def list_managed_nodes_with_agent_state(*, participation_payload: dict | None =
|
||||
"agent_version": str(metadata.get("agent_version") or "").strip(),
|
||||
"agent_hostname": str(metadata.get("hostname") or "").strip(),
|
||||
"agent_ip": str(metadata.get("ip") or "").strip(),
|
||||
"cluster_hostname": str(cluster_node.get("hostname") or "").strip(),
|
||||
"cluster_ip": str(cluster_node.get("ip") or "").strip(),
|
||||
"cluster_hostname": _prefer_non_loopback_identity(cluster_node.get("hostname"), metadata.get("hostname")),
|
||||
"cluster_ip": _prefer_non_loopback_identity(cluster_node.get("ip"), metadata.get("ip")),
|
||||
"cluster_status": cluster_status,
|
||||
"cluster_current_load": current_load,
|
||||
"current_load": current_load,
|
||||
"detect_runtime": detect_runtime,
|
||||
"runtime_state": detect_runtime,
|
||||
"active_job": active_job_snapshot,
|
||||
"cluster_last_heartbeat_at": str(cluster_node.get("last_heartbeat_at") or metadata.get("last_heartbeat_at") or "").strip(),
|
||||
"cluster_is_effective_worker": bool(cluster_node.get("is_effective_worker", metadata.get("is_effective_worker", False))),
|
||||
"cluster_detect_participating": bool(
|
||||
@@ -688,6 +747,7 @@ def list_managed_nodes_with_agent_state(*, participation_payload: dict | None =
|
||||
if not node_code or node_code in seen_node_codes:
|
||||
continue
|
||||
metadata = dict(cluster_node.get("metadata") or {})
|
||||
cluster_metadata = dict(cluster_node.get("metadata") or {})
|
||||
latest_token = latest_tokens.get(node_code, {})
|
||||
latest_job = latest_jobs.get(node_code, {})
|
||||
participation_row = dict(detect_participation_map.get(node_code) or {})
|
||||
@@ -706,6 +766,15 @@ def list_managed_nodes_with_agent_state(*, participation_payload: dict | None =
|
||||
current_load=current_load,
|
||||
)
|
||||
capabilities = list(metadata.get("capabilities") or [])
|
||||
detect_runtime = _merge_detect_runtime_snapshot(cluster_metadata, metadata, current_load)
|
||||
active_job_snapshot = {
|
||||
"job_code": str(cluster_metadata.get("active_job_code", metadata.get("active_job_code", "")) or "").strip(),
|
||||
"status": str(cluster_metadata.get("active_job_status", metadata.get("active_job_status", "")) or "").strip(),
|
||||
"items_total": int(cluster_metadata.get("job_items_total", metadata.get("job_items_total", 0)) or 0),
|
||||
"items_claimed": int(cluster_metadata.get("job_items_claimed", metadata.get("job_items_claimed", 0)) or 0),
|
||||
"items_running": int(cluster_metadata.get("job_items_running", metadata.get("job_items_running", 0)) or 0),
|
||||
"items_completed": int(cluster_metadata.get("job_items_completed", metadata.get("job_items_completed", 0)) or 0),
|
||||
}
|
||||
fallback_node = {
|
||||
"node_code": node_code,
|
||||
"region": str(cluster_node.get("region") or "").strip(),
|
||||
@@ -732,12 +801,16 @@ def list_managed_nodes_with_agent_state(*, participation_payload: dict | None =
|
||||
"capabilities": capabilities,
|
||||
"capabilities_count": len(capabilities),
|
||||
"agent_version": str(metadata.get("agent_version") or "").strip(),
|
||||
"agent_hostname": str(cluster_node.get("hostname") or metadata.get("hostname") or "").strip(),
|
||||
"agent_ip": str(cluster_node.get("ip") or metadata.get("ip") or "").strip(),
|
||||
"cluster_hostname": str(cluster_node.get("hostname") or "").strip(),
|
||||
"cluster_ip": str(cluster_node.get("ip") or "").strip(),
|
||||
"agent_hostname": _prefer_non_loopback_identity(cluster_node.get("hostname"), metadata.get("hostname")),
|
||||
"agent_ip": _prefer_non_loopback_identity(cluster_node.get("ip"), metadata.get("ip")),
|
||||
"cluster_hostname": _prefer_non_loopback_identity(cluster_node.get("hostname"), metadata.get("hostname")),
|
||||
"cluster_ip": _prefer_non_loopback_identity(cluster_node.get("ip"), metadata.get("ip")),
|
||||
"cluster_status": cluster_status,
|
||||
"cluster_current_load": current_load,
|
||||
"current_load": current_load,
|
||||
"detect_runtime": detect_runtime,
|
||||
"runtime_state": detect_runtime,
|
||||
"active_job": active_job_snapshot,
|
||||
"cluster_last_heartbeat_at": str(cluster_node.get("last_heartbeat_at") or "").strip(),
|
||||
"cluster_is_effective_worker": bool(cluster_node.get("is_effective_worker", False)),
|
||||
"cluster_detect_participating": bool(
|
||||
@@ -2359,7 +2432,7 @@ def build_node_agent_bootstrap_plan(
|
||||
f"SYNC_AGENT_SERVICE_NAME={settings.sync_agent_service_name}",
|
||||
"NODE_AGENT_SERVICE_NAME=domaincheck-node-agent",
|
||||
"",
|
||||
'OPS_AGENT_CAPABILITIES=["service.start","service.stop","service.restart","service.status","runtime.start_worker","runtime.stop_worker","runtime.restart_api","runtime.start_sync_agent","runtime.stop_sync_agent","health.snapshot","logs.collect","diagnostics.collect","deploy.release"]',
|
||||
'OPS_AGENT_CAPABILITIES=["service.start","service.stop","service.restart","service.status","runtime.start_worker","runtime.stop_worker","runtime.start_detection","runtime.stop_detection","runtime.pull_tasks","runtime.restart_api","runtime.start_sync_agent","runtime.stop_sync_agent","runtime.reset_lab_state","health.snapshot","logs.collect","diagnostics.collect","deploy.release"]',
|
||||
"OPS_AGENT_LABELS={}",
|
||||
]
|
||||
env_content = "\n".join(env_lines)
|
||||
@@ -2529,6 +2602,135 @@ def _upsert_agent_runtime(node_code: str, payload: dict) -> None:
|
||||
conn.commit()
|
||||
|
||||
|
||||
def _load_existing_detect_node_runtime(node_code: str) -> dict:
|
||||
normalized_node_code = str(node_code or "").strip()
|
||||
if not normalized_node_code:
|
||||
return {}
|
||||
|
||||
try:
|
||||
with get_db() as conn:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
SELECT status, current_load, metadata_json, last_heartbeat_at
|
||||
FROM detect_worker_nodes
|
||||
WHERE node_code = %s
|
||||
LIMIT 1
|
||||
""",
|
||||
(normalized_node_code,),
|
||||
)
|
||||
row = cur.fetchone()
|
||||
except Exception:
|
||||
return {}
|
||||
|
||||
if not row:
|
||||
return {}
|
||||
metadata = row[2] if isinstance(row[2], dict) else {}
|
||||
return {
|
||||
"status": str(row[0] or "").strip(),
|
||||
"current_load": int(row[1] or 0),
|
||||
"metadata": metadata,
|
||||
"last_heartbeat_at": row[3],
|
||||
}
|
||||
|
||||
|
||||
def _upsert_agent_detect_runtime(node_code: str, payload: dict) -> None:
|
||||
normalized_node_code = str(node_code or "").strip()
|
||||
if not normalized_node_code:
|
||||
return
|
||||
|
||||
metadata = payload.get("metadata") if isinstance(payload.get("metadata"), dict) else {}
|
||||
detect_runtime = metadata.get("detect_runtime") if isinstance(metadata.get("detect_runtime"), dict) else {}
|
||||
if not detect_runtime:
|
||||
return
|
||||
|
||||
try:
|
||||
from app.services.cluster_runtime_service import register_node_heartbeat
|
||||
|
||||
region = str(payload.get("region") or "unknown").strip() or "unknown"
|
||||
role = str(payload.get("role") or "worker").strip() or "worker"
|
||||
active_threads = max(0, int(detect_runtime.get("active_threads") or 0))
|
||||
max_threads = max(0, int(detect_runtime.get("max_threads") or 0))
|
||||
current_load = max(
|
||||
0,
|
||||
int(detect_runtime.get("current_load") or 0),
|
||||
active_threads,
|
||||
)
|
||||
worker_online = bool(detect_runtime.get("worker_online", False) or detect_runtime.get("service_running", False))
|
||||
detect_participating = bool(detect_runtime.get("detect_participating", False) or current_load > 0)
|
||||
phase_label = str(detect_runtime.get("phase_label") or "").strip()
|
||||
phase_detail = str(detect_runtime.get("phase_detail") or "").strip()
|
||||
recent_warning = str(detect_runtime.get("recent_warning") or "").strip()
|
||||
has_runtime_signal = any(
|
||||
[
|
||||
worker_online,
|
||||
detect_participating,
|
||||
current_load > 0,
|
||||
active_threads > 0,
|
||||
max_threads > 0,
|
||||
bool(phase_label),
|
||||
bool(phase_detail),
|
||||
bool(recent_warning),
|
||||
]
|
||||
)
|
||||
if not has_runtime_signal:
|
||||
return
|
||||
existing_runtime = _load_existing_detect_node_runtime(normalized_node_code)
|
||||
existing_metadata = existing_runtime.get("metadata") if isinstance(existing_runtime.get("metadata"), dict) else {}
|
||||
existing_active_threads = max(0, int(existing_metadata.get("active_threads", 0) or 0))
|
||||
existing_current_load = max(0, int(existing_runtime.get("current_load", 0) or 0), existing_active_threads)
|
||||
existing_status = str(existing_runtime.get("status") or "").strip()
|
||||
existing_last_heartbeat_at = existing_runtime.get("last_heartbeat_at")
|
||||
existing_is_recent = False
|
||||
if isinstance(existing_last_heartbeat_at, datetime):
|
||||
current_time = (
|
||||
datetime.now(existing_last_heartbeat_at.tzinfo)
|
||||
if existing_last_heartbeat_at.tzinfo
|
||||
else datetime.now()
|
||||
)
|
||||
existing_is_recent = (current_time - existing_last_heartbeat_at) <= timedelta(seconds=120)
|
||||
normalized_phase_detail = phase_detail.lower()
|
||||
generic_phase_detail = bool(normalized_phase_detail) and "/" in normalized_phase_detail and " " not in normalized_phase_detail
|
||||
weak_agent_snapshot = bool(
|
||||
worker_online
|
||||
and current_load <= 0
|
||||
and active_threads <= 0
|
||||
and max_threads > 0
|
||||
and not phase_label
|
||||
and (not phase_detail or generic_phase_detail)
|
||||
and not recent_warning
|
||||
)
|
||||
if weak_agent_snapshot and existing_is_recent and (
|
||||
existing_current_load > 0 or existing_status == "busy"
|
||||
):
|
||||
return
|
||||
status = "busy" if current_load > 0 else ("online" if worker_online else "unknown")
|
||||
heartbeat_metadata = {
|
||||
"service": "agent-heartbeat",
|
||||
"worker_online": worker_online,
|
||||
"detect_participating": detect_participating,
|
||||
"active_threads": active_threads,
|
||||
"max_threads": max_threads,
|
||||
"phase_label": phase_label,
|
||||
"phase_detail": phase_detail,
|
||||
"recent_warning": recent_warning,
|
||||
"updated_at": str(detect_runtime.get("updated_at") or "").strip(),
|
||||
"agent_heartbeat_at": datetime.now().isoformat(timespec="seconds"),
|
||||
}
|
||||
register_node_heartbeat(
|
||||
node_code=normalized_node_code,
|
||||
region=region,
|
||||
role=role,
|
||||
status=status,
|
||||
current_load=current_load,
|
||||
metadata=heartbeat_metadata,
|
||||
hostname_override=str(payload.get("hostname") or "").strip(),
|
||||
ip_override=str(payload.get("ip") or "").strip(),
|
||||
)
|
||||
except Exception:
|
||||
return
|
||||
|
||||
|
||||
def agent_register(payload: dict, *, token: str) -> tuple[bool, str, dict]:
|
||||
node_code = str(payload.get("node_code") or "").strip()
|
||||
if not node_code:
|
||||
@@ -2537,6 +2739,7 @@ def agent_register(payload: dict, *, token: str) -> tuple[bool, str, dict]:
|
||||
if not ok:
|
||||
return False, message, auth
|
||||
_upsert_agent_runtime(node_code, payload)
|
||||
_upsert_agent_detect_runtime(node_code, payload)
|
||||
return True, "Agent 注册成功", {
|
||||
"node_code": node_code,
|
||||
"expires_at": auth.get("expires_at", ""),
|
||||
@@ -2552,6 +2755,7 @@ def agent_heartbeat(payload: dict, *, token: str) -> tuple[bool, str, dict]:
|
||||
if not ok:
|
||||
return False, message, auth
|
||||
_upsert_agent_runtime(node_code, payload)
|
||||
_upsert_agent_detect_runtime(node_code, payload)
|
||||
return True, "heartbeat ok", {
|
||||
"node_code": node_code,
|
||||
"server_time": _format_time(datetime.now()),
|
||||
@@ -2559,6 +2763,57 @@ def agent_heartbeat(payload: dict, *, token: str) -> tuple[bool, str, dict]:
|
||||
}
|
||||
|
||||
|
||||
def _runtime_config_bundle_hash_payload(bundle: dict | None) -> dict:
|
||||
normalized = dict(bundle or {})
|
||||
normalized.pop("config_hash", None)
|
||||
normalized.pop("generated_at", None)
|
||||
return normalized
|
||||
|
||||
|
||||
def _build_agent_runtime_config_bundle(node_code: str) -> dict:
|
||||
settings_payload = get_settings_payload()
|
||||
runtime_settings = get_runtime_settings()
|
||||
sensitive_words_payload = get_sensitive_words_payload()
|
||||
bundle = {
|
||||
"node_code": str(node_code or "").strip(),
|
||||
"detect_options": dict(settings_payload.get("detect_options") or {}),
|
||||
"proxy_config": dict(settings_payload.get("proxy_config") or {}),
|
||||
"thread_count": int(settings_payload.get("thread_count", 2) or 2),
|
||||
"node_thread_counts": dict(settings_payload.get("node_thread_counts") or {}),
|
||||
"runtime_settings": dict(runtime_settings or {}),
|
||||
"sensitive_words": {
|
||||
"text": str(sensitive_words_payload.get("text") or ""),
|
||||
"total": int(sensitive_words_payload.get("total", 0) or 0),
|
||||
"items": list(sensitive_words_payload.get("items") or []),
|
||||
},
|
||||
"generated_at": _format_time(datetime.now()),
|
||||
}
|
||||
bundle["config_hash"] = hashlib.sha256(
|
||||
json.dumps(
|
||||
_runtime_config_bundle_hash_payload(bundle),
|
||||
ensure_ascii=False,
|
||||
sort_keys=True,
|
||||
).encode("utf-8")
|
||||
).hexdigest()
|
||||
return bundle
|
||||
|
||||
|
||||
def agent_pull_runtime_config(payload: dict, *, token: str) -> tuple[bool, str, dict]:
|
||||
node_code = str(payload.get("node_code") or "").strip()
|
||||
if not node_code:
|
||||
return _agent_error("node_code 不能为空", "agent_node_code_required")
|
||||
ok, message, auth = _authenticate_agent_token(token, expected_node_code=node_code)
|
||||
if not ok:
|
||||
return False, message, auth
|
||||
bundle = _build_agent_runtime_config_bundle(node_code)
|
||||
return True, "runtime config ok", {
|
||||
"node_code": node_code,
|
||||
"server_time": _format_time(datetime.now()),
|
||||
"expires_at": auth.get("expires_at", ""),
|
||||
"bundle": bundle,
|
||||
}
|
||||
|
||||
|
||||
def agent_pull_jobs(payload: dict, *, token: str, limit: int = 1) -> tuple[bool, str, dict]:
|
||||
node_code = str(payload.get("node_code") or "").strip()
|
||||
if not node_code:
|
||||
@@ -2568,6 +2823,7 @@ def agent_pull_jobs(payload: dict, *, token: str, limit: int = 1) -> tuple[bool,
|
||||
return False, message, _auth
|
||||
|
||||
safe_limit = min(max(int(limit or 1), 1), 10)
|
||||
dispatched_events: list[dict] = []
|
||||
with get_db() as conn:
|
||||
conn.autocommit = False
|
||||
with conn.cursor() as cur:
|
||||
@@ -2604,15 +2860,19 @@ def agent_pull_jobs(payload: dict, *, token: str, limit: int = 1) -> tuple[bool,
|
||||
""",
|
||||
(job_id,),
|
||||
)
|
||||
append_ops_job_event(
|
||||
job_id=job_id,
|
||||
node_code=node_code,
|
||||
event_type="agent_dispatched",
|
||||
message=f"任务已派发给节点 {node_code}",
|
||||
payload={"node_code": node_code},
|
||||
dispatched_events.append(
|
||||
{
|
||||
"job_id": job_id,
|
||||
"node_code": node_code,
|
||||
"event_type": "agent_dispatched",
|
||||
"message": f"任务已派发给节点 {node_code}",
|
||||
"payload": {"node_code": node_code},
|
||||
}
|
||||
)
|
||||
jobs.append(_agent_job_envelope(get_ops_job(job_id)))
|
||||
conn.commit()
|
||||
for event in dispatched_events:
|
||||
append_ops_job_event(**event)
|
||||
return True, "ok", {
|
||||
"jobs": jobs,
|
||||
"count": len(jobs),
|
||||
@@ -2631,6 +2891,7 @@ def agent_mark_job_started(job_id: int, payload: dict, *, token: str) -> tuple[b
|
||||
if not ok:
|
||||
return False, message, _auth
|
||||
|
||||
started_event: dict | None = None
|
||||
with get_db() as conn:
|
||||
conn.autocommit = False
|
||||
with conn.cursor() as cur:
|
||||
@@ -2662,14 +2923,16 @@ def agent_mark_job_started(job_id: int, payload: dict, *, token: str) -> tuple[b
|
||||
)
|
||||
step_rows = cur.fetchall()
|
||||
step_ids = [int(item[0]) for item in step_rows]
|
||||
append_ops_job_event(
|
||||
job_id=int(job_id),
|
||||
node_code=node_code,
|
||||
event_type="agent_started",
|
||||
message=f"节点 {node_code} 已开始执行任务",
|
||||
payload={"step_ids": step_ids},
|
||||
)
|
||||
started_event = {
|
||||
"job_id": int(job_id),
|
||||
"node_code": node_code,
|
||||
"event_type": "agent_started",
|
||||
"message": f"节点 {node_code} 已开始执行任务",
|
||||
"payload": {"step_ids": step_ids},
|
||||
}
|
||||
conn.commit()
|
||||
if started_event:
|
||||
append_ops_job_event(**started_event)
|
||||
job = get_ops_job(int(job_id))
|
||||
return True, "任务已标记为运行中", {
|
||||
"job": job,
|
||||
@@ -2710,6 +2973,7 @@ def agent_complete_job(job_id: int, payload: dict, *, token: str) -> tuple[bool,
|
||||
result["focus_ref"] = focus_ref
|
||||
event_level = "info" if job_status == "success" else ("warning" if job_status == "partially_succeeded" else "error")
|
||||
|
||||
completed_event: dict | None = None
|
||||
with get_db() as conn:
|
||||
conn.autocommit = False
|
||||
with conn.cursor() as cur:
|
||||
@@ -2789,22 +3053,24 @@ def agent_complete_job(job_id: int, payload: dict, *, token: str) -> tuple[bool,
|
||||
)
|
||||
step_rows = cur.fetchall()
|
||||
step_ids = [int(item[0]) for item in step_rows]
|
||||
append_ops_job_event(
|
||||
job_id=int(job_id),
|
||||
node_code=node_code,
|
||||
client_event_id=(f"complete:{client_request_id}" if client_request_id else ""),
|
||||
event_type="agent_completed",
|
||||
message=f"节点 {node_code} 已完成任务,状态: {job_status}",
|
||||
level=event_level,
|
||||
payload={
|
||||
completed_event = {
|
||||
"job_id": int(job_id),
|
||||
"node_code": node_code,
|
||||
"client_event_id": (f"complete:{client_request_id}" if client_request_id else ""),
|
||||
"event_type": "agent_completed",
|
||||
"message": f"节点 {node_code} 已完成任务,状态: {job_status}",
|
||||
"level": event_level,
|
||||
"payload": {
|
||||
"step_ids": step_ids,
|
||||
"result": result,
|
||||
"duration_ms": duration_ms,
|
||||
"summary_text": str(result.get("summary_text") or result.get("summary") or summary_text).strip(),
|
||||
"focus_ref": focus_ref,
|
||||
},
|
||||
)
|
||||
}
|
||||
conn.commit()
|
||||
if completed_event:
|
||||
append_ops_job_event(**completed_event)
|
||||
from app.services.ops_release_service import refresh_release_rollout_for_job
|
||||
|
||||
refresh_release_rollout_for_job(int(job_id))
|
||||
|
||||
Reference in New Issue
Block a user