feat: extend oneliner execution workspace
This commit is contained in:
@@ -71,6 +71,29 @@ class AdminIncidentReviewRequest(BaseModel):
|
||||
review_notes: str = ""
|
||||
|
||||
|
||||
class PlatformAgentSelfCheckRequest(BaseModel):
|
||||
project_id: str = ""
|
||||
sample_limit: int = Field(default=3, ge=1, le=12)
|
||||
remember_summary: bool = True
|
||||
|
||||
|
||||
class PlatformSkillReviewRequest(BaseModel):
|
||||
project_id: str = ""
|
||||
accepted: bool = True
|
||||
score: float = Field(default=0.8, ge=0.0, le=1.0)
|
||||
status: str = ""
|
||||
summary: str = ""
|
||||
review_notes: str = ""
|
||||
|
||||
|
||||
class OneLinerActionExecuteRequest(BaseModel):
|
||||
action_key: str
|
||||
project_id: str = ""
|
||||
platform: str = ""
|
||||
session_id: str = ""
|
||||
payload: dict[str, Any] = Field(default_factory=dict)
|
||||
|
||||
|
||||
INTENT_ACTIONS: dict[str, list[dict[str, Any]]] = {
|
||||
"create_project": [{"key": "goto-intake", "label": "去我的项目", "kind": "navigate"}],
|
||||
"create_assistant": [{"key": "open-create-assistant", "label": "创建 Agent", "kind": "ui_action"}],
|
||||
@@ -279,6 +302,28 @@ def register_oneliner_routes(app: Any, legacy: Any) -> None:
|
||||
def _safe_platform(platform_value: str | None, fallback: str = "douyin") -> str:
|
||||
return legacy.ensure_domestic_platform(platform_value or fallback, allow_blank=not fallback) or fallback
|
||||
|
||||
def _route_supported(path: str) -> bool:
|
||||
return any(getattr(route, "path", "") == path for route in app.routes)
|
||||
|
||||
def _platform_route_checks(platform: str) -> list[dict[str, Any]]:
|
||||
checks = [
|
||||
("accounts", f"/v2/{platform}/accounts"),
|
||||
("workspace", f"/v2/{platform}/accounts/{{account_id}}/workspace"),
|
||||
("videos", f"/v2/{platform}/accounts/{{account_id}}/videos"),
|
||||
("analyze_account", f"/v2/{platform}/accounts/{{account_id}}/analysis"),
|
||||
("analyze_top_videos", f"/v2/{platform}/accounts/{{account_id}}/videos/analyze-top"),
|
||||
("similar_searches", f"/v2/{platform}/similar-searches"),
|
||||
("benchmark_links", f"/v2/{platform}/accounts/{{account_id}}/benchmark-links"),
|
||||
]
|
||||
return [
|
||||
{
|
||||
"key": key,
|
||||
"path": path,
|
||||
"ok": _route_supported(path),
|
||||
}
|
||||
for key, path in checks
|
||||
]
|
||||
|
||||
def _fetch_profile_row(account: dict[str, Any], project_id: str = "") -> dict[str, Any] | None:
|
||||
return legacy.db.fetch_one(
|
||||
"SELECT * FROM oneliner_profiles WHERE user_id = ? AND project_id = ?",
|
||||
@@ -388,6 +433,39 @@ def register_oneliner_routes(app: Any, legacy: Any) -> None:
|
||||
""",
|
||||
(account["id"], project_id, platform),
|
||||
)["count"]
|
||||
recent_memory_row = legacy.db.fetch_one(
|
||||
"""
|
||||
SELECT * FROM agent_memories
|
||||
WHERE user_id = ? AND project_id = ? AND agent_scope = 'platform' AND platform = ?
|
||||
ORDER BY updated_at DESC
|
||||
LIMIT 1
|
||||
""",
|
||||
(account["id"], project_id, platform),
|
||||
)
|
||||
recent_skill_row = legacy.db.fetch_one(
|
||||
"""
|
||||
SELECT * FROM agent_skills
|
||||
WHERE user_id = ? AND project_id = ? AND agent_scope = 'platform' AND platform = ?
|
||||
ORDER BY
|
||||
CASE WHEN status = 'validated' THEN 0 WHEN status = 'draft' THEN 1 ELSE 2 END,
|
||||
updated_at DESC
|
||||
LIMIT 1
|
||||
""",
|
||||
(account["id"], project_id, platform),
|
||||
)
|
||||
readiness_items = [
|
||||
bool(row and row.get("status") == "active"),
|
||||
bool(assistant),
|
||||
bool(memory_count),
|
||||
bool(skill_count),
|
||||
]
|
||||
readiness_score = int(sum(1 for item in readiness_items if item) * 25)
|
||||
if readiness_score >= 100:
|
||||
readiness_label = "就绪"
|
||||
elif readiness_score >= 50:
|
||||
readiness_label = "可用"
|
||||
else:
|
||||
readiness_label = "待补全"
|
||||
return {
|
||||
"id": row["id"] if row else "",
|
||||
"user_id": account["id"],
|
||||
@@ -402,6 +480,10 @@ def register_oneliner_routes(app: Any, legacy: Any) -> None:
|
||||
"config": _parse_json((row or {}).get("config_json"), {}),
|
||||
"memory_count": memory_count,
|
||||
"skill_count": skill_count,
|
||||
"recent_memory": _memory_payload(recent_memory_row) if recent_memory_row else None,
|
||||
"recent_skill": _skill_payload(recent_skill_row) if recent_skill_row else None,
|
||||
"readiness_score": readiness_score,
|
||||
"readiness_label": readiness_label,
|
||||
"assistant": assistant,
|
||||
"created_at": (row or {}).get("created_at", ""),
|
||||
"updated_at": (row or {}).get("updated_at", ""),
|
||||
@@ -497,6 +579,25 @@ def register_oneliner_routes(app: Any, legacy: Any) -> None:
|
||||
"updated_at": row["updated_at"],
|
||||
}
|
||||
|
||||
def _platform_source_samples(
|
||||
account: dict[str, Any],
|
||||
*,
|
||||
project_id: str,
|
||||
platform: str,
|
||||
limit: int = 3,
|
||||
) -> list[dict[str, Any]]:
|
||||
safe_limit = max(1, min(int(limit or 3), 12))
|
||||
rows = legacy.db.fetch_all(
|
||||
f"""
|
||||
SELECT * FROM content_sources
|
||||
WHERE user_id = ? AND project_id = ? AND platform = ?
|
||||
ORDER BY updated_at DESC
|
||||
LIMIT {safe_limit}
|
||||
""",
|
||||
(account["id"], project_id, platform),
|
||||
)
|
||||
return [legacy.content_source_payload(row) for row in rows]
|
||||
|
||||
def _load_owned_session(session_id: str, account: dict[str, Any]) -> dict[str, Any]:
|
||||
row = legacy.db.fetch_one(
|
||||
"SELECT * FROM oneliner_sessions WHERE id = ? AND user_id = ?",
|
||||
@@ -739,6 +840,29 @@ def register_oneliner_routes(app: Any, legacy: Any) -> None:
|
||||
stored = legacy.db.fetch_one("SELECT * FROM agent_memories WHERE id = ?", (memory_id,))
|
||||
return _memory_payload(stored) if stored else None
|
||||
|
||||
def _remember_platform_observation(
|
||||
account: dict[str, Any],
|
||||
*,
|
||||
project_id: str,
|
||||
platform: str,
|
||||
memory_key: str,
|
||||
title: str,
|
||||
summary: str,
|
||||
details: dict[str, Any],
|
||||
confidence: float = 0.82,
|
||||
) -> dict[str, Any]:
|
||||
request = AgentMemoryUpsertRequest(
|
||||
project_id=project_id,
|
||||
subject_type="project",
|
||||
subject_id=project_id,
|
||||
memory_key=memory_key,
|
||||
title=title,
|
||||
summary=summary,
|
||||
details=details,
|
||||
confidence=confidence,
|
||||
)
|
||||
return _upsert_memory(account, agent_scope="platform", platform=platform, request=request)
|
||||
|
||||
def _session_context_summary(account: dict[str, Any], project_id: str, platform: str) -> dict[str, Any]:
|
||||
project = _resolve_project(account, project_id or None)
|
||||
assistant = None
|
||||
@@ -749,11 +873,43 @@ def register_oneliner_routes(app: Any, legacy: Any) -> None:
|
||||
"SELECT * FROM platform_agent_profiles WHERE user_id = ? AND project_id = ? AND platform = ?",
|
||||
(account["id"], project["id"], platform),
|
||||
) if platform else None
|
||||
oneliner_memory_rows = legacy.db.fetch_all(
|
||||
"""
|
||||
SELECT * FROM agent_memories
|
||||
WHERE user_id = ? AND project_id = ? AND agent_scope = 'oneliner'
|
||||
ORDER BY updated_at DESC
|
||||
LIMIT 3
|
||||
""",
|
||||
(account["id"], project["id"]),
|
||||
)
|
||||
platform_memory_rows = legacy.db.fetch_all(
|
||||
"""
|
||||
SELECT * FROM agent_memories
|
||||
WHERE user_id = ? AND project_id = ? AND agent_scope = 'platform' AND platform = ?
|
||||
ORDER BY updated_at DESC
|
||||
LIMIT 3
|
||||
""",
|
||||
(account["id"], project["id"], platform),
|
||||
) if platform else []
|
||||
platform_skill_rows = legacy.db.fetch_all(
|
||||
"""
|
||||
SELECT * FROM agent_skills
|
||||
WHERE user_id = ? AND project_id = ? AND agent_scope = 'platform' AND platform = ?
|
||||
ORDER BY
|
||||
CASE WHEN status = 'validated' THEN 0 WHEN status = 'draft' THEN 1 ELSE 2 END,
|
||||
updated_at DESC
|
||||
LIMIT 3
|
||||
""",
|
||||
(account["id"], project["id"], platform),
|
||||
) if platform else []
|
||||
return {
|
||||
"project": legacy.project_payload(project),
|
||||
"oneliner_profile": _profile_payload(profile_row, account=account) if profile_row else None,
|
||||
"platform_agent": _platform_agent_payload(account, platform_profile, platform=platform, project_id=project["id"]) if platform else None,
|
||||
"assistant": legacy.assistant_payload(assistant) if assistant else None,
|
||||
"oneliner_memories": [_memory_payload(row) for row in oneliner_memory_rows],
|
||||
"platform_memories": [_memory_payload(row) for row in platform_memory_rows],
|
||||
"platform_skills": [_skill_payload(row) for row in platform_skill_rows],
|
||||
}
|
||||
|
||||
async def _generate_oneliner_reply(
|
||||
@@ -764,6 +920,40 @@ def register_oneliner_routes(app: Any, legacy: Any) -> None:
|
||||
plan: dict[str, Any],
|
||||
) -> dict[str, Any]:
|
||||
context = _session_context_summary(account, project_id or "", plan.get("platform") or "")
|
||||
platform_agent = context.get("platform_agent") or {}
|
||||
primary_action = (plan.get("suggested_actions") or [{}])[0] if plan.get("suggested_actions") else None
|
||||
evidence = []
|
||||
if platform_agent.get("recent_memory"):
|
||||
evidence.append(
|
||||
{
|
||||
"kind": "memory",
|
||||
"title": platform_agent["recent_memory"].get("title") or platform_agent["recent_memory"].get("memory_key") or "最近记忆",
|
||||
"summary": platform_agent["recent_memory"].get("summary", ""),
|
||||
}
|
||||
)
|
||||
if platform_agent.get("recent_skill"):
|
||||
evidence.append(
|
||||
{
|
||||
"kind": "skill",
|
||||
"title": platform_agent["recent_skill"].get("name") or platform_agent["recent_skill"].get("skill_key") or "最近技能",
|
||||
"summary": platform_agent["recent_skill"].get("test_spec", {}).get("summary")
|
||||
or platform_agent["recent_skill"].get("method", {}).get("summary")
|
||||
or "",
|
||||
"score": platform_agent["recent_skill"].get("last_score", 0),
|
||||
}
|
||||
)
|
||||
blocked_reason = ""
|
||||
if plan.get("intent_key") == "ops_admin" and account.get("role") != "super_admin":
|
||||
blocked_reason = "当前账号不是平台最高权限用户,所以不会开放运维 Agent。"
|
||||
elif plan.get("delivery_mode") == "oneliner":
|
||||
blocked_reason = "当前更适合由 OneLiner 对话承接,等前端产品化后再下沉到固定 UI。"
|
||||
next_steps = []
|
||||
if primary_action:
|
||||
next_steps.append(f"优先执行「{primary_action.get('label', primary_action.get('key', '下一步'))}」。")
|
||||
if platform_agent.get("assistant", {}).get("name"):
|
||||
next_steps.append(f"默认调度 {platform_agent['assistant']['name']} 作为执行 Agent。")
|
||||
if evidence:
|
||||
next_steps.append("我会优先参考该平台 Agent 最近沉淀的方法与技能。")
|
||||
summary_lines = [
|
||||
f"我理解你的目标是:{plan.get('intent_label', '自定义任务')}。",
|
||||
f"建议优先处理的平台:{plan.get('platform_label', '待判断')}。",
|
||||
@@ -775,9 +965,78 @@ def register_oneliner_routes(app: Any, legacy: Any) -> None:
|
||||
summary_lines.append("当前账号不是平台最高权限用户,所以我不会放出运维 Agent 入口。")
|
||||
if context.get("platform_agent"):
|
||||
summary_lines.append(f"当前 {context['platform_agent']['platform_label']} Agent 已绑定:{context['platform_agent'].get('assistant', {}).get('name') or '未绑定执行 Agent'}。")
|
||||
if platform_agent.get("recent_memory"):
|
||||
summary_lines.append(f"最近有效经验:{platform_agent['recent_memory'].get('title') or '一条平台记忆'}。")
|
||||
if platform_agent.get("recent_skill"):
|
||||
summary_lines.append(f"最近有效技能:{platform_agent['recent_skill'].get('name') or '一条技能'}。")
|
||||
secondary_actions: list[dict[str, Any]] = []
|
||||
if plan.get("platform"):
|
||||
secondary_actions.append(
|
||||
{
|
||||
"key": "run-oneliner-action",
|
||||
"label": "运行平台自检",
|
||||
"kind": "api_action",
|
||||
"executor_key": "platform-self-check",
|
||||
"platform": plan.get("platform", ""),
|
||||
}
|
||||
)
|
||||
secondary_actions.append(
|
||||
{
|
||||
"key": "open-platform-agent-detail",
|
||||
"label": f"查看{plan.get('platform_label', '平台')} Agent",
|
||||
"kind": "ui_action",
|
||||
"platform": plan.get("platform", ""),
|
||||
}
|
||||
)
|
||||
if plan.get("intent_key") in {"storage_status", "custom"}:
|
||||
secondary_actions.append(
|
||||
{
|
||||
"key": "run-oneliner-action",
|
||||
"label": "查看当前存储状态",
|
||||
"kind": "api_action",
|
||||
"executor_key": "storage-status",
|
||||
"platform": plan.get("platform", ""),
|
||||
}
|
||||
)
|
||||
if plan.get("intent_key") == "live_recorder":
|
||||
secondary_actions.append(
|
||||
{
|
||||
"key": "run-oneliner-action",
|
||||
"label": "查看录制状态",
|
||||
"kind": "api_action",
|
||||
"executor_key": "live-recorder-status",
|
||||
"platform": plan.get("platform", ""),
|
||||
}
|
||||
)
|
||||
if account.get("role") == "super_admin":
|
||||
secondary_actions.append(
|
||||
{
|
||||
"key": "run-oneliner-action",
|
||||
"label": "重新扫描故障",
|
||||
"kind": "api_action",
|
||||
"executor_key": "scan-admin-ops",
|
||||
"platform": "",
|
||||
}
|
||||
)
|
||||
return {
|
||||
"summary_text": "\n".join([line for line in summary_lines if line.strip()]),
|
||||
"context": context,
|
||||
"execution_card": {
|
||||
"intent_key": plan.get("intent_key", "custom"),
|
||||
"intent_label": plan.get("intent_label", "自定义任务"),
|
||||
"delivery_mode": plan.get("delivery_mode", "oneliner"),
|
||||
"platform": plan.get("platform", ""),
|
||||
"platform_label": plan.get("platform_label", "待判断"),
|
||||
"platform_agent_name": platform_agent.get("name") or "",
|
||||
"assistant_name": platform_agent.get("assistant", {}).get("name") or context.get("assistant", {}).get("name") or "",
|
||||
"readiness_label": platform_agent.get("readiness_label") or "",
|
||||
"readiness_score": platform_agent.get("readiness_score") or 0,
|
||||
"primary_action": primary_action or {},
|
||||
"blocked_reason": blocked_reason,
|
||||
"evidence": evidence,
|
||||
"next_steps": next_steps,
|
||||
"secondary_actions": secondary_actions,
|
||||
},
|
||||
"safe_boundary": {
|
||||
"core_code_locked": True,
|
||||
"tenant_isolation": True,
|
||||
@@ -800,6 +1059,12 @@ def register_oneliner_routes(app: Any, legacy: Any) -> None:
|
||||
def _upsert_platform_profile(account: dict[str, Any], platform: str, request: PlatformAgentProfileRequest) -> dict[str, Any]:
|
||||
project = _resolve_project(account, request.project_id or None)
|
||||
assistant = _resolve_assistant(account, request.assistant_id or None, project["id"])
|
||||
if not assistant:
|
||||
fallback_profile = _fetch_profile_row(account, project["id"]) or _ensure_oneliner_profile(account, project["id"])
|
||||
if fallback_profile.get("assistant_id"):
|
||||
assistant = _resolve_assistant(account, fallback_profile.get("assistant_id"), project["id"])
|
||||
if not assistant:
|
||||
assistant = _resolve_assistant(account, None, project["id"])
|
||||
existing = legacy.db.fetch_one(
|
||||
"SELECT * FROM platform_agent_profiles WHERE user_id = ? AND project_id = ? AND platform = ?",
|
||||
(account["id"], project["id"], platform),
|
||||
@@ -1105,6 +1370,287 @@ def register_oneliner_routes(app: Any, legacy: Any) -> None:
|
||||
"count": len(created),
|
||||
}
|
||||
|
||||
def _admin_ops_overview_payload(admin: dict[str, Any]) -> dict[str, Any]:
|
||||
incidents = [
|
||||
_incident_payload(row)
|
||||
for row in legacy.db.fetch_all(
|
||||
"SELECT * FROM admin_ops_incidents ORDER BY updated_at DESC LIMIT 50"
|
||||
)
|
||||
]
|
||||
open_incidents = [item for item in incidents if item.get("status") in {"open", "watching", ""}]
|
||||
severity_counts = {
|
||||
"error": len([item for item in incidents if item.get("severity") == "error"]),
|
||||
"warn": len([item for item in incidents if item.get("severity") == "warn"]),
|
||||
"info": len([item for item in incidents if item.get("severity") == "info"]),
|
||||
}
|
||||
failed_jobs = [
|
||||
legacy.job_payload(row)
|
||||
for row in legacy.db.fetch_all(
|
||||
"SELECT * FROM jobs WHERE status = 'failed' ORDER BY updated_at DESC LIMIT 12"
|
||||
)
|
||||
]
|
||||
pending_accounts = [
|
||||
legacy.normalize_account(row)
|
||||
for row in legacy.db.fetch_all("SELECT * FROM accounts WHERE approval_status = 'pending' ORDER BY created_at ASC LIMIT 20")
|
||||
]
|
||||
return {
|
||||
"incidents": incidents,
|
||||
"incident_count": len(incidents),
|
||||
"open_incident_count": len(open_incidents),
|
||||
"severity_counts": severity_counts,
|
||||
"failed_jobs": failed_jobs,
|
||||
"failed_job_count": len(failed_jobs),
|
||||
"pending_accounts": pending_accounts,
|
||||
"pending_account_count": len(pending_accounts),
|
||||
"integration_health": legacy.integrations_health(admin),
|
||||
}
|
||||
|
||||
def _platform_self_check(
|
||||
account: dict[str, Any],
|
||||
*,
|
||||
platform: str,
|
||||
project_id: str,
|
||||
sample_limit: int = 3,
|
||||
remember_summary: bool = True,
|
||||
) -> dict[str, Any]:
|
||||
project = _resolve_project(account, project_id or None)
|
||||
normalized_platform = _safe_platform(platform)
|
||||
profile = _platform_agent_payload(
|
||||
account,
|
||||
legacy.db.fetch_one(
|
||||
"SELECT * FROM platform_agent_profiles WHERE user_id = ? AND project_id = ? AND platform = ?",
|
||||
(account["id"], project["id"], normalized_platform),
|
||||
),
|
||||
platform=normalized_platform,
|
||||
project_id=project["id"],
|
||||
)
|
||||
route_checks = _platform_route_checks(normalized_platform)
|
||||
route_ok_count = len([item for item in route_checks if item["ok"]])
|
||||
route_ratio = (route_ok_count / len(route_checks)) if route_checks else 0
|
||||
source_samples = _platform_source_samples(account, project_id=project["id"], platform=normalized_platform, limit=sample_limit)
|
||||
signal_checks = [
|
||||
("配置激活", bool(profile.get("status") == "active")),
|
||||
("已绑定执行 Agent", bool(profile.get("assistant_id"))),
|
||||
("已有平台记忆", bool(profile.get("memory_count"))),
|
||||
("已有平台技能", bool(profile.get("skill_count"))),
|
||||
("已有平台账号源", bool(source_samples)),
|
||||
]
|
||||
signal_score = sum(1 for _, ok in signal_checks if ok) * 12
|
||||
route_score = int(route_ratio * 40)
|
||||
score = min(100, signal_score + route_score)
|
||||
if score >= 85:
|
||||
verdict = "validated"
|
||||
label = "稳定"
|
||||
elif score >= 60:
|
||||
verdict = "usable"
|
||||
label = "可用"
|
||||
else:
|
||||
verdict = "needs_work"
|
||||
label = "待加强"
|
||||
suggestions = []
|
||||
if not profile.get("assistant_id"):
|
||||
suggestions.append("先给平台 Agent 绑定一个执行 Agent。")
|
||||
if not profile.get("memory_count"):
|
||||
suggestions.append("补一条平台记忆,沉淀最近有效经验。")
|
||||
if not profile.get("skill_count"):
|
||||
suggestions.append("补一条可验收的平台技能。")
|
||||
if not source_samples:
|
||||
suggestions.append("先导入至少一个该平台账号源,避免空跑。")
|
||||
if route_ratio < 1:
|
||||
suggestions.append("补齐当前平台 workbench 路由,避免调度时出现断点。")
|
||||
payload = {
|
||||
"platform": normalized_platform,
|
||||
"platform_label": legacy.platform_label(normalized_platform),
|
||||
"project_id": project["id"],
|
||||
"score": score,
|
||||
"readiness_label": label,
|
||||
"verdict": verdict,
|
||||
"route_checks": route_checks,
|
||||
"signals": [{"label": name, "ok": ok} for name, ok in signal_checks],
|
||||
"source_count": len(source_samples),
|
||||
"source_samples": source_samples,
|
||||
"checked_at": now(),
|
||||
"suggestions": suggestions,
|
||||
"profile": profile,
|
||||
}
|
||||
if remember_summary:
|
||||
_remember_platform_observation(
|
||||
account,
|
||||
project_id=project["id"],
|
||||
platform=normalized_platform,
|
||||
memory_key=f"self_check::{normalized_platform}",
|
||||
title=f"{legacy.platform_label(normalized_platform)} Agent 自检",
|
||||
summary=f"平台自检得分 {score},当前判定为{label}。",
|
||||
details=payload,
|
||||
confidence=0.88 if score >= 85 else 0.72,
|
||||
)
|
||||
return payload
|
||||
|
||||
def _review_platform_skill(
|
||||
account: dict[str, Any],
|
||||
*,
|
||||
platform: str,
|
||||
skill_id: str,
|
||||
request: PlatformSkillReviewRequest,
|
||||
) -> dict[str, Any]:
|
||||
project = _resolve_project(account, request.project_id or None)
|
||||
normalized_platform = _safe_platform(platform)
|
||||
current = legacy.db.fetch_one(
|
||||
"""
|
||||
SELECT * FROM agent_skills
|
||||
WHERE id = ? AND user_id = ? AND project_id = ? AND agent_scope = 'platform' AND platform = ?
|
||||
""",
|
||||
(skill_id, account["id"], project["id"], normalized_platform),
|
||||
)
|
||||
if not current:
|
||||
raise HTTPException(status_code=404, detail="Platform skill not found")
|
||||
accepted = bool(request.accepted)
|
||||
next_status = (request.status or "").strip() or ("validated" if accepted else "needs_revision")
|
||||
timestamp = now()
|
||||
next_success = int(current.get("success_count") or 0) + (1 if accepted else 0)
|
||||
next_failure = int(current.get("failure_count") or 0) + (0 if accepted else 1)
|
||||
result_payload = {
|
||||
**_parse_json(current.get("last_result_json"), {}),
|
||||
"accepted": accepted,
|
||||
"review_notes": request.review_notes.strip(),
|
||||
"summary": request.summary.strip(),
|
||||
"reviewed_at": timestamp,
|
||||
"reviewed_by": account["id"],
|
||||
}
|
||||
legacy.db.execute(
|
||||
"""
|
||||
UPDATE agent_skills
|
||||
SET status = ?, last_result_json = ?, success_count = ?, failure_count = ?, last_score = ?, last_validated_at = ?, updated_at = ?
|
||||
WHERE id = ?
|
||||
""",
|
||||
(
|
||||
next_status,
|
||||
_dump(result_payload),
|
||||
next_success,
|
||||
next_failure,
|
||||
request.score,
|
||||
timestamp,
|
||||
timestamp,
|
||||
skill_id,
|
||||
),
|
||||
)
|
||||
updated = legacy.db.fetch_one("SELECT * FROM agent_skills WHERE id = ?", (skill_id,))
|
||||
feedback_summary = (request.summary or request.review_notes or "").strip()
|
||||
feedback_memory = None
|
||||
if feedback_summary:
|
||||
feedback_memory = _remember_platform_observation(
|
||||
account,
|
||||
project_id=project["id"],
|
||||
platform=normalized_platform,
|
||||
memory_key=f"skill_feedback::{current.get('skill_key')}",
|
||||
title=f"{current.get('name') or current.get('skill_key') or '技能'}·{'已验证' if accepted else '待优化'}",
|
||||
summary=feedback_summary[:280],
|
||||
details={
|
||||
"skill_id": skill_id,
|
||||
"skill_key": current.get("skill_key", ""),
|
||||
"accepted": accepted,
|
||||
"score": request.score,
|
||||
"review_notes": request.review_notes.strip(),
|
||||
"status": next_status,
|
||||
},
|
||||
confidence=0.9 if accepted else 0.66,
|
||||
)
|
||||
payload = _skill_payload(updated)
|
||||
if feedback_memory:
|
||||
payload["feedback_memory"] = feedback_memory
|
||||
return payload
|
||||
|
||||
async def _execute_oneliner_action(
|
||||
account: dict[str, Any],
|
||||
request: OneLinerActionExecuteRequest,
|
||||
) -> dict[str, Any]:
|
||||
project = _resolve_project(account, request.project_id or None)
|
||||
normalized_platform = normalize_platform_from_text(request.platform) or _safe_platform(request.platform or "", fallback="")
|
||||
action_key = (request.action_key or "").strip()
|
||||
if not action_key:
|
||||
raise HTTPException(status_code=400, detail="Action key is required")
|
||||
|
||||
async def _run_platform_self_check() -> dict[str, Any]:
|
||||
if not normalized_platform:
|
||||
raise HTTPException(status_code=400, detail="Platform is required for self-check")
|
||||
payload = _platform_self_check(
|
||||
account,
|
||||
platform=normalized_platform,
|
||||
project_id=project["id"],
|
||||
sample_limit=int((request.payload or {}).get("sample_limit") or 3),
|
||||
remember_summary=True,
|
||||
)
|
||||
return {
|
||||
"title": f"{payload['platform_label']} Agent 自检",
|
||||
"summary": f"平台自检得分 {payload['score']},当前状态:{payload['readiness_label']}。",
|
||||
"payload": payload,
|
||||
}
|
||||
|
||||
async def _run_storage_status() -> dict[str, Any]:
|
||||
payload = legacy.storage_status(project_id=project["id"], account=account)
|
||||
tenant_usage = payload.get("tenant_usage", {})
|
||||
return {
|
||||
"title": "当前存储状态",
|
||||
"summary": (
|
||||
f"项目 jobs 占用 {tenant_usage.get('project_jobs', {}).get('human_size', '0B')},"
|
||||
f"downloads 占用 {tenant_usage.get('project_downloads', {}).get('human_size', '0B')}。"
|
||||
),
|
||||
"payload": payload,
|
||||
}
|
||||
|
||||
async def _run_live_recorder_status() -> dict[str, Any]:
|
||||
payload = legacy.live_recorder_status(project_id=project["id"], account=account)
|
||||
return {
|
||||
"title": "直播录制状态",
|
||||
"summary": f"当前共 {len(payload.get('items', []))} 条录制源,最近文件 {len(payload.get('files', []))} 个。",
|
||||
"payload": payload,
|
||||
}
|
||||
|
||||
async def _run_ops_scan() -> dict[str, Any]:
|
||||
admin = legacy.require_super_admin(account)
|
||||
payload = _scan_admin_incidents(admin)
|
||||
return {
|
||||
"title": "运维 Agent 故障扫描",
|
||||
"summary": f"本轮共归集 {payload.get('count', 0)} 条事件。",
|
||||
"payload": payload,
|
||||
}
|
||||
|
||||
executors = {
|
||||
"platform-self-check": _run_platform_self_check,
|
||||
"storage-status": _run_storage_status,
|
||||
"live-recorder-status": _run_live_recorder_status,
|
||||
"scan-admin-ops": _run_ops_scan,
|
||||
}
|
||||
executor = executors.get(action_key)
|
||||
if not executor:
|
||||
raise HTTPException(status_code=400, detail=f"Unsupported OneLiner action: {action_key}")
|
||||
result = await executor()
|
||||
if request.session_id:
|
||||
session = _load_owned_session(request.session_id, account)
|
||||
_insert_message(
|
||||
session["id"],
|
||||
account["id"],
|
||||
"assistant",
|
||||
result["summary"],
|
||||
{
|
||||
"intent_key": "custom",
|
||||
"delivery_mode": "oneliner",
|
||||
"platform": normalized_platform,
|
||||
"suggested_actions": [],
|
||||
},
|
||||
{
|
||||
"summary_text": result["summary"],
|
||||
"execution_result": result,
|
||||
},
|
||||
)
|
||||
return {
|
||||
"action_key": action_key,
|
||||
"project_id": project["id"],
|
||||
"platform": normalized_platform,
|
||||
"executed_at": now(),
|
||||
**result,
|
||||
}
|
||||
|
||||
@app.get("/v2/oneliner/profile")
|
||||
def get_oneliner_profile(
|
||||
project_id: str | None = Query(default=None),
|
||||
@@ -1265,6 +1811,13 @@ def register_oneliner_routes(app: Any, legacy: Any) -> None:
|
||||
"result": result,
|
||||
}
|
||||
|
||||
@app.post("/v2/oneliner/actions/execute")
|
||||
async def execute_oneliner_action(
|
||||
request: OneLinerActionExecuteRequest,
|
||||
account: dict[str, Any] = Depends(legacy.require_approved),
|
||||
) -> dict[str, Any]:
|
||||
return await _execute_oneliner_action(account, request)
|
||||
|
||||
@app.get("/v2/platform-agents")
|
||||
def list_platform_agents(
|
||||
project_id: str | None = Query(default=None),
|
||||
@@ -1349,30 +1902,34 @@ def register_oneliner_routes(app: Any, legacy: Any) -> None:
|
||||
normalized_platform = _safe_platform(platform)
|
||||
return _upsert_skill(account, agent_scope="platform", platform=normalized_platform, request=request, skill_id=skill_id)
|
||||
|
||||
@app.post("/v2/platform-agents/{platform}/self-check")
|
||||
def run_platform_agent_self_check(
|
||||
platform: str,
|
||||
request: PlatformAgentSelfCheckRequest,
|
||||
account: dict[str, Any] = Depends(legacy.require_approved),
|
||||
) -> dict[str, Any]:
|
||||
normalized_platform = _safe_platform(platform)
|
||||
return _platform_self_check(
|
||||
account,
|
||||
platform=normalized_platform,
|
||||
project_id=request.project_id,
|
||||
sample_limit=request.sample_limit,
|
||||
remember_summary=request.remember_summary,
|
||||
)
|
||||
|
||||
@app.post("/v2/platform-agents/{platform}/skills/{skill_id}/review")
|
||||
def review_platform_skill(
|
||||
platform: str,
|
||||
skill_id: str,
|
||||
request: PlatformSkillReviewRequest,
|
||||
account: dict[str, Any] = Depends(legacy.require_approved),
|
||||
) -> dict[str, Any]:
|
||||
normalized_platform = _safe_platform(platform)
|
||||
return _review_platform_skill(account, platform=normalized_platform, skill_id=skill_id, request=request)
|
||||
|
||||
@app.get("/v2/admin/ops/overview")
|
||||
def admin_ops_overview(admin: dict[str, Any] = Depends(legacy.require_super_admin)) -> dict[str, Any]:
|
||||
incidents = [
|
||||
_incident_payload(row)
|
||||
for row in legacy.db.fetch_all(
|
||||
"SELECT * FROM admin_ops_incidents ORDER BY updated_at DESC LIMIT 50"
|
||||
)
|
||||
]
|
||||
failed_jobs = [
|
||||
legacy.job_payload(row)
|
||||
for row in legacy.db.fetch_all(
|
||||
"SELECT * FROM jobs WHERE status = 'failed' ORDER BY updated_at DESC LIMIT 12"
|
||||
)
|
||||
]
|
||||
pending_accounts = [legacy.normalize_account(row) for row in legacy.db.fetch_all("SELECT * FROM accounts WHERE approval_status = 'pending' ORDER BY created_at ASC LIMIT 20")]
|
||||
return {
|
||||
"incidents": incidents,
|
||||
"incident_count": len(incidents),
|
||||
"failed_jobs": failed_jobs,
|
||||
"failed_job_count": len(failed_jobs),
|
||||
"pending_accounts": pending_accounts,
|
||||
"pending_account_count": len(pending_accounts),
|
||||
"integration_health": legacy.integrations_health(admin),
|
||||
}
|
||||
return _admin_ops_overview_payload(admin)
|
||||
|
||||
@app.post("/v2/admin/ops/incidents/scan")
|
||||
def admin_ops_scan(admin: dict[str, Any] = Depends(legacy.require_super_admin)) -> dict[str, Any]:
|
||||
|
||||
@@ -716,7 +716,9 @@ function renderOneLinerMessagesHtml() {
|
||||
const roleClass = message.role === "assistant" ? "assistant" : "user";
|
||||
const result = message.result || {};
|
||||
const plan = message.plan || {};
|
||||
const executionCard = result.execution_card || {};
|
||||
const actions = safeArray(plan.suggested_actions);
|
||||
const secondaryActions = safeArray(executionCard.secondary_actions);
|
||||
return `
|
||||
<div class="oneliner-message ${roleClass}">
|
||||
<div class="oneliner-bubble">
|
||||
@@ -734,6 +736,47 @@ function renderOneLinerMessagesHtml() {
|
||||
${actions.map((item) => `<span class="tag clickable-tag" data-action="${escapeHtml(item.key)}">${escapeHtml(item.label)}</span>`).join("")}
|
||||
</div>
|
||||
` : ""}
|
||||
${message.role === "assistant" && (executionCard.intent_label || executionCard.platform_label || executionCard.primary_action?.label || safeArray(executionCard.evidence).length) ? `
|
||||
<div class="task-item compact" style="margin-top:12px;">
|
||||
<h4>${escapeHtml(executionCard.intent_label || "本轮执行建议")}</h4>
|
||||
<p>${escapeHtml(executionCard.blocked_reason || `${executionCard.platform_label || "待判断平台"} · ${executionCard.delivery_mode === "ui" ? "优先走前端固定动作" : "优先由 OneLiner 对话承接"}`)}</p>
|
||||
<div class="task-meta">
|
||||
${executionCard.platform_label ? `<span class="tag blue">${escapeHtml(executionCard.platform_label)}</span>` : ""}
|
||||
${executionCard.platform_agent_name ? `<span class="tag">${escapeHtml(executionCard.platform_agent_name)}</span>` : ""}
|
||||
${executionCard.assistant_name ? `<span class="tag green">${escapeHtml(executionCard.assistant_name)}</span>` : ""}
|
||||
${executionCard.readiness_label ? `<span class="tag ${executionCard.readiness_score >= 75 ? "green" : executionCard.readiness_score >= 50 ? "blue" : "orange"}">${escapeHtml(executionCard.readiness_label)} ${escapeHtml(formatNumber(executionCard.readiness_score || 0))}</span>` : ""}
|
||||
${executionCard.primary_action?.key ? `<span class="tag clickable-tag" data-action="${escapeHtml(executionCard.primary_action.key)}">${escapeHtml(executionCard.primary_action.label || "执行下一步")}</span>` : ""}
|
||||
</div>
|
||||
${safeArray(executionCard.evidence).length ? `
|
||||
<div class="list" style="margin-top:10px;">
|
||||
${safeArray(executionCard.evidence).slice(0, 2).map((item) => `
|
||||
<div class="task-item compact">
|
||||
<h4>${escapeHtml(item.kind === "skill" ? "技能证据" : "记忆证据")} · ${escapeHtml(item.title || "未命名")}</h4>
|
||||
<p>${escapeHtml(item.summary || "暂无摘要")}</p>
|
||||
</div>
|
||||
`).join("")}
|
||||
</div>
|
||||
` : ""}
|
||||
${safeArray(executionCard.next_steps).length ? `
|
||||
<div class="task-meta" style="margin-top:10px;">
|
||||
${safeArray(executionCard.next_steps).slice(0, 3).map((item) => `<span class="tag">${escapeHtml(item)}</span>`).join("")}
|
||||
</div>
|
||||
` : ""}
|
||||
${secondaryActions.length ? `
|
||||
<div class="task-meta" style="margin-top:10px;">
|
||||
${secondaryActions.map((item) => actionTag(
|
||||
item.label || item.key || "执行",
|
||||
item.key || "",
|
||||
[
|
||||
item.executor_key ? `data-executor-key="${escapeHtml(item.executor_key)}"` : "",
|
||||
item.platform ? `data-platform="${escapeHtml(item.platform)}"` : "",
|
||||
message.session_id ? `data-session-id="${escapeHtml(message.session_id)}"` : ""
|
||||
].filter(Boolean).join(" ")
|
||||
)).join("")}
|
||||
</div>
|
||||
` : ""}
|
||||
</div>
|
||||
` : ""}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -1073,6 +1116,108 @@ async function submitOneLinerMessage(content) {
|
||||
return payload;
|
||||
}
|
||||
|
||||
function renderOneLinerExecutionPayloadHtml(payload) {
|
||||
if (!payload || typeof payload !== "object") {
|
||||
return `<div class="task-item compact"><h4>没有返回执行结果</h4><p>当前执行器没有附带额外数据。</p></div>`;
|
||||
}
|
||||
if (payload.route_checks) {
|
||||
return `
|
||||
<div class="detail-grid">
|
||||
<div class="mini-card"><small>平台</small><strong>${escapeHtml(payload.platform_label || payload.platform || "-")}</strong></div>
|
||||
<div class="mini-card"><small>得分</small><strong>${escapeHtml(formatNumber(payload.score || 0))}</strong></div>
|
||||
<div class="mini-card"><small>状态</small><strong>${escapeHtml(payload.readiness_label || payload.verdict || "-")}</strong></div>
|
||||
<div class="mini-card"><small>账号源</small><strong>${escapeHtml(formatNumber(payload.source_count || 0))}</strong></div>
|
||||
</div>
|
||||
<div class="two-col" style="margin-top:12px;">
|
||||
<div>
|
||||
<div class="panel-subtitle">路由检查</div>
|
||||
<div class="list" style="margin-top:8px;">
|
||||
${safeArray(payload.route_checks).map((item) => `
|
||||
<div class="task-item compact">
|
||||
<h4>${escapeHtml(item.key || item.path || "route")}</h4>
|
||||
<p>${escapeHtml(item.path || "")}</p>
|
||||
<div class="task-meta"><span class="tag ${item.ok ? "green" : "orange"}">${escapeHtml(item.ok ? "可用" : "缺失")}</span></div>
|
||||
</div>
|
||||
`).join("")}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="panel-subtitle">建议动作</div>
|
||||
<div class="list" style="margin-top:8px;">
|
||||
${(safeArray(payload.suggestions).length ? safeArray(payload.suggestions) : ["当前已经达到可运行状态。"]).map((item) => `
|
||||
<div class="task-item compact">
|
||||
<h4>下一步</h4>
|
||||
<p>${escapeHtml(item)}</p>
|
||||
</div>
|
||||
`).join("")}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
if (payload.strategy && payload.tenant_usage) {
|
||||
return `
|
||||
<div class="detail-grid">
|
||||
<div class="mini-card"><small>jobs</small><strong>${escapeHtml(payload.tenant_usage?.project_jobs?.human_size || "0B")}</strong></div>
|
||||
<div class="mini-card"><small>downloads</small><strong>${escapeHtml(payload.tenant_usage?.project_downloads?.human_size || "0B")}</strong></div>
|
||||
<div class="mini-card"><small>模型目录</small><strong>${escapeHtml(payload.strategy?.models?.mode || "-")}</strong></div>
|
||||
<div class="mini-card"><small>录像</small><strong>${escapeHtml(payload.strategy?.live_recorder?.mode || "-")}</strong></div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
if (payload.items || payload.files) {
|
||||
return `
|
||||
<div class="detail-grid">
|
||||
<div class="mini-card"><small>录制源</small><strong>${escapeHtml(formatNumber(safeArray(payload.items).length))}</strong></div>
|
||||
<div class="mini-card"><small>最近文件</small><strong>${escapeHtml(formatNumber(safeArray(payload.files).length))}</strong></div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
return `
|
||||
<div class="task-item compact">
|
||||
<h4>原始结果</h4>
|
||||
<p>${escapeHtml(brief(JSON.stringify(payload, null, 2), 1200))}</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
async function executeOneLinerAction(executorKey, options = {}) {
|
||||
if (!backendSupports("/v2/oneliner/actions/execute")) {
|
||||
throw new Error("当前后端还没有接入 OneLiner 动作执行器。");
|
||||
}
|
||||
const projectId = getOneLinerProjectId();
|
||||
const session = getCurrentOneLinerSession() || await ensureOneLinerSession();
|
||||
const payload = await storyforgeFetch("/v2/oneliner/actions/execute", {
|
||||
method: "POST",
|
||||
body: {
|
||||
action_key: executorKey,
|
||||
project_id: projectId,
|
||||
platform: options.platform || getPreferredPlatform(),
|
||||
session_id: session?.id || "",
|
||||
payload: options.payload || {}
|
||||
}
|
||||
});
|
||||
await loadAgentControlSurfaces(projectId);
|
||||
if (appState.selectedOnelinerSessionId) {
|
||||
await loadOneLinerMessages(appState.selectedOnelinerSessionId);
|
||||
}
|
||||
openActionModal({
|
||||
title: payload.title || "OneLiner 执行结果",
|
||||
description: payload.summary || "已完成一次对话内执行。",
|
||||
hideSubmit: true,
|
||||
fields: [
|
||||
{
|
||||
type: "html",
|
||||
label: "执行结果",
|
||||
html: `<div class="sheet-html">${renderOneLinerExecutionPayloadHtml(payload.payload || {})}</div>`
|
||||
}
|
||||
]
|
||||
});
|
||||
rememberAction("OneLiner 已执行", payload.summary || "当前动作已在对话内执行。", "green", payload);
|
||||
renderAll();
|
||||
return payload;
|
||||
}
|
||||
|
||||
async function loadPlatformAccount(platform, accountId) {
|
||||
if (!accountId) return;
|
||||
const normalizedPlatform = normalizePlatformValue(platform, getPreferredPlatform());
|
||||
@@ -1928,11 +2073,33 @@ function renderPlatformAgentPanel() {
|
||||
<div class="cell-desc">${escapeHtml(item.mission || item.notes || "先绑定执行 Agent,再补任务目标和方法论。")}</div>
|
||||
<div class="entity-meta">
|
||||
<span class="tag ${item.status === "active" ? "green" : "blue"}">${escapeHtml(item.status || "draft")}</span>
|
||||
${item.readiness_label ? `<span class="tag ${item.readiness_score >= 75 ? "green" : item.readiness_score >= 50 ? "blue" : "orange"}">${escapeHtml(item.readiness_label)} ${escapeHtml(formatNumber(item.readiness_score || 0))}</span>` : ""}
|
||||
<span class="tag">记忆 ${escapeHtml(formatNumber(item.memory_count))}</span>
|
||||
<span class="tag">技能 ${escapeHtml(formatNumber(item.skill_count))}</span>
|
||||
<span class="tag">${escapeHtml(item.assistant?.name || "未绑 Agent")}</span>
|
||||
</div>
|
||||
${item.recent_memory || item.recent_skill ? `
|
||||
<div class="list" style="margin-top:10px;">
|
||||
${item.recent_memory ? `
|
||||
<div class="task-item compact">
|
||||
<h4>最近记忆 · ${escapeHtml(item.recent_memory.title || item.recent_memory.memory_key || "未命名")}</h4>
|
||||
<p>${escapeHtml(brief(item.recent_memory.summary || "暂无摘要", 68))}</p>
|
||||
</div>
|
||||
` : ""}
|
||||
${item.recent_skill ? `
|
||||
<div class="task-item compact">
|
||||
<h4>最近技能 · ${escapeHtml(item.recent_skill.name || item.recent_skill.skill_key || "未命名")}</h4>
|
||||
<p>${escapeHtml(brief(item.recent_skill.test_spec?.summary || item.recent_skill.method?.summary || "暂无方法摘要", 68))}</p>
|
||||
<div class="task-meta">
|
||||
<span class="tag">${escapeHtml(item.recent_skill.status || "draft")}</span>
|
||||
<span class="tag blue">得分 ${escapeHtml(formatNumber(item.recent_skill.last_score || 0))}</span>
|
||||
</div>
|
||||
</div>
|
||||
` : ""}
|
||||
</div>
|
||||
` : ""}
|
||||
<div class="task-meta" style="margin-top:10px;">
|
||||
<span class="tag clickable-tag" data-action="open-platform-agent-detail" data-platform="${escapeHtml(item.platform)}">查看详情</span>
|
||||
<span class="tag clickable-tag" data-action="open-platform-agent-profile" data-platform="${escapeHtml(item.platform)}">配置</span>
|
||||
<span class="tag clickable-tag" data-action="open-platform-agent-memory" data-platform="${escapeHtml(item.platform)}">补记忆</span>
|
||||
<span class="tag clickable-tag" data-action="open-platform-agent-skill" data-platform="${escapeHtml(item.platform)}">补技能</span>
|
||||
@@ -1965,6 +2132,8 @@ function renderAdminOpsPanel() {
|
||||
</div>
|
||||
<div class="task-meta">
|
||||
<span class="tag blue">${escapeHtml(formatNumber(overview.incident_count))} 条事件</span>
|
||||
<span class="tag orange">待处理 ${escapeHtml(formatNumber(overview.open_incident_count || 0))}</span>
|
||||
<span class="tag red">错误 ${escapeHtml(formatNumber(overview.severity_counts?.error || 0))}</span>
|
||||
<span class="tag">${escapeHtml(formatNumber(overview.failed_job_count))} 个失败任务</span>
|
||||
<span class="tag clickable-tag" data-action="scan-admin-ops">重新扫描</span>
|
||||
</div>
|
||||
@@ -1977,7 +2146,11 @@ function renderAdminOpsPanel() {
|
||||
<div class="task-meta">
|
||||
<span class="tag ${item.severity === "error" ? "red" : "orange"}">${escapeHtml(item.severity || "warn")}</span>
|
||||
<span class="tag">${escapeHtml(item.status || "open")}</span>
|
||||
${item.source_type ? `<span class="tag">${escapeHtml(item.source_type)}</span>` : ""}
|
||||
${item.tenant_user_id ? `<span class="tag">租户 ${escapeHtml(brief(item.tenant_user_id, 12))}</span>` : ""}
|
||||
${item.source_type === "job" ? actionTag("看任务详情", "open-job-detail", `data-job-id="${escapeHtml(item.source_id || "")}"`) : ""}
|
||||
${item.source_type === "integration" ? actionTag("去自动流程", "goto-automation") : ""}
|
||||
${item.tenant_project_id ? actionTag("去生产中心", "goto-production") : ""}
|
||||
<span class="tag clickable-tag" data-action="review-admin-incident" data-incident-id="${escapeHtml(item.id)}">审计处理</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -3770,6 +3943,129 @@ function openPlatformAgentSkillAction(platform) {
|
||||
});
|
||||
}
|
||||
|
||||
async function openPlatformAgentDetailAction(platform) {
|
||||
const project = requireSelectedProject();
|
||||
const normalizedPlatform = normalizePlatformValue(platform, getPreferredPlatform());
|
||||
const profile = safeArray(appState.platformAgents).find((item) => item.platform === normalizedPlatform) || null;
|
||||
if (!profile) {
|
||||
alert("没有找到这个平台 Agent。");
|
||||
return;
|
||||
}
|
||||
const [memoriesPayload, skillsPayload] = await Promise.all([
|
||||
storyforgeFetch(`/v2/platform-agents/${encodeURIComponent(normalizedPlatform)}/memories?project_id=${encodeURIComponent(project.id)}`).catch(() => ({ items: [] })),
|
||||
storyforgeFetch(`/v2/platform-agents/${encodeURIComponent(normalizedPlatform)}/skills?project_id=${encodeURIComponent(project.id)}`).catch(() => ({ items: [] }))
|
||||
]);
|
||||
const memories = safeArray(memoriesPayload?.items || memoriesPayload).slice(0, 6);
|
||||
const skills = safeArray(skillsPayload?.items || skillsPayload).slice(0, 6);
|
||||
openActionModal({
|
||||
title: `${platformLabel(normalizedPlatform)} Agent 详情`,
|
||||
description: "查看当前平台 Agent 最近沉淀的记忆、技能和就绪度。",
|
||||
hideSubmit: true,
|
||||
fields: [
|
||||
{
|
||||
type: "html",
|
||||
label: "详情",
|
||||
html: `
|
||||
<div class="sheet-html">
|
||||
<div class="task-item compact">
|
||||
<h4>${escapeHtml(profile.name || `${platformLabel(normalizedPlatform)} Agent`)}</h4>
|
||||
<p>${escapeHtml(profile.mission || profile.notes || "暂无任务目标说明")}</p>
|
||||
<div class="task-meta">
|
||||
<span class="tag ${profile.status === "active" ? "green" : "blue"}">${escapeHtml(profile.status || "draft")}</span>
|
||||
${profile.readiness_label ? `<span class="tag ${profile.readiness_score >= 75 ? "green" : profile.readiness_score >= 50 ? "blue" : "orange"}">${escapeHtml(profile.readiness_label)} ${escapeHtml(formatNumber(profile.readiness_score || 0))}</span>` : ""}
|
||||
<span class="tag">${escapeHtml(profile.assistant?.name || "未绑执行 Agent")}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="two-col" style="margin-top:12px;">
|
||||
<div>
|
||||
<div class="panel-subtitle">最近记忆</div>
|
||||
<div class="list" style="margin-top:8px;">
|
||||
${memories.map((item) => `
|
||||
<div class="task-item compact">
|
||||
<h4>${escapeHtml(item.title || item.memory_key || "未命名")}</h4>
|
||||
<p>${escapeHtml(item.summary || "暂无摘要")}</p>
|
||||
<div class="task-meta"><span class="tag blue">${escapeHtml(item.memory_key || "memory")}</span><span class="tag">${escapeHtml(formatNumber(item.confidence || 0))}</span></div>
|
||||
</div>
|
||||
`).join("") || `<div class="task-item compact"><h4>还没有平台记忆</h4><p>先把这段时间验证有效的方法沉淀进来。</p></div>`}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="panel-subtitle">最近技能</div>
|
||||
<div class="list" style="margin-top:8px;">
|
||||
${skills.map((item) => `
|
||||
<div class="task-item compact">
|
||||
<h4>${escapeHtml(item.name || item.skill_key || "未命名")}</h4>
|
||||
<p>${escapeHtml(item.test_spec?.summary || item.method?.summary || "暂无方法摘要")}</p>
|
||||
<div class="task-meta">
|
||||
<span class="tag ${item.status === "validated" ? "green" : item.status === "needs_revision" ? "orange" : "blue"}">${escapeHtml(item.status || "draft")}</span>
|
||||
<span class="tag blue">得分 ${escapeHtml(formatNumber(item.last_score || 0))}</span>
|
||||
<span class="tag clickable-tag" data-action="review-platform-skill" data-platform="${escapeHtml(normalizedPlatform)}" data-skill-id="${escapeHtml(item.id || "")}" data-accepted="true">验收通过</span>
|
||||
<span class="tag clickable-tag" data-action="review-platform-skill" data-platform="${escapeHtml(normalizedPlatform)}" data-skill-id="${escapeHtml(item.id || "")}" data-accepted="false">标记待优化</span>
|
||||
</div>
|
||||
</div>
|
||||
`).join("") || `<div class="task-item compact"><h4>还没有平台技能</h4><p>等子 Agent 跑出稳定结果后,把方法固化成技能。</p></div>`}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="task-meta" style="margin-top:12px;">
|
||||
<span class="tag clickable-tag" data-action="run-oneliner-action" data-executor-key="platform-self-check" data-platform="${escapeHtml(normalizedPlatform)}">运行平台自检</span>
|
||||
<span class="tag clickable-tag" data-action="open-platform-agent-profile" data-platform="${escapeHtml(normalizedPlatform)}">编辑配置</span>
|
||||
<span class="tag clickable-tag" data-action="open-platform-agent-memory" data-platform="${escapeHtml(normalizedPlatform)}">继续补记忆</span>
|
||||
<span class="tag clickable-tag" data-action="open-platform-agent-skill" data-platform="${escapeHtml(normalizedPlatform)}">继续补技能</span>
|
||||
<span class="tag clickable-tag" data-action="open-oneliner">让 OneLiner 调度</span>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
function openPlatformSkillReviewAction(platform, skillId, accepted) {
|
||||
const project = requireSelectedProject();
|
||||
const normalizedPlatform = normalizePlatformValue(platform, getPreferredPlatform());
|
||||
const profile = safeArray(appState.platformAgents).find((item) => item.platform === normalizedPlatform) || null;
|
||||
const skill = safeArray(profile?.recent_skill ? [profile.recent_skill] : [])
|
||||
.concat([])
|
||||
.find((item) => item.id === skillId)
|
||||
|| null;
|
||||
openActionModal({
|
||||
title: accepted ? "验收平台技能" : "标记技能待优化",
|
||||
description: accepted
|
||||
? `把这条 ${platformLabel(normalizedPlatform)} 技能标记为当前可复用的方法。`
|
||||
: `这条 ${platformLabel(normalizedPlatform)} 技能暂时不通过,要求继续优化。`,
|
||||
submitLabel: accepted ? "确认通过" : "确认待优化",
|
||||
fields: [
|
||||
{ name: "summary", label: "结论摘要", placeholder: accepted ? "例如:当前抓取结果和验收数据一致,可固化成技能" : "例如:账号匹配不稳定,需要继续优化抓取方式" },
|
||||
{ name: "reviewNotes", label: "审计备注", type: "textarea", rows: 4, value: skill?.last_result?.review_notes || "", placeholder: "写清楚为什么通过或退回" },
|
||||
{ name: "score", label: "得分", type: "number", value: accepted ? 0.9 : 0.45, min: 0, max: 1, step: 0.05 }
|
||||
],
|
||||
onSubmit: async (values) => {
|
||||
if (!backendSupports("/v2/platform-agents/{platform}/skills/{skill_id}/review")) {
|
||||
throw new Error("当前后端还没有接入平台技能验收接口。");
|
||||
}
|
||||
const saved = await storyforgeFetch(`/v2/platform-agents/${encodeURIComponent(normalizedPlatform)}/skills/${encodeURIComponent(skillId)}/review`, {
|
||||
method: "POST",
|
||||
body: {
|
||||
project_id: project.id,
|
||||
accepted,
|
||||
score: Number(values.score || (accepted ? 0.9 : 0.45)),
|
||||
summary: values.summary || "",
|
||||
review_notes: values.reviewNotes || ""
|
||||
}
|
||||
});
|
||||
rememberAction(
|
||||
accepted ? "平台技能已通过" : "平台技能待优化",
|
||||
`技能「${saved.name || saved.skill_key || skillId}」已更新为 ${saved.status || (accepted ? "validated" : "needs_revision")}。`,
|
||||
accepted ? "green" : "orange",
|
||||
saved
|
||||
);
|
||||
await loadAgentControlSurfaces(project.id);
|
||||
renderAll();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function openCreateAssistantAction() {
|
||||
const project = requireSelectedProject();
|
||||
const kbOptions = getKnowledgeBaseOptions(project.id);
|
||||
@@ -4051,7 +4347,31 @@ function openAdminIncidentReviewAction(incidentId) {
|
||||
description: "这里代表管理员侧审计 Agent 的放行/退回动作。",
|
||||
submitLabel: "保存审计结果",
|
||||
fields: [
|
||||
{ name: "summary", label: "事件摘要", type: "html", html: `<div class="sheet-html"><strong>${escapeHtml(incident.title)}</strong><p>${escapeHtml(incident.summary || "暂无摘要")}</p></div>` },
|
||||
{
|
||||
name: "summary",
|
||||
label: "事件摘要",
|
||||
type: "html",
|
||||
html: `
|
||||
<div class="sheet-html">
|
||||
<div class="task-item compact">
|
||||
<h4>${escapeHtml(incident.title)}</h4>
|
||||
<p>${escapeHtml(incident.summary || "暂无摘要")}</p>
|
||||
<div class="task-meta">
|
||||
<span class="tag ${incident.severity === "error" ? "red" : "orange"}">${escapeHtml(incident.severity || "warn")}</span>
|
||||
<span class="tag">${escapeHtml(incident.status || "open")}</span>
|
||||
${incident.source_type ? `<span class="tag">${escapeHtml(incident.source_type)}</span>` : ""}
|
||||
${incident.tenant_user_id ? `<span class="tag">租户 ${escapeHtml(brief(incident.tenant_user_id, 12))}</span>` : ""}
|
||||
</div>
|
||||
<div class="task-meta" style="margin-top:10px;">
|
||||
${incident.source_type === "job" ? actionTag("看任务详情", "open-job-detail", `data-job-id="${escapeHtml(incident.source_id || "")}"`) : ""}
|
||||
${incident.source_type === "integration" ? actionTag("去自动流程", "goto-automation") : ""}
|
||||
${incident.tenant_project_id ? actionTag("去生产中心", "goto-production") : ""}
|
||||
<span class="tag clickable-tag" data-action="scan-admin-ops">重新扫描</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
},
|
||||
{ name: "status", label: "处理状态", type: "select", value: incident.status || "reviewed", options: [{ value: "reviewed", label: "已审阅" }, { value: "watching", label: "继续观察" }, { value: "resolved", label: "已解决" }, { value: "rejected", label: "驳回修复方案" }] },
|
||||
{ name: "reviewNotes", label: "审计备注", type: "textarea", rows: 5, value: incident.review_notes || "", placeholder: "写清楚为什么放行、退回或继续观察" }
|
||||
],
|
||||
@@ -4614,6 +4934,24 @@ document.addEventListener("click", async (event) => {
|
||||
openPlatformAgentProfileAction(action.dataset.platform || "");
|
||||
return;
|
||||
}
|
||||
if (name === "open-platform-agent-detail") {
|
||||
await openPlatformAgentDetailAction(action.dataset.platform || "");
|
||||
return;
|
||||
}
|
||||
if (name === "run-oneliner-action") {
|
||||
setBusy(true, "OneLiner 正在执行动作...");
|
||||
try {
|
||||
await executeOneLinerAction(action.dataset.executorKey || "", {
|
||||
platform: action.dataset.platform || "",
|
||||
payload: {}
|
||||
});
|
||||
} catch (error) {
|
||||
alert("执行 OneLiner 动作失败: " + error.message);
|
||||
} finally {
|
||||
setBusy(false, "");
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (name === "open-platform-agent-memory") {
|
||||
openPlatformAgentMemoryAction(action.dataset.platform || "");
|
||||
return;
|
||||
@@ -4622,6 +4960,10 @@ document.addEventListener("click", async (event) => {
|
||||
openPlatformAgentSkillAction(action.dataset.platform || "");
|
||||
return;
|
||||
}
|
||||
if (name === "review-platform-skill") {
|
||||
openPlatformSkillReviewAction(action.dataset.platform || "", action.dataset.skillId || "", action.dataset.accepted !== "false");
|
||||
return;
|
||||
}
|
||||
if (name === "analyze-selected-account") {
|
||||
openAnalyzeSelectedAccountAction();
|
||||
return;
|
||||
@@ -4762,10 +5104,16 @@ document.addEventListener("click", async (event) => {
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (name === "submit-oneliner") {
|
||||
return;
|
||||
}
|
||||
if (name === "scroll-selected") {
|
||||
document.getElementById("selected-account-anchor")?.scrollIntoView({ behavior: "smooth", block: "start" });
|
||||
return;
|
||||
}
|
||||
rememberAction("动作待接入", `前端还没有处理动作「${name}」,当前仍可继续通过 OneLiner 对话承接。`, "orange");
|
||||
renderAll();
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user