feat: extend oneliner execution workspace

This commit is contained in:
kris
2026-03-23 15:31:36 +08:00
parent 8d54c21786
commit 6928cb4201
2 changed files with 928 additions and 23 deletions

View File

@@ -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]:

View File

@@ -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;
}
});