8427 lines
382 KiB
Python
8427 lines
382 KiB
Python
from __future__ import annotations
|
||
|
||
import httpx
|
||
import json
|
||
import re
|
||
from datetime import datetime, timezone
|
||
from typing import Any
|
||
|
||
from fastapi import Depends, HTTPException, Query
|
||
from pydantic import BaseModel, Field
|
||
|
||
|
||
class OneLinerProfileRequest(BaseModel):
|
||
project_id: str = ""
|
||
assistant_id: str = ""
|
||
display_name: str = "OneLiner"
|
||
long_term_goal: str = ""
|
||
notes: str = ""
|
||
default_platform: str = ""
|
||
config: dict[str, Any] = Field(default_factory=dict)
|
||
reason: str = ""
|
||
|
||
|
||
class OneLinerProfileRollbackRequest(BaseModel):
|
||
project_id: str = ""
|
||
version_id: str
|
||
reason: str = ""
|
||
|
||
|
||
class OneLinerSessionCreateRequest(BaseModel):
|
||
project_id: str = ""
|
||
title: str = ""
|
||
preferred_platform: str = ""
|
||
initial_message: str = ""
|
||
|
||
|
||
class OneLinerMessageRequest(BaseModel):
|
||
content: str
|
||
project_id: str = ""
|
||
platform: str = ""
|
||
target_account_id: str = ""
|
||
remember_preference: bool = False
|
||
|
||
|
||
class PlatformAgentProfileRequest(BaseModel):
|
||
project_id: str = ""
|
||
assistant_id: str = ""
|
||
name: str = ""
|
||
mission: str = ""
|
||
notes: str = ""
|
||
status: str = "active"
|
||
config: dict[str, Any] = Field(default_factory=dict)
|
||
reason: str = ""
|
||
|
||
|
||
class PlatformAgentProfileRollbackRequest(BaseModel):
|
||
project_id: str = ""
|
||
version_id: str
|
||
reason: str = ""
|
||
|
||
|
||
class AgentMemoryUpsertRequest(BaseModel):
|
||
project_id: str = ""
|
||
subject_type: str = "project"
|
||
subject_id: str = ""
|
||
memory_key: str
|
||
title: str = ""
|
||
summary: str
|
||
details: dict[str, Any] = Field(default_factory=dict)
|
||
confidence: float = Field(default=0.7, ge=0.0, le=1.0)
|
||
|
||
|
||
class AgentSkillUpsertRequest(BaseModel):
|
||
project_id: str = ""
|
||
skill_key: str
|
||
name: str
|
||
status: str = "draft"
|
||
method: dict[str, Any] = Field(default_factory=dict)
|
||
test_spec: dict[str, Any] = Field(default_factory=dict)
|
||
last_result: dict[str, Any] = Field(default_factory=dict)
|
||
success_count: int = Field(default=0, ge=0)
|
||
failure_count: int = Field(default=0, ge=0)
|
||
last_score: float = 0.0
|
||
|
||
|
||
class AdminIncidentReviewRequest(BaseModel):
|
||
status: str = "reviewed"
|
||
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)
|
||
|
||
|
||
class OneLinerActionDefinitionRequest(BaseModel):
|
||
label: str = ""
|
||
description: str = ""
|
||
category: str = "custom"
|
||
status: str = "enabled"
|
||
admin_only: bool | None = None
|
||
requires_platform: bool | None = None
|
||
config: dict[str, Any] = Field(default_factory=dict)
|
||
|
||
|
||
class AdminFixPlanRequest(BaseModel):
|
||
incident_id: str = ""
|
||
scope: str = "plan"
|
||
notes: str = ""
|
||
|
||
|
||
class AdminFixRunReviewRequest(BaseModel):
|
||
review_status: str = "approved"
|
||
review_notes: str = ""
|
||
|
||
|
||
class PlatformSkillRollbackRequest(BaseModel):
|
||
project_id: str = ""
|
||
version_id: str = ""
|
||
|
||
|
||
class TenantQuotaRequest(BaseModel):
|
||
package_label: str = ""
|
||
monthly_budget_cents: int = Field(default=0, ge=0)
|
||
storage_limit_bytes: int = Field(default=0, ge=0)
|
||
analysis_quota: int = Field(default=0, ge=0)
|
||
copy_quota: int = Field(default=0, ge=0)
|
||
ai_video_quota: int = Field(default=0, ge=0)
|
||
real_cut_quota: int = Field(default=0, ge=0)
|
||
recorder_quota: int = Field(default=0, ge=0)
|
||
enabled: bool = True
|
||
config: dict[str, Any] = Field(default_factory=dict)
|
||
|
||
|
||
class AgentPolicyUpsertRequest(BaseModel):
|
||
project_id: str = ""
|
||
target_user_id: str = ""
|
||
target_project_id: str = ""
|
||
platform: str = ""
|
||
title: str = ""
|
||
summary: str = ""
|
||
policy: dict[str, Any] = Field(default_factory=dict)
|
||
effect_mode: str = "ongoing"
|
||
starts_at: str = ""
|
||
ends_at: str = ""
|
||
config: dict[str, Any] = Field(default_factory=dict)
|
||
reason: str = ""
|
||
|
||
|
||
class AgentPolicyRollbackRequest(BaseModel):
|
||
project_id: str = ""
|
||
target_user_id: str = ""
|
||
target_project_id: str = ""
|
||
platform: str = ""
|
||
version_id: str
|
||
reason: str = ""
|
||
|
||
|
||
class AgentRunCreateRequest(BaseModel):
|
||
project_id: str = ""
|
||
session_id: str = ""
|
||
source_screen: str = "dashboard"
|
||
source_action_key: str = ""
|
||
title: str = ""
|
||
summary: str = ""
|
||
intent_key: str = "custom"
|
||
platform: str = ""
|
||
platform_scope: str = "single_platform"
|
||
delivery_mode: str = "hybrid"
|
||
scheduling_mode: str = "queued"
|
||
plan_request: dict[str, Any] = Field(default_factory=dict)
|
||
payload: dict[str, Any] = Field(default_factory=dict)
|
||
|
||
|
||
class AgentRunConfirmRequest(BaseModel):
|
||
reason: str = ""
|
||
|
||
|
||
class AgentRunCancelRequest(BaseModel):
|
||
reason: str = ""
|
||
|
||
|
||
class AgentRunRetryRequest(BaseModel):
|
||
reason: str = ""
|
||
|
||
|
||
INTENT_ACTIONS: dict[str, list[dict[str, Any]]] = {
|
||
"create_project": [{"key": "goto-intake", "label": "去我的项目", "kind": "navigate"}],
|
||
"create_assistant": [{"key": "direct-create-assistant", "label": "创建 Agent", "kind": "ui_action"}],
|
||
"import_homepage": [{"key": "direct-import-selected-account", "label": "导入主页", "kind": "ui_action"}],
|
||
"track_account": [{"key": "direct-track-selected-account", "label": "跟踪当前账号", "kind": "ui_action"}],
|
||
"analyze_account": [{"key": "direct-analyze-selected-account", "label": "分析当前账号", "kind": "ui_action"}],
|
||
"analyze_top_videos": [{"key": "direct-analyze-top-videos", "label": "分析高分作品", "kind": "ui_action"}],
|
||
"generate_copy": [{"key": "direct-generate-copy", "label": "生成文案", "kind": "ui_action"}],
|
||
"ai_video": [{"key": "direct-create-ai-video", "label": "做 AI 视频", "kind": "ui_action"}],
|
||
"real_cut": [{"key": "direct-create-real-cut", "label": "做实拍剪辑", "kind": "ui_action"}],
|
||
"review": [{"key": "goto-review", "label": "去发布与复盘", "kind": "navigate"}],
|
||
"live_recorder": [{"key": "open-live-recorder", "label": "打开录制控制", "kind": "ui_action"}],
|
||
"storage_status": [{"key": "goto-production", "label": "查看生产与存储", "kind": "navigate"}],
|
||
"ops_admin": [{"key": "goto-automation", "label": "去自动流程", "kind": "navigate"}],
|
||
}
|
||
|
||
INTENT_LABELS = {
|
||
"create_project": "创建项目",
|
||
"create_assistant": "创建 Agent",
|
||
"import_homepage": "导入主页",
|
||
"track_account": "跟踪账号",
|
||
"analyze_account": "分析账号",
|
||
"analyze_top_videos": "分析高分作品",
|
||
"generate_copy": "生成文案",
|
||
"ai_video": "生成 AI 视频",
|
||
"real_cut": "实拍剪辑",
|
||
"review": "发布复盘",
|
||
"live_recorder": "直播录制",
|
||
"storage_status": "查看存储",
|
||
"ops_admin": "运维巡检",
|
||
"custom": "自定义任务",
|
||
}
|
||
|
||
ACTION_REGISTRY_DEFAULTS: dict[str, dict[str, Any]] = {
|
||
"platform-self-check": {
|
||
"label": "运行平台自检",
|
||
"description": "检查当前平台 Agent 的路由、执行 Agent、技能和记忆是否达到可运行状态。",
|
||
"category": "platform",
|
||
"handler_key": "platform-self-check",
|
||
"status": "enabled",
|
||
"admin_only": False,
|
||
"requires_platform": True,
|
||
"config": {},
|
||
},
|
||
"storage-status": {
|
||
"label": "查看当前存储状态",
|
||
"description": "查看当前租户项目的 jobs、downloads、NAS 目录占用和最近产物。",
|
||
"category": "storage",
|
||
"handler_key": "storage-status",
|
||
"status": "enabled",
|
||
"admin_only": False,
|
||
"requires_platform": False,
|
||
"config": {},
|
||
},
|
||
"live-recorder-status": {
|
||
"label": "查看录制状态",
|
||
"description": "查看当前租户名下的录制源、运行状态和最近录像文件。",
|
||
"category": "recorder",
|
||
"handler_key": "live-recorder-status",
|
||
"status": "enabled",
|
||
"admin_only": False,
|
||
"requires_platform": False,
|
||
"config": {},
|
||
},
|
||
"scan-admin-ops": {
|
||
"label": "重新扫描故障",
|
||
"description": "让管理员运维 Agent 重新汇总当前失败任务与集成异常。",
|
||
"category": "admin_ops",
|
||
"handler_key": "scan-admin-ops",
|
||
"status": "enabled",
|
||
"admin_only": True,
|
||
"requires_platform": False,
|
||
"config": {},
|
||
},
|
||
"generate-copy": {
|
||
"label": "直接生成一版文案",
|
||
"description": "基于当前项目、执行 Agent 和最近上下文直接生成一版文案。",
|
||
"category": "content",
|
||
"handler_key": "generate-copy",
|
||
"status": "enabled",
|
||
"admin_only": False,
|
||
"requires_platform": False,
|
||
"config": {},
|
||
},
|
||
"review-draft": {
|
||
"label": "生成复盘草稿",
|
||
"description": "基于最近完成任务自动生成或回收一版复盘草稿。",
|
||
"category": "review",
|
||
"handler_key": "review-draft",
|
||
"status": "enabled",
|
||
"admin_only": False,
|
||
"requires_platform": False,
|
||
"config": {},
|
||
},
|
||
"import-homepage": {
|
||
"label": "直接导入主页",
|
||
"description": "把主页导入当前项目并触发内容源同步。",
|
||
"category": "intake",
|
||
"handler_key": "import-homepage",
|
||
"status": "enabled",
|
||
"admin_only": False,
|
||
"requires_platform": True,
|
||
"config": {"auto_trigger_analysis": True},
|
||
},
|
||
"import-video-link": {
|
||
"label": "直接导入作品链接",
|
||
"description": "把单条作品链接直接送进分析链,并创建可追踪的分析任务。",
|
||
"category": "analysis",
|
||
"handler_key": "import-video-link",
|
||
"status": "enabled",
|
||
"admin_only": False,
|
||
"requires_platform": False,
|
||
"config": {},
|
||
},
|
||
"import-text": {
|
||
"label": "直接导入文本素材",
|
||
"description": "把文本素材直接送进分析链,并创建可追踪的分析任务。",
|
||
"category": "analysis",
|
||
"handler_key": "import-text",
|
||
"status": "enabled",
|
||
"admin_only": False,
|
||
"requires_platform": False,
|
||
"config": {},
|
||
},
|
||
"analyze-top-videos": {
|
||
"label": "直接分析高分作品",
|
||
"description": "拆解当前平台账号的高分作品,并把结论沉淀到平台记忆。",
|
||
"category": "analysis",
|
||
"handler_key": "analyze-top-videos",
|
||
"status": "enabled",
|
||
"admin_only": False,
|
||
"requires_platform": True,
|
||
"config": {"top_video_count": 4},
|
||
},
|
||
"analyze-account": {
|
||
"label": "直接分析账号",
|
||
"description": "基于当前平台账号直接生成账号分析报告与下一步建议。",
|
||
"category": "analysis",
|
||
"handler_key": "analyze-account",
|
||
"status": "enabled",
|
||
"admin_only": False,
|
||
"requires_platform": True,
|
||
"config": {"max_videos": 6},
|
||
},
|
||
"track-account": {
|
||
"label": "直接加入跟踪",
|
||
"description": "把当前平台账号加入跟踪,并立即触发一次同步。",
|
||
"category": "tracking",
|
||
"handler_key": "track-account",
|
||
"status": "enabled",
|
||
"admin_only": False,
|
||
"requires_platform": True,
|
||
"config": {"refresh_now": True},
|
||
},
|
||
"refresh-tracking": {
|
||
"label": "直接同步跟踪池",
|
||
"description": "批量触发当前平台已跟踪账号的同步任务。",
|
||
"category": "tracking",
|
||
"handler_key": "refresh-tracking",
|
||
"status": "enabled",
|
||
"admin_only": False,
|
||
"requires_platform": True,
|
||
"config": {},
|
||
},
|
||
"mark-tracking-read": {
|
||
"label": "直接标记日报已读",
|
||
"description": "把当前平台跟踪日报更新为已读,下次从新的时间点继续汇总。",
|
||
"category": "tracking",
|
||
"handler_key": "mark-tracking-read",
|
||
"status": "enabled",
|
||
"admin_only": False,
|
||
"requires_platform": True,
|
||
"config": {},
|
||
},
|
||
"search-similar-accounts": {
|
||
"label": "直接查相似账号",
|
||
"description": "基于当前平台账号直接生成一批相似候选,并沉淀到当前项目。",
|
||
"category": "analysis",
|
||
"handler_key": "search-similar-accounts",
|
||
"status": "enabled",
|
||
"admin_only": False,
|
||
"requires_platform": True,
|
||
"config": {"max_candidates": 8},
|
||
},
|
||
"save-benchmark-link": {
|
||
"label": "直接存对标关系",
|
||
"description": "把当前相似候选直接加入对标关系,便于后续持续跟踪和拆解。",
|
||
"category": "analysis",
|
||
"handler_key": "save-benchmark-link",
|
||
"status": "enabled",
|
||
"admin_only": False,
|
||
"requires_platform": True,
|
||
"config": {"relation_type": "benchmark"},
|
||
},
|
||
"create-assistant": {
|
||
"label": "直接创建 Agent",
|
||
"description": "根据当前项目和平台上下文,直接创建可继续编辑的 Agent。",
|
||
"category": "agent",
|
||
"handler_key": "create-assistant",
|
||
"status": "enabled",
|
||
"admin_only": False,
|
||
"requires_platform": False,
|
||
"config": {},
|
||
},
|
||
"create-ai-video": {
|
||
"label": "直接创建 AI 视频",
|
||
"description": "基于最近可用源任务直接创建 AI 视频链任务。",
|
||
"category": "production",
|
||
"handler_key": "create-ai-video",
|
||
"status": "enabled",
|
||
"admin_only": False,
|
||
"requires_platform": False,
|
||
"config": {},
|
||
},
|
||
"create-real-cut": {
|
||
"label": "直接创建实拍剪辑",
|
||
"description": "基于最近可用源任务直接创建实拍剪辑链任务。",
|
||
"category": "production",
|
||
"handler_key": "create-real-cut",
|
||
"status": "enabled",
|
||
"admin_only": False,
|
||
"requires_platform": False,
|
||
"config": {},
|
||
},
|
||
"save-live-recorder-source": {
|
||
"label": "直接保存录制源",
|
||
"description": "把直播源直接保存到当前租户的 NAS 录制配置。",
|
||
"category": "recorder",
|
||
"handler_key": "save-live-recorder-source",
|
||
"status": "enabled",
|
||
"admin_only": False,
|
||
"requires_platform": True,
|
||
"config": {"auto_start": True},
|
||
},
|
||
}
|
||
|
||
USAGE_COST_DEFAULTS: dict[str, dict[str, Any]] = {
|
||
"analysis": {"cost_cents": 6, "quota_field": "analysis_quota"},
|
||
"content_source_sync": {"cost_cents": 8, "quota_field": "analysis_quota"},
|
||
"copy": {"cost_cents": 3, "quota_field": "copy_quota"},
|
||
"review": {"cost_cents": 1, "quota_field": "analysis_quota"},
|
||
"ai_video": {"cost_cents": 30, "quota_field": "ai_video_quota"},
|
||
"real_cut": {"cost_cents": 20, "quota_field": "real_cut_quota"},
|
||
"live_recorder": {"cost_cents": 2, "quota_field": "recorder_quota"},
|
||
}
|
||
|
||
TENANT_QUOTA_PACKAGE_PRESETS: dict[str, dict[str, Any]] = {
|
||
"trial": {
|
||
"title": "试用套餐",
|
||
"description": "适合先跑通主流程的小规模项目,预算和动作池会优先保护试错成本。",
|
||
"focus": "先验证项目是否跑得通,再决定是否扩容。",
|
||
"warn_threshold": 0.7,
|
||
"monthly_budget_cents": 9900,
|
||
"storage_limit_bytes": 5 * 1024 * 1024 * 1024,
|
||
"analysis_quota": 30,
|
||
"copy_quota": 60,
|
||
"ai_video_quota": 2,
|
||
"real_cut_quota": 1,
|
||
"recorder_quota": 4,
|
||
},
|
||
"growth": {
|
||
"title": "增长套餐",
|
||
"description": "适合已经形成固定内容节奏的项目,兼顾分析、文案和视频动作的持续投放。",
|
||
"focus": "先把稳定增长跑顺,再看哪里需要单独加码。",
|
||
"warn_threshold": 0.8,
|
||
"monthly_budget_cents": 49900,
|
||
"storage_limit_bytes": 20 * 1024 * 1024 * 1024,
|
||
"analysis_quota": 160,
|
||
"copy_quota": 320,
|
||
"ai_video_quota": 12,
|
||
"real_cut_quota": 8,
|
||
"recorder_quota": 20,
|
||
},
|
||
"scale": {
|
||
"title": "规模套餐",
|
||
"description": "适合多账号、多批次的量产项目,预算、存储和视频动作都会按高负载配置。",
|
||
"focus": "优先保证量产吞吐,再按平台专项做局部优化。",
|
||
"warn_threshold": 0.85,
|
||
"monthly_budget_cents": 199000,
|
||
"storage_limit_bytes": 80 * 1024 * 1024 * 1024,
|
||
"analysis_quota": 800,
|
||
"copy_quota": 1600,
|
||
"ai_video_quota": 40,
|
||
"real_cut_quota": 24,
|
||
"recorder_quota": 80,
|
||
},
|
||
}
|
||
|
||
ACTION_USAGE_KEYS: dict[str, str] = {
|
||
"generate-copy": "copy",
|
||
"review-draft": "review",
|
||
"import-homepage": "content_source_sync",
|
||
"import-video-link": "analysis",
|
||
"import-text": "analysis",
|
||
"analyze-top-videos": "analysis",
|
||
"analyze-account": "analysis",
|
||
"track-account": "content_source_sync",
|
||
"refresh-tracking": "content_source_sync",
|
||
"search-similar-accounts": "analysis",
|
||
"create-ai-video": "ai_video",
|
||
"create-real-cut": "real_cut",
|
||
"save-live-recorder-source": "live_recorder",
|
||
}
|
||
|
||
|
||
def register_oneliner_routes(app: Any, legacy: Any) -> None:
|
||
def now() -> str:
|
||
return legacy.utc_now()
|
||
|
||
def make_id(prefix: str) -> str:
|
||
return legacy.make_id(prefix)
|
||
|
||
def _parse_json(raw: str | None, fallback: Any) -> Any:
|
||
cleaned = str(raw or "").strip()
|
||
if not cleaned:
|
||
return fallback
|
||
try:
|
||
return json.loads(cleaned)
|
||
except json.JSONDecodeError:
|
||
return fallback
|
||
|
||
def _dump(value: Any) -> str:
|
||
return json.dumps(value or {}, ensure_ascii=False)
|
||
|
||
def _normalize_package_label(value: Any) -> str:
|
||
label = str(value or "").strip().lower()
|
||
if not label:
|
||
return "custom"
|
||
return label if label in TENANT_QUOTA_PACKAGE_PRESETS else "custom"
|
||
|
||
def _clamp_warn_threshold(value: Any, fallback: float = 0.8) -> float:
|
||
try:
|
||
parsed = float(value)
|
||
except (TypeError, ValueError):
|
||
parsed = float(fallback)
|
||
if parsed <= 0:
|
||
parsed = float(fallback)
|
||
return max(0.1, min(parsed, 0.95))
|
||
|
||
def _tenant_quota_package_label(data: dict[str, Any]) -> str:
|
||
config = _parse_json(data.get("config_json"), {})
|
||
label = _normalize_package_label(config.get("package_label"))
|
||
if label != "custom":
|
||
return label
|
||
for preset_label, preset_values in TENANT_QUOTA_PACKAGE_PRESETS.items():
|
||
numeric_fields = (
|
||
"monthly_budget_cents",
|
||
"storage_limit_bytes",
|
||
"analysis_quota",
|
||
"copy_quota",
|
||
"ai_video_quota",
|
||
"real_cut_quota",
|
||
"recorder_quota",
|
||
)
|
||
if all(int(data.get(field) or 0) == int(preset_values.get(field) or 0) for field in numeric_fields):
|
||
return preset_label
|
||
return "custom"
|
||
|
||
def _tenant_quota_config(data: dict[str, Any]) -> dict[str, Any]:
|
||
config = _parse_json(data.get("config_json"), {})
|
||
package_label = _tenant_quota_package_label(data)
|
||
config["package_label"] = package_label
|
||
preset = TENANT_QUOTA_PACKAGE_PRESETS.get(package_label)
|
||
config["warn_threshold"] = _clamp_warn_threshold(
|
||
config.get("warn_threshold", preset.get("warn_threshold", 0.8) if preset else 0.8),
|
||
preset.get("warn_threshold", 0.8) if preset else 0.8,
|
||
)
|
||
if preset:
|
||
config.update(
|
||
{
|
||
"package_title": preset["title"],
|
||
"package_description": preset["description"],
|
||
"package_focus": preset["focus"],
|
||
"package_is_preset": True,
|
||
"package_defaults": {
|
||
"monthly_budget_cents": int(preset["monthly_budget_cents"]),
|
||
"storage_limit_bytes": int(preset["storage_limit_bytes"]),
|
||
"analysis_quota": int(preset["analysis_quota"]),
|
||
"copy_quota": int(preset["copy_quota"]),
|
||
"ai_video_quota": int(preset["ai_video_quota"]),
|
||
"real_cut_quota": int(preset["real_cut_quota"]),
|
||
"recorder_quota": int(preset["recorder_quota"]),
|
||
},
|
||
}
|
||
)
|
||
else:
|
||
config.update(
|
||
{
|
||
"package_title": "自定义套餐",
|
||
"package_description": "按当前项目的预算、动作池和阶段手动配置套餐。",
|
||
"package_focus": "适合已经明确成本模型或需要特殊额度策略的项目。",
|
||
"package_is_preset": False,
|
||
"package_defaults": {},
|
||
}
|
||
)
|
||
return config
|
||
|
||
def _tenant_quota_values(request: TenantQuotaRequest, package_label: str) -> dict[str, int]:
|
||
preset = TENANT_QUOTA_PACKAGE_PRESETS.get(package_label)
|
||
if preset:
|
||
return {
|
||
"monthly_budget_cents": int(preset["monthly_budget_cents"]),
|
||
"storage_limit_bytes": int(preset["storage_limit_bytes"]),
|
||
"analysis_quota": int(preset["analysis_quota"]),
|
||
"copy_quota": int(preset["copy_quota"]),
|
||
"ai_video_quota": int(preset["ai_video_quota"]),
|
||
"real_cut_quota": int(preset["real_cut_quota"]),
|
||
"recorder_quota": int(preset["recorder_quota"]),
|
||
}
|
||
return {
|
||
"monthly_budget_cents": int(request.monthly_budget_cents or 0),
|
||
"storage_limit_bytes": int(request.storage_limit_bytes or 0),
|
||
"analysis_quota": int(request.analysis_quota or 0),
|
||
"copy_quota": int(request.copy_quota or 0),
|
||
"ai_video_quota": int(request.ai_video_quota or 0),
|
||
"real_cut_quota": int(request.real_cut_quota or 0),
|
||
"recorder_quota": int(request.recorder_quota or 0),
|
||
}
|
||
|
||
def _bool_flag(value: Any) -> bool:
|
||
if isinstance(value, bool):
|
||
return value
|
||
if value in {1, "1", "true", "True", "yes", "on"}:
|
||
return True
|
||
return False
|
||
|
||
def _current_cycle_start() -> str:
|
||
current = datetime.now(timezone.utc)
|
||
return current.replace(day=1, hour=0, minute=0, second=0, microsecond=0).isoformat().replace("+00:00", "Z")
|
||
|
||
def _parse_policy_datetime(value: str | None) -> datetime | None:
|
||
text = str(value or "").strip()
|
||
if not text:
|
||
return None
|
||
try:
|
||
normalized = text.replace("Z", "+00:00")
|
||
parsed = datetime.fromisoformat(normalized)
|
||
if parsed.tzinfo is None:
|
||
parsed = parsed.replace(tzinfo=timezone.utc)
|
||
return parsed.astimezone(timezone.utc)
|
||
except ValueError:
|
||
return None
|
||
|
||
def ensure_schema() -> None:
|
||
schema = """
|
||
CREATE TABLE IF NOT EXISTS oneliner_profiles (
|
||
id TEXT PRIMARY KEY,
|
||
user_id TEXT NOT NULL,
|
||
project_id TEXT NOT NULL DEFAULT '',
|
||
assistant_id TEXT NOT NULL DEFAULT '',
|
||
display_name TEXT NOT NULL DEFAULT 'OneLiner',
|
||
long_term_goal TEXT NOT NULL DEFAULT '',
|
||
notes TEXT NOT NULL DEFAULT '',
|
||
default_platform TEXT NOT NULL DEFAULT '',
|
||
config_json TEXT NOT NULL DEFAULT '{}',
|
||
created_at TEXT NOT NULL,
|
||
updated_at TEXT NOT NULL,
|
||
UNIQUE(user_id, project_id),
|
||
FOREIGN KEY(user_id) REFERENCES accounts(id) ON DELETE CASCADE,
|
||
FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE SET NULL,
|
||
FOREIGN KEY(assistant_id) REFERENCES assistants(id) ON DELETE SET NULL
|
||
);
|
||
|
||
CREATE TABLE IF NOT EXISTS oneliner_profile_versions (
|
||
id TEXT PRIMARY KEY,
|
||
profile_id TEXT NOT NULL,
|
||
user_id TEXT NOT NULL,
|
||
project_id TEXT NOT NULL DEFAULT '',
|
||
assistant_id TEXT NOT NULL DEFAULT '',
|
||
version_no INTEGER NOT NULL DEFAULT 1,
|
||
display_name TEXT NOT NULL DEFAULT 'OneLiner',
|
||
long_term_goal TEXT NOT NULL DEFAULT '',
|
||
notes TEXT NOT NULL DEFAULT '',
|
||
default_platform TEXT NOT NULL DEFAULT '',
|
||
config_json TEXT NOT NULL DEFAULT '{}',
|
||
summary TEXT NOT NULL DEFAULT '',
|
||
reason TEXT NOT NULL DEFAULT '',
|
||
source_type TEXT NOT NULL DEFAULT 'user',
|
||
rollback_from_version_id TEXT NOT NULL DEFAULT '',
|
||
actor_user_id TEXT NOT NULL DEFAULT '',
|
||
created_at TEXT NOT NULL,
|
||
UNIQUE(profile_id, version_no),
|
||
FOREIGN KEY(profile_id) REFERENCES oneliner_profiles(id) ON DELETE CASCADE,
|
||
FOREIGN KEY(user_id) REFERENCES accounts(id) ON DELETE CASCADE,
|
||
FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE SET NULL
|
||
);
|
||
|
||
CREATE TABLE IF NOT EXISTS oneliner_profile_audit_logs (
|
||
id TEXT PRIMARY KEY,
|
||
profile_id TEXT NOT NULL,
|
||
version_id TEXT NOT NULL DEFAULT '',
|
||
actor_user_id TEXT NOT NULL DEFAULT '',
|
||
action_key TEXT NOT NULL DEFAULT '',
|
||
summary TEXT NOT NULL DEFAULT '',
|
||
details_json TEXT NOT NULL DEFAULT '{}',
|
||
created_at TEXT NOT NULL,
|
||
FOREIGN KEY(profile_id) REFERENCES oneliner_profiles(id) ON DELETE CASCADE,
|
||
FOREIGN KEY(version_id) REFERENCES oneliner_profile_versions(id) ON DELETE SET NULL
|
||
);
|
||
|
||
CREATE TABLE IF NOT EXISTS oneliner_sessions (
|
||
id TEXT PRIMARY KEY,
|
||
user_id TEXT NOT NULL,
|
||
project_id TEXT NOT NULL DEFAULT '',
|
||
profile_id TEXT,
|
||
title TEXT NOT NULL DEFAULT '',
|
||
status TEXT NOT NULL DEFAULT 'active',
|
||
preferred_platform TEXT NOT NULL DEFAULT '',
|
||
last_platform TEXT NOT NULL DEFAULT '',
|
||
last_intent_key TEXT NOT NULL DEFAULT '',
|
||
last_message_at TEXT NOT NULL,
|
||
created_at TEXT NOT NULL,
|
||
updated_at TEXT NOT NULL,
|
||
FOREIGN KEY(user_id) REFERENCES accounts(id) ON DELETE CASCADE,
|
||
FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE SET NULL,
|
||
FOREIGN KEY(profile_id) REFERENCES oneliner_profiles(id) ON DELETE SET NULL
|
||
);
|
||
|
||
CREATE TABLE IF NOT EXISTS oneliner_messages (
|
||
id TEXT PRIMARY KEY,
|
||
session_id TEXT NOT NULL,
|
||
user_id TEXT NOT NULL,
|
||
role TEXT NOT NULL,
|
||
content TEXT NOT NULL DEFAULT '',
|
||
plan_json TEXT NOT NULL DEFAULT '{}',
|
||
result_json TEXT NOT NULL DEFAULT '{}',
|
||
created_at TEXT NOT NULL,
|
||
FOREIGN KEY(session_id) REFERENCES oneliner_sessions(id) ON DELETE CASCADE,
|
||
FOREIGN KEY(user_id) REFERENCES accounts(id) ON DELETE CASCADE
|
||
);
|
||
|
||
CREATE TABLE IF NOT EXISTS platform_agent_profiles (
|
||
id TEXT PRIMARY KEY,
|
||
user_id TEXT NOT NULL,
|
||
project_id TEXT NOT NULL DEFAULT '',
|
||
platform TEXT NOT NULL,
|
||
assistant_id TEXT NOT NULL DEFAULT '',
|
||
name TEXT NOT NULL DEFAULT '',
|
||
mission TEXT NOT NULL DEFAULT '',
|
||
notes TEXT NOT NULL DEFAULT '',
|
||
status TEXT NOT NULL DEFAULT 'active',
|
||
config_json TEXT NOT NULL DEFAULT '{}',
|
||
created_at TEXT NOT NULL,
|
||
updated_at TEXT NOT NULL,
|
||
UNIQUE(user_id, project_id, platform),
|
||
FOREIGN KEY(user_id) REFERENCES accounts(id) ON DELETE CASCADE,
|
||
FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE SET NULL,
|
||
FOREIGN KEY(assistant_id) REFERENCES assistants(id) ON DELETE SET NULL
|
||
);
|
||
|
||
CREATE TABLE IF NOT EXISTS platform_agent_profile_versions (
|
||
id TEXT PRIMARY KEY,
|
||
profile_id TEXT NOT NULL,
|
||
user_id TEXT NOT NULL,
|
||
project_id TEXT NOT NULL DEFAULT '',
|
||
platform TEXT NOT NULL DEFAULT '',
|
||
assistant_id TEXT NOT NULL DEFAULT '',
|
||
version_no INTEGER NOT NULL DEFAULT 1,
|
||
name TEXT NOT NULL DEFAULT '',
|
||
mission TEXT NOT NULL DEFAULT '',
|
||
notes TEXT NOT NULL DEFAULT '',
|
||
status TEXT NOT NULL DEFAULT 'active',
|
||
config_json TEXT NOT NULL DEFAULT '{}',
|
||
summary TEXT NOT NULL DEFAULT '',
|
||
reason TEXT NOT NULL DEFAULT '',
|
||
source_type TEXT NOT NULL DEFAULT 'user_update',
|
||
rollback_from_version_id TEXT NOT NULL DEFAULT '',
|
||
actor_user_id TEXT NOT NULL DEFAULT '',
|
||
created_at TEXT NOT NULL,
|
||
UNIQUE(profile_id, version_no),
|
||
FOREIGN KEY(profile_id) REFERENCES platform_agent_profiles(id) ON DELETE CASCADE,
|
||
FOREIGN KEY(user_id) REFERENCES accounts(id) ON DELETE CASCADE,
|
||
FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE SET NULL,
|
||
FOREIGN KEY(assistant_id) REFERENCES assistants(id) ON DELETE SET NULL
|
||
);
|
||
|
||
CREATE TABLE IF NOT EXISTS platform_agent_profile_audit_logs (
|
||
id TEXT PRIMARY KEY,
|
||
profile_id TEXT NOT NULL,
|
||
version_id TEXT NOT NULL DEFAULT '',
|
||
actor_user_id TEXT NOT NULL DEFAULT '',
|
||
action_key TEXT NOT NULL DEFAULT '',
|
||
summary TEXT NOT NULL DEFAULT '',
|
||
details_json TEXT NOT NULL DEFAULT '{}',
|
||
created_at TEXT NOT NULL,
|
||
FOREIGN KEY(profile_id) REFERENCES platform_agent_profiles(id) ON DELETE CASCADE,
|
||
FOREIGN KEY(version_id) REFERENCES platform_agent_profile_versions(id) ON DELETE SET NULL
|
||
);
|
||
|
||
CREATE TABLE IF NOT EXISTS agent_memories (
|
||
id TEXT PRIMARY KEY,
|
||
user_id TEXT NOT NULL,
|
||
project_id TEXT NOT NULL DEFAULT '',
|
||
agent_scope TEXT NOT NULL,
|
||
platform TEXT NOT NULL DEFAULT '',
|
||
subject_type TEXT NOT NULL DEFAULT 'project',
|
||
subject_id TEXT NOT NULL DEFAULT '',
|
||
memory_key TEXT NOT NULL,
|
||
title TEXT NOT NULL DEFAULT '',
|
||
summary TEXT NOT NULL DEFAULT '',
|
||
details_json TEXT NOT NULL DEFAULT '{}',
|
||
confidence REAL NOT NULL DEFAULT 0.7,
|
||
last_validated_at TEXT NOT NULL DEFAULT '',
|
||
created_at TEXT NOT NULL,
|
||
updated_at TEXT NOT NULL,
|
||
UNIQUE(user_id, project_id, agent_scope, platform, subject_type, subject_id, memory_key),
|
||
FOREIGN KEY(user_id) REFERENCES accounts(id) ON DELETE CASCADE,
|
||
FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE SET NULL
|
||
);
|
||
|
||
CREATE TABLE IF NOT EXISTS agent_skills (
|
||
id TEXT PRIMARY KEY,
|
||
user_id TEXT NOT NULL,
|
||
project_id TEXT NOT NULL DEFAULT '',
|
||
agent_scope TEXT NOT NULL,
|
||
platform TEXT NOT NULL DEFAULT '',
|
||
parent_skill_id TEXT NOT NULL DEFAULT '',
|
||
skill_key TEXT NOT NULL,
|
||
name TEXT NOT NULL,
|
||
status TEXT NOT NULL DEFAULT 'draft',
|
||
method_json TEXT NOT NULL DEFAULT '{}',
|
||
test_spec_json TEXT NOT NULL DEFAULT '{}',
|
||
last_result_json TEXT NOT NULL DEFAULT '{}',
|
||
success_count INTEGER NOT NULL DEFAULT 0,
|
||
failure_count INTEGER NOT NULL DEFAULT 0,
|
||
last_score REAL NOT NULL DEFAULT 0,
|
||
last_validated_at TEXT NOT NULL DEFAULT '',
|
||
created_at TEXT NOT NULL,
|
||
updated_at TEXT NOT NULL,
|
||
UNIQUE(user_id, project_id, agent_scope, platform, skill_key),
|
||
FOREIGN KEY(user_id) REFERENCES accounts(id) ON DELETE CASCADE
|
||
);
|
||
|
||
CREATE TABLE IF NOT EXISTS agent_policy_scopes (
|
||
id TEXT PRIMARY KEY,
|
||
scope_kind TEXT NOT NULL,
|
||
subject_user_id TEXT NOT NULL DEFAULT '',
|
||
subject_project_id TEXT NOT NULL DEFAULT '',
|
||
platform TEXT NOT NULL DEFAULT '',
|
||
status TEXT NOT NULL DEFAULT 'active',
|
||
title TEXT NOT NULL DEFAULT '',
|
||
summary TEXT NOT NULL DEFAULT '',
|
||
current_version_id TEXT NOT NULL DEFAULT '',
|
||
created_at TEXT NOT NULL,
|
||
updated_at TEXT NOT NULL,
|
||
UNIQUE(scope_kind, subject_user_id, subject_project_id, platform)
|
||
);
|
||
|
||
CREATE TABLE IF NOT EXISTS agent_policy_versions (
|
||
id TEXT PRIMARY KEY,
|
||
scope_id TEXT NOT NULL,
|
||
scope_kind TEXT NOT NULL,
|
||
subject_user_id TEXT NOT NULL DEFAULT '',
|
||
subject_project_id TEXT NOT NULL DEFAULT '',
|
||
platform TEXT NOT NULL DEFAULT '',
|
||
version_no INTEGER NOT NULL DEFAULT 1,
|
||
title TEXT NOT NULL DEFAULT '',
|
||
summary TEXT NOT NULL DEFAULT '',
|
||
policy_json TEXT NOT NULL DEFAULT '{}',
|
||
reason TEXT NOT NULL DEFAULT '',
|
||
source_type TEXT NOT NULL DEFAULT 'user',
|
||
rollback_from_version_id TEXT NOT NULL DEFAULT '',
|
||
actor_user_id TEXT NOT NULL DEFAULT '',
|
||
created_at TEXT NOT NULL,
|
||
UNIQUE(scope_id, version_no)
|
||
);
|
||
|
||
CREATE TABLE IF NOT EXISTS agent_policy_effectivity (
|
||
id TEXT PRIMARY KEY,
|
||
scope_id TEXT NOT NULL,
|
||
version_id TEXT NOT NULL,
|
||
effect_mode TEXT NOT NULL DEFAULT 'ongoing',
|
||
starts_at TEXT NOT NULL DEFAULT '',
|
||
ends_at TEXT NOT NULL DEFAULT '',
|
||
status TEXT NOT NULL DEFAULT 'active',
|
||
config_json TEXT NOT NULL DEFAULT '{}',
|
||
created_at TEXT NOT NULL,
|
||
updated_at TEXT NOT NULL,
|
||
UNIQUE(version_id)
|
||
);
|
||
|
||
CREATE TABLE IF NOT EXISTS agent_policy_audit_logs (
|
||
id TEXT PRIMARY KEY,
|
||
scope_id TEXT NOT NULL DEFAULT '',
|
||
version_id TEXT NOT NULL DEFAULT '',
|
||
actor_user_id TEXT NOT NULL DEFAULT '',
|
||
action_key TEXT NOT NULL DEFAULT '',
|
||
summary TEXT NOT NULL DEFAULT '',
|
||
details_json TEXT NOT NULL DEFAULT '{}',
|
||
created_at TEXT NOT NULL
|
||
);
|
||
|
||
CREATE TABLE IF NOT EXISTS agent_runs (
|
||
id TEXT PRIMARY KEY,
|
||
user_id TEXT NOT NULL,
|
||
project_id TEXT NOT NULL DEFAULT '',
|
||
session_id TEXT NOT NULL DEFAULT '',
|
||
source_screen TEXT NOT NULL DEFAULT '',
|
||
source_action_key TEXT NOT NULL DEFAULT '',
|
||
title TEXT NOT NULL DEFAULT '',
|
||
summary TEXT NOT NULL DEFAULT '',
|
||
intent_key TEXT NOT NULL DEFAULT 'custom',
|
||
platform TEXT NOT NULL DEFAULT '',
|
||
platform_scope TEXT NOT NULL DEFAULT 'single_platform',
|
||
delivery_mode TEXT NOT NULL DEFAULT 'hybrid',
|
||
run_status TEXT NOT NULL DEFAULT 'needs_confirmation',
|
||
scheduling_mode TEXT NOT NULL DEFAULT 'queued',
|
||
active_executor_key TEXT NOT NULL DEFAULT 'main_agent',
|
||
plan_json TEXT NOT NULL DEFAULT '{}',
|
||
governance_json TEXT NOT NULL DEFAULT '{}',
|
||
result_json TEXT NOT NULL DEFAULT '{}',
|
||
status_summary TEXT NOT NULL DEFAULT '',
|
||
needs_user_input INTEGER NOT NULL DEFAULT 1,
|
||
blocked_reason TEXT NOT NULL DEFAULT '',
|
||
active_admin_override_notice_json TEXT NOT NULL DEFAULT '{}',
|
||
created_at TEXT NOT NULL,
|
||
updated_at TEXT NOT NULL,
|
||
started_at TEXT NOT NULL DEFAULT '',
|
||
finished_at TEXT NOT NULL DEFAULT '',
|
||
FOREIGN KEY(user_id) REFERENCES accounts(id) ON DELETE CASCADE,
|
||
FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE SET NULL,
|
||
FOREIGN KEY(session_id) REFERENCES oneliner_sessions(id) ON DELETE SET NULL
|
||
);
|
||
|
||
CREATE TABLE IF NOT EXISTS agent_run_events (
|
||
id TEXT PRIMARY KEY,
|
||
run_id TEXT NOT NULL,
|
||
event_type TEXT NOT NULL,
|
||
summary TEXT NOT NULL DEFAULT '',
|
||
details_json TEXT NOT NULL DEFAULT '{}',
|
||
created_at TEXT NOT NULL,
|
||
FOREIGN KEY(run_id) REFERENCES agent_runs(id) ON DELETE CASCADE
|
||
);
|
||
|
||
CREATE TABLE IF NOT EXISTS admin_ops_incidents (
|
||
id TEXT PRIMARY KEY,
|
||
tenant_user_id TEXT NOT NULL DEFAULT '',
|
||
tenant_project_id TEXT NOT NULL DEFAULT '',
|
||
source_type TEXT NOT NULL,
|
||
source_id TEXT NOT NULL DEFAULT '',
|
||
severity TEXT NOT NULL DEFAULT 'warn',
|
||
title TEXT NOT NULL,
|
||
summary TEXT NOT NULL DEFAULT '',
|
||
payload_json TEXT NOT NULL DEFAULT '{}',
|
||
status TEXT NOT NULL DEFAULT 'open',
|
||
assigned_to TEXT NOT NULL DEFAULT '',
|
||
reviewed_by TEXT NOT NULL DEFAULT '',
|
||
review_notes TEXT NOT NULL DEFAULT '',
|
||
created_at TEXT NOT NULL,
|
||
updated_at TEXT NOT NULL,
|
||
UNIQUE(source_type, source_id, title),
|
||
FOREIGN KEY(tenant_user_id) REFERENCES accounts(id) ON DELETE CASCADE,
|
||
FOREIGN KEY(tenant_project_id) REFERENCES projects(id) ON DELETE SET NULL,
|
||
FOREIGN KEY(assigned_to) REFERENCES accounts(id) ON DELETE SET NULL,
|
||
FOREIGN KEY(reviewed_by) REFERENCES accounts(id) ON DELETE SET NULL
|
||
);
|
||
|
||
CREATE TABLE IF NOT EXISTS admin_ops_audit_logs (
|
||
id TEXT PRIMARY KEY,
|
||
actor_user_id TEXT NOT NULL DEFAULT '',
|
||
incident_id TEXT NOT NULL DEFAULT '',
|
||
action_key TEXT NOT NULL DEFAULT '',
|
||
status TEXT NOT NULL DEFAULT 'recorded',
|
||
summary TEXT NOT NULL DEFAULT '',
|
||
details_json TEXT NOT NULL DEFAULT '{}',
|
||
created_at TEXT NOT NULL,
|
||
FOREIGN KEY(actor_user_id) REFERENCES accounts(id) ON DELETE SET NULL,
|
||
FOREIGN KEY(incident_id) REFERENCES admin_ops_incidents(id) ON DELETE CASCADE
|
||
);
|
||
|
||
CREATE TABLE IF NOT EXISTS oneliner_action_definitions (
|
||
id TEXT PRIMARY KEY,
|
||
user_id TEXT NOT NULL,
|
||
project_id TEXT NOT NULL DEFAULT '',
|
||
action_key TEXT NOT NULL,
|
||
handler_key TEXT NOT NULL DEFAULT '',
|
||
label TEXT NOT NULL DEFAULT '',
|
||
description TEXT NOT NULL DEFAULT '',
|
||
category TEXT NOT NULL DEFAULT 'custom',
|
||
status TEXT NOT NULL DEFAULT 'enabled',
|
||
admin_only INTEGER NOT NULL DEFAULT 0,
|
||
requires_platform INTEGER NOT NULL DEFAULT 0,
|
||
config_json TEXT NOT NULL DEFAULT '{}',
|
||
created_at TEXT NOT NULL,
|
||
updated_at TEXT NOT NULL,
|
||
UNIQUE(user_id, project_id, action_key),
|
||
FOREIGN KEY(user_id) REFERENCES accounts(id) ON DELETE CASCADE,
|
||
FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE SET NULL
|
||
);
|
||
|
||
CREATE TABLE IF NOT EXISTS agent_skill_versions (
|
||
id TEXT PRIMARY KEY,
|
||
skill_id TEXT NOT NULL,
|
||
user_id TEXT NOT NULL,
|
||
project_id TEXT NOT NULL DEFAULT '',
|
||
agent_scope TEXT NOT NULL,
|
||
platform TEXT NOT NULL DEFAULT '',
|
||
version_no INTEGER NOT NULL DEFAULT 1,
|
||
snapshot_reason TEXT NOT NULL DEFAULT 'updated',
|
||
snapshot_json TEXT NOT NULL DEFAULT '{}',
|
||
actor_user_id TEXT NOT NULL DEFAULT '',
|
||
created_at TEXT NOT NULL,
|
||
UNIQUE(skill_id, version_no),
|
||
FOREIGN KEY(skill_id) REFERENCES agent_skills(id) ON DELETE CASCADE,
|
||
FOREIGN KEY(user_id) REFERENCES accounts(id) ON DELETE CASCADE,
|
||
FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE SET NULL,
|
||
FOREIGN KEY(actor_user_id) REFERENCES accounts(id) ON DELETE SET NULL
|
||
);
|
||
|
||
CREATE TABLE IF NOT EXISTS admin_ops_fix_runs (
|
||
id TEXT PRIMARY KEY,
|
||
incident_id TEXT NOT NULL,
|
||
actor_user_id TEXT NOT NULL DEFAULT '',
|
||
tenant_user_id TEXT NOT NULL DEFAULT '',
|
||
tenant_project_id TEXT NOT NULL DEFAULT '',
|
||
plan_scope TEXT NOT NULL DEFAULT 'plan',
|
||
status TEXT NOT NULL DEFAULT 'planned',
|
||
audit_status TEXT NOT NULL DEFAULT 'pending',
|
||
review_notes TEXT NOT NULL DEFAULT '',
|
||
plan_json TEXT NOT NULL DEFAULT '{}',
|
||
verification_json TEXT NOT NULL DEFAULT '{}',
|
||
created_at TEXT NOT NULL,
|
||
updated_at TEXT NOT NULL,
|
||
FOREIGN KEY(incident_id) REFERENCES admin_ops_incidents(id) ON DELETE CASCADE,
|
||
FOREIGN KEY(actor_user_id) REFERENCES accounts(id) ON DELETE SET NULL,
|
||
FOREIGN KEY(tenant_user_id) REFERENCES accounts(id) ON DELETE SET NULL,
|
||
FOREIGN KEY(tenant_project_id) REFERENCES projects(id) ON DELETE SET NULL
|
||
);
|
||
|
||
CREATE TABLE IF NOT EXISTS tenant_quota_profiles (
|
||
id TEXT PRIMARY KEY,
|
||
user_id TEXT NOT NULL,
|
||
project_id TEXT NOT NULL DEFAULT '',
|
||
monthly_budget_cents INTEGER NOT NULL DEFAULT 0,
|
||
storage_limit_bytes INTEGER NOT NULL DEFAULT 0,
|
||
analysis_quota INTEGER NOT NULL DEFAULT 0,
|
||
copy_quota INTEGER NOT NULL DEFAULT 0,
|
||
ai_video_quota INTEGER NOT NULL DEFAULT 0,
|
||
real_cut_quota INTEGER NOT NULL DEFAULT 0,
|
||
recorder_quota INTEGER NOT NULL DEFAULT 0,
|
||
enabled INTEGER NOT NULL DEFAULT 1,
|
||
config_json TEXT NOT NULL DEFAULT '{}',
|
||
created_at TEXT NOT NULL,
|
||
updated_at TEXT NOT NULL,
|
||
UNIQUE(user_id, project_id),
|
||
FOREIGN KEY(user_id) REFERENCES accounts(id) ON DELETE CASCADE,
|
||
FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE SET NULL
|
||
);
|
||
|
||
CREATE TABLE IF NOT EXISTS tenant_usage_ledger (
|
||
id TEXT PRIMARY KEY,
|
||
user_id TEXT NOT NULL,
|
||
project_id TEXT NOT NULL DEFAULT '',
|
||
category TEXT NOT NULL,
|
||
quantity INTEGER NOT NULL DEFAULT 1,
|
||
cost_cents INTEGER NOT NULL DEFAULT 0,
|
||
reference_type TEXT NOT NULL DEFAULT '',
|
||
reference_id TEXT NOT NULL DEFAULT '',
|
||
details_json TEXT NOT NULL DEFAULT '{}',
|
||
created_at TEXT NOT NULL,
|
||
FOREIGN KEY(user_id) REFERENCES accounts(id) ON DELETE CASCADE,
|
||
FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE SET NULL
|
||
);
|
||
"""
|
||
with legacy.db.session() as conn:
|
||
conn.executescript(schema)
|
||
|
||
ensure_schema()
|
||
|
||
def _resolve_project(account: dict[str, Any], project_id: str | None) -> dict[str, Any]:
|
||
return legacy.resolve_target_project(account["id"], project_id or None, username=account["username"])
|
||
|
||
def _resolve_project_for_read(account: dict[str, Any], project_id: str | None) -> dict[str, Any] | None:
|
||
if project_id:
|
||
return legacy.resolve_target_project(account["id"], project_id, username=account["username"])
|
||
return legacy.db.fetch_one(
|
||
"SELECT * FROM projects WHERE user_id = ? ORDER BY created_at ASC LIMIT 1",
|
||
(account["id"],),
|
||
)
|
||
|
||
def _resolve_assistant(account: dict[str, Any], assistant_id: str | None, project_id: str = "") -> dict[str, Any] | None:
|
||
return legacy.resolve_target_assistant(account["id"], assistant_id or None, project_id)
|
||
|
||
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 = ?",
|
||
(account["id"], project_id),
|
||
)
|
||
|
||
def _summarize_oneliner_profile(row: dict[str, Any] | None) -> str:
|
||
def _limit(text: str, limit: int) -> str:
|
||
value = str(text or "").strip()
|
||
if len(value) <= limit:
|
||
return value
|
||
return f"{value[: max(limit - 1, 0)].rstrip()}…"
|
||
|
||
data = row or {}
|
||
assistant_id = str(data.get("assistant_id") or "").strip()
|
||
assistant_row = legacy.db.fetch_one("SELECT * FROM assistants WHERE id = ?", (assistant_id,)) if assistant_id else None
|
||
assistant_name = (assistant_row or {}).get("name", "").strip()
|
||
parts = [
|
||
f"默认平台 {legacy.platform_label(data.get('default_platform', '') or 'douyin')}",
|
||
"已绑定执行 Agent" if assistant_id else "暂未绑定执行 Agent",
|
||
]
|
||
if assistant_name:
|
||
parts.append(f"执行 Agent 为 {assistant_name}")
|
||
long_term_goal = str(data.get("long_term_goal") or "").strip()
|
||
if long_term_goal:
|
||
parts.append(_limit(long_term_goal, 36))
|
||
notes = str(data.get("notes") or "").strip()
|
||
if notes:
|
||
parts.append(f"备注 {_limit(notes, 32)}")
|
||
return ",".join(parts[:4])
|
||
|
||
def _oneliner_profile_version_payload(row: dict[str, Any] | None) -> dict[str, Any] | None:
|
||
if not row:
|
||
return None
|
||
assistant = None
|
||
if row.get("assistant_id"):
|
||
assistant_row = legacy.db.fetch_one("SELECT * FROM assistants WHERE id = ?", (row["assistant_id"],))
|
||
if assistant_row:
|
||
assistant = legacy.assistant_payload(assistant_row)
|
||
return {
|
||
"id": row["id"],
|
||
"profile_id": row.get("profile_id", ""),
|
||
"user_id": row.get("user_id", ""),
|
||
"project_id": row.get("project_id", ""),
|
||
"assistant_id": row.get("assistant_id", ""),
|
||
"version_no": int(row.get("version_no") or 0),
|
||
"display_name": row.get("display_name", "OneLiner"),
|
||
"long_term_goal": row.get("long_term_goal", ""),
|
||
"notes": row.get("notes", ""),
|
||
"default_platform": row.get("default_platform", ""),
|
||
"config": _parse_json(row.get("config_json"), {}),
|
||
"summary": row.get("summary", ""),
|
||
"reason": row.get("reason", ""),
|
||
"source_type": row.get("source_type", ""),
|
||
"rollback_from_version_id": row.get("rollback_from_version_id", ""),
|
||
"actor_user_id": row.get("actor_user_id", ""),
|
||
"assistant": assistant,
|
||
"created_at": row.get("created_at", ""),
|
||
}
|
||
|
||
def _oneliner_profile_audit_payload(row: dict[str, Any] | None) -> dict[str, Any] | None:
|
||
if not row:
|
||
return None
|
||
version_row = legacy.db.fetch_one("SELECT * FROM oneliner_profile_versions WHERE id = ?", (row.get("version_id", ""),)) if row.get("version_id") else None
|
||
return {
|
||
"id": row["id"],
|
||
"profile_id": row.get("profile_id", ""),
|
||
"version_id": row.get("version_id", ""),
|
||
"actor_user_id": row.get("actor_user_id", ""),
|
||
"action_key": row.get("action_key", ""),
|
||
"summary": row.get("summary", ""),
|
||
"details": _parse_json(row.get("details_json"), {}),
|
||
"version": _oneliner_profile_version_payload(version_row),
|
||
"created_at": row.get("created_at", ""),
|
||
}
|
||
|
||
def _list_oneliner_profile_versions(profile_row: dict[str, Any] | None) -> list[dict[str, Any]]:
|
||
if not profile_row:
|
||
return []
|
||
rows = legacy.db.fetch_all(
|
||
"""
|
||
SELECT * FROM oneliner_profile_versions
|
||
WHERE profile_id = ?
|
||
ORDER BY version_no DESC, created_at DESC
|
||
""",
|
||
(profile_row["id"],),
|
||
)
|
||
return [_oneliner_profile_version_payload(row) for row in rows if row]
|
||
|
||
def _list_oneliner_profile_audits(profile_row: dict[str, Any] | None, *, limit: int = 12) -> list[dict[str, Any]]:
|
||
if not profile_row:
|
||
return []
|
||
rows = legacy.db.fetch_all(
|
||
"""
|
||
SELECT * FROM oneliner_profile_audit_logs
|
||
WHERE profile_id = ?
|
||
ORDER BY created_at DESC
|
||
LIMIT ?
|
||
""",
|
||
(profile_row["id"], limit),
|
||
)
|
||
return [_oneliner_profile_audit_payload(row) for row in rows if row]
|
||
|
||
def _current_oneliner_profile_version_row(profile_row: dict[str, Any] | None) -> dict[str, Any] | None:
|
||
if not profile_row:
|
||
return None
|
||
return legacy.db.fetch_one(
|
||
"""
|
||
SELECT * FROM oneliner_profile_versions
|
||
WHERE profile_id = ?
|
||
ORDER BY version_no DESC, created_at DESC
|
||
LIMIT 1
|
||
""",
|
||
(profile_row["id"],),
|
||
)
|
||
|
||
def _log_oneliner_profile_audit(
|
||
*,
|
||
profile_id: str,
|
||
version_id: str,
|
||
actor_user_id: str,
|
||
action_key: str,
|
||
summary: str,
|
||
details: dict[str, Any] | None = None,
|
||
) -> dict[str, Any]:
|
||
audit_id = make_id("oneliner_profile_audit")
|
||
legacy.db.execute(
|
||
"""
|
||
INSERT INTO oneliner_profile_audit_logs (
|
||
id, profile_id, version_id, actor_user_id, action_key, summary, details_json, created_at
|
||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||
""",
|
||
(
|
||
audit_id,
|
||
profile_id,
|
||
version_id,
|
||
actor_user_id,
|
||
action_key,
|
||
summary,
|
||
_dump(details or {}),
|
||
now(),
|
||
),
|
||
)
|
||
row = legacy.db.fetch_one("SELECT * FROM oneliner_profile_audit_logs WHERE id = ?", (audit_id,))
|
||
assert row is not None
|
||
return _oneliner_profile_audit_payload(row)
|
||
|
||
def _create_oneliner_profile_version(
|
||
profile_row: dict[str, Any],
|
||
*,
|
||
actor_user_id: str,
|
||
source_type: str,
|
||
reason: str,
|
||
rollback_from_version_id: str = "",
|
||
) -> dict[str, Any]:
|
||
current = legacy.db.fetch_one(
|
||
"SELECT COALESCE(MAX(version_no), 0) AS max_version FROM oneliner_profile_versions WHERE profile_id = ?",
|
||
(profile_row["id"],),
|
||
)
|
||
version_no = int((current or {}).get("max_version") or 0) + 1
|
||
version_id = make_id("oneliner_profile_ver")
|
||
summary = _summarize_oneliner_profile(profile_row)
|
||
legacy.db.execute(
|
||
"""
|
||
INSERT INTO oneliner_profile_versions (
|
||
id, profile_id, user_id, project_id, assistant_id, version_no,
|
||
display_name, long_term_goal, notes, default_platform, config_json,
|
||
summary, reason, source_type, rollback_from_version_id, actor_user_id, created_at
|
||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||
""",
|
||
(
|
||
version_id,
|
||
profile_row["id"],
|
||
profile_row.get("user_id", ""),
|
||
profile_row.get("project_id", ""),
|
||
profile_row.get("assistant_id", ""),
|
||
version_no,
|
||
profile_row.get("display_name", "OneLiner"),
|
||
profile_row.get("long_term_goal", ""),
|
||
profile_row.get("notes", ""),
|
||
profile_row.get("default_platform", ""),
|
||
profile_row.get("config_json", "{}"),
|
||
summary,
|
||
reason.strip(),
|
||
source_type,
|
||
rollback_from_version_id,
|
||
actor_user_id,
|
||
now(),
|
||
),
|
||
)
|
||
row = legacy.db.fetch_one("SELECT * FROM oneliner_profile_versions WHERE id = ?", (version_id,))
|
||
assert row is not None
|
||
return _oneliner_profile_version_payload(row)
|
||
|
||
def _ensure_oneliner_profile_version_seed(profile_row: dict[str, Any], *, actor_user_id: str = "") -> dict[str, Any]:
|
||
current_version = _current_oneliner_profile_version_row(profile_row)
|
||
if current_version:
|
||
payload = _oneliner_profile_version_payload(current_version)
|
||
assert payload is not None
|
||
return payload
|
||
payload = _create_oneliner_profile_version(
|
||
profile_row,
|
||
actor_user_id=actor_user_id or profile_row.get("user_id", ""),
|
||
source_type="system_seed",
|
||
reason="初始化 OneLiner 主配置",
|
||
)
|
||
_log_oneliner_profile_audit(
|
||
profile_id=profile_row["id"],
|
||
version_id=payload["id"],
|
||
actor_user_id=actor_user_id or profile_row.get("user_id", ""),
|
||
action_key="seed-oneliner-profile",
|
||
summary="初始化 OneLiner 主配置版本",
|
||
details={"project_id": profile_row.get("project_id", "")},
|
||
)
|
||
return payload
|
||
|
||
def _oneliner_profile_bundle(row: dict[str, Any], *, account: dict[str, Any] | None = None) -> dict[str, Any]:
|
||
_ensure_oneliner_profile_version_seed(row, actor_user_id=(account or {}).get("id", ""))
|
||
payload = _profile_payload(row, account=account)
|
||
current_version = _oneliner_profile_version_payload(_current_oneliner_profile_version_row(row))
|
||
versions = _list_oneliner_profile_versions(row)
|
||
audits = _list_oneliner_profile_audits(row)
|
||
payload.update(
|
||
{
|
||
"current_version": current_version,
|
||
"versions": {"items": versions, "count": len(versions)},
|
||
"audits": {"items": audits, "count": len(audits)},
|
||
}
|
||
)
|
||
return payload
|
||
|
||
def _summarize_platform_agent_profile(row: dict[str, Any] | None) -> str:
|
||
data = row or {}
|
||
def _clip(value: str, limit: int) -> str:
|
||
text = str(value or "").strip()
|
||
if len(text) <= limit:
|
||
return text
|
||
return f"{text[: max(limit - 1, 0)].rstrip()}…"
|
||
parts: list[str] = []
|
||
name = str(data.get("name") or "").strip()
|
||
if name:
|
||
parts.append(name)
|
||
mission = str(data.get("mission") or "").strip()
|
||
if mission:
|
||
parts.append(_clip(mission, 36))
|
||
notes = str(data.get("notes") or "").strip()
|
||
if notes:
|
||
parts.append(f"备注 {_clip(notes, 32)}")
|
||
return ",".join(parts[:4])
|
||
|
||
def _platform_agent_profile_version_payload(row: dict[str, Any] | None) -> dict[str, Any] | None:
|
||
if not row:
|
||
return None
|
||
assistant = None
|
||
if row.get("assistant_id"):
|
||
assistant_row = legacy.db.fetch_one("SELECT * FROM assistants WHERE id = ?", (row["assistant_id"],))
|
||
if assistant_row:
|
||
assistant = legacy.assistant_payload(assistant_row)
|
||
return {
|
||
"id": row["id"],
|
||
"profile_id": row.get("profile_id", ""),
|
||
"user_id": row.get("user_id", ""),
|
||
"project_id": row.get("project_id", ""),
|
||
"platform": row.get("platform", ""),
|
||
"assistant_id": row.get("assistant_id", ""),
|
||
"version_no": int(row.get("version_no") or 0),
|
||
"name": row.get("name", ""),
|
||
"mission": row.get("mission", ""),
|
||
"notes": row.get("notes", ""),
|
||
"status": row.get("status", "active"),
|
||
"config": _parse_json(row.get("config_json"), {}),
|
||
"summary": row.get("summary", ""),
|
||
"reason": row.get("reason", ""),
|
||
"source_type": row.get("source_type", ""),
|
||
"rollback_from_version_id": row.get("rollback_from_version_id", ""),
|
||
"actor_user_id": row.get("actor_user_id", ""),
|
||
"assistant": assistant,
|
||
"created_at": row.get("created_at", ""),
|
||
}
|
||
|
||
def _platform_agent_profile_audit_payload(row: dict[str, Any] | None) -> dict[str, Any] | None:
|
||
if not row:
|
||
return None
|
||
version_row = legacy.db.fetch_one("SELECT * FROM platform_agent_profile_versions WHERE id = ?", (row.get("version_id", ""),)) if row.get("version_id") else None
|
||
return {
|
||
"id": row["id"],
|
||
"profile_id": row.get("profile_id", ""),
|
||
"version_id": row.get("version_id", ""),
|
||
"actor_user_id": row.get("actor_user_id", ""),
|
||
"action_key": row.get("action_key", ""),
|
||
"summary": row.get("summary", ""),
|
||
"details": _parse_json(row.get("details_json"), {}),
|
||
"version": _platform_agent_profile_version_payload(version_row),
|
||
"created_at": row.get("created_at", ""),
|
||
}
|
||
|
||
def _list_platform_agent_profile_versions(profile_row: dict[str, Any] | None) -> list[dict[str, Any]]:
|
||
if not profile_row:
|
||
return []
|
||
rows = legacy.db.fetch_all(
|
||
"""
|
||
SELECT * FROM platform_agent_profile_versions
|
||
WHERE profile_id = ?
|
||
ORDER BY version_no DESC, created_at DESC
|
||
""",
|
||
(profile_row["id"],),
|
||
)
|
||
return [_platform_agent_profile_version_payload(row) for row in rows if row]
|
||
|
||
def _list_platform_agent_profile_audits(profile_row: dict[str, Any] | None, *, limit: int = 12) -> list[dict[str, Any]]:
|
||
if not profile_row:
|
||
return []
|
||
rows = legacy.db.fetch_all(
|
||
"""
|
||
SELECT * FROM platform_agent_profile_audit_logs
|
||
WHERE profile_id = ?
|
||
ORDER BY created_at DESC
|
||
LIMIT ?
|
||
""",
|
||
(profile_row["id"], limit),
|
||
)
|
||
return [_platform_agent_profile_audit_payload(row) for row in rows if row]
|
||
|
||
def _current_platform_agent_profile_version_row(profile_row: dict[str, Any] | None) -> dict[str, Any] | None:
|
||
if not profile_row:
|
||
return None
|
||
return legacy.db.fetch_one(
|
||
"""
|
||
SELECT * FROM platform_agent_profile_versions
|
||
WHERE profile_id = ?
|
||
ORDER BY version_no DESC, created_at DESC
|
||
LIMIT 1
|
||
""",
|
||
(profile_row["id"],),
|
||
)
|
||
|
||
def _log_platform_agent_profile_audit(
|
||
*,
|
||
profile_id: str,
|
||
version_id: str,
|
||
actor_user_id: str,
|
||
action_key: str,
|
||
summary: str,
|
||
details: dict[str, Any] | None = None,
|
||
) -> dict[str, Any]:
|
||
audit_id = make_id("plat_agent_audit")
|
||
legacy.db.execute(
|
||
"""
|
||
INSERT INTO platform_agent_profile_audit_logs (
|
||
id, profile_id, version_id, actor_user_id, action_key, summary, details_json, created_at
|
||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||
""",
|
||
(
|
||
audit_id,
|
||
profile_id,
|
||
version_id,
|
||
actor_user_id,
|
||
action_key,
|
||
summary,
|
||
_dump(details or {}),
|
||
now(),
|
||
),
|
||
)
|
||
row = legacy.db.fetch_one("SELECT * FROM platform_agent_profile_audit_logs WHERE id = ?", (audit_id,))
|
||
assert row is not None
|
||
return _platform_agent_profile_audit_payload(row)
|
||
|
||
def _create_platform_agent_profile_version(
|
||
profile_row: dict[str, Any],
|
||
*,
|
||
actor_user_id: str,
|
||
source_type: str,
|
||
reason: str,
|
||
rollback_from_version_id: str = "",
|
||
) -> dict[str, Any]:
|
||
current = legacy.db.fetch_one(
|
||
"SELECT COALESCE(MAX(version_no), 0) AS max_version FROM platform_agent_profile_versions WHERE profile_id = ?",
|
||
(profile_row["id"],),
|
||
)
|
||
version_no = int((current or {}).get("max_version") or 0) + 1
|
||
version_id = make_id("plat_agent_ver")
|
||
summary = _summarize_platform_agent_profile(profile_row)
|
||
version_params = (
|
||
version_id,
|
||
profile_row["id"],
|
||
profile_row.get("user_id", ""),
|
||
profile_row.get("project_id", ""),
|
||
profile_row.get("platform", ""),
|
||
profile_row.get("assistant_id", ""),
|
||
version_no,
|
||
profile_row.get("name", ""),
|
||
profile_row.get("mission", ""),
|
||
profile_row.get("notes", ""),
|
||
profile_row.get("status", "active"),
|
||
profile_row.get("config_json", "{}"),
|
||
summary,
|
||
reason.strip(),
|
||
source_type,
|
||
rollback_from_version_id,
|
||
actor_user_id,
|
||
now(),
|
||
)
|
||
insert_sql = """
|
||
INSERT INTO platform_agent_profile_versions (
|
||
id, profile_id, user_id, project_id, platform, assistant_id, version_no,
|
||
name, mission, notes, status, config_json, summary, reason,
|
||
source_type, rollback_from_version_id, actor_user_id, created_at
|
||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||
"""
|
||
if profile_row.get("assistant_id"):
|
||
legacy.db.execute(insert_sql, version_params)
|
||
else:
|
||
with legacy.db.session() as conn:
|
||
conn.execute("PRAGMA foreign_keys=OFF")
|
||
conn.execute(insert_sql, version_params)
|
||
conn.execute("PRAGMA foreign_keys=ON")
|
||
row = legacy.db.fetch_one("SELECT * FROM platform_agent_profile_versions WHERE id = ?", (version_id,))
|
||
assert row is not None
|
||
return _platform_agent_profile_version_payload(row)
|
||
|
||
def _ensure_platform_agent_profile_version_seed(profile_row: dict[str, Any], *, actor_user_id: str = "") -> dict[str, Any]:
|
||
current_version = _current_platform_agent_profile_version_row(profile_row)
|
||
if current_version:
|
||
payload = _platform_agent_profile_version_payload(current_version)
|
||
assert payload is not None
|
||
return payload
|
||
payload = _create_platform_agent_profile_version(
|
||
profile_row,
|
||
actor_user_id=actor_user_id or profile_row.get("user_id", ""),
|
||
source_type="system_seed",
|
||
reason="初始化平台 Agent 配置",
|
||
)
|
||
_log_platform_agent_profile_audit(
|
||
profile_id=profile_row["id"],
|
||
version_id=payload["id"],
|
||
actor_user_id=actor_user_id or profile_row.get("user_id", ""),
|
||
action_key="seed-platform-agent-profile",
|
||
summary="初始化平台 Agent 配置版本",
|
||
details={"project_id": profile_row.get("project_id", ""), "platform": profile_row.get("platform", "")},
|
||
)
|
||
return payload
|
||
|
||
def _oneliner_profile_runtime_snapshot(row: dict[str, Any], *, account: dict[str, Any] | None = None) -> dict[str, Any]:
|
||
bundle = _oneliner_profile_bundle(row, account=account)
|
||
return {
|
||
"id": str(bundle.get("id") or "").strip(),
|
||
"assistant_id": str(bundle.get("assistant_id") or "").strip(),
|
||
"display_name": str(bundle.get("display_name") or "").strip(),
|
||
"long_term_goal": str(bundle.get("long_term_goal") or "").strip(),
|
||
"default_platform": str(bundle.get("default_platform") or "").strip(),
|
||
"notes": str(bundle.get("notes") or "").strip(),
|
||
"summary": str(bundle.get("summary") or "").strip(),
|
||
"current_version": {
|
||
"id": str((bundle.get("current_version") or {}).get("id") or "").strip(),
|
||
"version_no": int((bundle.get("current_version") or {}).get("version_no") or 0),
|
||
"title": str((bundle.get("current_version") or {}).get("title") or "").strip(),
|
||
"summary": str((bundle.get("current_version") or {}).get("summary") or "").strip(),
|
||
"reason": str((bundle.get("current_version") or {}).get("reason") or "").strip(),
|
||
"created_at": str((bundle.get("current_version") or {}).get("created_at") or "").strip(),
|
||
},
|
||
}
|
||
|
||
def _profile_payload(row: dict[str, Any], *, account: dict[str, Any] | None = None) -> dict[str, Any]:
|
||
assistant = None
|
||
if row.get("assistant_id"):
|
||
assistant_row = legacy.db.fetch_one("SELECT * FROM assistants WHERE id = ?", (row["assistant_id"],))
|
||
if assistant_row and (not account or assistant_row.get("user_id") == account["id"]):
|
||
assistant = legacy.assistant_payload(assistant_row)
|
||
return {
|
||
"id": row["id"],
|
||
"user_id": row["user_id"],
|
||
"project_id": row.get("project_id", ""),
|
||
"assistant_id": row.get("assistant_id", ""),
|
||
"display_name": row.get("display_name", "OneLiner"),
|
||
"long_term_goal": row.get("long_term_goal", ""),
|
||
"notes": row.get("notes", ""),
|
||
"default_platform": row.get("default_platform", ""),
|
||
"config": _parse_json(row.get("config_json"), {}),
|
||
"assistant": assistant,
|
||
"created_at": row["created_at"],
|
||
"updated_at": row["updated_at"],
|
||
}
|
||
|
||
def _ensure_oneliner_profile(account: dict[str, Any], project_id: str = "") -> dict[str, Any]:
|
||
row = _fetch_profile_row(account, project_id)
|
||
if row:
|
||
return row
|
||
assistant_id = ""
|
||
if project_id:
|
||
assistant_row = legacy.db.fetch_one(
|
||
"SELECT * FROM assistants WHERE user_id = ? AND (project_id = ? OR project_id = '') ORDER BY created_at ASC LIMIT 1",
|
||
(account["id"], project_id),
|
||
)
|
||
else:
|
||
assistant_row = legacy.db.fetch_one(
|
||
"SELECT * FROM assistants WHERE user_id = ? ORDER BY created_at ASC LIMIT 1",
|
||
(account["id"],),
|
||
)
|
||
if assistant_row:
|
||
assistant_id = assistant_row["id"]
|
||
profile_id = make_id("oneliner")
|
||
created_at = now()
|
||
default_platform = "douyin"
|
||
insert_sql = """
|
||
INSERT INTO oneliner_profiles (
|
||
id, user_id, project_id, assistant_id, display_name, long_term_goal, notes,
|
||
default_platform, config_json, created_at, updated_at
|
||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||
"""
|
||
insert_params = (
|
||
profile_id,
|
||
account["id"],
|
||
project_id,
|
||
assistant_id,
|
||
"OneLiner",
|
||
"",
|
||
"",
|
||
default_platform,
|
||
_dump({"chat_only_for_unreleased_ui": True}),
|
||
created_at,
|
||
created_at,
|
||
)
|
||
if assistant_id:
|
||
legacy.db.execute(insert_sql, insert_params)
|
||
else:
|
||
with legacy.db.session() as conn:
|
||
conn.execute("PRAGMA foreign_keys=OFF")
|
||
conn.execute(insert_sql, insert_params)
|
||
conn.execute("PRAGMA foreign_keys=ON")
|
||
return legacy.db.fetch_one("SELECT * FROM oneliner_profiles WHERE id = ?", (profile_id,))
|
||
|
||
def _list_platform_profiles(account: dict[str, Any], project_id: str = "") -> list[dict[str, Any]]:
|
||
rows = legacy.db.fetch_all(
|
||
"SELECT * FROM platform_agent_profiles WHERE user_id = ? AND project_id = ? ORDER BY platform ASC",
|
||
(account["id"], project_id),
|
||
)
|
||
mapping = {row["platform"]: row for row in rows}
|
||
results: list[dict[str, Any]] = []
|
||
for platform in sorted(legacy.DOMESTIC_PLATFORMS):
|
||
row = mapping.get(platform)
|
||
payload = _platform_agent_payload(account, row, platform=platform, project_id=project_id)
|
||
results.append(payload)
|
||
return results
|
||
|
||
def _platform_agent_payload(
|
||
account: dict[str, Any],
|
||
row: dict[str, Any] | None,
|
||
*,
|
||
platform: str,
|
||
project_id: str = "",
|
||
) -> dict[str, Any]:
|
||
assistant = None
|
||
if row and row.get("assistant_id"):
|
||
assistant_row = legacy.db.fetch_one("SELECT * FROM assistants WHERE id = ?", (row["assistant_id"],))
|
||
if assistant_row and assistant_row.get("user_id") == account["id"]:
|
||
assistant = legacy.assistant_payload(assistant_row)
|
||
memory_count = legacy.db.fetch_one(
|
||
"""
|
||
SELECT COUNT(*) AS count FROM agent_memories
|
||
WHERE user_id = ? AND project_id = ? AND agent_scope = 'platform' AND platform = ?
|
||
""",
|
||
(account["id"], project_id, platform),
|
||
)["count"]
|
||
skill_count = legacy.db.fetch_one(
|
||
"""
|
||
SELECT COUNT(*) AS count FROM agent_skills
|
||
WHERE user_id = ? AND project_id = ? AND agent_scope = 'platform' AND platform = ?
|
||
""",
|
||
(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 = "待补全"
|
||
|
||
def build_recent_execution_payload(base_payload: dict[str, Any], run_row: dict[str, Any] | None) -> dict[str, Any]:
|
||
if not run_row:
|
||
return base_payload
|
||
latest_result = _parse_json(run_row.get("result_json"), {})
|
||
latest_governance = _parse_json(run_row.get("governance_json"), {})
|
||
latest_plan = _parse_json(run_row.get("plan_json"), {})
|
||
execution_card = (latest_result.get("execution_card") or {}) if isinstance(latest_result, dict) else {}
|
||
result_sections = (latest_result.get("result_sections") or {}) if isinstance(latest_result, dict) else {}
|
||
recommended_action = {}
|
||
if isinstance(latest_result, dict):
|
||
recommended_action = latest_result.get("recommended_action") or latest_result.get("recommended_preview_action") or {}
|
||
oneliner_profile_version = (
|
||
execution_card.get("oneliner_profile_version")
|
||
or latest_governance.get("oneliner_profile_version")
|
||
or (latest_governance.get("oneliner_profile") or {}).get("current_version")
|
||
or {}
|
||
)
|
||
platform_profile_version = (
|
||
execution_card.get("platform_agent_profile")
|
||
or ((latest_governance.get("platform_agent_profile") or {}).get("current_version") or {})
|
||
)
|
||
return {
|
||
**base_payload,
|
||
"title": str(run_row.get("title") or latest_plan.get("goal") or "").strip(),
|
||
"goal": str(latest_plan.get("goal") or run_row.get("title") or "").strip(),
|
||
"platform_scope": str(run_row.get("platform_scope") or "").strip(),
|
||
"delivery_mode": str(run_row.get("delivery_mode") or "").strip(),
|
||
"active_executor_key": str(run_row.get("active_executor_key") or "").strip(),
|
||
"source_action_key": str(run_row.get("source_action_key") or "").strip(),
|
||
"oneliner_profile_version_id": str(oneliner_profile_version.get("version_id") or oneliner_profile_version.get("id") or "").strip(),
|
||
"platform_agent_profile_version_id": str(platform_profile_version.get("version_id") or platform_profile_version.get("id") or "").strip(),
|
||
"recommended_action": {
|
||
"action": str(recommended_action.get("action") or "").strip(),
|
||
"screen": str(recommended_action.get("screen") or "").strip(),
|
||
"label": str(recommended_action.get("label") or "").strip(),
|
||
"summary": str(recommended_action.get("summary") or "").strip(),
|
||
},
|
||
"workstream_key": str(result_sections.get("workstream_key") or "").strip(),
|
||
"workstream_label": str(result_sections.get("workstream_label") or "").strip(),
|
||
}
|
||
|
||
recent_execution = None
|
||
current_version = None
|
||
if row:
|
||
_ensure_platform_agent_profile_version_seed(row, actor_user_id=account.get("id", ""))
|
||
current_version = _platform_agent_profile_version_payload(_current_platform_agent_profile_version_row(row))
|
||
if row and str(row.get("last_run_id") or "").strip():
|
||
recent_execution = build_recent_execution_payload({
|
||
"run_id": str(row.get("last_run_id") or "").strip(),
|
||
"run_status": str(row.get("last_run_status") or "").strip(),
|
||
"used_at": str(row.get("last_used_at") or "").strip(),
|
||
"intent_key": str(row.get("last_intent_key") or "").strip(),
|
||
"intent_label": INTENT_LABELS.get(str(row.get("last_intent_key") or "").strip() or "custom", "主 Agent 任务"),
|
||
"oneliner_profile_version_no": int(row.get("last_oneliner_profile_version_no") or 0),
|
||
"platform_agent_profile_version_no": int(row.get("last_platform_profile_version_no") or 0),
|
||
"summary": str(row.get("last_execution_summary") or "").strip(),
|
||
"source_screen": str(row.get("last_source_screen") or "").strip(),
|
||
}, legacy.db.fetch_one("SELECT * FROM agent_runs WHERE id = ?", (str(row.get("last_run_id") or "").strip(),)))
|
||
if recent_execution is None and str(project_id or "").strip() and str(platform or "").strip():
|
||
latest_run_row = legacy.db.fetch_one(
|
||
"""
|
||
SELECT * FROM agent_runs
|
||
WHERE user_id = ? AND project_id = ? AND platform = ?
|
||
ORDER BY updated_at DESC, created_at DESC
|
||
LIMIT 1
|
||
""",
|
||
(account["id"], project_id, platform),
|
||
)
|
||
if latest_run_row:
|
||
latest_result = _parse_json(latest_run_row.get("result_json"), {})
|
||
latest_governance = _parse_json(latest_run_row.get("governance_json"), {})
|
||
latest_profile_version = (
|
||
((latest_result.get("execution_card") or {}).get("oneliner_profile_version"))
|
||
or latest_governance.get("oneliner_profile_version")
|
||
or {}
|
||
)
|
||
recent_execution = build_recent_execution_payload({
|
||
"run_id": str(latest_run_row.get("id") or "").strip(),
|
||
"run_status": str(latest_run_row.get("run_status") or "").strip(),
|
||
"used_at": str(latest_run_row.get("finished_at") or latest_run_row.get("updated_at") or "").strip(),
|
||
"intent_key": str(latest_run_row.get("intent_key") or "").strip(),
|
||
"intent_label": INTENT_LABELS.get(str(latest_run_row.get("intent_key") or "").strip() or "custom", "主 Agent 任务"),
|
||
"oneliner_profile_version_no": int(latest_profile_version.get("version_no") or 0),
|
||
"platform_agent_profile_version_no": int(
|
||
(((latest_result.get("execution_card") or {}).get("platform_agent_profile") or {}).get("version_no"))
|
||
or (((latest_governance.get("platform_agent_profile") or {}).get("current_version") or {}).get("version_no"))
|
||
or 0
|
||
),
|
||
"summary": str(latest_run_row.get("status_summary") or "").strip(),
|
||
"source_screen": str(latest_run_row.get("source_screen") or "").strip(),
|
||
}, latest_run_row)
|
||
return {
|
||
"id": row["id"] if row else "",
|
||
"user_id": account["id"],
|
||
"project_id": project_id,
|
||
"platform": platform,
|
||
"platform_label": legacy.platform_label(platform),
|
||
"assistant_id": row.get("assistant_id", "") if row else "",
|
||
"name": row.get("name", f"{legacy.platform_label(platform)} Agent") if row else f"{legacy.platform_label(platform)} Agent",
|
||
"mission": row.get("mission", "") if row else "",
|
||
"notes": row.get("notes", "") if row else "",
|
||
"status": row.get("status", "draft") if row else "draft",
|
||
"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,
|
||
"recent_execution": recent_execution,
|
||
"readiness_score": readiness_score,
|
||
"readiness_label": readiness_label,
|
||
"assistant": assistant,
|
||
"current_version": current_version,
|
||
"created_at": (row or {}).get("created_at", ""),
|
||
"updated_at": (row or {}).get("updated_at", ""),
|
||
}
|
||
|
||
def _platform_agent_runtime_snapshot(
|
||
account: dict[str, Any],
|
||
row: dict[str, Any] | None,
|
||
*,
|
||
platform: str,
|
||
project_id: str = "",
|
||
) -> dict[str, Any]:
|
||
payload = _platform_agent_payload(account, row, platform=platform, project_id=project_id)
|
||
assistant = payload.get("assistant") or {}
|
||
recent_memory = payload.get("recent_memory") or {}
|
||
recent_skill = payload.get("recent_skill") or {}
|
||
return {
|
||
"id": str(payload.get("id") or "").strip(),
|
||
"platform": str(payload.get("platform") or platform or "").strip(),
|
||
"platform_label": str(payload.get("platform_label") or legacy.platform_label(platform) or "").strip(),
|
||
"assistant_id": str(payload.get("assistant_id") or "").strip(),
|
||
"assistant_name": str((assistant or {}).get("name") or "").strip(),
|
||
"name": str(payload.get("name") or "").strip(),
|
||
"mission": str(payload.get("mission") or "").strip(),
|
||
"notes": str(payload.get("notes") or "").strip(),
|
||
"status": str(payload.get("status") or "").strip(),
|
||
"memory_count": int(payload.get("memory_count") or 0),
|
||
"skill_count": int(payload.get("skill_count") or 0),
|
||
"readiness_score": int(payload.get("readiness_score") or 0),
|
||
"readiness_label": str(payload.get("readiness_label") or "").strip(),
|
||
"recent_memory_title": str(recent_memory.get("title") or "").strip(),
|
||
"recent_skill_title": str(recent_skill.get("title") or "").strip(),
|
||
"recent_execution": payload.get("recent_execution") or {},
|
||
"current_version": payload.get("current_version") or {},
|
||
}
|
||
|
||
def _record_platform_agent_execution_feedback(
|
||
account_id: str,
|
||
*,
|
||
project_id: str,
|
||
platform: str,
|
||
run_id: str,
|
||
run_status: str,
|
||
intent_key: str,
|
||
source_screen: str,
|
||
oneliner_profile_version_no: int,
|
||
platform_agent_profile_version_no: int,
|
||
execution_summary: str,
|
||
) -> None:
|
||
normalized_platform = _safe_platform(platform, fallback="") if str(platform or "").strip() else ""
|
||
if not normalized_platform or not str(project_id or "").strip() or not str(run_id or "").strip():
|
||
return
|
||
legacy.db.execute(
|
||
"""
|
||
UPDATE platform_agent_profiles
|
||
SET last_run_id = ?, last_run_status = ?, last_used_at = ?, last_intent_key = ?,
|
||
last_oneliner_profile_version_no = ?, last_platform_profile_version_no = ?, last_execution_summary = ?, last_source_screen = ?, updated_at = ?
|
||
WHERE user_id = ? AND project_id = ? AND platform = ?
|
||
""",
|
||
(
|
||
str(run_id).strip(),
|
||
str(run_status or "").strip(),
|
||
now(),
|
||
str(intent_key or "custom").strip() or "custom",
|
||
int(oneliner_profile_version_no or 0),
|
||
int(platform_agent_profile_version_no or 0),
|
||
str(execution_summary or "").strip(),
|
||
str(source_screen or "").strip(),
|
||
now(),
|
||
account_id,
|
||
project_id,
|
||
normalized_platform,
|
||
),
|
||
)
|
||
|
||
def _memory_payload(row: dict[str, Any]) -> dict[str, Any]:
|
||
return {
|
||
"id": row["id"],
|
||
"user_id": row["user_id"],
|
||
"project_id": row.get("project_id", ""),
|
||
"agent_scope": row.get("agent_scope", ""),
|
||
"platform": row.get("platform", ""),
|
||
"platform_label": legacy.platform_label(row.get("platform", "")) if row.get("platform") else "",
|
||
"subject_type": row.get("subject_type", ""),
|
||
"subject_id": row.get("subject_id", ""),
|
||
"memory_key": row.get("memory_key", ""),
|
||
"title": row.get("title", ""),
|
||
"summary": row.get("summary", ""),
|
||
"details": _parse_json(row.get("details_json"), {}),
|
||
"confidence": float(row.get("confidence") or 0),
|
||
"last_validated_at": row.get("last_validated_at", ""),
|
||
"created_at": row["created_at"],
|
||
"updated_at": row["updated_at"],
|
||
}
|
||
|
||
def _skill_payload(row: dict[str, Any]) -> dict[str, Any]:
|
||
return {
|
||
"id": row["id"],
|
||
"user_id": row["user_id"],
|
||
"project_id": row.get("project_id", ""),
|
||
"agent_scope": row.get("agent_scope", ""),
|
||
"platform": row.get("platform", ""),
|
||
"platform_label": legacy.platform_label(row.get("platform", "")) if row.get("platform") else "",
|
||
"parent_skill_id": row.get("parent_skill_id", ""),
|
||
"skill_key": row.get("skill_key", ""),
|
||
"name": row.get("name", ""),
|
||
"status": row.get("status", "draft"),
|
||
"method": _parse_json(row.get("method_json"), {}),
|
||
"test_spec": _parse_json(row.get("test_spec_json"), {}),
|
||
"last_result": _parse_json(row.get("last_result_json"), {}),
|
||
"success_count": int(row.get("success_count") or 0),
|
||
"failure_count": int(row.get("failure_count") or 0),
|
||
"last_score": float(row.get("last_score") or 0),
|
||
"last_validated_at": row.get("last_validated_at", ""),
|
||
"created_at": row["created_at"],
|
||
"updated_at": row["updated_at"],
|
||
}
|
||
|
||
def _session_payload(row: dict[str, Any]) -> dict[str, Any]:
|
||
return {
|
||
"id": row["id"],
|
||
"user_id": row["user_id"],
|
||
"project_id": row.get("project_id", ""),
|
||
"profile_id": row.get("profile_id", ""),
|
||
"title": row.get("title", ""),
|
||
"status": row.get("status", "active"),
|
||
"preferred_platform": row.get("preferred_platform", ""),
|
||
"last_platform": row.get("last_platform", ""),
|
||
"last_intent_key": row.get("last_intent_key", ""),
|
||
"last_message_at": row.get("last_message_at", ""),
|
||
"created_at": row["created_at"],
|
||
"updated_at": row["updated_at"],
|
||
}
|
||
|
||
def _message_payload(row: dict[str, Any]) -> dict[str, Any]:
|
||
return {
|
||
"id": row["id"],
|
||
"session_id": row["session_id"],
|
||
"user_id": row["user_id"],
|
||
"role": row.get("role", "user"),
|
||
"content": row.get("content", ""),
|
||
"plan": _parse_json(row.get("plan_json"), {}),
|
||
"result": _parse_json(row.get("result_json"), {}),
|
||
"created_at": row["created_at"],
|
||
}
|
||
|
||
def _agent_run_event_payload(row: dict[str, Any]) -> dict[str, Any]:
|
||
return {
|
||
"id": row["id"],
|
||
"run_id": row.get("run_id", ""),
|
||
"event_type": row.get("event_type", ""),
|
||
"summary": row.get("summary", ""),
|
||
"details": _parse_json(row.get("details_json"), {}),
|
||
"created_at": row.get("created_at", ""),
|
||
}
|
||
|
||
def _list_agent_run_events(run_id: str) -> list[dict[str, Any]]:
|
||
rows = legacy.db.fetch_all(
|
||
"""
|
||
SELECT * FROM agent_run_events
|
||
WHERE run_id = ?
|
||
ORDER BY created_at ASC
|
||
""",
|
||
(run_id,),
|
||
)
|
||
return [_agent_run_event_payload(row) for row in rows]
|
||
|
||
def _agent_run_payload(row: dict[str, Any], *, include_events: bool = True) -> dict[str, Any]:
|
||
plan = _parse_json(row.get("plan_json"), {})
|
||
result = _parse_json(row.get("result_json"), {})
|
||
recommended_preview_action = result.get("recommended_action") if isinstance(result, dict) else {}
|
||
if not recommended_preview_action:
|
||
recommended_preview_action = _build_agent_run_recommended_action(row, plan)
|
||
payload = {
|
||
"id": row["id"],
|
||
"user_id": row.get("user_id", ""),
|
||
"project_id": row.get("project_id", ""),
|
||
"session_id": row.get("session_id", ""),
|
||
"source_screen": row.get("source_screen", ""),
|
||
"source_action_key": row.get("source_action_key", ""),
|
||
"title": row.get("title", ""),
|
||
"summary": row.get("summary", ""),
|
||
"intent_key": row.get("intent_key", "custom"),
|
||
"platform": row.get("platform", ""),
|
||
"platform_label": legacy.platform_label(row.get("platform", "")) if row.get("platform") else "",
|
||
"platform_scope": row.get("platform_scope", "single_platform"),
|
||
"delivery_mode": row.get("delivery_mode", "hybrid"),
|
||
"run_status": row.get("run_status", "needs_confirmation"),
|
||
"scheduling_mode": row.get("scheduling_mode", "queued"),
|
||
"active_executor_key": row.get("active_executor_key", "main_agent"),
|
||
"plan": plan,
|
||
"governance": _parse_json(row.get("governance_json"), {}),
|
||
"result": result,
|
||
"recommended_preview_action": recommended_preview_action,
|
||
"status_summary": row.get("status_summary", ""),
|
||
"needs_user_input": bool(row.get("needs_user_input")),
|
||
"blocked_reason": row.get("blocked_reason", ""),
|
||
"active_admin_override_notice": _parse_json(row.get("active_admin_override_notice_json"), {}),
|
||
"created_at": row.get("created_at", ""),
|
||
"updated_at": row.get("updated_at", ""),
|
||
"started_at": row.get("started_at", ""),
|
||
"finished_at": row.get("finished_at", ""),
|
||
}
|
||
if include_events:
|
||
payload["events"] = _list_agent_run_events(row["id"])
|
||
return payload
|
||
|
||
def _incident_payload(row: dict[str, Any]) -> dict[str, Any]:
|
||
return {
|
||
"id": row["id"],
|
||
"tenant_user_id": row.get("tenant_user_id", ""),
|
||
"tenant_project_id": row.get("tenant_project_id", ""),
|
||
"source_type": row.get("source_type", ""),
|
||
"source_id": row.get("source_id", ""),
|
||
"severity": row.get("severity", "warn"),
|
||
"title": row.get("title", ""),
|
||
"summary": row.get("summary", ""),
|
||
"payload": _parse_json(row.get("payload_json"), {}),
|
||
"status": row.get("status", "open"),
|
||
"assigned_to": row.get("assigned_to", ""),
|
||
"reviewed_by": row.get("reviewed_by", ""),
|
||
"review_notes": row.get("review_notes", ""),
|
||
"created_at": row["created_at"],
|
||
"updated_at": row["updated_at"],
|
||
}
|
||
|
||
def _admin_audit_payload(row: dict[str, Any]) -> dict[str, Any]:
|
||
return {
|
||
"id": row["id"],
|
||
"actor_user_id": row.get("actor_user_id", ""),
|
||
"incident_id": row.get("incident_id", ""),
|
||
"action_key": row.get("action_key", ""),
|
||
"status": row.get("status", "recorded"),
|
||
"summary": row.get("summary", ""),
|
||
"details": _parse_json(row.get("details_json"), {}),
|
||
"created_at": row.get("created_at", ""),
|
||
}
|
||
|
||
def _action_definition_payload(row: dict[str, Any] | None, *, fallback_key: str = "") -> dict[str, Any]:
|
||
fallback = ACTION_REGISTRY_DEFAULTS.get(fallback_key or "", {})
|
||
data = row or {}
|
||
action_key = data.get("action_key") or fallback_key
|
||
return {
|
||
"id": data.get("id", ""),
|
||
"user_id": data.get("user_id", ""),
|
||
"project_id": data.get("project_id", ""),
|
||
"action_key": action_key,
|
||
"handler_key": data.get("handler_key") or fallback.get("handler_key") or action_key,
|
||
"label": data.get("label") or fallback.get("label") or action_key,
|
||
"description": data.get("description") or fallback.get("description") or "",
|
||
"category": data.get("category") or fallback.get("category") or "custom",
|
||
"status": data.get("status") or fallback.get("status") or "enabled",
|
||
"admin_only": _bool_flag(data.get("admin_only", fallback.get("admin_only", False))),
|
||
"requires_platform": _bool_flag(data.get("requires_platform", fallback.get("requires_platform", False))),
|
||
"config": _parse_json(data.get("config_json"), fallback.get("config") or {}),
|
||
"created_at": data.get("created_at", ""),
|
||
"updated_at": data.get("updated_at", ""),
|
||
"source": "override" if row else "default",
|
||
}
|
||
|
||
def _list_action_registry(account: dict[str, Any], *, project_id: str) -> list[dict[str, Any]]:
|
||
rows = legacy.db.fetch_all(
|
||
"""
|
||
SELECT * FROM oneliner_action_definitions
|
||
WHERE user_id = ? AND project_id = ?
|
||
ORDER BY category ASC, action_key ASC
|
||
""",
|
||
(account["id"], project_id),
|
||
)
|
||
row_map = {row["action_key"]: row for row in rows}
|
||
items = []
|
||
for action_key in sorted(ACTION_REGISTRY_DEFAULTS.keys()):
|
||
items.append(_action_definition_payload(row_map.get(action_key), fallback_key=action_key))
|
||
for action_key, row in row_map.items():
|
||
if action_key in ACTION_REGISTRY_DEFAULTS:
|
||
continue
|
||
items.append(_action_definition_payload(row, fallback_key=action_key))
|
||
return items
|
||
|
||
def _get_action_definition(account: dict[str, Any], *, project_id: str, action_key: str) -> dict[str, Any] | None:
|
||
normalized_key = str(action_key or "").strip()
|
||
if not normalized_key:
|
||
return None
|
||
row = legacy.db.fetch_one(
|
||
"""
|
||
SELECT * FROM oneliner_action_definitions
|
||
WHERE user_id = ? AND project_id = ? AND action_key = ?
|
||
""",
|
||
(account["id"], project_id, normalized_key),
|
||
)
|
||
if row:
|
||
return _action_definition_payload(row, fallback_key=normalized_key)
|
||
if normalized_key in ACTION_REGISTRY_DEFAULTS:
|
||
return _action_definition_payload(None, fallback_key=normalized_key)
|
||
return None
|
||
|
||
def _decorate_oneliner_action(account: dict[str, Any], *, project_id: str, action: dict[str, Any]) -> dict[str, Any]:
|
||
cloned = dict(action or {})
|
||
executor_key = str(cloned.get("executor_key") or "").strip()
|
||
if not executor_key:
|
||
return cloned
|
||
definition = _get_action_definition(account, project_id=project_id, action_key=executor_key)
|
||
if not definition:
|
||
cloned["disabled_reason"] = "当前租户还没有接入这条动作。"
|
||
return cloned
|
||
cloned["executor_label"] = definition.get("label") or cloned.get("label") or executor_key
|
||
if cloned.get("kind") == "api_action" and not cloned.get("label"):
|
||
cloned["label"] = definition.get("label") or executor_key
|
||
if definition.get("status") != "enabled":
|
||
cloned["disabled_reason"] = "当前租户已停用这条动作。"
|
||
elif definition.get("admin_only") and account.get("role") != "super_admin":
|
||
cloned["disabled_reason"] = "只有平台管理者才能执行这条动作。"
|
||
elif definition.get("requires_platform") and not cloned.get("platform"):
|
||
cloned["disabled_reason"] = "这条动作需要先选定平台。"
|
||
return cloned
|
||
|
||
def _upsert_action_definition(
|
||
account: dict[str, Any],
|
||
*,
|
||
project_id: str,
|
||
action_key: str,
|
||
request: OneLinerActionDefinitionRequest,
|
||
) -> dict[str, Any]:
|
||
normalized_key = str(action_key or "").strip()
|
||
if not normalized_key:
|
||
raise HTTPException(status_code=400, detail="Action key is required")
|
||
fallback = ACTION_REGISTRY_DEFAULTS.get(normalized_key)
|
||
existing = legacy.db.fetch_one(
|
||
"""
|
||
SELECT * FROM oneliner_action_definitions
|
||
WHERE user_id = ? AND project_id = ? AND action_key = ?
|
||
""",
|
||
(account["id"], project_id, normalized_key),
|
||
)
|
||
if not existing and not fallback:
|
||
raise HTTPException(status_code=404, detail="Action definition not found")
|
||
timestamp = now()
|
||
handler_key = (existing or {}).get("handler_key") or (fallback or {}).get("handler_key") or normalized_key
|
||
next_admin_only = (fallback or {}).get("admin_only", False)
|
||
next_requires_platform = (fallback or {}).get("requires_platform", False)
|
||
if account.get("role") == "super_admin":
|
||
if request.admin_only is not None:
|
||
next_admin_only = bool(request.admin_only)
|
||
if request.requires_platform is not None:
|
||
next_requires_platform = bool(request.requires_platform)
|
||
if existing:
|
||
legacy.db.execute(
|
||
"""
|
||
UPDATE oneliner_action_definitions
|
||
SET label = ?, description = ?, category = ?, status = ?, admin_only = ?, requires_platform = ?, config_json = ?, updated_at = ?
|
||
WHERE id = ?
|
||
""",
|
||
(
|
||
request.label.strip() or existing.get("label") or (fallback or {}).get("label") or normalized_key,
|
||
request.description.strip() or existing.get("description") or (fallback or {}).get("description") or "",
|
||
request.category.strip() or existing.get("category") or (fallback or {}).get("category") or "custom",
|
||
request.status.strip() or existing.get("status") or (fallback or {}).get("status") or "enabled",
|
||
1 if next_admin_only else 0,
|
||
1 if next_requires_platform else 0,
|
||
_dump(request.config or _parse_json(existing.get("config_json"), (fallback or {}).get("config") or {})),
|
||
timestamp,
|
||
existing["id"],
|
||
),
|
||
)
|
||
row = legacy.db.fetch_one("SELECT * FROM oneliner_action_definitions WHERE id = ?", (existing["id"],))
|
||
else:
|
||
definition_id = make_id("oline_action")
|
||
legacy.db.execute(
|
||
"""
|
||
INSERT INTO oneliner_action_definitions (
|
||
id, user_id, project_id, action_key, handler_key, label, description, category, status,
|
||
admin_only, requires_platform, config_json, created_at, updated_at
|
||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||
""",
|
||
(
|
||
definition_id,
|
||
account["id"],
|
||
project_id,
|
||
normalized_key,
|
||
handler_key,
|
||
request.label.strip() or (fallback or {}).get("label") or normalized_key,
|
||
request.description.strip() or (fallback or {}).get("description") or "",
|
||
request.category.strip() or (fallback or {}).get("category") or "custom",
|
||
request.status.strip() or (fallback or {}).get("status") or "enabled",
|
||
1 if next_admin_only else 0,
|
||
1 if next_requires_platform else 0,
|
||
_dump(request.config or (fallback or {}).get("config") or {}),
|
||
timestamp,
|
||
timestamp,
|
||
),
|
||
)
|
||
row = legacy.db.fetch_one("SELECT * FROM oneliner_action_definitions WHERE id = ?", (definition_id,))
|
||
return _action_definition_payload(row, fallback_key=normalized_key)
|
||
|
||
def _skill_version_payload(row: dict[str, Any]) -> dict[str, Any]:
|
||
snapshot = _parse_json(row.get("snapshot_json"), {})
|
||
return {
|
||
"id": row["id"],
|
||
"skill_id": row.get("skill_id", ""),
|
||
"user_id": row.get("user_id", ""),
|
||
"project_id": row.get("project_id", ""),
|
||
"agent_scope": row.get("agent_scope", ""),
|
||
"platform": row.get("platform", ""),
|
||
"platform_label": legacy.platform_label(row.get("platform", "")) if row.get("platform") else "",
|
||
"version_no": int(row.get("version_no") or 0),
|
||
"snapshot_reason": row.get("snapshot_reason", "updated"),
|
||
"snapshot": snapshot,
|
||
"actor_user_id": row.get("actor_user_id", ""),
|
||
"created_at": row.get("created_at", ""),
|
||
}
|
||
|
||
def _snapshot_skill_version(
|
||
skill_row: dict[str, Any],
|
||
*,
|
||
actor_user_id: str,
|
||
reason: str,
|
||
metadata: dict[str, Any] | None = None,
|
||
) -> dict[str, Any]:
|
||
current = legacy.db.fetch_one(
|
||
"SELECT COALESCE(MAX(version_no), 0) AS max_version FROM agent_skill_versions WHERE skill_id = ?",
|
||
(skill_row["id"],),
|
||
)
|
||
next_version = int((current or {}).get("max_version") or 0) + 1
|
||
version_id = make_id("skill_ver")
|
||
snapshot = {
|
||
"skill": _skill_payload(skill_row),
|
||
"metadata": metadata or {},
|
||
}
|
||
legacy.db.execute(
|
||
"""
|
||
INSERT INTO agent_skill_versions (
|
||
id, skill_id, user_id, project_id, agent_scope, platform, version_no, snapshot_reason,
|
||
snapshot_json, actor_user_id, created_at
|
||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||
""",
|
||
(
|
||
version_id,
|
||
skill_row["id"],
|
||
skill_row.get("user_id", ""),
|
||
skill_row.get("project_id", ""),
|
||
skill_row.get("agent_scope", ""),
|
||
skill_row.get("platform", ""),
|
||
next_version,
|
||
reason,
|
||
_dump(snapshot),
|
||
actor_user_id,
|
||
now(),
|
||
),
|
||
)
|
||
row = legacy.db.fetch_one("SELECT * FROM agent_skill_versions WHERE id = ?", (version_id,))
|
||
return _skill_version_payload(row)
|
||
|
||
def _normalize_policy_platform(platform: str | None) -> str:
|
||
if not str(platform or "").strip():
|
||
return ""
|
||
return _safe_platform(platform, fallback="")
|
||
|
||
def _deep_merge_policy(base: Any, override: Any) -> Any:
|
||
if not isinstance(base, dict) or not isinstance(override, dict):
|
||
return override if override is not None else base
|
||
merged = {key: value for key, value in base.items()}
|
||
for key, value in override.items():
|
||
if key in merged and isinstance(merged[key], dict) and isinstance(value, dict):
|
||
merged[key] = _deep_merge_policy(merged[key], value)
|
||
else:
|
||
merged[key] = value
|
||
return merged
|
||
|
||
def _policy_scope_default_title(scope_kind: str, *, platform: str = "") -> str:
|
||
if scope_kind == "system_main":
|
||
return "系统主 Agent 策略"
|
||
if scope_kind == "system_platform":
|
||
return f"{legacy.platform_label(platform)} 系统平台策略"
|
||
if scope_kind == "user_global":
|
||
return "用户全局策略"
|
||
if scope_kind == "user_platform":
|
||
return f"{legacy.platform_label(platform)} 用户平台策略"
|
||
if scope_kind == "admin_override":
|
||
return "管理员覆盖策略"
|
||
return "Agent 策略"
|
||
|
||
def _policy_scope_row(
|
||
*,
|
||
scope_kind: str,
|
||
subject_user_id: str = "",
|
||
subject_project_id: str = "",
|
||
platform: str = "",
|
||
) -> dict[str, Any] | None:
|
||
return legacy.db.fetch_one(
|
||
"""
|
||
SELECT * FROM agent_policy_scopes
|
||
WHERE scope_kind = ? AND subject_user_id = ? AND subject_project_id = ? AND platform = ?
|
||
""",
|
||
(scope_kind, subject_user_id, subject_project_id, platform),
|
||
)
|
||
|
||
def _policy_scope_payload(row: dict[str, Any] | None, *, fallback_kind: str = "", fallback_platform: str = "", fallback_user_id: str = "", fallback_project_id: str = "") -> dict[str, Any]:
|
||
data = row or {}
|
||
scope_kind = data.get("scope_kind") or fallback_kind
|
||
platform = data.get("platform") or fallback_platform
|
||
return {
|
||
"id": data.get("id", ""),
|
||
"scope_kind": scope_kind,
|
||
"subject_user_id": data.get("subject_user_id", fallback_user_id),
|
||
"subject_project_id": data.get("subject_project_id", fallback_project_id),
|
||
"platform": platform,
|
||
"platform_label": legacy.platform_label(platform) if platform else "",
|
||
"status": data.get("status", "active"),
|
||
"title": data.get("title") or _policy_scope_default_title(scope_kind, platform=platform),
|
||
"summary": data.get("summary", ""),
|
||
"current_version_id": data.get("current_version_id", ""),
|
||
"created_at": data.get("created_at", ""),
|
||
"updated_at": data.get("updated_at", ""),
|
||
}
|
||
|
||
def _policy_version_payload(row: dict[str, Any] | None) -> dict[str, Any] | None:
|
||
if not row:
|
||
return None
|
||
return {
|
||
"id": row["id"],
|
||
"scope_id": row.get("scope_id", ""),
|
||
"scope_kind": row.get("scope_kind", ""),
|
||
"subject_user_id": row.get("subject_user_id", ""),
|
||
"subject_project_id": row.get("subject_project_id", ""),
|
||
"platform": row.get("platform", ""),
|
||
"platform_label": legacy.platform_label(row.get("platform", "")) if row.get("platform") else "",
|
||
"version_no": int(row.get("version_no") or 0),
|
||
"title": row.get("title", ""),
|
||
"summary": row.get("summary", ""),
|
||
"policy": _parse_json(row.get("policy_json"), {}),
|
||
"reason": row.get("reason", ""),
|
||
"source_type": row.get("source_type", ""),
|
||
"rollback_from_version_id": row.get("rollback_from_version_id", ""),
|
||
"actor_user_id": row.get("actor_user_id", ""),
|
||
"created_at": row.get("created_at", ""),
|
||
}
|
||
|
||
def _policy_version_public_payload(row: dict[str, Any] | None) -> dict[str, Any] | None:
|
||
if not row:
|
||
return None
|
||
return {
|
||
"id": row["id"],
|
||
"scope_id": row.get("scope_id", ""),
|
||
"scope_kind": row.get("scope_kind", ""),
|
||
"subject_user_id": row.get("subject_user_id", ""),
|
||
"subject_project_id": row.get("subject_project_id", ""),
|
||
"platform": row.get("platform", ""),
|
||
"platform_label": legacy.platform_label(row.get("platform", "")) if row.get("platform") else "",
|
||
"version_no": int(row.get("version_no") or 0),
|
||
"title": row.get("title", ""),
|
||
"summary": row.get("summary", ""),
|
||
"source_type": row.get("source_type", ""),
|
||
"created_at": row.get("created_at", ""),
|
||
}
|
||
|
||
def _policy_effectivity_payload(row: dict[str, Any] | None) -> dict[str, Any] | None:
|
||
if not row:
|
||
return None
|
||
return {
|
||
"id": row["id"],
|
||
"scope_id": row.get("scope_id", ""),
|
||
"version_id": row.get("version_id", ""),
|
||
"effect_mode": row.get("effect_mode", "ongoing"),
|
||
"starts_at": row.get("starts_at", ""),
|
||
"ends_at": row.get("ends_at", ""),
|
||
"status": row.get("status", "active"),
|
||
"config": _parse_json(row.get("config_json"), {}),
|
||
"created_at": row.get("created_at", ""),
|
||
"updated_at": row.get("updated_at", ""),
|
||
}
|
||
|
||
def _policy_audit_payload(row: dict[str, Any] | None) -> dict[str, Any] | None:
|
||
if not row:
|
||
return None
|
||
return {
|
||
"id": row["id"],
|
||
"scope_id": row.get("scope_id", ""),
|
||
"version_id": row.get("version_id", ""),
|
||
"actor_user_id": row.get("actor_user_id", ""),
|
||
"action_key": row.get("action_key", ""),
|
||
"summary": row.get("summary", ""),
|
||
"details": _parse_json(row.get("details_json"), {}),
|
||
"created_at": row.get("created_at", ""),
|
||
}
|
||
|
||
def _policy_public_audit_details(details: dict[str, Any] | None) -> dict[str, Any]:
|
||
data = details or {}
|
||
public_keys = {"project_id", "platform", "target_project_id"}
|
||
return {key: data.get(key) for key in public_keys if data.get(key) not in (None, "")}
|
||
|
||
def _ensure_policy_scope(
|
||
*,
|
||
scope_kind: str,
|
||
subject_user_id: str = "",
|
||
subject_project_id: str = "",
|
||
platform: str = "",
|
||
title: str = "",
|
||
summary: str = "",
|
||
) -> dict[str, Any]:
|
||
normalized_platform = _normalize_policy_platform(platform)
|
||
existing = _policy_scope_row(
|
||
scope_kind=scope_kind,
|
||
subject_user_id=subject_user_id,
|
||
subject_project_id=subject_project_id,
|
||
platform=normalized_platform,
|
||
)
|
||
if existing:
|
||
return existing
|
||
scope_id = make_id("policy_scope")
|
||
timestamp = now()
|
||
legacy.db.execute(
|
||
"""
|
||
INSERT INTO agent_policy_scopes (
|
||
id, scope_kind, subject_user_id, subject_project_id, platform, status,
|
||
title, summary, current_version_id, created_at, updated_at
|
||
) VALUES (?, ?, ?, ?, ?, 'active', ?, ?, '', ?, ?)
|
||
""",
|
||
(
|
||
scope_id,
|
||
scope_kind,
|
||
subject_user_id,
|
||
subject_project_id,
|
||
normalized_platform,
|
||
title.strip() or _policy_scope_default_title(scope_kind, platform=normalized_platform),
|
||
summary.strip(),
|
||
timestamp,
|
||
timestamp,
|
||
),
|
||
)
|
||
row = legacy.db.fetch_one("SELECT * FROM agent_policy_scopes WHERE id = ?", (scope_id,))
|
||
assert row is not None
|
||
return row
|
||
|
||
def _policy_effectivity_is_active(
|
||
effectivity_row: dict[str, Any] | None,
|
||
*,
|
||
current_time: datetime | None = None,
|
||
) -> bool:
|
||
if not effectivity_row:
|
||
return True
|
||
status = str(effectivity_row.get("status") or "active").strip().lower()
|
||
if status and status != "active":
|
||
return False
|
||
effect_mode = str(effectivity_row.get("effect_mode") or "ongoing").strip().lower()
|
||
starts_at = _parse_policy_datetime(effectivity_row.get("starts_at"))
|
||
ends_at = _parse_policy_datetime(effectivity_row.get("ends_at"))
|
||
reference_time = current_time or datetime.now(timezone.utc)
|
||
if effect_mode == "scheduled" and not starts_at:
|
||
return False
|
||
if starts_at and reference_time < starts_at:
|
||
return False
|
||
if ends_at and reference_time > ends_at:
|
||
return False
|
||
if effect_mode in {"disabled", "inactive", "draft", "archived"}:
|
||
return False
|
||
return True
|
||
|
||
def _current_policy_version_row(
|
||
scope_row: dict[str, Any] | None,
|
||
*,
|
||
active_only: bool = False,
|
||
) -> dict[str, Any] | None:
|
||
if not scope_row:
|
||
return None
|
||
if scope_row.get("current_version_id") and not active_only:
|
||
row = legacy.db.fetch_one(
|
||
"SELECT * FROM agent_policy_versions WHERE id = ?",
|
||
(scope_row["current_version_id"],),
|
||
)
|
||
if row:
|
||
return row
|
||
rows = legacy.db.fetch_all(
|
||
"""
|
||
SELECT * FROM agent_policy_versions
|
||
WHERE scope_id = ?
|
||
ORDER BY version_no DESC, created_at DESC
|
||
""",
|
||
(scope_row["id"],),
|
||
)
|
||
if not rows:
|
||
return None
|
||
if not active_only:
|
||
return rows[0]
|
||
reference_time = datetime.now(timezone.utc)
|
||
for row in rows:
|
||
effectivity_row = _policy_effectivity_row(row["id"])
|
||
if _policy_effectivity_is_active(effectivity_row, current_time=reference_time):
|
||
return row
|
||
return None
|
||
|
||
def _policy_effectivity_row(version_id: str) -> dict[str, Any] | None:
|
||
return legacy.db.fetch_one(
|
||
"SELECT * FROM agent_policy_effectivity WHERE version_id = ?",
|
||
(version_id,),
|
||
)
|
||
|
||
def _policy_scope_bundle(
|
||
scope_row: dict[str, Any] | None,
|
||
*,
|
||
fallback_kind: str = "",
|
||
fallback_platform: str = "",
|
||
fallback_user_id: str = "",
|
||
fallback_project_id: str = "",
|
||
active_only: bool = False,
|
||
) -> dict[str, Any]:
|
||
current_version_row = _current_policy_version_row(scope_row, active_only=active_only)
|
||
effectivity_row = _policy_effectivity_row(current_version_row["id"]) if current_version_row else None
|
||
return {
|
||
"scope": _policy_scope_payload(
|
||
scope_row,
|
||
fallback_kind=fallback_kind,
|
||
fallback_platform=fallback_platform,
|
||
fallback_user_id=fallback_user_id,
|
||
fallback_project_id=fallback_project_id,
|
||
),
|
||
"current_version": _policy_version_payload(current_version_row),
|
||
"effectivity": _policy_effectivity_payload(effectivity_row),
|
||
}
|
||
|
||
def _list_policy_versions(scope_row: dict[str, Any] | None) -> list[dict[str, Any]]:
|
||
if not scope_row:
|
||
return []
|
||
rows = legacy.db.fetch_all(
|
||
"""
|
||
SELECT * FROM agent_policy_versions
|
||
WHERE scope_id = ?
|
||
ORDER BY version_no DESC, created_at DESC
|
||
""",
|
||
(scope_row["id"],),
|
||
)
|
||
return [_policy_version_payload(row) for row in rows]
|
||
|
||
def _log_policy_audit(
|
||
*,
|
||
scope_id: str,
|
||
version_id: str,
|
||
actor_user_id: str,
|
||
action_key: str,
|
||
summary: str,
|
||
details: dict[str, Any] | None = None,
|
||
) -> dict[str, Any]:
|
||
audit_id = make_id("policy_audit")
|
||
legacy.db.execute(
|
||
"""
|
||
INSERT INTO agent_policy_audit_logs (
|
||
id, scope_id, version_id, actor_user_id, action_key, summary, details_json, created_at
|
||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||
""",
|
||
(
|
||
audit_id,
|
||
scope_id,
|
||
version_id,
|
||
actor_user_id,
|
||
action_key,
|
||
summary,
|
||
_dump(details or {}),
|
||
now(),
|
||
),
|
||
)
|
||
row = legacy.db.fetch_one("SELECT * FROM agent_policy_audit_logs WHERE id = ?", (audit_id,))
|
||
assert row is not None
|
||
return _policy_audit_payload(row)
|
||
|
||
def _policy_audit_record_payload(row: dict[str, Any] | None) -> dict[str, Any] | None:
|
||
if not row:
|
||
return None
|
||
payload = _policy_audit_payload(row) or {}
|
||
scope_row = legacy.db.fetch_one("SELECT * FROM agent_policy_scopes WHERE id = ?", (row.get("scope_id", ""),)) if row.get("scope_id") else None
|
||
version_row = legacy.db.fetch_one("SELECT * FROM agent_policy_versions WHERE id = ?", (row.get("version_id", ""),)) if row.get("version_id") else None
|
||
scope_kind = (scope_row or {}).get("scope_kind", "") or (version_row or {}).get("scope_kind", "")
|
||
platform = (scope_row or {}).get("platform", "") or (version_row or {}).get("platform", "")
|
||
subject_user_id = (scope_row or {}).get("subject_user_id", "") or (version_row or {}).get("subject_user_id", "")
|
||
subject_project_id = (scope_row or {}).get("subject_project_id", "") or (version_row or {}).get("subject_project_id", "")
|
||
payload.update(
|
||
{
|
||
"scope_kind": scope_kind,
|
||
"subject_user_id": subject_user_id,
|
||
"subject_project_id": subject_project_id,
|
||
"platform": platform,
|
||
"platform_label": legacy.platform_label(platform) if platform else "",
|
||
"scope": _policy_scope_payload(
|
||
scope_row,
|
||
fallback_kind=scope_kind,
|
||
fallback_platform=platform,
|
||
fallback_user_id=subject_user_id,
|
||
fallback_project_id=subject_project_id,
|
||
),
|
||
"version": _policy_version_payload(version_row),
|
||
}
|
||
)
|
||
return payload
|
||
|
||
def _policy_audit_record_public_payload(row: dict[str, Any] | None) -> dict[str, Any] | None:
|
||
payload = _policy_audit_record_payload(row)
|
||
if not payload:
|
||
return None
|
||
payload.pop("actor_user_id", None)
|
||
payload["details"] = _policy_public_audit_details(payload.get("details"))
|
||
payload["version"] = _policy_version_public_payload(
|
||
legacy.db.fetch_one("SELECT * FROM agent_policy_versions WHERE id = ?", (payload.get("version_id", ""),))
|
||
) if payload.get("version_id") else None
|
||
return payload
|
||
|
||
def _fetch_policy_audit_records(
|
||
where_sql: str,
|
||
params: tuple[Any, ...],
|
||
*,
|
||
limit: int = 20,
|
||
public_view: bool = False,
|
||
) -> list[dict[str, Any]]:
|
||
rows = legacy.db.fetch_all(
|
||
f"""
|
||
SELECT audit.*
|
||
FROM agent_policy_audit_logs audit
|
||
JOIN agent_policy_scopes scope ON scope.id = audit.scope_id
|
||
{where_sql}
|
||
ORDER BY audit.created_at DESC
|
||
LIMIT ?
|
||
""",
|
||
params + (limit,),
|
||
)
|
||
builder = _policy_audit_record_public_payload if public_view else _policy_audit_record_payload
|
||
return [builder(row) for row in rows if row]
|
||
|
||
def _create_policy_version(
|
||
scope_row: dict[str, Any],
|
||
*,
|
||
actor_user_id: str,
|
||
title: str,
|
||
summary: str,
|
||
policy: dict[str, Any],
|
||
effect_mode: str,
|
||
starts_at: str,
|
||
ends_at: str,
|
||
config: dict[str, Any],
|
||
reason: str,
|
||
source_type: str,
|
||
rollback_from_version_id: str = "",
|
||
) -> dict[str, Any]:
|
||
current = legacy.db.fetch_one(
|
||
"SELECT COALESCE(MAX(version_no), 0) AS max_version FROM agent_policy_versions WHERE scope_id = ?",
|
||
(scope_row["id"],),
|
||
)
|
||
version_no = int((current or {}).get("max_version") or 0) + 1
|
||
version_id = make_id("policy_ver")
|
||
effectivity_id = make_id("policy_eff")
|
||
timestamp = now()
|
||
legacy.db.execute(
|
||
"""
|
||
INSERT INTO agent_policy_versions (
|
||
id, scope_id, scope_kind, subject_user_id, subject_project_id, platform,
|
||
version_no, title, summary, policy_json, reason, source_type,
|
||
rollback_from_version_id, actor_user_id, created_at
|
||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||
""",
|
||
(
|
||
version_id,
|
||
scope_row["id"],
|
||
scope_row.get("scope_kind", ""),
|
||
scope_row.get("subject_user_id", ""),
|
||
scope_row.get("subject_project_id", ""),
|
||
scope_row.get("platform", ""),
|
||
version_no,
|
||
title.strip() or scope_row.get("title") or _policy_scope_default_title(scope_row.get("scope_kind", ""), platform=scope_row.get("platform", "")),
|
||
summary.strip(),
|
||
_dump(policy),
|
||
reason.strip(),
|
||
source_type,
|
||
rollback_from_version_id,
|
||
actor_user_id,
|
||
timestamp,
|
||
),
|
||
)
|
||
legacy.db.execute(
|
||
"""
|
||
INSERT INTO agent_policy_effectivity (
|
||
id, scope_id, version_id, effect_mode, starts_at, ends_at, status, config_json, created_at, updated_at
|
||
) VALUES (?, ?, ?, ?, ?, ?, 'active', ?, ?, ?)
|
||
""",
|
||
(
|
||
effectivity_id,
|
||
scope_row["id"],
|
||
version_id,
|
||
(effect_mode or "ongoing").strip() or "ongoing",
|
||
starts_at.strip(),
|
||
ends_at.strip(),
|
||
_dump(config),
|
||
timestamp,
|
||
timestamp,
|
||
),
|
||
)
|
||
legacy.db.execute(
|
||
"""
|
||
UPDATE agent_policy_scopes
|
||
SET title = ?, summary = ?, current_version_id = ?, status = 'active', updated_at = ?
|
||
WHERE id = ?
|
||
""",
|
||
(
|
||
title.strip() or scope_row.get("title") or _policy_scope_default_title(scope_row.get("scope_kind", ""), platform=scope_row.get("platform", "")),
|
||
summary.strip(),
|
||
version_id,
|
||
timestamp,
|
||
scope_row["id"],
|
||
),
|
||
)
|
||
row = legacy.db.fetch_one("SELECT * FROM agent_policy_scopes WHERE id = ?", (scope_row["id"],))
|
||
assert row is not None
|
||
return _policy_scope_bundle(row)
|
||
|
||
def _rollback_policy_scope(
|
||
scope_row: dict[str, Any],
|
||
*,
|
||
actor_user_id: str,
|
||
version_id: str,
|
||
reason: str,
|
||
source_type: str,
|
||
) -> dict[str, Any]:
|
||
target_version = legacy.db.fetch_one(
|
||
"SELECT * FROM agent_policy_versions WHERE id = ? AND scope_id = ?",
|
||
(version_id, scope_row["id"]),
|
||
)
|
||
if not target_version:
|
||
raise HTTPException(status_code=404, detail="Policy version not found")
|
||
target_effectivity = _policy_effectivity_row(target_version["id"]) or {}
|
||
bundle = _create_policy_version(
|
||
scope_row,
|
||
actor_user_id=actor_user_id,
|
||
title=target_version.get("title", ""),
|
||
summary=target_version.get("summary", ""),
|
||
policy=_parse_json(target_version.get("policy_json"), {}),
|
||
effect_mode=target_effectivity.get("effect_mode", "ongoing"),
|
||
starts_at=target_effectivity.get("starts_at", ""),
|
||
ends_at=target_effectivity.get("ends_at", ""),
|
||
config=_parse_json(target_effectivity.get("config_json"), {}),
|
||
reason=reason.strip() or f"回滚到版本 {target_version.get('version_no') or version_id}",
|
||
source_type=source_type,
|
||
rollback_from_version_id=target_version["id"],
|
||
)
|
||
return bundle
|
||
|
||
def _load_policy_subject_account(user_id: str) -> dict[str, Any]:
|
||
row = legacy.db.fetch_one("SELECT * FROM accounts WHERE id = ?", (user_id,))
|
||
if not row:
|
||
raise HTTPException(status_code=404, detail="Target account not found")
|
||
return row
|
||
|
||
def _load_policy_subject_project(*, user_id: str, project_id: str) -> dict[str, Any]:
|
||
row = legacy.db.fetch_one("SELECT * FROM projects WHERE id = ?", (project_id,))
|
||
if not row:
|
||
raise HTTPException(status_code=404, detail="Target project not found")
|
||
if user_id and row.get("user_id") != user_id:
|
||
raise HTTPException(status_code=400, detail="Target project does not belong to target user")
|
||
return row
|
||
|
||
def _governance_directory_payload() -> dict[str, Any]:
|
||
account_rows = legacy.db.fetch_all(
|
||
"""
|
||
SELECT id, username, display_name, role, approval_status, created_at, updated_at
|
||
FROM accounts
|
||
WHERE approval_status = 'approved'
|
||
ORDER BY CASE WHEN role = 'super_admin' THEN 0 ELSE 1 END ASC, updated_at DESC, created_at DESC
|
||
"""
|
||
)
|
||
project_rows = legacy.db.fetch_all(
|
||
"""
|
||
SELECT id, user_id, name, description, created_at, updated_at
|
||
FROM projects
|
||
ORDER BY updated_at DESC, created_at DESC
|
||
"""
|
||
)
|
||
projects_by_user: dict[str, list[dict[str, Any]]] = {}
|
||
for row in project_rows:
|
||
projects_by_user.setdefault(row.get("user_id", ""), []).append(
|
||
{
|
||
"id": row["id"],
|
||
"user_id": row.get("user_id", ""),
|
||
"name": row.get("name", ""),
|
||
"description": row.get("description", ""),
|
||
"created_at": row.get("created_at", ""),
|
||
"updated_at": row.get("updated_at", ""),
|
||
}
|
||
)
|
||
items = []
|
||
for row in account_rows:
|
||
projects = projects_by_user.get(row["id"], [])
|
||
items.append(
|
||
{
|
||
"id": row["id"],
|
||
"username": row.get("username", ""),
|
||
"display_name": row.get("display_name", ""),
|
||
"role": row.get("role", ""),
|
||
"approval_status": row.get("approval_status", ""),
|
||
"project_count": len(projects),
|
||
"projects": projects,
|
||
"created_at": row.get("created_at", ""),
|
||
"updated_at": row.get("updated_at", ""),
|
||
}
|
||
)
|
||
return {"items": items, "count": len(items)}
|
||
|
||
def _effective_policy_payload(
|
||
*,
|
||
subject_account: dict[str, Any],
|
||
subject_project_id: str,
|
||
platform: str,
|
||
) -> dict[str, Any]:
|
||
normalized_platform = _normalize_policy_platform(platform)
|
||
candidate_scopes: list[dict[str, Any]] = []
|
||
seen_scope_ids: set[str] = set()
|
||
|
||
def add_candidate(scope_row: dict[str, Any] | None) -> None:
|
||
if not scope_row or not scope_row.get("id"):
|
||
return
|
||
if scope_row["id"] in seen_scope_ids:
|
||
return
|
||
seen_scope_ids.add(scope_row["id"])
|
||
candidate_scopes.append(scope_row)
|
||
|
||
add_candidate(_policy_scope_row(scope_kind="system_main"))
|
||
if normalized_platform:
|
||
add_candidate(_policy_scope_row(scope_kind="system_platform", platform=normalized_platform))
|
||
add_candidate(
|
||
_policy_scope_row(scope_kind="user_global", subject_user_id=subject_account["id"], subject_project_id=subject_project_id)
|
||
)
|
||
if normalized_platform:
|
||
add_candidate(
|
||
_policy_scope_row(
|
||
scope_kind="user_platform",
|
||
subject_user_id=subject_account["id"],
|
||
subject_project_id=subject_project_id,
|
||
platform=normalized_platform,
|
||
)
|
||
)
|
||
add_candidate(
|
||
_policy_scope_row(scope_kind="admin_override", subject_user_id=subject_account["id"], subject_project_id="", platform="")
|
||
)
|
||
if normalized_platform:
|
||
add_candidate(
|
||
_policy_scope_row(
|
||
scope_kind="admin_override",
|
||
subject_user_id=subject_account["id"],
|
||
subject_project_id="",
|
||
platform=normalized_platform,
|
||
)
|
||
)
|
||
if subject_project_id:
|
||
add_candidate(
|
||
_policy_scope_row(
|
||
scope_kind="admin_override",
|
||
subject_user_id=subject_account["id"],
|
||
subject_project_id=subject_project_id,
|
||
platform="",
|
||
)
|
||
)
|
||
if normalized_platform:
|
||
add_candidate(
|
||
_policy_scope_row(
|
||
scope_kind="admin_override",
|
||
subject_user_id=subject_account["id"],
|
||
subject_project_id=subject_project_id,
|
||
platform=normalized_platform,
|
||
)
|
||
)
|
||
layers: list[dict[str, Any]] = []
|
||
effective_policy: dict[str, Any] = {}
|
||
for scope_row in candidate_scopes:
|
||
if not scope_row:
|
||
continue
|
||
bundle = _policy_scope_bundle(scope_row, active_only=True)
|
||
version = bundle.get("current_version")
|
||
if not version:
|
||
continue
|
||
layer_policy = dict(version.get("policy") or {})
|
||
effective_policy = _deep_merge_policy(effective_policy, layer_policy)
|
||
layers.append(
|
||
{
|
||
"scope_kind": bundle["scope"]["scope_kind"],
|
||
"scope": bundle["scope"],
|
||
"current_version": version,
|
||
"effectivity": bundle.get("effectivity"),
|
||
}
|
||
)
|
||
active_admin_override_notice = next(
|
||
(
|
||
{
|
||
"scope_id": layer["scope"].get("id", ""),
|
||
"version_id": (layer.get("current_version") or {}).get("id", ""),
|
||
"title": (layer.get("current_version") or {}).get("title") or layer["scope"].get("title") or "管理员覆盖生效中",
|
||
"summary": (layer.get("current_version") or {}).get("summary") or layer["scope"].get("summary") or "",
|
||
"platform": layer["scope"].get("platform", ""),
|
||
"platform_label": layer["scope"].get("platform_label", ""),
|
||
"subject_project_id": layer["scope"].get("subject_project_id", ""),
|
||
"created_at": (layer.get("current_version") or {}).get("created_at", ""),
|
||
}
|
||
for layer in reversed(layers)
|
||
if layer.get("scope_kind") == "admin_override"
|
||
),
|
||
None,
|
||
)
|
||
return {
|
||
"user_id": subject_account["id"],
|
||
"project_id": subject_project_id,
|
||
"platform": normalized_platform,
|
||
"platform_label": legacy.platform_label(normalized_platform) if normalized_platform else "",
|
||
"layers": layers,
|
||
"effective_policy": effective_policy,
|
||
"active_admin_override_notice": active_admin_override_notice,
|
||
}
|
||
|
||
def _bundle_with_versions(
|
||
scope_row: dict[str, Any] | None,
|
||
*,
|
||
fallback_kind: str,
|
||
fallback_platform: str = "",
|
||
fallback_user_id: str = "",
|
||
fallback_project_id: str = "",
|
||
active_version_only: bool = False,
|
||
) -> dict[str, Any]:
|
||
bundle = _policy_scope_bundle(
|
||
scope_row,
|
||
fallback_kind=fallback_kind,
|
||
fallback_platform=fallback_platform,
|
||
fallback_user_id=fallback_user_id,
|
||
fallback_project_id=fallback_project_id,
|
||
active_only=active_version_only,
|
||
)
|
||
versions = _list_policy_versions(scope_row)
|
||
bundle["versions"] = {"items": versions, "count": len(versions)}
|
||
return bundle
|
||
|
||
def _fix_run_payload(row: dict[str, Any]) -> dict[str, Any]:
|
||
return {
|
||
"id": row["id"],
|
||
"incident_id": row.get("incident_id", ""),
|
||
"actor_user_id": row.get("actor_user_id", ""),
|
||
"tenant_user_id": row.get("tenant_user_id", ""),
|
||
"tenant_project_id": row.get("tenant_project_id", ""),
|
||
"plan_scope": row.get("plan_scope", "plan"),
|
||
"status": row.get("status", "planned"),
|
||
"audit_status": row.get("audit_status", "pending"),
|
||
"review_notes": row.get("review_notes", ""),
|
||
"plan": _parse_json(row.get("plan_json"), {}),
|
||
"verification": _parse_json(row.get("verification_json"), {}),
|
||
"created_at": row.get("created_at", ""),
|
||
"updated_at": row.get("updated_at", ""),
|
||
}
|
||
|
||
def _tenant_quota_payload(row: dict[str, Any] | None, *, usage: dict[str, Any] | None = None) -> dict[str, Any]:
|
||
data = row or {}
|
||
config = _tenant_quota_config(data)
|
||
return {
|
||
"id": data.get("id", ""),
|
||
"user_id": data.get("user_id", ""),
|
||
"project_id": data.get("project_id", ""),
|
||
"package_label": config["package_label"],
|
||
"monthly_budget_cents": int(data.get("monthly_budget_cents") or 0),
|
||
"storage_limit_bytes": int(data.get("storage_limit_bytes") or 0),
|
||
"analysis_quota": int(data.get("analysis_quota") or 0),
|
||
"copy_quota": int(data.get("copy_quota") or 0),
|
||
"ai_video_quota": int(data.get("ai_video_quota") or 0),
|
||
"real_cut_quota": int(data.get("real_cut_quota") or 0),
|
||
"recorder_quota": int(data.get("recorder_quota") or 0),
|
||
"enabled": True if row is None else _bool_flag(data.get("enabled", 1)),
|
||
"config": config,
|
||
"usage": usage or {},
|
||
"created_at": data.get("created_at", ""),
|
||
"updated_at": data.get("updated_at", ""),
|
||
}
|
||
|
||
def _tenant_usage_payload(row: dict[str, Any]) -> dict[str, Any]:
|
||
return {
|
||
"id": row["id"],
|
||
"user_id": row.get("user_id", ""),
|
||
"project_id": row.get("project_id", ""),
|
||
"category": row.get("category", ""),
|
||
"quantity": int(row.get("quantity") or 0),
|
||
"cost_cents": int(row.get("cost_cents") or 0),
|
||
"reference_type": row.get("reference_type", ""),
|
||
"reference_id": row.get("reference_id", ""),
|
||
"details": _parse_json(row.get("details_json"), {}),
|
||
"created_at": row.get("created_at", ""),
|
||
}
|
||
|
||
def _log_admin_audit_event(
|
||
*,
|
||
actor_user_id: str,
|
||
incident_id: str = "",
|
||
action_key: str,
|
||
status: str,
|
||
summary: str,
|
||
details: dict[str, Any] | None = None,
|
||
) -> dict[str, Any]:
|
||
audit_id = make_id("ops_audit")
|
||
timestamp = now()
|
||
sql = """
|
||
INSERT INTO admin_ops_audit_logs (
|
||
id, actor_user_id, incident_id, action_key, status, summary, details_json, created_at
|
||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||
"""
|
||
params = (
|
||
audit_id,
|
||
actor_user_id,
|
||
incident_id,
|
||
action_key,
|
||
status,
|
||
summary,
|
||
_dump(details or {}),
|
||
timestamp,
|
||
)
|
||
if incident_id:
|
||
legacy.db.execute(sql, params)
|
||
else:
|
||
with legacy.db.session() as conn:
|
||
conn.execute("PRAGMA foreign_keys=OFF")
|
||
conn.execute(sql, params)
|
||
conn.execute("PRAGMA foreign_keys=ON")
|
||
row = legacy.db.fetch_one("SELECT * FROM admin_ops_audit_logs WHERE id = ?", (audit_id,))
|
||
return _admin_audit_payload(row)
|
||
|
||
def _project_storage_bytes(account: dict[str, Any], *, project_id: str) -> int:
|
||
try:
|
||
payload = legacy.storage_status(project_id=project_id, account=account)
|
||
except Exception:
|
||
return 0
|
||
tenant_usage = payload.get("tenant_usage", {}) if isinstance(payload, dict) else {}
|
||
jobs_bytes = int((((tenant_usage.get("project_jobs") or {}).get("bytes")) or 0))
|
||
downloads_bytes = int((((tenant_usage.get("project_downloads") or {}).get("bytes")) or 0))
|
||
return jobs_bytes + downloads_bytes
|
||
|
||
def _tenant_usage_summary(account: dict[str, Any], *, project_id: str) -> dict[str, Any]:
|
||
cycle_start = _current_cycle_start()
|
||
rows = legacy.db.fetch_all(
|
||
"""
|
||
SELECT category, SUM(quantity) AS quantity, SUM(cost_cents) AS cost_cents
|
||
FROM tenant_usage_ledger
|
||
WHERE user_id = ? AND project_id = ? AND created_at >= ?
|
||
GROUP BY category
|
||
ORDER BY category ASC
|
||
""",
|
||
(account["id"], project_id, cycle_start),
|
||
)
|
||
by_category: dict[str, dict[str, Any]] = {}
|
||
for row in rows:
|
||
category = row.get("category", "")
|
||
by_category[category] = {
|
||
"category": category,
|
||
"quantity": int(row.get("quantity") or 0),
|
||
"cost_cents": int(row.get("cost_cents") or 0),
|
||
}
|
||
recent_rows = legacy.db.fetch_all(
|
||
"""
|
||
SELECT * FROM tenant_usage_ledger
|
||
WHERE user_id = ? AND project_id = ?
|
||
ORDER BY created_at DESC
|
||
LIMIT 20
|
||
""",
|
||
(account["id"], project_id),
|
||
)
|
||
total_cost = sum(item["cost_cents"] for item in by_category.values())
|
||
storage_bytes = _project_storage_bytes(account, project_id=project_id)
|
||
return {
|
||
"cycle_start": cycle_start,
|
||
"categories": by_category,
|
||
"total_cost_cents": total_cost,
|
||
"recent_items": [_tenant_usage_payload(row) for row in recent_rows],
|
||
"storage_bytes": storage_bytes,
|
||
}
|
||
|
||
def _get_tenant_quota_row(account: dict[str, Any], *, project_id: str) -> dict[str, Any] | None:
|
||
return legacy.db.fetch_one(
|
||
"SELECT * FROM tenant_quota_profiles WHERE user_id = ? AND project_id = ?",
|
||
(account["id"], project_id),
|
||
)
|
||
|
||
def _get_tenant_quota(account: dict[str, Any], *, project_id: str) -> dict[str, Any]:
|
||
usage = _tenant_usage_summary(account, project_id=project_id)
|
||
row = _get_tenant_quota_row(account, project_id=project_id)
|
||
payload = _tenant_quota_payload(row, usage=usage)
|
||
storage_limit = int(payload.get("storage_limit_bytes") or 0)
|
||
payload["storage_over_limit"] = bool(storage_limit and usage["storage_bytes"] >= storage_limit)
|
||
return payload
|
||
|
||
def _record_tenant_usage(
|
||
account: dict[str, Any],
|
||
*,
|
||
project_id: str,
|
||
category: str,
|
||
reference_type: str,
|
||
reference_id: str,
|
||
details: dict[str, Any] | None = None,
|
||
quantity: int = 1,
|
||
) -> dict[str, Any]:
|
||
usage_meta = USAGE_COST_DEFAULTS.get(category, {})
|
||
usage_id = make_id("usage")
|
||
legacy.db.execute(
|
||
"""
|
||
INSERT INTO tenant_usage_ledger (
|
||
id, user_id, project_id, category, quantity, cost_cents, reference_type, reference_id, details_json, created_at
|
||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||
""",
|
||
(
|
||
usage_id,
|
||
account["id"],
|
||
project_id,
|
||
category,
|
||
int(quantity or 1),
|
||
int(usage_meta.get("cost_cents") or 0) * int(quantity or 1),
|
||
reference_type,
|
||
reference_id,
|
||
_dump(details or {}),
|
||
now(),
|
||
),
|
||
)
|
||
row = legacy.db.fetch_one("SELECT * FROM tenant_usage_ledger WHERE id = ?", (usage_id,))
|
||
return _tenant_usage_payload(row)
|
||
|
||
def _enforce_tenant_quota(account: dict[str, Any], *, project_id: str, usage_category: str) -> None:
|
||
quota = _get_tenant_quota(account, project_id=project_id)
|
||
if not quota.get("enabled", True):
|
||
return
|
||
usage = quota.get("usage", {})
|
||
category_meta = USAGE_COST_DEFAULTS.get(usage_category, {})
|
||
quota_field = category_meta.get("quota_field")
|
||
if quota_field:
|
||
allowed = int(quota.get(quota_field) or 0)
|
||
consumed = int(((usage.get("categories") or {}).get(usage_category) or {}).get("quantity") or 0)
|
||
if allowed and consumed >= allowed:
|
||
raise HTTPException(status_code=403, detail=f"当前租户本周期的 {usage_category} 配额已用完")
|
||
budget = int(quota.get("monthly_budget_cents") or 0)
|
||
total_cost = int((usage.get("total_cost_cents") or 0))
|
||
next_cost = int(category_meta.get("cost_cents") or 0)
|
||
if budget and total_cost + next_cost > budget:
|
||
raise HTTPException(status_code=403, detail="当前租户本周期预算不足,已阻止本次动作执行")
|
||
storage_limit = int(quota.get("storage_limit_bytes") or 0)
|
||
if storage_limit and usage_category in {"analysis", "content_source_sync", "ai_video", "real_cut"}:
|
||
storage_bytes = int(usage.get("storage_bytes") or 0)
|
||
if storage_bytes >= storage_limit:
|
||
raise HTTPException(status_code=403, detail="当前租户存储额度已满,已阻止继续产生大文件缓存")
|
||
|
||
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 _resolve_execution_assistant(account: dict[str, Any], *, project_id: str, platform: str = "") -> dict[str, Any] | None:
|
||
normalized_platform = _safe_platform(platform or "", fallback="")
|
||
if normalized_platform:
|
||
profile_row = legacy.db.fetch_one(
|
||
"SELECT * FROM platform_agent_profiles WHERE user_id = ? AND project_id = ? AND platform = ?",
|
||
(account["id"], project_id, normalized_platform),
|
||
)
|
||
if profile_row and profile_row.get("assistant_id"):
|
||
assistant = _resolve_assistant(account, profile_row.get("assistant_id"), project_id)
|
||
if assistant:
|
||
return assistant
|
||
profile_row = _fetch_profile_row(account, project_id) or _ensure_oneliner_profile(account, project_id)
|
||
if profile_row.get("assistant_id"):
|
||
assistant = _resolve_assistant(account, profile_row.get("assistant_id"), project_id)
|
||
if assistant:
|
||
return assistant
|
||
return _resolve_assistant(account, None, project_id)
|
||
|
||
def _latest_project_job(account: dict[str, Any], *, project_id: str) -> dict[str, Any] | None:
|
||
return legacy.db.fetch_one(
|
||
"""
|
||
SELECT * FROM jobs
|
||
WHERE user_id = ? AND project_id = ? AND status IN ('completed', 'done', 'succeeded')
|
||
ORDER BY updated_at DESC, created_at DESC
|
||
LIMIT 1
|
||
""",
|
||
(account["id"], project_id),
|
||
)
|
||
|
||
def _last_user_message_text(session_id: str, account_id: str) -> str:
|
||
row = legacy.db.fetch_one(
|
||
"""
|
||
SELECT * FROM oneliner_messages
|
||
WHERE session_id = ? AND user_id = ? AND role = 'user'
|
||
ORDER BY created_at DESC
|
||
LIMIT 1
|
||
""",
|
||
(session_id, account_id),
|
||
)
|
||
return str((row or {}).get("content") or "").strip()
|
||
|
||
def _extract_first_url(text: str) -> str:
|
||
cleaned = str(text or "").strip()
|
||
if not cleaned:
|
||
return ""
|
||
match = re.search(r"https?://[^\s<>'\"]+", cleaned)
|
||
if not match:
|
||
return ""
|
||
return match.group(0).rstrip(",。;;,.)]》】!?!?")
|
||
|
||
def _find_creator_source_by_url(
|
||
account: dict[str, Any],
|
||
*,
|
||
project_id: str,
|
||
platform: str,
|
||
source_url: str,
|
||
) -> dict[str, Any] | None:
|
||
return legacy.db.fetch_one(
|
||
"""
|
||
SELECT * FROM content_sources
|
||
WHERE user_id = ? AND project_id = ? AND platform = ? AND source_kind = 'creator_account' AND source_url = ?
|
||
ORDER BY updated_at DESC, created_at DESC
|
||
LIMIT 1
|
||
""",
|
||
(account["id"], project_id, platform, source_url),
|
||
)
|
||
|
||
def _latest_platform_account(
|
||
account: dict[str, Any],
|
||
*,
|
||
project_id: str,
|
||
platform: str,
|
||
) -> dict[str, Any] | None:
|
||
return legacy.db.fetch_one(
|
||
"""
|
||
SELECT * FROM content_sources
|
||
WHERE user_id = ? AND project_id = ? AND platform = ? AND source_kind = 'creator_account'
|
||
ORDER BY updated_at DESC, created_at DESC
|
||
LIMIT 1
|
||
""",
|
||
(account["id"], project_id, platform),
|
||
)
|
||
|
||
def _latest_douyin_account(
|
||
account: dict[str, Any],
|
||
*,
|
||
project_id: str,
|
||
) -> dict[str, Any] | None:
|
||
return legacy.db.fetch_one(
|
||
"""
|
||
SELECT * FROM douyin_accounts
|
||
WHERE user_id = ?
|
||
ORDER BY updated_at DESC, created_at DESC
|
||
LIMIT 1
|
||
""",
|
||
(account["id"],),
|
||
)
|
||
|
||
def _resolve_platform_target_account(
|
||
account: dict[str, Any],
|
||
*,
|
||
project_id: str,
|
||
platform: str,
|
||
requested_account_id: str = "",
|
||
) -> dict[str, Any] | None:
|
||
normalized_platform = _safe_platform(platform, fallback="douyin")
|
||
normalized_requested = str(requested_account_id or "").strip()
|
||
if normalized_platform == "douyin":
|
||
if normalized_requested:
|
||
return legacy.db.fetch_one(
|
||
"SELECT * FROM douyin_accounts WHERE id = ? AND user_id = ?",
|
||
(normalized_requested, account["id"]),
|
||
)
|
||
return _latest_douyin_account(account, project_id=project_id)
|
||
if normalized_requested:
|
||
return legacy.db.fetch_one(
|
||
"""
|
||
SELECT * FROM content_sources
|
||
WHERE id = ? AND user_id = ? AND project_id = ? AND platform = ? AND source_kind = 'creator_account'
|
||
""",
|
||
(normalized_requested, account["id"], project_id, normalized_platform),
|
||
)
|
||
return _latest_platform_account(account, project_id=project_id, platform=normalized_platform)
|
||
|
||
def _latest_similarity_candidate(
|
||
account: dict[str, Any],
|
||
*,
|
||
platform: str,
|
||
source_account_id: str,
|
||
) -> dict[str, Any] | None:
|
||
normalized_platform = _safe_platform(platform, fallback="douyin")
|
||
normalized_source_id = str(source_account_id or "").strip()
|
||
if not normalized_source_id:
|
||
return None
|
||
table_prefix = "douyin" if normalized_platform == "douyin" else normalized_platform
|
||
search_row = legacy.db.fetch_one(
|
||
f"""
|
||
SELECT * FROM {table_prefix}_similarity_searches
|
||
WHERE user_id = ? AND source_account_id = ?
|
||
ORDER BY created_at DESC
|
||
LIMIT 1
|
||
""",
|
||
(account["id"], normalized_source_id),
|
||
)
|
||
if not search_row:
|
||
return None
|
||
candidate_row = legacy.db.fetch_one(
|
||
f"""
|
||
SELECT * FROM {table_prefix}_similarity_candidates
|
||
WHERE search_id = ?
|
||
ORDER BY rank_index ASC
|
||
LIMIT 1
|
||
""",
|
||
(search_row["id"],),
|
||
)
|
||
if not candidate_row:
|
||
return {"search_id": search_row["id"], "candidate": {}}
|
||
candidate_payload = _parse_json(candidate_row.get("raw_output_json") or "{}", {})
|
||
candidate_payload.setdefault("candidate_account_id", candidate_row.get("candidate_account_id", ""))
|
||
candidate_payload.setdefault("candidate_profile_url", candidate_row.get("candidate_profile_url", ""))
|
||
candidate_payload.setdefault("candidate_nickname", candidate_row.get("candidate_nickname", ""))
|
||
candidate_payload.setdefault("rationale_text", candidate_row.get("rationale_text", ""))
|
||
candidate_payload.setdefault("agent_score", candidate_row.get("agent_score", 0))
|
||
candidate_payload.setdefault("heuristic_score", candidate_row.get("heuristic_score", 0))
|
||
return {
|
||
"search_id": search_row["id"],
|
||
"candidate": candidate_payload,
|
||
}
|
||
|
||
async def _call_local_api(
|
||
account: dict[str, Any],
|
||
*,
|
||
method: str,
|
||
path: str,
|
||
json_body: dict[str, Any] | None = None,
|
||
query: dict[str, Any] | None = None,
|
||
) -> Any:
|
||
issued = legacy.issue_auth_token(account, mode="internal")
|
||
token = issued["token"]
|
||
transport = httpx.ASGITransport(app=app)
|
||
try:
|
||
async with httpx.AsyncClient(transport=transport, base_url="http://storyforge.internal") as client:
|
||
response = await client.request(
|
||
method.upper(),
|
||
path,
|
||
params=query or None,
|
||
json=json_body,
|
||
headers={"Authorization": f"Bearer {token}"},
|
||
timeout=60.0,
|
||
)
|
||
if response.status_code >= 400:
|
||
try:
|
||
payload = response.json()
|
||
except Exception:
|
||
payload = {"detail": response.text}
|
||
detail = payload.get("detail", payload)
|
||
raise HTTPException(status_code=response.status_code, detail=detail)
|
||
if not response.content:
|
||
return {}
|
||
return response.json()
|
||
finally:
|
||
legacy.db.execute("DELETE FROM auth_tokens WHERE token = ?", (token,))
|
||
|
||
def _load_owned_job(account: dict[str, Any], job_id: str) -> dict[str, Any] | None:
|
||
normalized_job_id = str(job_id or "").strip()
|
||
if not normalized_job_id:
|
||
return None
|
||
return legacy.db.fetch_one(
|
||
"SELECT * FROM jobs WHERE id = ? AND user_id = ?",
|
||
(normalized_job_id, account["id"]),
|
||
)
|
||
|
||
def _latest_derivable_job(
|
||
account: dict[str, Any],
|
||
*,
|
||
project_id: str,
|
||
exclude_line_types: set[str] | None = None,
|
||
) -> dict[str, Any] | None:
|
||
excluded = {item.strip() for item in (exclude_line_types or set()) if str(item or "").strip()}
|
||
rows = legacy.db.fetch_all(
|
||
"""
|
||
SELECT * FROM jobs
|
||
WHERE user_id = ? AND project_id = ? AND status IN ('completed', 'done', 'succeeded')
|
||
ORDER BY updated_at DESC, created_at DESC
|
||
LIMIT 24
|
||
""",
|
||
(account["id"], project_id),
|
||
)
|
||
for row in rows:
|
||
if str(row.get("line_type") or "").strip() in excluded:
|
||
continue
|
||
return row
|
||
return rows[0] if rows else None
|
||
|
||
def _job_performance_score(job_row: dict[str, Any] | None) -> float:
|
||
if not job_row:
|
||
return 0.0
|
||
result_map = _parse_json(job_row.get("result_json") or "{}", {})
|
||
artifacts_map = _parse_json(job_row.get("artifacts_json") or "{}", {})
|
||
candidates = [
|
||
result_map.get("performance_score"),
|
||
(result_map.get("analysis") or {}).get("performance_score"),
|
||
(result_map.get("scores") or {}).get("performance_score"),
|
||
artifacts_map.get("performance_score"),
|
||
(artifacts_map.get("scores") or {}).get("performance_score"),
|
||
]
|
||
for value in candidates:
|
||
try:
|
||
return float(value)
|
||
except (TypeError, ValueError):
|
||
continue
|
||
return 0.0
|
||
|
||
def _linked_platform_videos(
|
||
account: dict[str, Any],
|
||
*,
|
||
project_id: str,
|
||
platform: str,
|
||
account_row: dict[str, Any],
|
||
limit: int = 8,
|
||
) -> list[dict[str, Any]]:
|
||
rows = legacy.db.fetch_all(
|
||
"""
|
||
SELECT * FROM content_sources
|
||
WHERE user_id = ? AND project_id = ? AND platform = ? AND source_kind = 'video_link'
|
||
ORDER BY updated_at DESC, created_at DESC
|
||
""",
|
||
(account["id"], project_id, platform),
|
||
)
|
||
account_source_url = str(account_row.get("source_url") or "").strip()
|
||
items: list[dict[str, Any]] = []
|
||
for row in rows:
|
||
payload = legacy.content_source_payload(row)
|
||
metadata = payload.get("metadata") or {}
|
||
if metadata.get("origin_content_source_id") != account_row["id"] and metadata.get("source_account_url") != account_source_url:
|
||
continue
|
||
latest_job = legacy.db.fetch_one(
|
||
"SELECT * FROM jobs WHERE content_source_id = ? ORDER BY updated_at DESC, created_at DESC LIMIT 1",
|
||
(row["id"],),
|
||
)
|
||
result_map = _parse_json((latest_job or {}).get("result_json") or "{}", {})
|
||
published_at = (
|
||
metadata.get("published_at")
|
||
or metadata.get("publish_time")
|
||
or metadata.get("created_at")
|
||
or payload.get("updated_at")
|
||
or payload.get("created_at")
|
||
or ""
|
||
)
|
||
items.append(
|
||
{
|
||
"id": payload["id"],
|
||
"title": payload.get("title") or payload.get("handle") or payload.get("source_url") or payload["id"],
|
||
"source_url": payload.get("source_url", ""),
|
||
"published_at": published_at,
|
||
"score": {
|
||
"performance_score": _job_performance_score(latest_job),
|
||
},
|
||
"latest_job_id": (latest_job or {}).get("id", ""),
|
||
"latest_job_status": (latest_job or {}).get("status", ""),
|
||
"summary": str(result_map.get("summary") or result_map.get("headline_summary") or "")[:240],
|
||
}
|
||
)
|
||
items.sort(key=lambda item: (float((item.get("score") or {}).get("performance_score") or 0), item.get("published_at") or ""), reverse=True)
|
||
return items[: max(1, min(int(limit or 8), 16))]
|
||
|
||
def _fallback_platform_videos(
|
||
account: dict[str, Any],
|
||
*,
|
||
project_id: str,
|
||
platform: str,
|
||
requested_account_id: str = "",
|
||
limit: int = 8,
|
||
) -> tuple[dict[str, Any] | None, list[dict[str, Any]]]:
|
||
safe_limit = max(1, min(int(limit or 8), 16))
|
||
if platform == "douyin":
|
||
target_account = None
|
||
if requested_account_id:
|
||
target_account = legacy.db.fetch_one(
|
||
"SELECT * FROM douyin_accounts WHERE id = ? AND user_id = ?",
|
||
(requested_account_id, account["id"]),
|
||
)
|
||
if not target_account:
|
||
target_account = legacy.db.fetch_one(
|
||
"SELECT * FROM douyin_accounts WHERE user_id = ? ORDER BY updated_at DESC, created_at DESC LIMIT 1",
|
||
(account["id"],),
|
||
)
|
||
if not target_account:
|
||
return None, []
|
||
rows = legacy.db.fetch_all(
|
||
"""
|
||
SELECT * FROM douyin_videos
|
||
WHERE account_id = ?
|
||
ORDER BY COALESCE(published_at, updated_at) DESC, updated_at DESC
|
||
LIMIT ?
|
||
""",
|
||
(target_account["id"], safe_limit),
|
||
)
|
||
items: list[dict[str, Any]] = []
|
||
for row in rows:
|
||
stats = _parse_json(row.get("stats_json") or "{}", {})
|
||
play_count = float(stats.get("play_count") or stats.get("play") or 0)
|
||
like_count = float(stats.get("digg_count") or stats.get("like_count") or 0)
|
||
comment_count = float(stats.get("comment_count") or 0)
|
||
share_count = float(stats.get("share_count") or 0)
|
||
score = min(
|
||
100.0,
|
||
play_count / 10000 * 55
|
||
+ like_count / 1000 * 25
|
||
+ comment_count / 100 * 10
|
||
+ share_count / 100 * 10,
|
||
)
|
||
items.append(
|
||
{
|
||
"id": row["id"],
|
||
"title": row.get("title") or row.get("description") or row.get("share_url") or row["id"],
|
||
"source_url": row.get("share_url", ""),
|
||
"published_at": row.get("published_at") or "",
|
||
"score": {"performance_score": round(score, 2)},
|
||
"latest_job_id": "",
|
||
"latest_job_status": "",
|
||
"summary": "",
|
||
}
|
||
)
|
||
account_payload = {
|
||
"id": target_account["id"],
|
||
"title": target_account.get("nickname") or target_account.get("douyin_id") or "抖音账号",
|
||
"handle": target_account.get("douyin_id") or "",
|
||
"source_url": target_account.get("canonical_profile_url") or target_account.get("profile_url") or "",
|
||
"platform": "douyin",
|
||
}
|
||
items.sort(key=lambda item: (float((item.get("score") or {}).get("performance_score") or 0), item.get("published_at") or ""), reverse=True)
|
||
return account_payload, items[:safe_limit]
|
||
|
||
source_account = _latest_platform_account(account, project_id=project_id, platform=platform)
|
||
if not source_account:
|
||
return None, []
|
||
source_payload = legacy.content_source_payload(source_account)
|
||
metadata = source_payload.get("metadata") or {}
|
||
summary_videos = ((metadata.get("video_summary") or {}).get("videos") or [])[:safe_limit]
|
||
items = []
|
||
for item in summary_videos:
|
||
score = float(item.get("performance_score") or item.get("score") or 0)
|
||
items.append(
|
||
{
|
||
"id": str(item.get("id") or item.get("aweme_id") or item.get("video_id") or make_id(f"{platform}_video")),
|
||
"title": str(item.get("title") or item.get("description") or item.get("share_url") or "平台作品"),
|
||
"source_url": str(item.get("share_url") or item.get("url") or ""),
|
||
"published_at": str(item.get("published_at") or item.get("publish_time") or ""),
|
||
"score": {"performance_score": score},
|
||
"latest_job_id": "",
|
||
"latest_job_status": "",
|
||
"summary": "",
|
||
}
|
||
)
|
||
items.sort(key=lambda item: (float((item.get("score") or {}).get("performance_score") or 0), item.get("published_at") or ""), reverse=True)
|
||
return source_payload, items[:safe_limit]
|
||
|
||
def _assistant_brief_from_job(job_row: dict[str, Any] | None) -> str:
|
||
if not job_row:
|
||
return ""
|
||
result_map = _parse_json(job_row.get("result_json") or "{}", {})
|
||
artifacts_map = _parse_json(job_row.get("artifacts_json") or "{}", {})
|
||
candidates = [
|
||
result_map.get("summary"),
|
||
result_map.get("headline_summary"),
|
||
artifacts_map.get("summary"),
|
||
artifacts_map.get("objective"),
|
||
artifacts_map.get("brief"),
|
||
job_row.get("title"),
|
||
]
|
||
for value in candidates:
|
||
cleaned = str(value or "").strip()
|
||
if cleaned:
|
||
return cleaned[:480]
|
||
return ""
|
||
|
||
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 = ?",
|
||
(session_id, account["id"]),
|
||
)
|
||
if not row:
|
||
raise HTTPException(status_code=404, detail="OneLiner session not found")
|
||
return row
|
||
|
||
def _load_owned_agent_run(run_id: str, account: dict[str, Any]) -> dict[str, Any]:
|
||
row = legacy.db.fetch_one(
|
||
"SELECT * FROM agent_runs WHERE id = ? AND user_id = ?",
|
||
(run_id, account["id"]),
|
||
)
|
||
if not row:
|
||
raise HTTPException(status_code=404, detail="OneLiner run not found")
|
||
return row
|
||
|
||
def _normalize_platform_scope(value: str | None) -> str:
|
||
normalized = str(value or "").strip().lower()
|
||
if normalized == "all_platforms":
|
||
return "all_platforms"
|
||
return "single_platform"
|
||
|
||
def _normalize_delivery_mode(value: str | None) -> str:
|
||
normalized = str(value or "").strip().lower()
|
||
if normalized in {"ui", "oneliner", "hybrid"}:
|
||
return normalized
|
||
return "hybrid"
|
||
|
||
def _normalize_scheduling_mode(value: str | None) -> str:
|
||
normalized = str(value or "").strip().lower()
|
||
if normalized == "parallel":
|
||
return "parallel"
|
||
return "queued"
|
||
|
||
def _ensure_run_session(
|
||
account: dict[str, Any],
|
||
*,
|
||
project_id: str,
|
||
requested_session_id: str,
|
||
title: str,
|
||
preferred_platform: str,
|
||
) -> dict[str, Any]:
|
||
if requested_session_id:
|
||
return _load_owned_session(requested_session_id, account)
|
||
latest_row = legacy.db.fetch_one(
|
||
"""
|
||
SELECT * FROM oneliner_sessions
|
||
WHERE user_id = ? AND project_id = ?
|
||
ORDER BY updated_at DESC, created_at DESC
|
||
LIMIT 1
|
||
""",
|
||
(account["id"], project_id),
|
||
)
|
||
if latest_row:
|
||
return latest_row
|
||
profile = _fetch_profile_row(account, project_id)
|
||
session_id = make_id("oline")
|
||
timestamp = now()
|
||
legacy.db.execute(
|
||
"""
|
||
INSERT INTO oneliner_sessions (
|
||
id, user_id, project_id, profile_id, title, status, preferred_platform,
|
||
last_platform, last_intent_key, last_message_at, created_at, updated_at
|
||
) VALUES (?, ?, ?, ?, ?, 'active', ?, '', '', ?, ?, ?)
|
||
""",
|
||
(
|
||
session_id,
|
||
account["id"],
|
||
project_id,
|
||
(profile or {}).get("id"),
|
||
title.strip() or "新的 OneLiner 会话",
|
||
preferred_platform,
|
||
timestamp,
|
||
timestamp,
|
||
timestamp,
|
||
),
|
||
)
|
||
created = legacy.db.fetch_one("SELECT * FROM oneliner_sessions WHERE id = ?", (session_id,))
|
||
assert created is not None
|
||
return created
|
||
|
||
def _touch_session_for_run(session_id: str, *, platform: str, intent_key: str) -> None:
|
||
timestamp = now()
|
||
legacy.db.execute(
|
||
"""
|
||
UPDATE oneliner_sessions
|
||
SET last_platform = ?, last_intent_key = ?, last_message_at = ?, updated_at = ?
|
||
WHERE id = ?
|
||
""",
|
||
(platform, intent_key, timestamp, timestamp, session_id),
|
||
)
|
||
|
||
def _log_agent_run_event(
|
||
run_id: str,
|
||
*,
|
||
event_type: str,
|
||
summary: str,
|
||
details: dict[str, Any] | None = None,
|
||
) -> dict[str, Any]:
|
||
event_id = make_id("run_evt")
|
||
legacy.db.execute(
|
||
"""
|
||
INSERT INTO agent_run_events (id, run_id, event_type, summary, details_json, created_at)
|
||
VALUES (?, ?, ?, ?, ?, ?)
|
||
""",
|
||
(
|
||
event_id,
|
||
run_id,
|
||
event_type,
|
||
summary,
|
||
_dump(details or {}),
|
||
now(),
|
||
),
|
||
)
|
||
row = legacy.db.fetch_one("SELECT * FROM agent_run_events WHERE id = ?", (event_id,))
|
||
assert row is not None
|
||
return _agent_run_event_payload(row)
|
||
|
||
def _agent_run_plan_payload(
|
||
request: AgentRunCreateRequest,
|
||
*,
|
||
governance: dict[str, Any],
|
||
platform: str,
|
||
platform_scope: str,
|
||
) -> dict[str, Any]:
|
||
requested_plan = dict(request.plan_request or {})
|
||
requested_payload = dict(request.payload or {})
|
||
raw_steps = requested_plan.get("steps") or []
|
||
if not isinstance(raw_steps, list):
|
||
raw_steps = [raw_steps]
|
||
steps = [str(item).strip() for item in raw_steps if str(item).strip()]
|
||
if not steps:
|
||
steps = ["读取当前项目上下文", "结合治理层生成执行计划", "等待用户确认后执行"]
|
||
requested_context: dict[str, Any] = {}
|
||
for key in (
|
||
"target_account_id",
|
||
"targetAccountId",
|
||
"tracked_account_id",
|
||
"trackedAccountId",
|
||
"job_id",
|
||
"source_job_id",
|
||
"sourceJobId",
|
||
"review_id",
|
||
"reviewId",
|
||
"source_id",
|
||
"sourceId",
|
||
"assistant_id",
|
||
"assistantId",
|
||
"platform",
|
||
):
|
||
value = requested_payload.get(key)
|
||
text = str(value or "").strip() if value is not None else ""
|
||
if not text:
|
||
continue
|
||
normalized_key = {
|
||
"targetAccountId": "target_account_id",
|
||
"trackedAccountId": "tracked_account_id",
|
||
"sourceJobId": "source_job_id",
|
||
"reviewId": "review_id",
|
||
"sourceId": "source_id",
|
||
"assistantId": "assistant_id",
|
||
}.get(key, key)
|
||
requested_context[normalized_key] = text
|
||
return {
|
||
**requested_plan,
|
||
"goal": str(requested_plan.get("goal") or request.title or "主 Agent 任务").strip() or "主 Agent 任务",
|
||
"steps": steps,
|
||
"intent_key": str(request.intent_key or "custom").strip() or "custom",
|
||
"platform": platform,
|
||
"platform_scope": platform_scope,
|
||
"source_screen": str(request.source_screen or "").strip(),
|
||
"source_action_key": str(request.source_action_key or "").strip(),
|
||
"summary": str(request.summary or requested_plan.get("summary") or "").strip(),
|
||
"requested_delivery_mode": _normalize_delivery_mode(request.delivery_mode),
|
||
"requested_context": requested_context,
|
||
"active_admin_override_notice": governance.get("active_admin_override_notice") or {},
|
||
}
|
||
|
||
def _has_other_active_runs(*, account_id: str, project_id: str, run_id: str) -> bool:
|
||
row = legacy.db.fetch_one(
|
||
"""
|
||
SELECT id FROM agent_runs
|
||
WHERE user_id = ? AND project_id = ? AND id != ? AND run_status IN ('queued', 'running', 'blocked')
|
||
ORDER BY updated_at DESC
|
||
LIMIT 1
|
||
""",
|
||
(account_id, project_id, run_id),
|
||
)
|
||
return bool(row)
|
||
|
||
def _build_agent_run_recommended_action(row: dict[str, Any], plan: dict[str, Any]) -> dict[str, Any]:
|
||
source_screen = str(plan.get("source_screen") or row.get("source_screen") or "").strip().lower()
|
||
source_action_key = str(plan.get("source_action_key") or row.get("source_action_key") or "").strip().lower()
|
||
intent_key = str(plan.get("intent_key") or row.get("intent_key") or "custom").strip().lower() or "custom"
|
||
|
||
requested_context = plan.get("requested_context") if isinstance(plan.get("requested_context"), dict) else {}
|
||
|
||
def route(action: str, screen: str, label: str, summary: str, **extra: Any) -> dict[str, Any]:
|
||
payload = {
|
||
"action": action,
|
||
"screen": screen,
|
||
"label": label,
|
||
"summary": summary,
|
||
}
|
||
for key, value in extra.items():
|
||
text = str(value or "").strip() if value is not None else ""
|
||
if not text:
|
||
continue
|
||
payload[key] = value
|
||
return payload
|
||
|
||
target_account_id = str(requested_context.get("target_account_id") or "").strip()
|
||
tracked_account_id = str(requested_context.get("tracked_account_id") or "").strip()
|
||
job_id = str(requested_context.get("job_id") or requested_context.get("source_job_id") or "").strip()
|
||
review_id = str(requested_context.get("review_id") or "").strip()
|
||
source_id = str(requested_context.get("source_id") or "").strip()
|
||
assistant_id = str(requested_context.get("assistant_id") or "").strip()
|
||
|
||
if review_id:
|
||
return route("open-review-edit", "review", "打开复盘", "继续回到当前复盘对象完善结论和动作。", review_id=review_id, job_id=job_id)
|
||
if source_screen == "review" and job_id:
|
||
return route("open-review-from-job", "review", "继续写复盘", "继续围绕这条任务生成或完善复盘。", job_id=job_id)
|
||
if source_screen in {"discovery", "tracking"} and target_account_id:
|
||
return route("select-account", "discovery", "打开当前对象", "继续围绕当前账号查看详情、对标和分析结果。", account_id=target_account_id)
|
||
if source_screen == "tracking" and tracked_account_id:
|
||
return route("refresh-tracked-account", "tracking", "继续同步当前账号", "继续同步这条已跟踪账号,并回看最近更新。", tracked_account_id=tracked_account_id)
|
||
if source_screen == "production" and job_id:
|
||
return route("open-job-detail", "production", "看任务详情", "继续回到当前任务,查看执行状态和后续动作。", job_id=job_id)
|
||
if source_screen == "production" and source_id:
|
||
return route("edit-live-recorder-source", "production", "继续录制维护", "继续查看当前录制源的启停和录制文件。", source_id=source_id)
|
||
if source_screen in {"playbook", "agent"} and assistant_id:
|
||
return route("open-edit-assistant", "playbook", "继续编辑 Agent", "继续围绕当前 Agent 调整目标、说明和承接能力。", assistant_id=assistant_id)
|
||
|
||
source_routes = {
|
||
"strategy": route("goto-strategy", "strategy", "回到我的策略", "继续查看当前用户策略与覆盖状态。"),
|
||
"automation": route("goto-automation", "automation", "回到自动流程", "继续检查自动流程和依赖状态。"),
|
||
"playbook": route("goto-playbook", "playbook", "回到 Agent", "继续调整 Agent 与平台能力。"),
|
||
"agent": route("goto-playbook", "playbook", "回到 Agent", "继续调整 Agent 与平台能力。"),
|
||
"production": route("goto-production", "production", "回到生产中心", "继续推进生产任务与恢复动作。"),
|
||
"tracking": route("goto-tracking", "tracking", "回到跟踪账号", "继续查看跟踪摘要和更新提醒。"),
|
||
"review": route("goto-review", "review", "回到发布与复盘", "继续沉淀复盘结论和发布结果。"),
|
||
"discovery": route("goto-discovery", "discovery", "回到找对标", "继续查看账号拆解和高分样本。"),
|
||
"intake": route("goto-intake", "projects", "回到我的项目", "继续切换项目或补齐项目基础信息。"),
|
||
"projects": route("goto-intake", "projects", "回到我的项目", "继续切换项目或补齐项目基础信息。"),
|
||
}
|
||
if source_screen == "dashboard" and source_action_key == "homepage-primary-action":
|
||
return route("goto-discovery", "discovery", "回到找对标", "继续查看首页当前最优先的对标与高分样本动作。")
|
||
if source_screen == "dashboard" and source_action_key.startswith("homepage-secondary-action-"):
|
||
if intent_key == "track_account":
|
||
return route("goto-tracking", "tracking", "回到跟踪账号", "继续跟进首页建议的重点账号跟踪。")
|
||
return route("goto-production", "production", "回到生产中心", "继续处理首页建议的生产推进动作。")
|
||
if source_screen in source_routes:
|
||
return source_routes[source_screen]
|
||
|
||
intent_routes = {
|
||
"analyze_account": route(target_account_id and "select-account" or "goto-discovery", "discovery", target_account_id and "打开当前对象" or "回到找对标", "继续拆解当前账号和对标对象。", account_id=target_account_id),
|
||
"analyze_top_videos": route(target_account_id and "select-account" or "goto-discovery", "discovery", target_account_id and "打开当前对象" or "回到找对标", "继续查看高分作品分析结论。", account_id=target_account_id),
|
||
"track_account": route(tracked_account_id and "refresh-tracked-account" or "goto-tracking", "tracking", tracked_account_id and "继续同步当前账号" or "回到跟踪账号", "继续更新账号跟踪与日报。", tracked_account_id=tracked_account_id),
|
||
"ai_video": route(job_id and "open-job-detail" or "goto-production", "production", job_id and "看任务详情" or "回到生产中心", "继续推进 AI 视频生产任务。", job_id=job_id),
|
||
"real_cut": route(job_id and "open-job-detail" or "goto-production", "production", job_id and "看任务详情" or "回到生产中心", "继续推进实拍剪辑任务。", job_id=job_id),
|
||
"live_recorder": route(source_id and "edit-live-recorder-source" or "goto-production", "production", source_id and "继续录制维护" or "回到生产中心", "继续查看录制维护与产物。", source_id=source_id),
|
||
"review": route(review_id and "open-review-edit" or job_id and "open-review-from-job" or "goto-review", "review", review_id and "打开复盘" or job_id and "继续写复盘" or "回到发布与复盘", "继续补齐复盘与发布总结。", review_id=review_id, job_id=job_id),
|
||
"create_assistant": route(assistant_id and "open-edit-assistant" or "goto-playbook", "playbook", assistant_id and "继续编辑 Agent" or "回到 Agent", "继续创建或调整项目 Agent。", assistant_id=assistant_id),
|
||
"create_project": route("goto-intake", "projects", "回到我的项目", "继续创建或切换当前项目。"),
|
||
"import_homepage": route(target_account_id and "select-account" or "goto-discovery", "discovery", target_account_id and "打开当前对象" or "回到找对标", "继续处理主页导入后的账号分析。", account_id=target_account_id),
|
||
"ops_admin": route("goto-automation", "automation", "回到自动流程", "继续查看系统依赖和治理状态。"),
|
||
"storage_status": route("goto-automation", "automation", "回到自动流程", "继续查看存储与依赖健康状态。"),
|
||
}
|
||
if intent_key in intent_routes:
|
||
return intent_routes[intent_key]
|
||
return route("goto-production", "production", "回到生产中心", "继续推进当前主 Agent 任务的执行结果。")
|
||
|
||
def _build_agent_run_result_sections(
|
||
row: dict[str, Any],
|
||
plan: dict[str, Any],
|
||
recommended_action: dict[str, Any],
|
||
) -> dict[str, Any]:
|
||
source_screen = str(plan.get("source_screen") or row.get("source_screen") or "").strip().lower()
|
||
recommended_screen = str(recommended_action.get("screen") or "").strip().lower()
|
||
screen_key = recommended_screen or source_screen
|
||
intent_key = str(plan.get("intent_key") or row.get("intent_key") or "custom").strip().lower() or "custom"
|
||
platform = str(plan.get("platform") or row.get("platform") or "").strip()
|
||
platform_label = legacy.platform_label(platform) if platform else ""
|
||
plan_summary = str(plan.get("summary") or row.get("summary") or "").strip() or "主 Agent 已按当前上下文完成首轮收口。"
|
||
goal = str(plan.get("goal") or row.get("title") or "主 Agent 任务").strip() or "主 Agent 任务"
|
||
override_notice = _parse_json(row.get("active_admin_override_notice_json"), {})
|
||
|
||
workstream_map = {
|
||
"dashboard": ("dashboard", "首页动作"),
|
||
"discovery": ("discovery", "对标推进"),
|
||
"tracking": ("tracking", "跟踪推进"),
|
||
"production": ("production", "生产推进"),
|
||
"review": ("review", "复盘推进"),
|
||
"strategy": ("strategy", "策略治理"),
|
||
"playbook": ("playbook", "Agent 治理"),
|
||
"agent": ("playbook", "Agent 治理"),
|
||
"automation": ("automation", "自动流程"),
|
||
"intake": ("intake", "项目推进"),
|
||
"projects": ("intake", "项目推进"),
|
||
}
|
||
workstream_key, workstream_label = workstream_map.get(screen_key, ("production", "主 Agent 执行结果"))
|
||
|
||
if intent_key in {"analyze_account", "analyze_top_videos", "import_homepage"}:
|
||
workstream_key, workstream_label = "discovery", "对标推进"
|
||
elif intent_key == "track_account":
|
||
workstream_key, workstream_label = "tracking", "跟踪推进"
|
||
elif intent_key in {"ai_video", "real_cut", "live_recorder"}:
|
||
workstream_key, workstream_label = "production", "生产推进"
|
||
elif intent_key == "review":
|
||
workstream_key, workstream_label = "review", "复盘推进"
|
||
elif intent_key in {"create_assistant"}:
|
||
workstream_key, workstream_label = "playbook", "Agent 治理"
|
||
elif intent_key in {"create_project"}:
|
||
workstream_key, workstream_label = "intake", "项目推进"
|
||
elif intent_key in {"storage_status", "ops_admin"}:
|
||
workstream_key, workstream_label = "automation", "自动流程"
|
||
|
||
def card(title: str, body: str, *, tone: str = "blue", tags: list[str] | None = None) -> dict[str, Any]:
|
||
return {
|
||
"title": title,
|
||
"body": body,
|
||
"tone": tone,
|
||
"tags": [item for item in list(tags or []) if str(item).strip()],
|
||
}
|
||
|
||
cards = [
|
||
card(
|
||
"当前焦点",
|
||
f"围绕「{goal}」先完成一版可执行收口,避免你先在多个页面之间来回切换。",
|
||
tone="blue",
|
||
tags=[platform_label or "当前平台", workstream_label],
|
||
)
|
||
]
|
||
|
||
focus_body_map = {
|
||
"discovery": "优先回到找对标,继续看账号、相似关系和高分样本,再决定是否导入或加入跟踪。",
|
||
"tracking": "优先回到跟踪账号,看最近日报窗口和值得继续跟进的对象,再决定同步或加深跟踪。",
|
||
"production": "优先回到生产中心,看队列、失败恢复和产物,再决定下一步是推进还是补救。",
|
||
"review": "优先回到发布与复盘,把最近完成任务沉淀成结构化复盘,再决定是否继续发布。",
|
||
"strategy": "优先回到我的策略,先看当前生效层和管理员覆盖,再决定是否继续调整用户策略。",
|
||
"playbook": "优先回到 Agent 工作区,结合当前平台 Agent、模型和技能,继续完善执行能力。",
|
||
"automation": "优先回到自动流程,先看依赖健康和动作防呆,再决定是否恢复或放行动作。",
|
||
"intake": "优先回到我的项目,先补项目、账号和导入基础,再决定往哪个工作页继续推进。",
|
||
"dashboard": "优先回到首页当前推荐动作,把这一轮的重点从概览转成真实执行。",
|
||
}
|
||
cards.append(
|
||
card(
|
||
workstream_label,
|
||
focus_body_map.get(workstream_key, plan_summary),
|
||
tone="green",
|
||
tags=[recommended_action.get("label") or "回到业务页"],
|
||
)
|
||
)
|
||
cards.append(
|
||
card(
|
||
"建议落点",
|
||
str(recommended_action.get("summary") or plan_summary).strip() or "回到对应业务页面继续推进。",
|
||
tone="orange" if override_notice.get("title") else "blue",
|
||
tags=[
|
||
recommended_action.get("screen") or "",
|
||
"管理员覆盖生效中" if override_notice.get("title") else "",
|
||
"全平台" if str(plan.get("platform_scope") or row.get("platform_scope") or "") == "all_platforms" else "单平台",
|
||
],
|
||
)
|
||
)
|
||
if override_notice.get("title"):
|
||
cards.append(
|
||
card(
|
||
"管理员覆盖提醒",
|
||
str(override_notice.get("summary") or "当前执行会优先遵循管理员覆盖层。").strip(),
|
||
tone="orange",
|
||
tags=[str(override_notice.get("title") or "管理员覆盖").strip()],
|
||
)
|
||
)
|
||
|
||
return {
|
||
"workstream_key": workstream_key,
|
||
"workstream_label": workstream_label,
|
||
"cards": cards,
|
||
}
|
||
|
||
def _complete_agent_run_for_read(row: dict[str, Any]) -> dict[str, Any]:
|
||
current_status = str(row.get("run_status") or "")
|
||
run_id = str(row.get("id") or "")
|
||
if not run_id:
|
||
return row
|
||
|
||
if current_status == "queued" and not _has_other_active_runs(
|
||
account_id=str(row.get("user_id") or ""),
|
||
project_id=str(row.get("project_id") or ""),
|
||
run_id=run_id,
|
||
):
|
||
timestamp = now()
|
||
started_at = str(row.get("started_at") or timestamp)
|
||
legacy.db.execute(
|
||
"""
|
||
UPDATE agent_runs
|
||
SET run_status = 'running', status_summary = ?, updated_at = ?, started_at = ?
|
||
WHERE id = ?
|
||
""",
|
||
("主 Agent 正在执行", timestamp, started_at, run_id),
|
||
)
|
||
_log_agent_run_event(
|
||
run_id,
|
||
event_type="run.started",
|
||
summary="主 Agent 已开始执行",
|
||
details={"run_status": "running"},
|
||
)
|
||
refreshed = legacy.db.fetch_one("SELECT * FROM agent_runs WHERE id = ?", (run_id,))
|
||
if refreshed is not None:
|
||
row = refreshed
|
||
current_status = "running"
|
||
|
||
if current_status != "running" or str(row.get("finished_at") or "").strip():
|
||
return row
|
||
|
||
timestamp = now()
|
||
plan = _parse_json(row.get("plan_json"), {})
|
||
governance = _parse_json(row.get("governance_json"), {})
|
||
oneliner_profile_version = (
|
||
governance.get("oneliner_profile_version")
|
||
or (governance.get("oneliner_profile") or {}).get("current_version")
|
||
or {}
|
||
)
|
||
platform_agent_profile = governance.get("platform_agent_profile") or {}
|
||
steps = [str(item).strip() for item in list(plan.get("steps") or []) if str(item).strip()]
|
||
if not steps:
|
||
steps = ["读取当前项目上下文", "结合治理层生成执行计划", "收口为可执行建议"]
|
||
summary_text = str(plan.get("summary") or row.get("summary") or "").strip() or "主 Agent 已根据当前计划完成第一版执行收口。"
|
||
execution_summary = f"已完成「{str(plan.get('goal') or row.get('title') or '主 Agent 任务').strip() or '主 Agent 任务'}」的首轮执行建议。"
|
||
recommended_action = _build_agent_run_recommended_action(row, plan)
|
||
result_sections = _build_agent_run_result_sections(row, plan, recommended_action)
|
||
result_payload = {
|
||
"result_kind": "main_agent_plan",
|
||
"run_id": run_id,
|
||
"goal": str(plan.get("goal") or row.get("title") or "主 Agent 任务").strip() or "主 Agent 任务",
|
||
"summary_text": summary_text,
|
||
"execution_summary": execution_summary,
|
||
"next_steps": steps,
|
||
"intent_key": str(plan.get("intent_key") or row.get("intent_key") or "custom").strip() or "custom",
|
||
"platform": str(plan.get("platform") or row.get("platform") or "").strip(),
|
||
"platform_scope": str(plan.get("platform_scope") or row.get("platform_scope") or "single_platform").strip() or "single_platform",
|
||
"recommended_action": recommended_action,
|
||
"result_sections": result_sections,
|
||
"active_admin_override_notice": _parse_json(row.get("active_admin_override_notice_json"), {}),
|
||
"execution_card": {
|
||
"intent_key": str(plan.get("intent_key") or row.get("intent_key") or "custom").strip() or "custom",
|
||
"intent_label": str(plan.get("intent_label") or "").strip() or "主 Agent 任务",
|
||
"delivery_mode": str(plan.get("delivery_mode") or row.get("delivery_mode") or "oneliner").strip() or "oneliner",
|
||
"platform": str(plan.get("platform") or row.get("platform") or "").strip(),
|
||
"platform_label": str(plan.get("platform_label") or "").strip() or "待判断",
|
||
"active_admin_override_notice": _parse_json(row.get("active_admin_override_notice_json"), {}),
|
||
"oneliner_profile_version": {
|
||
"version_id": str(oneliner_profile_version.get("id") or "").strip(),
|
||
"version_no": int(oneliner_profile_version.get("version_no") or 0),
|
||
"title": str(oneliner_profile_version.get("title") or "").strip(),
|
||
"summary": str(oneliner_profile_version.get("summary") or "").strip(),
|
||
},
|
||
"platform_agent_profile": {
|
||
"platform": str(platform_agent_profile.get("platform") or "").strip(),
|
||
"platform_label": str(platform_agent_profile.get("platform_label") or "").strip(),
|
||
"name": str(platform_agent_profile.get("name") or "").strip(),
|
||
"assistant_name": str(platform_agent_profile.get("assistant_name") or "").strip(),
|
||
"version_id": str(((platform_agent_profile.get("current_version") or {}).get("id") or "").strip()),
|
||
"version_no": int(((platform_agent_profile.get("current_version") or {}).get("version_no") or 0)),
|
||
"version_title": str(((platform_agent_profile.get("current_version") or {}).get("title") or "").strip()),
|
||
"version_summary": str(((platform_agent_profile.get("current_version") or {}).get("summary") or "").strip()),
|
||
"mission": str(platform_agent_profile.get("mission") or "").strip(),
|
||
"status": str(platform_agent_profile.get("status") or "").strip(),
|
||
"readiness_label": str(platform_agent_profile.get("readiness_label") or "").strip(),
|
||
"readiness_score": int(platform_agent_profile.get("readiness_score") or 0),
|
||
},
|
||
"next_steps": steps,
|
||
},
|
||
}
|
||
_log_agent_run_event(
|
||
run_id,
|
||
event_type="run.progress",
|
||
summary="主 Agent 已完成首轮分析,正在收口执行建议",
|
||
details={"completed_steps": len(steps), "total_steps": len(steps)},
|
||
)
|
||
legacy.db.execute(
|
||
"""
|
||
UPDATE agent_runs
|
||
SET run_status = 'done',
|
||
status_summary = ?,
|
||
result_json = ?,
|
||
needs_user_input = 0,
|
||
blocked_reason = '',
|
||
updated_at = ?,
|
||
finished_at = ?
|
||
WHERE id = ?
|
||
""",
|
||
(execution_summary, _dump(result_payload), timestamp, timestamp, run_id),
|
||
)
|
||
_record_platform_agent_execution_feedback(
|
||
str(row.get("user_id") or "").strip(),
|
||
project_id=str(row.get("project_id") or "").strip(),
|
||
platform=str(row.get("platform") or "").strip(),
|
||
run_id=run_id,
|
||
run_status="done",
|
||
intent_key=str(plan.get("intent_key") or row.get("intent_key") or "custom").strip() or "custom",
|
||
source_screen=str(row.get("source_screen") or "").strip(),
|
||
oneliner_profile_version_no=int(oneliner_profile_version.get("version_no") or 0),
|
||
platform_agent_profile_version_no=int(((platform_agent_profile.get("current_version") or {}).get("version_no") or 0)),
|
||
execution_summary=execution_summary,
|
||
)
|
||
_log_agent_run_event(
|
||
run_id,
|
||
event_type="run.done",
|
||
summary=execution_summary,
|
||
details={"result_kind": "main_agent_plan", "status_summary": execution_summary},
|
||
)
|
||
refreshed = legacy.db.fetch_one("SELECT * FROM agent_runs WHERE id = ?", (run_id,))
|
||
return refreshed or row
|
||
|
||
def _deterministic_intent(message: str, platform_hint: str, account: dict[str, Any]) -> dict[str, Any]:
|
||
text = message.strip()
|
||
lowered = text.lower()
|
||
platform = normalize_platform_from_text(text) or _safe_platform(platform_hint or "", fallback="")
|
||
intent_key = "custom"
|
||
confidence = 0.45
|
||
summary = "先理解目标,再把任务路由到合适的平台 Agent 或固定能力。"
|
||
if any(keyword in text for keyword in ("新建项目", "创建项目", "建项目")):
|
||
intent_key = "create_project"
|
||
confidence = 0.96
|
||
summary = "这是一个新项目启动诉求,优先进入项目创建流。"
|
||
elif any(keyword in lowered for keyword in ("create agent",)) or any(keyword in text for keyword in ("创建agent", "创建 Agent", "新建agent", "新建 Agent")):
|
||
intent_key = "create_assistant"
|
||
confidence = 0.96
|
||
summary = "这是定义新 Agent 的需求,适合直接进入 Agent 创建流。"
|
||
elif any(keyword in text for keyword in ("导入主页", "主页链接", "账号主页", "主页账号")):
|
||
intent_key = "import_homepage"
|
||
confidence = 0.9
|
||
summary = "这是账号主页导入诉求,适合进入内容源接入。"
|
||
elif any(keyword in text for keyword in ("跟踪", "日报", "更新提醒", "持续跟")):
|
||
intent_key = "track_account"
|
||
confidence = 0.9
|
||
summary = "这是持续跟踪类任务,适合交给平台 Agent 和跟踪摘要链。"
|
||
elif any(keyword in text for keyword in ("高分", "爆款", "优质作品", "高表现")):
|
||
intent_key = "analyze_top_videos"
|
||
confidence = 0.88
|
||
summary = "这是高表现内容拆解诉求,优先分析高分作品。"
|
||
elif any(keyword in text for keyword in ("对标", "分析账号", "调研", "拆账号")):
|
||
intent_key = "analyze_account"
|
||
confidence = 0.86
|
||
summary = "这是账号层面的调研任务,优先交给对应平台 Agent。"
|
||
elif any(keyword in text for keyword in ("文案", "脚本", "口播", "改写")):
|
||
intent_key = "generate_copy"
|
||
confidence = 0.88
|
||
summary = "这是文案/脚本生成任务,适合走 Agent 生成链。"
|
||
elif any(keyword in text for keyword in ("AI视频", "AI 视频", "生成视频")):
|
||
intent_key = "ai_video"
|
||
confidence = 0.9
|
||
summary = "这是 AI 视频生产任务,适合走 AI 视频链。"
|
||
elif any(keyword in text for keyword in ("实拍", "剪辑", "混剪")):
|
||
intent_key = "real_cut"
|
||
confidence = 0.9
|
||
summary = "这是实拍剪辑任务,适合走 cutvideo 链。"
|
||
elif any(keyword in text for keyword in ("直播", "录制", "开录")):
|
||
intent_key = "live_recorder"
|
||
confidence = 0.9
|
||
summary = "这是直播录制任务,适合走 NAS 录制能力。"
|
||
elif any(keyword in text for keyword in ("复盘", "发布总结", "回看数据")):
|
||
intent_key = "review"
|
||
confidence = 0.84
|
||
summary = "这是发布复盘任务,适合进入复盘工作台。"
|
||
elif any(keyword in text for keyword in ("空间", "缓存", "存储", "NAS")):
|
||
intent_key = "storage_status"
|
||
confidence = 0.82
|
||
summary = "这是存储状态问题,适合查看租户存储面板。"
|
||
elif account.get("role") == "super_admin" and any(keyword in text for keyword in ("报错", "日志", "故障", "运维", "修复")):
|
||
intent_key = "ops_admin"
|
||
confidence = 0.84
|
||
summary = "这是平台级运维诉求,只能交给管理员运维/审计能力。"
|
||
return {
|
||
"intent_key": intent_key,
|
||
"platform": platform or "",
|
||
"confidence": confidence,
|
||
"summary": summary,
|
||
"reasoning_mode": "deterministic-first",
|
||
}
|
||
|
||
def normalize_platform_from_text(text: str) -> str:
|
||
for key, value in legacy.PLATFORM_ALIASES.items():
|
||
if key and key in text.lower():
|
||
if value in legacy.DOMESTIC_PLATFORMS:
|
||
return value
|
||
for key, value in legacy.PLATFORM_ALIASES.items():
|
||
if key and key in text:
|
||
if value in legacy.DOMESTIC_PLATFORMS:
|
||
return value
|
||
return ""
|
||
|
||
async def _model_refine_intent(
|
||
account: dict[str, Any],
|
||
*,
|
||
project_id: str,
|
||
message: str,
|
||
platform_hint: str,
|
||
) -> dict[str, Any]:
|
||
profile = legacy.model_profile_for_account(account["id"], None)
|
||
if not profile:
|
||
raise HTTPException(status_code=503, detail="No model profile available")
|
||
system_prompt = (
|
||
"你是 StoryForge 的 OneLiner 总控主Agent,只负责把用户目标分类成安全的系统动作。"
|
||
"必须输出 JSON,不要输出 Markdown。"
|
||
)
|
||
user_prompt = (
|
||
f"用户角色:{account.get('role','user')}\n"
|
||
f"项目:{project_id or '默认项目'}\n"
|
||
f"平台提示:{platform_hint or '未指定'}\n"
|
||
f"用户原话:{message}\n\n"
|
||
"请输出 JSON:"
|
||
"{"
|
||
'"intent_key":"","platform":"","confidence":0.0,'
|
||
'"summary":"","needs_oneliner_only":false,'
|
||
'"remember_preference":false'
|
||
"}\n"
|
||
"intent_key 只能取:create_project, create_assistant, import_homepage, track_account, analyze_account, analyze_top_videos, generate_copy, ai_video, real_cut, review, live_recorder, storage_status, ops_admin, custom。"
|
||
"如果前端 UI 还没有明确产品化,needs_oneliner_only 返回 true。"
|
||
)
|
||
raw = await legacy.call_model(profile, system_prompt, user_prompt, temperature=0.1)
|
||
parsed = legacy.parse_json_object(raw)
|
||
if not parsed:
|
||
raise HTTPException(status_code=502, detail="OneLiner planner returned empty result")
|
||
return {
|
||
"intent_key": str(parsed.get("intent_key") or "custom").strip() or "custom",
|
||
"platform": normalize_platform_from_text(str(parsed.get("platform") or "")) or _safe_platform(platform_hint or "", fallback=""),
|
||
"confidence": float(parsed.get("confidence") or 0),
|
||
"summary": str(parsed.get("summary") or "").strip() or "已按模型判断用户目标。",
|
||
"needs_oneliner_only": bool(parsed.get("needs_oneliner_only")),
|
||
"remember_preference": bool(parsed.get("remember_preference")),
|
||
"reasoning_mode": "model-refine",
|
||
}
|
||
|
||
async def _plan_oneliner_request(
|
||
account: dict[str, Any],
|
||
*,
|
||
project_id: str,
|
||
message: str,
|
||
platform_hint: str,
|
||
) -> dict[str, Any]:
|
||
plan = _deterministic_intent(message, platform_hint, account)
|
||
if plan["confidence"] < 0.82:
|
||
try:
|
||
refined = await _model_refine_intent(account, project_id=project_id, message=message, platform_hint=platform_hint)
|
||
if refined.get("confidence", 0) >= plan.get("confidence", 0):
|
||
plan = {**plan, **refined}
|
||
except Exception:
|
||
pass
|
||
intent_key = plan.get("intent_key") or "custom"
|
||
actions = INTENT_ACTIONS.get(intent_key, [])
|
||
if intent_key == "ops_admin" and account.get("role") != "super_admin":
|
||
actions = []
|
||
plan["summary"] = "这是平台级运维诉求,但当前账号没有管理员权限。"
|
||
plan["needs_oneliner_only"] = True
|
||
ui_supported = bool(actions)
|
||
if intent_key == "custom":
|
||
plan["needs_oneliner_only"] = True
|
||
plan["ui_supported"] = ui_supported
|
||
plan["delivery_mode"] = "ui" if ui_supported and not plan.get("needs_oneliner_only") else "oneliner"
|
||
plan["suggested_actions"] = actions
|
||
plan["intent_label"] = INTENT_LABELS.get(intent_key, intent_key)
|
||
plan["platform_label"] = legacy.platform_label(plan.get("platform")) if plan.get("platform") else "待判断"
|
||
plan["economicity"] = {
|
||
"policy": "deterministic-first",
|
||
"explanation": "先走固定流程,再走平台 Agent,最后才升级到 OneLiner 深度调度。",
|
||
}
|
||
return plan
|
||
|
||
def _remember_message_preference(
|
||
account: dict[str, Any],
|
||
*,
|
||
project_id: str,
|
||
plan: dict[str, Any],
|
||
message: str,
|
||
) -> dict[str, Any] | None:
|
||
cues = ("记住", "以后", "长期", "默认", "一直", "优先")
|
||
if not plan.get("remember_preference") and not any(cue in message for cue in cues):
|
||
return None
|
||
agent_scope = "oneliner"
|
||
platform = plan.get("platform") or ""
|
||
subject_type = "project" if project_id else "account"
|
||
subject_id = project_id or account["id"]
|
||
memory_key = f"preference::{plan.get('intent_key') or 'custom'}"
|
||
existing = legacy.db.fetch_one(
|
||
"""
|
||
SELECT * FROM agent_memories
|
||
WHERE user_id = ? AND project_id = ? AND agent_scope = ? AND platform = ?
|
||
AND subject_type = ? AND subject_id = ? AND memory_key = ?
|
||
""",
|
||
(account["id"], project_id, agent_scope, platform, subject_type, subject_id, memory_key),
|
||
)
|
||
timestamp = now()
|
||
details = {
|
||
"captured_from": "oneliner_chat",
|
||
"intent_key": plan.get("intent_key", "custom"),
|
||
"source_message": message,
|
||
"platform": platform,
|
||
}
|
||
if existing:
|
||
legacy.db.execute(
|
||
"""
|
||
UPDATE agent_memories
|
||
SET title = ?, summary = ?, details_json = ?, confidence = ?, last_validated_at = ?, updated_at = ?
|
||
WHERE id = ?
|
||
""",
|
||
(
|
||
f"{INTENT_LABELS.get(plan.get('intent_key') or 'custom', '偏好')}偏好",
|
||
message.strip()[:280],
|
||
_dump(details),
|
||
0.82,
|
||
timestamp,
|
||
timestamp,
|
||
existing["id"],
|
||
),
|
||
)
|
||
stored = legacy.db.fetch_one("SELECT * FROM agent_memories WHERE id = ?", (existing["id"],))
|
||
else:
|
||
memory_id = make_id("mem")
|
||
legacy.db.execute(
|
||
"""
|
||
INSERT INTO agent_memories (
|
||
id, user_id, project_id, agent_scope, platform, subject_type, subject_id,
|
||
memory_key, title, summary, details_json, confidence, last_validated_at, created_at, updated_at
|
||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||
""",
|
||
(
|
||
memory_id,
|
||
account["id"],
|
||
project_id,
|
||
agent_scope,
|
||
platform,
|
||
subject_type,
|
||
subject_id,
|
||
memory_key,
|
||
f"{INTENT_LABELS.get(plan.get('intent_key') or 'custom', '偏好')}偏好",
|
||
message.strip()[:280],
|
||
_dump(details),
|
||
0.82,
|
||
timestamp,
|
||
timestamp,
|
||
timestamp,
|
||
),
|
||
)
|
||
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
|
||
profile_row = _fetch_profile_row(account, project["id"])
|
||
if profile_row and profile_row.get("assistant_id"):
|
||
assistant = _resolve_assistant(account, profile_row.get("assistant_id"), project["id"])
|
||
platform_profile = legacy.db.fetch_one(
|
||
"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 []
|
||
governance_effective = _effective_policy_payload(
|
||
subject_account=account,
|
||
subject_project_id=project["id"],
|
||
platform=platform,
|
||
)
|
||
user_global_scope = _policy_scope_row(
|
||
scope_kind="user_global",
|
||
subject_user_id=account["id"],
|
||
subject_project_id=project["id"],
|
||
)
|
||
user_platform_scope = _policy_scope_row(
|
||
scope_kind="user_platform",
|
||
subject_user_id=account["id"],
|
||
subject_project_id=project["id"],
|
||
platform=_normalize_policy_platform(platform),
|
||
) if platform else None
|
||
return {
|
||
"project": legacy.project_payload(project),
|
||
"oneliner_profile": _oneliner_profile_bundle(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],
|
||
"governance": {
|
||
"effective": governance_effective,
|
||
"user_global": _bundle_with_versions(
|
||
user_global_scope,
|
||
fallback_kind="user_global",
|
||
fallback_user_id=account["id"],
|
||
fallback_project_id=project["id"],
|
||
active_version_only=True,
|
||
),
|
||
"user_platform": _bundle_with_versions(
|
||
user_platform_scope,
|
||
fallback_kind="user_platform",
|
||
fallback_platform=_normalize_policy_platform(platform),
|
||
fallback_user_id=account["id"],
|
||
fallback_project_id=project["id"],
|
||
active_version_only=True,
|
||
) if platform else None,
|
||
},
|
||
}
|
||
|
||
async def _generate_oneliner_reply(
|
||
account: dict[str, Any],
|
||
*,
|
||
project_id: str,
|
||
message: str,
|
||
plan: dict[str, Any],
|
||
) -> dict[str, Any]:
|
||
context = _session_context_summary(account, project_id or "", plan.get("platform") or "")
|
||
oneliner_profile = context.get("oneliner_profile") or {}
|
||
platform_agent = context.get("platform_agent") or {}
|
||
platform_agent_assistant = platform_agent.get("assistant") or {}
|
||
context_assistant = context.get("assistant") or {}
|
||
governance = context.get("governance") or {}
|
||
effective_policy = (governance.get("effective") or {}).get("effective_policy") or {}
|
||
governance_layers = (governance.get("effective") or {}).get("layers") or []
|
||
active_admin_override_notice = (governance.get("effective") or {}).get("active_admin_override_notice") 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_assistant.get("name"):
|
||
next_steps.append(f"默认调度 {platform_agent_assistant.get('name')} 作为执行 Agent。")
|
||
if evidence:
|
||
next_steps.append("我会优先参考该平台 Agent 最近沉淀的方法与技能。")
|
||
if governance_layers:
|
||
next_steps.append(f"当前会话已叠加 {len(governance_layers)} 层策略,我会先按生效策略执行。")
|
||
if active_admin_override_notice.get("title"):
|
||
next_steps.append(f"当前存在管理员覆盖:{active_admin_override_notice.get('title')}。")
|
||
oneliner_profile_version = oneliner_profile.get("current_version") or {}
|
||
if oneliner_profile_version.get("version_no"):
|
||
next_steps.append(f"当前主 Agent 配置版本:v{oneliner_profile_version.get('version_no')}。")
|
||
summary_lines = [
|
||
f"我理解你的目标是:{plan.get('intent_label', '自定义任务')}。",
|
||
f"建议优先处理的平台:{plan.get('platform_label', '待判断')}。",
|
||
plan.get("summary", ""),
|
||
]
|
||
if plan.get("delivery_mode") == "oneliner":
|
||
summary_lines.append("这项能力当前更适合先由 OneLiner 对话承接,而不是要求你先理解前端功能树。")
|
||
if plan.get("intent_key") == "ops_admin" and account.get("role") != "super_admin":
|
||
summary_lines.append("当前账号不是平台最高权限用户,所以我不会放出运维 Agent 入口。")
|
||
if context.get("platform_agent"):
|
||
summary_lines.append(f"当前 {context['platform_agent']['platform_label']} Agent 已绑定:{platform_agent_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 '一条技能'}。")
|
||
if effective_policy.get("guardrails", {}).get("require_admin_review"):
|
||
summary_lines.append("当前策略层要求管理员复核,所以我会把高风险动作先压成待确认。")
|
||
if active_admin_override_notice.get("title"):
|
||
summary_lines.append(f"管理员覆盖生效中:{active_admin_override_notice.get('title')}。")
|
||
if oneliner_profile_version.get("version_no"):
|
||
summary_lines.append(f"当前主 Agent 正在按配置版本 v{oneliner_profile_version.get('version_no')} 执行。")
|
||
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", ""),
|
||
}
|
||
)
|
||
first_url = _extract_first_url(message)
|
||
if plan.get("intent_key") in {"import_homepage", "custom"} and first_url:
|
||
secondary_actions.append(
|
||
{
|
||
"key": "run-oneliner-action",
|
||
"label": "直接导入主页",
|
||
"kind": "api_action",
|
||
"executor_key": "import-homepage",
|
||
"platform": plan.get("platform", ""),
|
||
"payload": {
|
||
"source_url": first_url,
|
||
},
|
||
}
|
||
)
|
||
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 first_url:
|
||
secondary_actions.append(
|
||
{
|
||
"key": "run-oneliner-action",
|
||
"label": "直接保存录制源",
|
||
"kind": "api_action",
|
||
"executor_key": "save-live-recorder-source",
|
||
"platform": plan.get("platform", ""),
|
||
"payload": {
|
||
"source_url": first_url,
|
||
"auto_start": True,
|
||
},
|
||
}
|
||
)
|
||
latest_platform_account = None
|
||
if plan.get("platform"):
|
||
latest_platform_account = _latest_platform_account(
|
||
account,
|
||
project_id=project_id or "",
|
||
platform=plan.get("platform", ""),
|
||
)
|
||
if plan.get("platform") and latest_platform_account and plan.get("intent_key") in {"analyze_top_videos", "analyze_account", "custom"}:
|
||
secondary_actions.append(
|
||
{
|
||
"key": "run-oneliner-action",
|
||
"label": "直接分析账号",
|
||
"kind": "api_action",
|
||
"executor_key": "analyze-account",
|
||
"platform": plan.get("platform", ""),
|
||
"payload": {
|
||
"target_account_id": latest_platform_account["id"],
|
||
},
|
||
}
|
||
)
|
||
secondary_actions.append(
|
||
{
|
||
"key": "run-oneliner-action",
|
||
"label": "直接分析高分作品",
|
||
"kind": "api_action",
|
||
"executor_key": "analyze-top-videos",
|
||
"platform": plan.get("platform", ""),
|
||
"payload": {
|
||
"target_account_id": latest_platform_account["id"],
|
||
},
|
||
}
|
||
)
|
||
secondary_actions.append(
|
||
{
|
||
"key": "run-oneliner-action",
|
||
"label": "直接查相似账号",
|
||
"kind": "api_action",
|
||
"executor_key": "search-similar-accounts",
|
||
"platform": plan.get("platform", ""),
|
||
"payload": {
|
||
"target_account_id": latest_platform_account["id"],
|
||
},
|
||
}
|
||
)
|
||
if plan.get("platform") and latest_platform_account and plan.get("intent_key") in {"track_account", "custom"}:
|
||
secondary_actions.append(
|
||
{
|
||
"key": "run-oneliner-action",
|
||
"label": "直接加入跟踪",
|
||
"kind": "api_action",
|
||
"executor_key": "track-account",
|
||
"platform": plan.get("platform", ""),
|
||
"payload": {
|
||
"target_account_id": latest_platform_account["id"],
|
||
"refresh_now": True,
|
||
},
|
||
}
|
||
)
|
||
latest_similarity = (
|
||
_latest_similarity_candidate(
|
||
account,
|
||
platform=plan.get("platform", ""),
|
||
source_account_id=latest_platform_account["id"],
|
||
)
|
||
if plan.get("platform") and latest_platform_account
|
||
else None
|
||
)
|
||
latest_similarity_candidate = (latest_similarity or {}).get("candidate") or {}
|
||
if (
|
||
plan.get("platform")
|
||
and latest_platform_account
|
||
and latest_similarity_candidate
|
||
and plan.get("intent_key") in {"analyze_top_videos", "analyze_account", "custom", "track_account"}
|
||
):
|
||
secondary_actions.append(
|
||
{
|
||
"key": "run-oneliner-action",
|
||
"label": "直接存对标关系",
|
||
"kind": "api_action",
|
||
"executor_key": "save-benchmark-link",
|
||
"platform": plan.get("platform", ""),
|
||
"payload": {
|
||
"source_account_id": latest_platform_account["id"],
|
||
"target_account_id": latest_similarity_candidate.get("candidate_account_id") or "",
|
||
"target_profile_url": latest_similarity_candidate.get("candidate_profile_url") or "",
|
||
"search_id": (latest_similarity or {}).get("search_id") or "",
|
||
},
|
||
}
|
||
)
|
||
if plan.get("platform") and plan.get("intent_key") in {"track_account", "custom"}:
|
||
secondary_actions.append(
|
||
{
|
||
"key": "run-oneliner-action",
|
||
"label": "直接同步跟踪池",
|
||
"kind": "api_action",
|
||
"executor_key": "refresh-tracking",
|
||
"platform": plan.get("platform", ""),
|
||
}
|
||
)
|
||
secondary_actions.append(
|
||
{
|
||
"key": "run-oneliner-action",
|
||
"label": "直接标记日报已读",
|
||
"kind": "api_action",
|
||
"executor_key": "mark-tracking-read",
|
||
"platform": plan.get("platform", ""),
|
||
}
|
||
)
|
||
if context_assistant:
|
||
secondary_actions.append(
|
||
{
|
||
"key": "run-oneliner-action",
|
||
"label": "直接生成一版文案",
|
||
"kind": "api_action",
|
||
"executor_key": "generate-copy",
|
||
"platform": plan.get("platform", ""),
|
||
}
|
||
)
|
||
if not context_assistant and plan.get("intent_key") in {"create_assistant", "custom"}:
|
||
secondary_actions.append(
|
||
{
|
||
"key": "run-oneliner-action",
|
||
"label": "直接创建 Agent",
|
||
"kind": "api_action",
|
||
"executor_key": "create-assistant",
|
||
"platform": plan.get("platform", ""),
|
||
"payload": {
|
||
"name": plan.get("platform_label")
|
||
and f"{plan.get('platform_label')} 执行 Agent"
|
||
or "项目执行 Agent",
|
||
},
|
||
}
|
||
)
|
||
latest_job = _latest_project_job(account, project_id=project_id or "")
|
||
if latest_job:
|
||
secondary_actions.append(
|
||
{
|
||
"key": "run-oneliner-action",
|
||
"label": "生成复盘草稿",
|
||
"kind": "api_action",
|
||
"executor_key": "review-draft",
|
||
"platform": plan.get("platform", ""),
|
||
}
|
||
)
|
||
derivable_job = _latest_derivable_job(account, project_id=project_id or "", exclude_line_types={"ai_video", "real_cut"})
|
||
if derivable_job and plan.get("intent_key") in {"ai_video", "real_cut", "review", "custom"}:
|
||
secondary_actions.append(
|
||
{
|
||
"key": "run-oneliner-action",
|
||
"label": "直接创建 AI 视频",
|
||
"kind": "api_action",
|
||
"executor_key": "create-ai-video",
|
||
"platform": plan.get("platform", ""),
|
||
"payload": {
|
||
"source_job_id": derivable_job["id"],
|
||
},
|
||
}
|
||
)
|
||
secondary_actions.append(
|
||
{
|
||
"key": "run-oneliner-action",
|
||
"label": "直接创建实拍剪辑",
|
||
"kind": "api_action",
|
||
"executor_key": "create-real-cut",
|
||
"platform": plan.get("platform", ""),
|
||
"payload": {
|
||
"source_job_id": derivable_job["id"],
|
||
},
|
||
}
|
||
)
|
||
if account.get("role") == "super_admin":
|
||
secondary_actions.append(
|
||
{
|
||
"key": "run-oneliner-action",
|
||
"label": "重新扫描故障",
|
||
"kind": "api_action",
|
||
"executor_key": "scan-admin-ops",
|
||
"platform": "",
|
||
}
|
||
)
|
||
decorated_primary_action = _decorate_oneliner_action(
|
||
account,
|
||
project_id=project_id or "",
|
||
action=primary_action or {},
|
||
) if primary_action else {}
|
||
secondary_actions = [
|
||
_decorate_oneliner_action(account, project_id=project_id or "", action=item)
|
||
for item in secondary_actions
|
||
]
|
||
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_assistant.get("name") or context_assistant.get("name") or "",
|
||
"readiness_label": platform_agent.get("readiness_label") or "",
|
||
"readiness_score": platform_agent.get("readiness_score") or 0,
|
||
"primary_action": decorated_primary_action,
|
||
"blocked_reason": blocked_reason,
|
||
"active_admin_override_notice": active_admin_override_notice,
|
||
"oneliner_profile_version": {
|
||
"version_id": oneliner_profile_version.get("id", ""),
|
||
"version_no": oneliner_profile_version.get("version_no", 0),
|
||
"title": oneliner_profile_version.get("title", ""),
|
||
"summary": oneliner_profile_version.get("summary", ""),
|
||
},
|
||
"platform_agent_profile": {
|
||
"platform": platform_agent.get("platform") or plan.get("platform", ""),
|
||
"platform_label": platform_agent.get("platform_label") or plan.get("platform_label", ""),
|
||
"name": platform_agent.get("name", ""),
|
||
"assistant_name": platform_agent_assistant.get("name") or context_assistant.get("name") or "",
|
||
"version_id": ((platform_agent.get("current_version") or {}).get("id") or ""),
|
||
"version_no": ((platform_agent.get("current_version") or {}).get("version_no") or 0),
|
||
"readiness_label": platform_agent.get("readiness_label") or "",
|
||
"readiness_score": platform_agent.get("readiness_score") or 0,
|
||
},
|
||
"evidence": evidence,
|
||
"next_steps": next_steps,
|
||
"secondary_actions": secondary_actions,
|
||
},
|
||
"safe_boundary": {
|
||
"core_code_locked": True,
|
||
"tenant_isolation": True,
|
||
"ops_admin_only": True,
|
||
},
|
||
}
|
||
|
||
def _insert_message(session_id: str, account_id: str, role: str, content: str, plan: dict[str, Any], result: dict[str, Any]) -> dict[str, Any]:
|
||
message_id = make_id("oline_msg")
|
||
created_at = now()
|
||
legacy.db.execute(
|
||
"""
|
||
INSERT INTO oneliner_messages (id, session_id, user_id, role, content, plan_json, result_json, created_at)
|
||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||
""",
|
||
(message_id, session_id, account_id, role, content, _dump(plan), _dump(result), created_at),
|
||
)
|
||
return legacy.db.fetch_one("SELECT * FROM oneliner_messages WHERE id = ?", (message_id,))
|
||
|
||
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),
|
||
)
|
||
timestamp = now()
|
||
resolved_assistant_id = (assistant or {}).get("id", "")
|
||
if existing:
|
||
update_sql = """
|
||
UPDATE platform_agent_profiles
|
||
SET assistant_id = ?, name = ?, mission = ?, notes = ?, status = ?, config_json = ?, updated_at = ?
|
||
WHERE id = ?
|
||
"""
|
||
update_params = (
|
||
resolved_assistant_id,
|
||
request.name.strip() or existing.get("name") or f"{legacy.platform_label(platform)} Agent",
|
||
request.mission.strip(),
|
||
request.notes.strip(),
|
||
request.status.strip() or "active",
|
||
_dump(request.config),
|
||
timestamp,
|
||
existing["id"],
|
||
)
|
||
if resolved_assistant_id:
|
||
legacy.db.execute(update_sql, update_params)
|
||
else:
|
||
with legacy.db.session() as conn:
|
||
conn.execute("PRAGMA foreign_keys=OFF")
|
||
conn.execute(update_sql, update_params)
|
||
conn.execute("PRAGMA foreign_keys=ON")
|
||
row = legacy.db.fetch_one("SELECT * FROM platform_agent_profiles WHERE id = ?", (existing["id"],))
|
||
else:
|
||
profile_id = make_id("plat_agent")
|
||
insert_sql = """
|
||
INSERT INTO platform_agent_profiles (
|
||
id, user_id, project_id, platform, assistant_id, name, mission, notes, status, config_json, created_at, updated_at
|
||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||
"""
|
||
insert_params = (
|
||
profile_id,
|
||
account["id"],
|
||
project["id"],
|
||
platform,
|
||
resolved_assistant_id,
|
||
request.name.strip() or f"{legacy.platform_label(platform)} Agent",
|
||
request.mission.strip(),
|
||
request.notes.strip(),
|
||
request.status.strip() or "active",
|
||
_dump(request.config),
|
||
timestamp,
|
||
timestamp,
|
||
)
|
||
if resolved_assistant_id:
|
||
legacy.db.execute(insert_sql, insert_params)
|
||
else:
|
||
with legacy.db.session() as conn:
|
||
conn.execute("PRAGMA foreign_keys=OFF")
|
||
conn.execute(insert_sql, insert_params)
|
||
conn.execute("PRAGMA foreign_keys=ON")
|
||
row = legacy.db.fetch_one("SELECT * FROM platform_agent_profiles WHERE id = ?", (profile_id,))
|
||
assert row is not None
|
||
version = _create_platform_agent_profile_version(
|
||
row,
|
||
actor_user_id=account["id"],
|
||
source_type="user_update",
|
||
reason=request.reason.strip() or f"更新 {legacy.platform_label(platform)} Agent 配置",
|
||
)
|
||
_log_platform_agent_profile_audit(
|
||
profile_id=row["id"],
|
||
version_id=version["id"],
|
||
actor_user_id=account["id"],
|
||
action_key="update-platform-agent-profile",
|
||
summary=f"已更新 {legacy.platform_label(platform)} Agent 配置",
|
||
details={"project_id": project["id"], "platform": platform, "assistant_id": row.get("assistant_id", "")},
|
||
)
|
||
return _platform_agent_payload(account, row, platform=platform, project_id=project["id"])
|
||
|
||
def _upsert_memory(
|
||
account: dict[str, Any],
|
||
*,
|
||
agent_scope: str,
|
||
platform: str,
|
||
request: AgentMemoryUpsertRequest,
|
||
) -> dict[str, Any]:
|
||
project = _resolve_project(account, request.project_id or None)
|
||
subject_type = request.subject_type.strip() or "project"
|
||
subject_id = request.subject_id.strip() or (project["id"] if subject_type == "project" else account["id"])
|
||
existing = legacy.db.fetch_one(
|
||
"""
|
||
SELECT * FROM agent_memories
|
||
WHERE user_id = ? AND project_id = ? AND agent_scope = ? AND platform = ?
|
||
AND subject_type = ? AND subject_id = ? AND memory_key = ?
|
||
""",
|
||
(account["id"], project["id"], agent_scope, platform, subject_type, subject_id, request.memory_key.strip()),
|
||
)
|
||
timestamp = now()
|
||
if existing:
|
||
legacy.db.execute(
|
||
"""
|
||
UPDATE agent_memories
|
||
SET title = ?, summary = ?, details_json = ?, confidence = ?, last_validated_at = ?, updated_at = ?
|
||
WHERE id = ?
|
||
""",
|
||
(
|
||
request.title.strip(),
|
||
request.summary.strip(),
|
||
_dump(request.details),
|
||
request.confidence,
|
||
timestamp,
|
||
timestamp,
|
||
existing["id"],
|
||
),
|
||
)
|
||
row = legacy.db.fetch_one("SELECT * FROM agent_memories WHERE id = ?", (existing["id"],))
|
||
else:
|
||
memory_id = make_id("mem")
|
||
legacy.db.execute(
|
||
"""
|
||
INSERT INTO agent_memories (
|
||
id, user_id, project_id, agent_scope, platform, subject_type, subject_id,
|
||
memory_key, title, summary, details_json, confidence, last_validated_at, created_at, updated_at
|
||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||
""",
|
||
(
|
||
memory_id,
|
||
account["id"],
|
||
project["id"],
|
||
agent_scope,
|
||
platform,
|
||
subject_type,
|
||
subject_id,
|
||
request.memory_key.strip(),
|
||
request.title.strip(),
|
||
request.summary.strip(),
|
||
_dump(request.details),
|
||
request.confidence,
|
||
timestamp,
|
||
timestamp,
|
||
timestamp,
|
||
),
|
||
)
|
||
row = legacy.db.fetch_one("SELECT * FROM agent_memories WHERE id = ?", (memory_id,))
|
||
return _memory_payload(row)
|
||
|
||
def _upsert_skill(
|
||
account: dict[str, Any],
|
||
*,
|
||
agent_scope: str,
|
||
platform: str,
|
||
request: AgentSkillUpsertRequest,
|
||
skill_id: str | None = None,
|
||
) -> dict[str, Any]:
|
||
project = _resolve_project(account, request.project_id or None)
|
||
existing = None
|
||
if skill_id:
|
||
existing = legacy.db.fetch_one(
|
||
"SELECT * FROM agent_skills WHERE id = ? AND user_id = ?",
|
||
(skill_id, account["id"]),
|
||
)
|
||
if not existing:
|
||
raise HTTPException(status_code=404, detail="Agent skill not found")
|
||
else:
|
||
existing = legacy.db.fetch_one(
|
||
"""
|
||
SELECT * FROM agent_skills
|
||
WHERE user_id = ? AND project_id = ? AND agent_scope = ? AND platform = ? AND skill_key = ?
|
||
""",
|
||
(account["id"], project["id"], agent_scope, platform, request.skill_key.strip()),
|
||
)
|
||
timestamp = now()
|
||
if existing:
|
||
legacy.db.execute(
|
||
"""
|
||
UPDATE agent_skills
|
||
SET name = ?, status = ?, method_json = ?, test_spec_json = ?, last_result_json = ?,
|
||
success_count = ?, failure_count = ?, last_score = ?, last_validated_at = ?, updated_at = ?
|
||
WHERE id = ?
|
||
""",
|
||
(
|
||
request.name.strip(),
|
||
request.status.strip() or "draft",
|
||
_dump(request.method),
|
||
_dump(request.test_spec),
|
||
_dump(request.last_result),
|
||
request.success_count,
|
||
request.failure_count,
|
||
request.last_score,
|
||
timestamp,
|
||
timestamp,
|
||
existing["id"],
|
||
),
|
||
)
|
||
row = legacy.db.fetch_one("SELECT * FROM agent_skills WHERE id = ?", (existing["id"],))
|
||
_snapshot_skill_version(
|
||
row,
|
||
actor_user_id=account["id"],
|
||
reason="updated",
|
||
metadata={"via": "upsert", "accepted": row.get("status") == "validated"},
|
||
)
|
||
else:
|
||
new_id = make_id("skill")
|
||
legacy.db.execute(
|
||
"""
|
||
INSERT INTO agent_skills (
|
||
id, user_id, project_id, agent_scope, platform, parent_skill_id, skill_key, name, status,
|
||
method_json, test_spec_json, last_result_json, success_count, failure_count, last_score,
|
||
last_validated_at, created_at, updated_at
|
||
) VALUES (?, ?, ?, ?, ?, '', ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||
""",
|
||
(
|
||
new_id,
|
||
account["id"],
|
||
project["id"],
|
||
agent_scope,
|
||
platform,
|
||
request.skill_key.strip(),
|
||
request.name.strip(),
|
||
request.status.strip() or "draft",
|
||
_dump(request.method),
|
||
_dump(request.test_spec),
|
||
_dump(request.last_result),
|
||
request.success_count,
|
||
request.failure_count,
|
||
request.last_score,
|
||
timestamp,
|
||
timestamp,
|
||
timestamp,
|
||
),
|
||
)
|
||
row = legacy.db.fetch_one("SELECT * FROM agent_skills WHERE id = ?", (new_id,))
|
||
_snapshot_skill_version(
|
||
row,
|
||
actor_user_id=account["id"],
|
||
reason="created",
|
||
metadata={"via": "upsert"},
|
||
)
|
||
return _skill_payload(row)
|
||
|
||
def _create_or_update_incident(
|
||
*,
|
||
tenant_user_id: str,
|
||
tenant_project_id: str,
|
||
source_type: str,
|
||
source_id: str,
|
||
severity: str,
|
||
title: str,
|
||
summary: str,
|
||
payload: dict[str, Any],
|
||
) -> dict[str, Any]:
|
||
existing = legacy.db.fetch_one(
|
||
"SELECT * FROM admin_ops_incidents WHERE source_type = ? AND source_id = ? AND title = ?",
|
||
(source_type, source_id, title),
|
||
)
|
||
timestamp = now()
|
||
disable_fk = not str(tenant_user_id or "").strip() or not str(tenant_project_id or "").strip()
|
||
if existing:
|
||
sql = """
|
||
UPDATE admin_ops_incidents
|
||
SET tenant_user_id = ?, tenant_project_id = ?, severity = ?, summary = ?, payload_json = ?, updated_at = ?
|
||
WHERE id = ?
|
||
"""
|
||
params = (
|
||
tenant_user_id,
|
||
tenant_project_id,
|
||
severity,
|
||
summary,
|
||
_dump(payload),
|
||
timestamp,
|
||
existing["id"],
|
||
)
|
||
if disable_fk:
|
||
with legacy.db.session() as conn:
|
||
conn.execute("PRAGMA foreign_keys=OFF")
|
||
conn.execute(sql, params)
|
||
conn.execute("PRAGMA foreign_keys=ON")
|
||
else:
|
||
legacy.db.execute(sql, params)
|
||
row = legacy.db.fetch_one("SELECT * FROM admin_ops_incidents WHERE id = ?", (existing["id"],))
|
||
else:
|
||
incident_id = make_id("incident")
|
||
sql = """
|
||
INSERT INTO admin_ops_incidents (
|
||
id, tenant_user_id, tenant_project_id, source_type, source_id, severity, title,
|
||
summary, payload_json, status, assigned_to, reviewed_by, review_notes, created_at, updated_at
|
||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'open', '', '', '', ?, ?)
|
||
"""
|
||
params = (
|
||
incident_id,
|
||
tenant_user_id,
|
||
tenant_project_id,
|
||
source_type,
|
||
source_id,
|
||
severity,
|
||
title,
|
||
summary,
|
||
_dump(payload),
|
||
timestamp,
|
||
timestamp,
|
||
)
|
||
with legacy.db.session() as conn:
|
||
conn.execute("PRAGMA foreign_keys=OFF")
|
||
conn.execute(sql, params)
|
||
conn.execute("PRAGMA foreign_keys=ON")
|
||
row = legacy.db.fetch_one("SELECT * FROM admin_ops_incidents WHERE id = ?", (incident_id,))
|
||
return _incident_payload(row)
|
||
|
||
def _scan_admin_incidents(admin: dict[str, Any]) -> dict[str, Any]:
|
||
_ = admin
|
||
created: list[dict[str, Any]] = []
|
||
failed_jobs = legacy.db.fetch_all(
|
||
"SELECT * FROM jobs WHERE status = 'failed' ORDER BY updated_at DESC LIMIT 20"
|
||
)
|
||
for job in failed_jobs:
|
||
created.append(
|
||
_create_or_update_incident(
|
||
tenant_user_id=job.get("user_id", "") or "",
|
||
tenant_project_id=job.get("project_id", "") or "",
|
||
source_type="job",
|
||
source_id=job["id"],
|
||
severity="error",
|
||
title=f"失败任务:{job.get('title') or job['id']}",
|
||
summary=job.get("error", "")[:280] or "任务失败,需要检查执行链。",
|
||
payload=legacy.job_payload(job),
|
||
)
|
||
)
|
||
try:
|
||
integration_health = legacy.integrations_health(admin)
|
||
except Exception as exc:
|
||
integration_health = {"collector": {"reachable": False, "error": str(exc)}}
|
||
for key, payload in integration_health.items():
|
||
reachable = bool(payload.get("reachable", False))
|
||
if reachable and key != "cutvideo":
|
||
continue
|
||
if key == "cutvideo" and payload.get("supports_uploads", True):
|
||
continue
|
||
created.append(
|
||
_create_or_update_incident(
|
||
tenant_user_id="",
|
||
tenant_project_id="",
|
||
source_type="integration",
|
||
source_id=key,
|
||
severity="warn" if key in {"cutvideo", "live_recorder"} else "error",
|
||
title=f"集成异常:{key}",
|
||
summary=str(payload.get("error") or payload.get("upload_error") or "集成健康检查未通过")[:280],
|
||
payload=payload,
|
||
)
|
||
)
|
||
return {
|
||
"created_or_updated": created,
|
||
"count": len(created),
|
||
"audit": _log_admin_audit_event(
|
||
actor_user_id=admin["id"],
|
||
action_key="scan",
|
||
status="completed",
|
||
summary=f"本轮扫描归集 {len(created)} 条事件。",
|
||
details={"count": len(created)},
|
||
),
|
||
}
|
||
|
||
def _build_incident_repair_plan(
|
||
incident: dict[str, Any],
|
||
*,
|
||
scope: str,
|
||
notes: str,
|
||
) -> dict[str, Any]:
|
||
payload = incident.get("payload") or {}
|
||
source_type = incident.get("source_type") or ""
|
||
severity = incident.get("severity") or "warn"
|
||
if source_type == "integration":
|
||
target = incident.get("source_id") or "integration"
|
||
steps = [
|
||
f"读取 {target} 当前健康状态和最近错误详情。",
|
||
f"对 {target} 执行最小可行 smoke,确认是网络、配置还是服务端版本问题。",
|
||
"若为外部依赖异常,则先生成修复建议而不是直接改核心代码。",
|
||
]
|
||
verification = [
|
||
"health 接口恢复可达",
|
||
"关键能力 smoke 返回 200",
|
||
"相关任务链不再新增 failed",
|
||
]
|
||
elif source_type == "job":
|
||
target = payload.get("title") or incident.get("source_id") or "job"
|
||
steps = [
|
||
f"读取失败任务 {target} 的 error/result/artifacts。",
|
||
"定位失败在哪个集成或哪段编排上。",
|
||
"生成补救建议或重试路径,并明确是否需要人工确认。",
|
||
]
|
||
verification = [
|
||
"同类任务能再次跑通",
|
||
"错误不再重复出现",
|
||
"租户数据和存储路径未被污染",
|
||
]
|
||
else:
|
||
steps = [
|
||
"读取当前事件上下文和最近变更。",
|
||
"先给出低风险修复建议,再决定是否进入人工处理。",
|
||
]
|
||
verification = ["事件状态可被复核", "没有破坏多租户隔离"]
|
||
return {
|
||
"summary": f"针对 {incident.get('title') or incident.get('id')} 生成一版{scope}级修复计划。",
|
||
"severity": severity,
|
||
"scope": scope,
|
||
"source_type": source_type,
|
||
"steps": steps,
|
||
"verification": verification,
|
||
"safe_boundary": {
|
||
"core_code_locked": True,
|
||
"tenant_isolation_required": True,
|
||
"audit_required": True,
|
||
},
|
||
"notes": notes.strip(),
|
||
}
|
||
|
||
def _create_fix_run(
|
||
admin: dict[str, Any],
|
||
*,
|
||
incident: dict[str, Any],
|
||
scope: str,
|
||
notes: str,
|
||
) -> dict[str, Any]:
|
||
plan = _build_incident_repair_plan(incident, scope=scope, notes=notes)
|
||
run_id = make_id("fix_run")
|
||
timestamp = now()
|
||
disable_fk = not str(incident.get("tenant_user_id") or "").strip() or not str(incident.get("tenant_project_id") or "").strip()
|
||
sql = """
|
||
INSERT INTO admin_ops_fix_runs (
|
||
id, incident_id, actor_user_id, tenant_user_id, tenant_project_id, plan_scope, status,
|
||
audit_status, review_notes, plan_json, verification_json, created_at, updated_at
|
||
) VALUES (?, ?, ?, ?, ?, ?, 'planned', 'pending', '', ?, ?, ?, ?)
|
||
"""
|
||
params = (
|
||
run_id,
|
||
incident["id"],
|
||
admin["id"],
|
||
incident.get("tenant_user_id", ""),
|
||
incident.get("tenant_project_id", ""),
|
||
scope,
|
||
_dump(plan),
|
||
_dump({"checks": plan.get("verification", [])}),
|
||
timestamp,
|
||
timestamp,
|
||
)
|
||
with legacy.db.session() as conn:
|
||
if disable_fk:
|
||
conn.execute("PRAGMA foreign_keys=OFF")
|
||
conn.execute(sql, params)
|
||
if disable_fk:
|
||
conn.execute("PRAGMA foreign_keys=ON")
|
||
row = legacy.db.fetch_one("SELECT * FROM admin_ops_fix_runs WHERE id = ?", (run_id,))
|
||
return _fix_run_payload(row)
|
||
|
||
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")
|
||
]
|
||
recent_audits = [
|
||
_admin_audit_payload(row)
|
||
for row in legacy.db.fetch_all(
|
||
"SELECT * FROM admin_ops_audit_logs ORDER BY created_at DESC LIMIT 20"
|
||
)
|
||
]
|
||
recent_fix_runs = [
|
||
_fix_run_payload(row)
|
||
for row in legacy.db.fetch_all(
|
||
"SELECT * FROM admin_ops_fix_runs ORDER BY updated_at DESC 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),
|
||
"recent_audits": recent_audits,
|
||
"audit_count": len(recent_audits),
|
||
"recent_fix_runs": recent_fix_runs,
|
||
"fix_run_count": len(recent_fix_runs),
|
||
"pending_fix_run_count": len([item for item in recent_fix_runs if item.get("audit_status") == "pending"]),
|
||
"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,))
|
||
version = _snapshot_skill_version(
|
||
updated,
|
||
actor_user_id=account["id"],
|
||
reason="validated" if accepted else "needs_revision",
|
||
metadata={"review_notes": request.review_notes.strip(), "score": request.score},
|
||
)
|
||
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)
|
||
payload["version"] = version
|
||
if feedback_memory:
|
||
payload["feedback_memory"] = feedback_memory
|
||
return payload
|
||
|
||
def _list_skill_versions(
|
||
account: dict[str, Any],
|
||
*,
|
||
platform: str,
|
||
project_id: str,
|
||
skill_id: str,
|
||
) -> list[dict[str, Any]]:
|
||
skill_row = 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, platform),
|
||
)
|
||
if not skill_row:
|
||
raise HTTPException(status_code=404, detail="Platform skill not found")
|
||
rows = legacy.db.fetch_all(
|
||
"SELECT * FROM agent_skill_versions WHERE skill_id = ? ORDER BY version_no DESC, created_at DESC",
|
||
(skill_id,),
|
||
)
|
||
return [_skill_version_payload(row) for row in rows]
|
||
|
||
def _rollback_platform_skill(
|
||
account: dict[str, Any],
|
||
*,
|
||
platform: str,
|
||
skill_id: str,
|
||
request: PlatformSkillRollbackRequest,
|
||
) -> dict[str, Any]:
|
||
project = _resolve_project(account, request.project_id or None)
|
||
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"], platform),
|
||
)
|
||
if not current:
|
||
raise HTTPException(status_code=404, detail="Platform skill not found")
|
||
target_version = legacy.db.fetch_one(
|
||
"""
|
||
SELECT * FROM agent_skill_versions
|
||
WHERE id = ? AND skill_id = ?
|
||
""",
|
||
(request.version_id.strip(), skill_id),
|
||
)
|
||
if not target_version:
|
||
raise HTTPException(status_code=404, detail="Skill version not found")
|
||
snapshot = _parse_json(target_version.get("snapshot_json"), {})
|
||
skill_snapshot = (snapshot.get("skill") or {})
|
||
timestamp = now()
|
||
legacy.db.execute(
|
||
"""
|
||
UPDATE agent_skills
|
||
SET name = ?, status = ?, method_json = ?, test_spec_json = ?, last_result_json = ?,
|
||
success_count = ?, failure_count = ?, last_score = ?, last_validated_at = ?, updated_at = ?
|
||
WHERE id = ?
|
||
""",
|
||
(
|
||
skill_snapshot.get("name") or current.get("name") or current.get("skill_key") or "平台技能",
|
||
skill_snapshot.get("status") or "draft",
|
||
_dump(skill_snapshot.get("method") or {}),
|
||
_dump(skill_snapshot.get("test_spec") or {}),
|
||
_dump(skill_snapshot.get("last_result") or {}),
|
||
int(skill_snapshot.get("success_count") or 0),
|
||
int(skill_snapshot.get("failure_count") or 0),
|
||
float(skill_snapshot.get("last_score") or 0),
|
||
skill_snapshot.get("last_validated_at") or timestamp,
|
||
timestamp,
|
||
skill_id,
|
||
),
|
||
)
|
||
updated = legacy.db.fetch_one("SELECT * FROM agent_skills WHERE id = ?", (skill_id,))
|
||
rollback_version = _snapshot_skill_version(
|
||
updated,
|
||
actor_user_id=account["id"],
|
||
reason="rollback",
|
||
metadata={"from_version_id": request.version_id.strip()},
|
||
)
|
||
payload = _skill_payload(updated)
|
||
payload["rollback_from_version"] = _skill_version_payload(target_version)
|
||
payload["version"] = rollback_version
|
||
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")
|
||
action_definition = _get_action_definition(account, project_id=project["id"], action_key=action_key)
|
||
if not action_definition:
|
||
raise HTTPException(status_code=404, detail=f"Action definition not found: {action_key}")
|
||
if action_definition.get("status") != "enabled":
|
||
raise HTTPException(status_code=403, detail=f"Action disabled for current tenant: {action_key}")
|
||
if action_definition.get("admin_only") and account.get("role") != "super_admin":
|
||
raise HTTPException(status_code=403, detail="Current action is only available to platform administrators")
|
||
if action_definition.get("requires_platform") and not normalized_platform:
|
||
raise HTTPException(status_code=400, detail="Platform is required for this action")
|
||
handler_key = action_definition.get("handler_key") or action_key
|
||
usage_category = ACTION_USAGE_KEYS.get(handler_key, "")
|
||
if usage_category:
|
||
_enforce_tenant_quota(account, project_id=project["id"], usage_category=usage_category)
|
||
latest_user_message = _last_user_message_text(request.session_id, account["id"]) if request.session_id else ""
|
||
requested_payload = request.payload or {}
|
||
|
||
def _recommended_action(
|
||
action: str,
|
||
*,
|
||
label: str,
|
||
summary: str,
|
||
screen: str = "",
|
||
**extra: Any,
|
||
) -> dict[str, Any]:
|
||
payload = {
|
||
"action": action,
|
||
"label": label,
|
||
"summary": summary,
|
||
"screen": screen,
|
||
}
|
||
for key, value in extra.items():
|
||
if value is None:
|
||
continue
|
||
text = str(value).strip() if isinstance(value, str) else value
|
||
if text == "":
|
||
continue
|
||
payload[key] = value
|
||
return payload
|
||
|
||
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,
|
||
"recommended_action": _recommended_action(
|
||
"open-platform-agent-detail",
|
||
label="查看平台 Agent",
|
||
summary=f"继续查看 {payload['platform_label']} Agent 当前状态、自检建议和最近执行。",
|
||
screen="playbook",
|
||
platform=normalized_platform,
|
||
),
|
||
}
|
||
|
||
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,
|
||
"recommended_action": _recommended_action(
|
||
"goto-automation",
|
||
label="去自动流程",
|
||
summary="继续查看存储、依赖和自动流程健康状态。",
|
||
screen="automation",
|
||
),
|
||
}
|
||
|
||
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,
|
||
"recommended_action": _recommended_action(
|
||
"open-live-recorder",
|
||
label="打开录制维护",
|
||
summary="继续查看录制源、文件和 NAS 录制状态。",
|
||
screen="production",
|
||
),
|
||
}
|
||
|
||
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,
|
||
"recommended_action": _recommended_action(
|
||
"goto-automation",
|
||
label="去自动流程",
|
||
summary="继续检查运维扫描结果、依赖健康和自动流程状态。",
|
||
screen="automation",
|
||
),
|
||
}
|
||
|
||
async def _run_generate_copy() -> dict[str, Any]:
|
||
requested_assistant_id = str(
|
||
requested_payload.get("assistant_id")
|
||
or requested_payload.get("assistantId")
|
||
or ""
|
||
).strip()
|
||
assistant = (
|
||
_resolve_assistant(account, requested_assistant_id, project["id"])
|
||
if requested_assistant_id
|
||
else _resolve_execution_assistant(account, project_id=project["id"], platform=normalized_platform)
|
||
)
|
||
if not assistant:
|
||
raise HTTPException(status_code=404, detail="No execution assistant available")
|
||
brief = str((request.payload or {}).get("brief") or latest_user_message or "").strip()
|
||
if not brief:
|
||
brief = f"请基于当前项目目标,输出一版适合{legacy.platform_label(normalized_platform or 'douyin')}发布的短视频文案。"
|
||
payload = await legacy.generate_copy(
|
||
assistant["id"],
|
||
legacy.GenerateCopyRequest(
|
||
brief=brief,
|
||
platform=normalized_platform or "douyin",
|
||
audience=str((request.payload or {}).get("audience") or "创业者"),
|
||
extra_requirements=str((request.payload or {}).get("extra_requirements") or ""),
|
||
knowledge_base_ids=list((request.payload or {}).get("knowledge_base_ids") or []),
|
||
),
|
||
account,
|
||
)
|
||
return {
|
||
"title": "OneLiner 已生成文案",
|
||
"summary": f"已用 {assistant.get('name') or '默认 Agent'} 生成一版可发布文案。",
|
||
"payload": payload,
|
||
"recommended_action": _recommended_action(
|
||
"open-generated-copy-results",
|
||
label="查看最近生成",
|
||
summary="直接回到最近生成结果区,继续查看、复制和迭代这版文案。",
|
||
screen="playbook",
|
||
platform=normalized_platform or "douyin",
|
||
),
|
||
}
|
||
|
||
async def _run_review_draft() -> dict[str, Any]:
|
||
requested_job_id = str(
|
||
requested_payload.get("source_job_id")
|
||
or requested_payload.get("sourceJobId")
|
||
or requested_payload.get("job_id")
|
||
or requested_payload.get("jobId")
|
||
or ""
|
||
).strip()
|
||
latest_job = _load_owned_job(account, requested_job_id) if requested_job_id else None
|
||
if latest_job and str(latest_job.get("project_id") or "") != str(project["id"]):
|
||
latest_job = None
|
||
if not latest_job:
|
||
latest_job = _latest_project_job(account, project_id=project["id"])
|
||
if not latest_job:
|
||
raise HTTPException(status_code=404, detail="No completed job available for review draft")
|
||
existing = legacy.db.fetch_one(
|
||
"SELECT * FROM publish_reviews WHERE user_id = ? AND source_job_id = ? ORDER BY created_at DESC LIMIT 1",
|
||
(account["id"], latest_job["id"]),
|
||
)
|
||
if existing:
|
||
payload = legacy.review_payload(existing)
|
||
return {
|
||
"title": "OneLiner 找到已有复盘",
|
||
"summary": f"任务「{latest_job.get('title') or latest_job['id']}」已经有复盘记录。",
|
||
"payload": payload,
|
||
"recommended_action": _recommended_action(
|
||
"open-review-edit",
|
||
label="打开复盘",
|
||
summary="继续完善这条复盘记录的 verdict、亮点和下一步。",
|
||
screen="review",
|
||
review_id=payload.get("id", ""),
|
||
job_id=payload.get("source_job_id", ""),
|
||
),
|
||
}
|
||
assistant = _resolve_execution_assistant(account, project_id=project["id"], platform=normalized_platform)
|
||
result = latest_job.get("result_json") or "{}"
|
||
try:
|
||
result_map = json.loads(result)
|
||
except json.JSONDecodeError:
|
||
result_map = {}
|
||
payload = legacy.create_review(
|
||
legacy.ReviewCreateRequest(
|
||
project_id=project["id"],
|
||
source_job_id=latest_job["id"],
|
||
assistant_id=(assistant or {}).get("id", ""),
|
||
title=f"{latest_job.get('title') or '任务'} 复盘草稿",
|
||
platform=normalized_platform or "douyin",
|
||
content_type="video",
|
||
verdict="待补充",
|
||
highlights=str(result_map.get("summary") or result_map.get("headline_summary") or "")[:400],
|
||
next_actions="补充发布结果、完善指标、确认下一步动作。",
|
||
notes=str((request.payload or {}).get("notes") or "由 OneLiner 自动生成复盘草稿。"),
|
||
),
|
||
account,
|
||
)
|
||
return {
|
||
"title": "OneLiner 已生成复盘草稿",
|
||
"summary": f"已基于最近完成任务「{latest_job.get('title') or latest_job['id']}」生成复盘草稿。",
|
||
"payload": payload,
|
||
"recommended_action": _recommended_action(
|
||
"open-review-edit",
|
||
label="打开复盘",
|
||
summary="继续完善这条复盘草稿,并确认 verdict 和下一步动作。",
|
||
screen="review",
|
||
review_id=payload.get("id", ""),
|
||
job_id=payload.get("source_job_id", ""),
|
||
),
|
||
}
|
||
|
||
async def _run_import_homepage() -> dict[str, Any]:
|
||
source_url = str(
|
||
requested_payload.get("source_url")
|
||
or requested_payload.get("sourceUrl")
|
||
or _extract_first_url(latest_user_message)
|
||
or ""
|
||
).strip()
|
||
if not source_url:
|
||
raise HTTPException(status_code=400, detail="No homepage URL available for import")
|
||
inferred_platform = normalize_platform_from_text(
|
||
requested_payload.get("platform")
|
||
or requested_payload.get("platform_label")
|
||
or normalized_platform
|
||
or legacy.infer_platform_from_url(source_url)
|
||
) or _safe_platform(legacy.infer_platform_from_url(source_url), fallback="douyin")
|
||
requested_assistant_id = str(
|
||
requested_payload.get("assistant_id")
|
||
or requested_payload.get("assistantId")
|
||
or ""
|
||
).strip()
|
||
assistant = (
|
||
_resolve_assistant(account, requested_assistant_id, project["id"])
|
||
if requested_assistant_id
|
||
else _resolve_execution_assistant(account, project_id=project["id"], platform=inferred_platform)
|
||
)
|
||
existing_source = _find_creator_source_by_url(
|
||
account,
|
||
project_id=project["id"],
|
||
platform=inferred_platform,
|
||
source_url=source_url,
|
||
)
|
||
sync_job = await legacy.create_content_source_sync_job(
|
||
legacy.ContentSourceSyncRequest(
|
||
project_id=project["id"],
|
||
knowledge_base_id=str(requested_payload.get("knowledge_base_id") or requested_payload.get("knowledgeBaseId") or ""),
|
||
assistant_id=(assistant or {}).get("id", ""),
|
||
content_source_id=(existing_source or {}).get("id", ""),
|
||
platform=inferred_platform,
|
||
handle=str(requested_payload.get("handle") or ""),
|
||
source_url=source_url,
|
||
title=str(requested_payload.get("title") or requested_payload.get("name") or ""),
|
||
analysis_model_profile_id=str(requested_payload.get("analysis_model_profile_id") or requested_payload.get("analysisModelProfileId") or ""),
|
||
language=str(requested_payload.get("language") or "auto"),
|
||
max_items=max(1, min(int(requested_payload.get("max_items") or requested_payload.get("maxItems") or 5), 20)),
|
||
skip_existing=bool(requested_payload.get("skip_existing", requested_payload.get("skipExisting", True))),
|
||
auto_trigger_analysis=bool(requested_payload.get("auto_trigger_analysis", requested_payload.get("autoTriggerAnalysis", True))),
|
||
),
|
||
account,
|
||
)
|
||
return {
|
||
"title": "OneLiner 已导入主页",
|
||
"summary": f"已把主页接入当前项目,并触发 {legacy.platform_label(inferred_platform)} 内容源同步。",
|
||
"payload": {
|
||
"job": sync_job,
|
||
"platform": inferred_platform,
|
||
"source_url": source_url,
|
||
"existing_source_id": (existing_source or {}).get("id", ""),
|
||
},
|
||
"recommended_action": _recommended_action(
|
||
"open-job-detail",
|
||
label="看任务详情",
|
||
summary=f"继续查看 {legacy.platform_label(inferred_platform)} 主页导入任务的同步进度和后续分析结果。",
|
||
screen="production",
|
||
platform=inferred_platform,
|
||
job_id=sync_job.get("id", ""),
|
||
),
|
||
}
|
||
|
||
async def _run_import_video_link() -> dict[str, Any]:
|
||
video_url = str(
|
||
requested_payload.get("video_url")
|
||
or requested_payload.get("videoUrl")
|
||
or _extract_first_url(latest_user_message)
|
||
or ""
|
||
).strip()
|
||
if not video_url:
|
||
raise HTTPException(status_code=400, detail="No video URL available for import")
|
||
requested_assistant_id = str(
|
||
requested_payload.get("assistant_id")
|
||
or requested_payload.get("assistantId")
|
||
or ""
|
||
).strip()
|
||
job = await legacy.create_video_link_job(
|
||
legacy.ExploreVideoLinkRequest(
|
||
project_id=project["id"],
|
||
knowledge_base_id=str(
|
||
requested_payload.get("knowledge_base_id")
|
||
or requested_payload.get("knowledgeBaseId")
|
||
or ""
|
||
),
|
||
assistant_id=requested_assistant_id,
|
||
analysis_model_profile_id=str(
|
||
requested_payload.get("analysis_model_profile_id")
|
||
or requested_payload.get("analysisModelProfileId")
|
||
or ""
|
||
),
|
||
title=str(requested_payload.get("title") or "").strip(),
|
||
video_url=video_url,
|
||
language=str(requested_payload.get("language") or "auto"),
|
||
),
|
||
account,
|
||
)
|
||
return {
|
||
"title": "OneLiner 已导入作品链接",
|
||
"summary": "已把作品链接送入分析链,并创建可追踪的分析任务。",
|
||
"payload": {
|
||
"job": job,
|
||
"video_url": video_url,
|
||
},
|
||
"recommended_action": _recommended_action(
|
||
"open-job-detail",
|
||
label="看任务详情",
|
||
summary="继续查看这条作品分析任务的进度和结果。",
|
||
screen="production",
|
||
job_id=job.get("id", ""),
|
||
),
|
||
}
|
||
|
||
async def _run_analyze_account() -> dict[str, Any]:
|
||
if not normalized_platform:
|
||
raise HTTPException(status_code=400, detail="Platform is required for account analysis")
|
||
target_account = _resolve_platform_target_account(
|
||
account,
|
||
project_id=project["id"],
|
||
platform=normalized_platform,
|
||
requested_account_id=str(requested_payload.get("target_account_id") or requested_payload.get("targetAccountId") or ""),
|
||
)
|
||
if not target_account:
|
||
raise HTTPException(status_code=404, detail="No platform account available for analysis")
|
||
body = {
|
||
"model_profile_ids": list(requested_payload.get("model_profile_ids") or requested_payload.get("modelProfileIds") or []),
|
||
"linked_account_ids": list(requested_payload.get("linked_account_ids") or requested_payload.get("linkedAccountIds") or []),
|
||
"include_linked_accounts": bool(requested_payload.get("include_linked_accounts", requested_payload.get("includeLinkedAccounts", True))),
|
||
"include_recent_similar_candidates": bool(requested_payload.get("include_recent_similar_candidates", requested_payload.get("includeRecentSimilarCandidates", True))),
|
||
"max_videos": max(1, min(int(requested_payload.get("max_videos") or requested_payload.get("maxVideos") or 6), 20)),
|
||
"extra_focus": str(requested_payload.get("extra_focus") or requested_payload.get("extraFocus") or latest_user_message or ""),
|
||
"temperature": float(requested_payload.get("temperature") or 0.35),
|
||
}
|
||
if normalized_platform == "douyin":
|
||
body["auto_analyze_top_videos"] = bool(requested_payload.get("auto_analyze_top_videos", requested_payload.get("autoAnalyzeTopVideos", True)))
|
||
body["top_video_analysis_count"] = max(1, min(int(requested_payload.get("top_video_analysis_count") or requested_payload.get("topVideoAnalysisCount") or 6), 10))
|
||
else:
|
||
body["auto_analyze_top_videos"] = bool(requested_payload.get("auto_analyze_top_videos", requested_payload.get("autoAnalyzeTopVideos", False)))
|
||
body["top_video_analysis_count"] = max(1, min(int(requested_payload.get("top_video_analysis_count") or requested_payload.get("topVideoAnalysisCount") or 4), 10))
|
||
payload = await _call_local_api(
|
||
account,
|
||
method="POST",
|
||
path=f"/v2/{normalized_platform}/accounts/{target_account['id']}/analysis",
|
||
json_body=body,
|
||
)
|
||
return {
|
||
"title": "OneLiner 已分析账号",
|
||
"summary": f"已为 {legacy.platform_label(normalized_platform)} 账号生成分析结论和下一步建议。",
|
||
"payload": {
|
||
"platform": normalized_platform,
|
||
"account_id": target_account["id"],
|
||
"analysis": payload,
|
||
},
|
||
"recommended_action": _recommended_action(
|
||
"select-account",
|
||
label="打开当前对象",
|
||
summary=f"继续查看 {legacy.platform_label(normalized_platform)} 账号分析报告、快照和拆解结果。",
|
||
screen="discovery",
|
||
account_id=target_account["id"],
|
||
),
|
||
}
|
||
|
||
async def _run_track_account() -> dict[str, Any]:
|
||
if not normalized_platform:
|
||
raise HTTPException(status_code=400, detail="Platform is required for tracking")
|
||
target_account = _resolve_platform_target_account(
|
||
account,
|
||
project_id=project["id"],
|
||
platform=normalized_platform,
|
||
requested_account_id=str(requested_payload.get("target_account_id") or requested_payload.get("targetAccountId") or ""),
|
||
)
|
||
if not target_account:
|
||
raise HTTPException(status_code=404, detail="No platform account available for tracking")
|
||
requested_assistant_id = str(
|
||
requested_payload.get("assistant_id")
|
||
or requested_payload.get("assistantId")
|
||
or ""
|
||
).strip()
|
||
assistant = (
|
||
_resolve_assistant(account, requested_assistant_id, project["id"])
|
||
if requested_assistant_id
|
||
else _resolve_execution_assistant(account, project_id=project["id"], platform=normalized_platform)
|
||
)
|
||
tracked_payload = await _call_local_api(
|
||
account,
|
||
method="POST",
|
||
path=f"/v2/{normalized_platform}/tracking/accounts",
|
||
json_body={
|
||
"tracked_account_id": target_account["id"],
|
||
"assistant_id": (assistant or {}).get("id", ""),
|
||
"note": str(requested_payload.get("note") or "由 OneLiner 直接加入跟踪。"),
|
||
},
|
||
)
|
||
refreshed_payload = None
|
||
if bool(requested_payload.get("refresh_now", True)):
|
||
refreshed_payload = await _call_local_api(
|
||
account,
|
||
method="POST",
|
||
path=f"/v2/{normalized_platform}/tracking/accounts/{target_account['id']}/refresh",
|
||
json_body={},
|
||
)
|
||
sync_job_id = str((refreshed_payload or {}).get("sync_job_id") or "").strip()
|
||
recommended_action = (
|
||
_recommended_action(
|
||
"open-job-detail",
|
||
label="看任务详情",
|
||
summary="继续查看这条跟踪同步任务的执行进度。",
|
||
screen="production",
|
||
job_id=sync_job_id,
|
||
)
|
||
if sync_job_id
|
||
else _recommended_action(
|
||
"refresh-tracked-account",
|
||
label="继续同步当前账号",
|
||
summary="继续查看当前已跟踪账号的最新同步和日报。",
|
||
screen="tracking",
|
||
tracked_account_id=target_account["id"],
|
||
)
|
||
)
|
||
return {
|
||
"title": "OneLiner 已加入跟踪",
|
||
"summary": f"已把当前 {legacy.platform_label(normalized_platform)} 账号加入跟踪,并触发一次同步。",
|
||
"payload": {
|
||
"platform": normalized_platform,
|
||
"account_id": target_account["id"],
|
||
"tracking": tracked_payload,
|
||
"refresh": refreshed_payload or {},
|
||
},
|
||
"recommended_action": recommended_action,
|
||
}
|
||
|
||
async def _run_import_text() -> dict[str, Any]:
|
||
title = str(requested_payload.get("title") or "").strip()
|
||
content = str(requested_payload.get("content") or "").strip()
|
||
if not title:
|
||
raise HTTPException(status_code=400, detail="No text title available for import")
|
||
if not content:
|
||
raise HTTPException(status_code=400, detail="No text content available for import")
|
||
requested_assistant_id = str(
|
||
requested_payload.get("assistant_id")
|
||
or requested_payload.get("assistantId")
|
||
or ""
|
||
).strip()
|
||
job = await legacy.create_text_job(
|
||
legacy.ExploreTextRequest(
|
||
project_id=project["id"],
|
||
knowledge_base_id=str(
|
||
requested_payload.get("knowledge_base_id")
|
||
or requested_payload.get("knowledgeBaseId")
|
||
or ""
|
||
),
|
||
assistant_id=requested_assistant_id,
|
||
analysis_model_profile_id=str(
|
||
requested_payload.get("analysis_model_profile_id")
|
||
or requested_payload.get("analysisModelProfileId")
|
||
or ""
|
||
),
|
||
title=title,
|
||
content=content,
|
||
),
|
||
account,
|
||
)
|
||
return {
|
||
"title": "OneLiner 已导入文本素材",
|
||
"summary": "已把文本素材送入分析链,并创建可追踪的分析任务。",
|
||
"payload": {
|
||
"job": job,
|
||
"title": title,
|
||
},
|
||
"recommended_action": _recommended_action(
|
||
"open-job-detail",
|
||
label="看任务详情",
|
||
summary="继续查看这条文本分析任务的进度和结果。",
|
||
screen="production",
|
||
job_id=job.get("id", ""),
|
||
),
|
||
}
|
||
|
||
async def _run_refresh_tracking() -> dict[str, Any]:
|
||
if not normalized_platform:
|
||
raise HTTPException(status_code=400, detail="Platform is required for tracking refresh")
|
||
payload = await _call_local_api(
|
||
account,
|
||
method="POST",
|
||
path=f"/v2/{normalized_platform}/tracking/refresh",
|
||
json_body={},
|
||
)
|
||
items = list(payload.get("items") or [])
|
||
sync_job_id = str((items[0] or {}).get("sync_job_id") or "").strip() if len(items) == 1 else ""
|
||
recommended_action = (
|
||
_recommended_action(
|
||
"open-job-detail",
|
||
label="看同步任务",
|
||
summary="继续查看这批跟踪同步任务里最新的一条执行进度。",
|
||
screen="production",
|
||
job_id=sync_job_id,
|
||
)
|
||
if sync_job_id
|
||
else _recommended_action(
|
||
"goto-tracking",
|
||
label="回到跟踪工作区",
|
||
summary="继续查看当前平台跟踪池的同步结果和日报变化。",
|
||
screen="tracking",
|
||
platform=normalized_platform,
|
||
)
|
||
)
|
||
return {
|
||
"title": "OneLiner 已同步跟踪池",
|
||
"summary": f"已为 {legacy.platform_label(normalized_platform)} 跟踪池触发同步,成功 {int(payload.get('refreshed') or 0)} 条。",
|
||
"payload": {
|
||
"platform": normalized_platform,
|
||
"refresh": payload,
|
||
},
|
||
"recommended_action": recommended_action,
|
||
}
|
||
|
||
async def _run_mark_tracking_read() -> dict[str, Any]:
|
||
if not normalized_platform:
|
||
raise HTTPException(status_code=400, detail="Platform is required for tracking cursor update")
|
||
last_seen_at = str(
|
||
requested_payload.get("last_seen_at")
|
||
or requested_payload.get("lastSeenAt")
|
||
or legacy.utc_now()
|
||
).strip() or legacy.utc_now()
|
||
payload = await _call_local_api(
|
||
account,
|
||
method="POST",
|
||
path=f"/v2/{normalized_platform}/tracking/cursor",
|
||
json_body={"last_seen_at": last_seen_at},
|
||
)
|
||
return {
|
||
"title": "OneLiner 已标记日报已读",
|
||
"summary": f"已把 {legacy.platform_label(normalized_platform)} 跟踪日报更新为已读,后续将从新的时间点继续汇总。",
|
||
"payload": {
|
||
"platform": normalized_platform,
|
||
"cursor": payload,
|
||
},
|
||
"recommended_action": _recommended_action(
|
||
"goto-tracking",
|
||
label="回到跟踪工作区",
|
||
summary="继续查看当前平台跟踪日报和下一步跟进动作。",
|
||
screen="tracking",
|
||
platform=normalized_platform,
|
||
),
|
||
}
|
||
|
||
async def _run_search_similar_accounts() -> dict[str, Any]:
|
||
if not normalized_platform:
|
||
raise HTTPException(status_code=400, detail="Platform is required for similarity search")
|
||
source_account = _resolve_platform_target_account(
|
||
account,
|
||
project_id=project["id"],
|
||
platform=normalized_platform,
|
||
requested_account_id=str(
|
||
requested_payload.get("target_account_id")
|
||
or requested_payload.get("targetAccountId")
|
||
or requested_payload.get("source_account_id")
|
||
or requested_payload.get("sourceAccountId")
|
||
or ""
|
||
),
|
||
)
|
||
if not source_account:
|
||
raise HTTPException(status_code=404, detail="No platform account available for similarity search")
|
||
search_payload = await _call_local_api(
|
||
account,
|
||
method="POST",
|
||
path=f"/v2/{normalized_platform}/similar-searches",
|
||
json_body={
|
||
"source_account_id": source_account["id"],
|
||
"candidate_urls": list(requested_payload.get("candidate_urls") or requested_payload.get("candidateUrls") or []),
|
||
"seed_linked_accounts": bool(requested_payload.get("seed_linked_accounts", requested_payload.get("seedLinkedAccounts", True))),
|
||
"search_public_pages": bool(requested_payload.get("search_public_pages", requested_payload.get("searchPublicPages", True))),
|
||
"model_profile_id": str(requested_payload.get("model_profile_id") or requested_payload.get("modelProfileId") or ""),
|
||
"max_candidates": max(1, min(int(requested_payload.get("max_candidates") or requested_payload.get("maxCandidates") or 8), 20)),
|
||
"extra_requirements": str(requested_payload.get("extra_requirements") or requested_payload.get("extraRequirements") or latest_user_message or ""),
|
||
},
|
||
)
|
||
search_id = str(search_payload.get("search_id") or search_payload.get("id") or "").strip()
|
||
detail = await _call_local_api(
|
||
account,
|
||
method="GET",
|
||
path=f"/v2/{normalized_platform}/similar-searches/{search_id}",
|
||
) if search_id else {"candidates": []}
|
||
candidates = list(detail.get("candidates") or [])
|
||
top_candidate = candidates[0] if candidates else {}
|
||
top_candidate_account_id = str(top_candidate.get("candidate_account_id") or "").strip()
|
||
recommended_action = (
|
||
_recommended_action(
|
||
"select-account",
|
||
label="打开当前候选",
|
||
summary=f"继续查看 {legacy.platform_label(normalized_platform)} 相似候选的详情、拆解和对标关系。",
|
||
screen="discovery",
|
||
account_id=top_candidate_account_id,
|
||
search_id=search_id,
|
||
)
|
||
if top_candidate_account_id
|
||
else _recommended_action(
|
||
"goto-discovery",
|
||
label="回到找对标",
|
||
summary=f"继续查看 {legacy.platform_label(normalized_platform)} 相似候选列表和高分样本。",
|
||
screen="discovery",
|
||
search_id=search_id,
|
||
)
|
||
)
|
||
return {
|
||
"title": "OneLiner 已查到相似账号",
|
||
"summary": f"已为 {legacy.platform_label(normalized_platform)} 当前账号生成 {len(candidates)} 个相似候选。",
|
||
"payload": {
|
||
"platform": normalized_platform,
|
||
"source_account_id": source_account["id"],
|
||
"search": {
|
||
"id": search_id,
|
||
"candidate_count": len(candidates),
|
||
"top_candidate_account_id": top_candidate_account_id,
|
||
},
|
||
"detail": detail,
|
||
},
|
||
"recommended_action": recommended_action,
|
||
}
|
||
|
||
async def _run_save_benchmark_link() -> dict[str, Any]:
|
||
if not normalized_platform:
|
||
raise HTTPException(status_code=400, detail="Platform is required for benchmark linking")
|
||
source_account = _resolve_platform_target_account(
|
||
account,
|
||
project_id=project["id"],
|
||
platform=normalized_platform,
|
||
requested_account_id=str(
|
||
requested_payload.get("source_account_id")
|
||
or requested_payload.get("sourceAccountId")
|
||
or requested_payload.get("target_account_id")
|
||
or requested_payload.get("targetAccountId")
|
||
or ""
|
||
),
|
||
)
|
||
if not source_account:
|
||
raise HTTPException(status_code=404, detail="No platform account available for benchmark linking")
|
||
latest_similarity = _latest_similarity_candidate(
|
||
account,
|
||
platform=normalized_platform,
|
||
source_account_id=source_account["id"],
|
||
) or {}
|
||
candidate_payload = latest_similarity.get("candidate") or {}
|
||
target_account_id = str(
|
||
requested_payload.get("target_account_id")
|
||
or requested_payload.get("targetAccountId")
|
||
or candidate_payload.get("candidate_account_id")
|
||
or ""
|
||
).strip()
|
||
target_profile_url = str(
|
||
requested_payload.get("target_profile_url")
|
||
or requested_payload.get("targetProfileUrl")
|
||
or ("" if target_account_id else candidate_payload.get("candidate_profile_url"))
|
||
or ""
|
||
).strip()
|
||
if not target_account_id and not target_profile_url:
|
||
raise HTTPException(status_code=404, detail="No benchmark candidate available to save")
|
||
created = await _call_local_api(
|
||
account,
|
||
method="POST",
|
||
path=f"/v2/{normalized_platform}/accounts/{source_account['id']}/benchmark-links",
|
||
json_body={
|
||
"target_account_ids": [target_account_id] if target_account_id else [],
|
||
"target_profile_urls": [] if target_account_id else [target_profile_url],
|
||
"relation_type": str(requested_payload.get("relation_type") or requested_payload.get("relationType") or "benchmark"),
|
||
"note": str(requested_payload.get("note") or candidate_payload.get("rationale_text") or "由 OneLiner 直接加入对标库。"),
|
||
"search_id": str(requested_payload.get("search_id") or requested_payload.get("searchId") or latest_similarity.get("search_id") or ""),
|
||
},
|
||
)
|
||
links = list(created.get("links") or [])
|
||
latest_link = links[0] if links else {}
|
||
recommended_action = (
|
||
_recommended_action(
|
||
"select-account",
|
||
label="打开当前对标",
|
||
summary=f"继续查看 {legacy.platform_label(normalized_platform)} 当前对标账号的详情、关系和高分样本。",
|
||
screen="discovery",
|
||
account_id=target_account_id,
|
||
)
|
||
if target_account_id
|
||
else _recommended_action(
|
||
"goto-discovery",
|
||
label="回到找对标",
|
||
summary=f"继续查看 {legacy.platform_label(normalized_platform)} 对标关系和相似候选。",
|
||
screen="discovery",
|
||
)
|
||
)
|
||
return {
|
||
"title": "OneLiner 已存对标关系",
|
||
"summary": f"已把当前候选加入 {legacy.platform_label(normalized_platform)} 对标关系。",
|
||
"payload": {
|
||
"platform": normalized_platform,
|
||
"source_account_id": source_account["id"],
|
||
"target_account_id": target_account_id,
|
||
"target_profile_url": target_profile_url,
|
||
"search_id": str(latest_similarity.get("search_id") or ""),
|
||
"link": latest_link,
|
||
"links": links,
|
||
},
|
||
"recommended_action": recommended_action,
|
||
}
|
||
|
||
async def _run_analyze_top_videos() -> dict[str, Any]:
|
||
if not normalized_platform:
|
||
raise HTTPException(status_code=400, detail="Platform is required for top video analysis")
|
||
target_account = None
|
||
requested_account_id = str(requested_payload.get("target_account_id") or requested_payload.get("targetAccountId") or "").strip()
|
||
if requested_account_id and normalized_platform != "douyin":
|
||
target_account = legacy.db.fetch_one(
|
||
"""
|
||
SELECT * FROM content_sources
|
||
WHERE id = ? AND user_id = ? AND project_id = ? AND platform = ? AND source_kind = 'creator_account'
|
||
""",
|
||
(requested_account_id, account["id"], project["id"], normalized_platform),
|
||
)
|
||
if not target_account:
|
||
target_account = _latest_platform_account(account, project_id=project["id"], platform=normalized_platform)
|
||
videos = _linked_platform_videos(
|
||
account,
|
||
project_id=project["id"],
|
||
platform=normalized_platform,
|
||
account_row=target_account or {},
|
||
limit=max(2, min(int(requested_payload.get("top_video_count") or requested_payload.get("topVideoCount") or 6), 12)),
|
||
) if target_account else []
|
||
account_payload = legacy.content_source_payload(target_account) if target_account else None
|
||
if not videos:
|
||
account_payload, videos = _fallback_platform_videos(
|
||
account,
|
||
project_id=project["id"],
|
||
platform=normalized_platform,
|
||
requested_account_id=requested_account_id,
|
||
limit=max(2, min(int(requested_payload.get("top_video_count") or requested_payload.get("topVideoCount") or 6), 12)),
|
||
)
|
||
if not account_payload:
|
||
raise HTTPException(status_code=404, detail="No platform account available for top video analysis")
|
||
min_score = float(requested_payload.get("min_score") or requested_payload.get("minScore") or 0)
|
||
ranked = [item for item in videos if float((item.get("score") or {}).get("performance_score") or 0) >= min_score]
|
||
if not ranked:
|
||
raise HTTPException(status_code=404, detail="No candidate videos available for analysis")
|
||
profile = legacy.model_profile_for_account(
|
||
account["id"],
|
||
str(requested_payload.get("model_profile_id") or requested_payload.get("modelProfileId") or ""),
|
||
)
|
||
items: list[dict[str, Any]] = []
|
||
parse_json_object = getattr(legacy, "parse_json_object", None)
|
||
for video in ranked:
|
||
prompt = (
|
||
f"请拆解这条{legacy.platform_label(normalized_platform)}作品为什么值得关注,"
|
||
"输出 JSON,字段包括 summary、borrow_points、risks、next_actions。"
|
||
f"\n\n输入:\n{json.dumps(video, ensure_ascii=False, indent=2)}"
|
||
)
|
||
output = await legacy.call_model(
|
||
profile,
|
||
"你是平台内容拆解助手。尽量输出 JSON,字段包括 summary、borrow_points、risks、next_actions。",
|
||
prompt,
|
||
temperature=float(requested_payload.get("temperature") or 0.25),
|
||
)
|
||
parsed = parse_json_object(output) if callable(parse_json_object) else _parse_json(output, {})
|
||
summary_text = str(parsed.get("summary") or parsed.get("headline_summary") or output).strip()[:240]
|
||
items.append(
|
||
{
|
||
"id": make_id(f"{normalized_platform}_va"),
|
||
"video_id": video["id"],
|
||
"video_title": video["title"],
|
||
"status": "ok",
|
||
"summary_text": summary_text,
|
||
"parsed_json": parsed,
|
||
"performance_score": (video.get("score") or {}).get("performance_score", 0),
|
||
"latest_job_id": video.get("latest_job_id", ""),
|
||
"created_at": now(),
|
||
}
|
||
)
|
||
memory = _remember_platform_observation(
|
||
account,
|
||
project_id=project["id"],
|
||
platform=normalized_platform,
|
||
memory_key=f"top_videos::{target_account['id']}",
|
||
title=f"{legacy.platform_label(normalized_platform)} 高分作品拆解",
|
||
summary=f"已拆解 {len(items)} 条高分作品,继续固化有效结构与风险判断。",
|
||
details={
|
||
"account_id": account_payload["id"],
|
||
"items": items,
|
||
},
|
||
confidence=0.84,
|
||
)
|
||
return {
|
||
"title": "OneLiner 已分析高分作品",
|
||
"summary": f"已为 {legacy.platform_label(normalized_platform)} 账号拆解 {len(items)} 条高分作品。",
|
||
"payload": {
|
||
"platform": normalized_platform,
|
||
"account": account_payload,
|
||
"analyzed_count": len(items),
|
||
"items": items,
|
||
"memory": memory,
|
||
},
|
||
"recommended_action": _recommended_action(
|
||
"select-account",
|
||
label="打开当前对象",
|
||
summary=f"继续查看 {legacy.platform_label(normalized_platform)} 当前对象的高分作品拆解和相似账号。",
|
||
screen="discovery",
|
||
account_id=account_payload.get("id", ""),
|
||
platform=normalized_platform,
|
||
),
|
||
}
|
||
|
||
async def _run_create_ai_video() -> dict[str, Any]:
|
||
source_job = _load_owned_job(account, str(requested_payload.get("source_job_id") or requested_payload.get("sourceJobId") or "")) or _latest_derivable_job(
|
||
account,
|
||
project_id=project["id"],
|
||
exclude_line_types={"ai_video"},
|
||
)
|
||
if not source_job:
|
||
raise HTTPException(status_code=404, detail="No completed source job available for AI video")
|
||
requested_assistant_id = str(
|
||
requested_payload.get("assistant_id")
|
||
or requested_payload.get("assistantId")
|
||
or ""
|
||
).strip()
|
||
assistant = (
|
||
_resolve_assistant(account, requested_assistant_id, project["id"])
|
||
if requested_assistant_id
|
||
else _resolve_execution_assistant(account, project_id=project["id"], platform=normalized_platform)
|
||
)
|
||
brief = str(
|
||
requested_payload.get("brief")
|
||
or requested_payload.get("video_brief")
|
||
or requested_payload.get("videoBrief")
|
||
or latest_user_message
|
||
or _assistant_brief_from_job(source_job)
|
||
or ""
|
||
).strip()
|
||
if not brief:
|
||
brief = f"请基于任务「{source_job.get('title') or source_job['id']}」输出一版适合短视频平台的 AI 视频。"
|
||
source_artifacts = legacy.parse_job_artifacts(source_job)
|
||
video_provider = str(
|
||
requested_payload.get("video_provider")
|
||
or requested_payload.get("videoProvider")
|
||
or source_artifacts.get("video_provider")
|
||
or "doubao"
|
||
).strip() or "doubao"
|
||
video_model = str(
|
||
requested_payload.get("video_model")
|
||
or requested_payload.get("videoModel")
|
||
or source_artifacts.get("video_model")
|
||
or ""
|
||
).strip()
|
||
if video_provider == "seedance2" and not video_model:
|
||
video_model = "seedance-2.0-pro"
|
||
job = await legacy.create_ai_video_job(
|
||
legacy.AiVideoJobRequest(
|
||
project_id=project["id"],
|
||
assistant_id=(assistant or {}).get("id", ""),
|
||
knowledge_base_id=str(requested_payload.get("knowledge_base_id") or requested_payload.get("knowledgeBaseId") or source_job.get("knowledge_base_id") or ""),
|
||
source_job_id=source_job["id"],
|
||
title=str(requested_payload.get("title") or f"{source_job.get('title') or '任务'} · AI 视频"),
|
||
brief=brief,
|
||
style=str(requested_payload.get("style") or "realistic"),
|
||
shots=max(1, min(int(requested_payload.get("shots") or 4), 12)),
|
||
duration=max(3, min(int(requested_payload.get("duration") or 5), 12)),
|
||
video_provider=video_provider,
|
||
video_model=video_model,
|
||
),
|
||
account,
|
||
)
|
||
return {
|
||
"title": "OneLiner 已创建 AI 视频任务",
|
||
"summary": f"已基于「{source_job.get('title') or source_job['id']}」创建 AI 视频任务,当前引擎 {('Seedance 2.0' if video_provider == 'seedance2' else '默认视频引擎')}。",
|
||
"payload": {
|
||
"job": job,
|
||
"source_job": legacy.job_payload(source_job),
|
||
"brief": brief,
|
||
"video_provider": video_provider,
|
||
"video_model": video_model,
|
||
},
|
||
"recommended_action": _recommended_action(
|
||
"open-job-detail",
|
||
label="看任务详情",
|
||
summary="继续查看这条 AI 视频任务的执行进度和后续动作。",
|
||
screen="production",
|
||
job_id=job.get("id", ""),
|
||
),
|
||
}
|
||
|
||
async def _run_create_assistant() -> dict[str, Any]:
|
||
default_name = str(
|
||
requested_payload.get("name")
|
||
or requested_payload.get("assistant_name")
|
||
or requested_payload.get("assistantName")
|
||
or (f"{legacy.platform_label(normalized_platform)} 执行 Agent" if normalized_platform else f"{project.get('name') or '项目'} Agent")
|
||
).strip()
|
||
assistant = legacy.create_assistant(
|
||
legacy.AssistantCreateRequest(
|
||
project_id=project["id"],
|
||
name=default_name,
|
||
description=str(requested_payload.get("description") or f"由 OneLiner 基于当前项目上下文创建,用于承接{legacy.platform_label(normalized_platform) if normalized_platform else '当前项目'}任务。"),
|
||
generation_goal=str(requested_payload.get("generation_goal") or requested_payload.get("goal") or f"承接{legacy.platform_label(normalized_platform) if normalized_platform else '当前项目'}内容生产和分析动作。"),
|
||
system_prompt=str(requested_payload.get("system_prompt") or requested_payload.get("systemPrompt") or ""),
|
||
knowledge_base_ids=list(requested_payload.get("knowledge_base_ids") or requested_payload.get("knowledgeBaseIds") or []),
|
||
model_profile_id=str(requested_payload.get("model_profile_id") or requested_payload.get("modelProfileId") or ""),
|
||
),
|
||
account,
|
||
)
|
||
return {
|
||
"title": "OneLiner 已创建 Agent",
|
||
"summary": f"已在当前项目下创建 Agent「{assistant.get('name') or '新 Agent'}」。",
|
||
"payload": {"assistant": assistant},
|
||
"recommended_action": _recommended_action(
|
||
"open-edit-assistant",
|
||
label="继续编辑 Agent",
|
||
summary="继续补齐这条 Agent 的说明、目标和知识绑定。",
|
||
screen="playbook",
|
||
assistant_id=assistant.get("id", ""),
|
||
),
|
||
}
|
||
|
||
async def _run_create_real_cut() -> dict[str, Any]:
|
||
source_job = _load_owned_job(account, str(requested_payload.get("source_job_id") or requested_payload.get("sourceJobId") or "")) or _latest_derivable_job(
|
||
account,
|
||
project_id=project["id"],
|
||
exclude_line_types={"ai_video", "real_cut"},
|
||
)
|
||
if not source_job:
|
||
raise HTTPException(status_code=404, detail="No completed source job available for real cut")
|
||
job = await legacy.create_real_cut_job(
|
||
legacy.RealCutJobRequest(
|
||
project_id=project["id"],
|
||
title=str(requested_payload.get("title") or f"{source_job.get('title') or '任务'} · 实拍剪辑"),
|
||
source_job_id=source_job["id"],
|
||
objective=str(
|
||
requested_payload.get("objective")
|
||
or "保留高信息密度片段,输出适合短视频平台的粗剪结果"
|
||
),
|
||
target_duration_sec=max(10, min(int(requested_payload.get("target_duration_sec") or requested_payload.get("targetDurationSec") or 60), 300)),
|
||
target_aspect_ratio=str(requested_payload.get("target_aspect_ratio") or requested_payload.get("targetAspectRatio") or "9:16"),
|
||
review_enabled=bool(requested_payload.get("review_enabled", requested_payload.get("reviewEnabled", False))),
|
||
),
|
||
account,
|
||
)
|
||
return {
|
||
"title": "OneLiner 已创建实拍剪辑任务",
|
||
"summary": f"已基于「{source_job.get('title') or source_job['id']}」创建实拍剪辑任务。",
|
||
"payload": {
|
||
"job": job,
|
||
"source_job": legacy.job_payload(source_job),
|
||
},
|
||
"recommended_action": _recommended_action(
|
||
"open-job-detail",
|
||
label="看任务详情",
|
||
summary="继续查看这条实拍剪辑任务的执行进度和恢复动作。",
|
||
screen="production",
|
||
job_id=job.get("id", ""),
|
||
),
|
||
}
|
||
|
||
async def _run_save_live_recorder_source() -> dict[str, Any]:
|
||
source_url = str(
|
||
requested_payload.get("source_url")
|
||
or requested_payload.get("sourceUrl")
|
||
or _extract_first_url(latest_user_message)
|
||
or ""
|
||
).strip()
|
||
if not source_url:
|
||
raise HTTPException(status_code=400, detail="No live recorder source URL available")
|
||
recorder_platform = normalize_platform_from_text(
|
||
requested_payload.get("platform")
|
||
or requested_payload.get("platform_label")
|
||
or normalized_platform
|
||
or legacy.infer_platform_from_url(source_url)
|
||
) or _safe_platform(legacy.infer_platform_from_url(source_url), fallback="kuaishou")
|
||
assistant = _resolve_execution_assistant(account, project_id=project["id"], platform=recorder_platform)
|
||
saved = legacy.create_live_recorder_source(
|
||
legacy.LiveRecorderSourceCreateRequest(
|
||
project_id=project["id"],
|
||
assistant_id=(assistant or {}).get("id", ""),
|
||
platform=recorder_platform,
|
||
source_url=source_url,
|
||
title=str(requested_payload.get("title") or ""),
|
||
quality=str(requested_payload.get("quality") or "原画"),
|
||
enabled=bool(requested_payload.get("enabled", True)),
|
||
),
|
||
account,
|
||
)
|
||
started = None
|
||
if bool(requested_payload.get("auto_start", requested_payload.get("autoStart", True))):
|
||
try:
|
||
started = legacy.live_recorder_start(account)
|
||
except Exception as exc:
|
||
started = {"ok": False, "message": str(exc)}
|
||
return {
|
||
"title": "OneLiner 已保存录制源",
|
||
"summary": f"已把直播源保存到当前租户的 NAS 录制配置里。",
|
||
"payload": {
|
||
"saved": saved,
|
||
"started": started,
|
||
"platform": recorder_platform,
|
||
"source_url": source_url,
|
||
},
|
||
"recommended_action": _recommended_action(
|
||
"edit-live-recorder-source",
|
||
label="继续录制维护",
|
||
summary="继续查看这条录制源的启停状态、项目归属和录制文件。",
|
||
screen="production",
|
||
source_id=(saved or {}).get("id", ""),
|
||
),
|
||
}
|
||
|
||
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,
|
||
"generate-copy": _run_generate_copy,
|
||
"review-draft": _run_review_draft,
|
||
"import-homepage": _run_import_homepage,
|
||
"import-video-link": _run_import_video_link,
|
||
"import-text": _run_import_text,
|
||
"analyze-account": _run_analyze_account,
|
||
"track-account": _run_track_account,
|
||
"refresh-tracking": _run_refresh_tracking,
|
||
"mark-tracking-read": _run_mark_tracking_read,
|
||
"search-similar-accounts": _run_search_similar_accounts,
|
||
"save-benchmark-link": _run_save_benchmark_link,
|
||
"analyze-top-videos": _run_analyze_top_videos,
|
||
"create-assistant": _run_create_assistant,
|
||
"create-ai-video": _run_create_ai_video,
|
||
"create-real-cut": _run_create_real_cut,
|
||
"save-live-recorder-source": _run_save_live_recorder_source,
|
||
}
|
||
executor = executors.get(handler_key)
|
||
if not executor:
|
||
raise HTTPException(status_code=400, detail=f"Unsupported OneLiner action: {handler_key}")
|
||
result = await executor()
|
||
usage_entry = None
|
||
if usage_category:
|
||
payload_map = result.get("payload") or {}
|
||
job_map = payload_map.get("job") or {}
|
||
saved_map = (payload_map.get("saved") or {}).get("item") or {}
|
||
usage_entry = _record_tenant_usage(
|
||
account,
|
||
project_id=project["id"],
|
||
category=usage_category,
|
||
reference_type=(
|
||
"job"
|
||
if job_map.get("id")
|
||
else "live_recorder_source"
|
||
if saved_map.get("binding_id")
|
||
else "action"
|
||
),
|
||
reference_id=job_map.get("id") or saved_map.get("binding_id") or handler_key,
|
||
details={
|
||
"action_key": action_key,
|
||
"handler_key": handler_key,
|
||
"platform": normalized_platform,
|
||
},
|
||
)
|
||
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,
|
||
"handler_key": handler_key,
|
||
"project_id": project["id"],
|
||
"platform": normalized_platform,
|
||
"executed_at": now(),
|
||
"action_definition": action_definition,
|
||
"usage_entry": usage_entry,
|
||
**result,
|
||
}
|
||
|
||
@app.post("/v2/oneliner/runs")
|
||
def create_oneliner_run(
|
||
request: AgentRunCreateRequest,
|
||
account: dict[str, Any] = Depends(legacy.require_approved),
|
||
) -> dict[str, Any]:
|
||
project = _resolve_project(account, request.project_id or None)
|
||
normalized_platform = _safe_platform(request.platform, fallback="") if str(request.platform or "").strip() else ""
|
||
platform_scope = _normalize_platform_scope(request.platform_scope)
|
||
governance = _effective_policy_payload(
|
||
subject_account=account,
|
||
subject_project_id=project["id"],
|
||
platform=normalized_platform,
|
||
)
|
||
profile_row = _fetch_profile_row(account, project["id"]) or _ensure_oneliner_profile(account, project["id"])
|
||
platform_profile_row = legacy.db.fetch_one(
|
||
"SELECT * FROM platform_agent_profiles WHERE user_id = ? AND project_id = ? AND platform = ?",
|
||
(account["id"], project["id"], normalized_platform),
|
||
) if normalized_platform else None
|
||
oneliner_profile_bundle = _oneliner_profile_runtime_snapshot(profile_row, account=account)
|
||
governance_snapshot = {
|
||
**governance,
|
||
"oneliner_profile": oneliner_profile_bundle,
|
||
"oneliner_profile_version": oneliner_profile_bundle.get("current_version") or {},
|
||
"platform_agent_profile": _platform_agent_runtime_snapshot(
|
||
account,
|
||
platform_profile_row,
|
||
platform=normalized_platform,
|
||
project_id=project["id"],
|
||
) if normalized_platform else {},
|
||
}
|
||
plan = _agent_run_plan_payload(
|
||
request,
|
||
governance=governance_snapshot,
|
||
platform=normalized_platform,
|
||
platform_scope=platform_scope,
|
||
)
|
||
session = _ensure_run_session(
|
||
account,
|
||
project_id=project["id"],
|
||
requested_session_id=request.session_id,
|
||
title=request.title.strip() or plan["goal"],
|
||
preferred_platform=normalized_platform or "douyin",
|
||
)
|
||
_touch_session_for_run(session["id"], platform=normalized_platform, intent_key=plan.get("intent_key", "custom"))
|
||
run_id = make_id("oline_run")
|
||
timestamp = now()
|
||
active_admin_override_notice = governance_snapshot.get("active_admin_override_notice") or {}
|
||
legacy.db.execute(
|
||
"""
|
||
INSERT INTO agent_runs (
|
||
id, user_id, project_id, session_id, source_screen, source_action_key, title, summary,
|
||
intent_key, platform, platform_scope, delivery_mode, run_status, scheduling_mode,
|
||
active_executor_key, plan_json, governance_json, result_json, status_summary,
|
||
needs_user_input, blocked_reason, active_admin_override_notice_json,
|
||
created_at, updated_at, started_at, finished_at
|
||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'needs_confirmation', ?, 'main_agent', ?, ?, '{}', ?, 1, '', ?, ?, ?, '', '')
|
||
""",
|
||
(
|
||
run_id,
|
||
account["id"],
|
||
project["id"],
|
||
session["id"],
|
||
str(request.source_screen or "").strip(),
|
||
str(request.source_action_key or "").strip(),
|
||
request.title.strip() or plan["goal"],
|
||
request.summary.strip(),
|
||
str(request.intent_key or "custom").strip() or "custom",
|
||
normalized_platform,
|
||
platform_scope,
|
||
_normalize_delivery_mode(request.delivery_mode),
|
||
_normalize_scheduling_mode(request.scheduling_mode),
|
||
_dump(plan),
|
||
_dump(governance_snapshot),
|
||
"等待你确认执行计划",
|
||
_dump(active_admin_override_notice),
|
||
timestamp,
|
||
timestamp,
|
||
),
|
||
)
|
||
_log_agent_run_event(
|
||
run_id,
|
||
event_type="run.created",
|
||
summary=f"已创建待确认任务:{request.title.strip() or plan['goal']}",
|
||
details={
|
||
"run_status": "needs_confirmation",
|
||
"source_screen": str(request.source_screen or "").strip(),
|
||
"source_action_key": str(request.source_action_key or "").strip(),
|
||
"platform": normalized_platform,
|
||
"platform_scope": platform_scope,
|
||
},
|
||
)
|
||
row = legacy.db.fetch_one("SELECT * FROM agent_runs WHERE id = ?", (run_id,))
|
||
assert row is not None
|
||
return _agent_run_payload(row)
|
||
|
||
@app.post("/v2/oneliner/runs/{run_id}/retry")
|
||
def retry_oneliner_run(
|
||
run_id: str,
|
||
request: AgentRunRetryRequest,
|
||
account: dict[str, Any] = Depends(legacy.require_approved),
|
||
) -> dict[str, Any]:
|
||
row = _load_owned_agent_run(run_id, account)
|
||
current_status = str(row.get("run_status") or "")
|
||
if current_status not in {"cancelled", "blocked", "failed", "done"}:
|
||
raise HTTPException(status_code=409, detail="Run can not be retried yet")
|
||
|
||
project_id = str(row.get("project_id") or "").strip()
|
||
platform = str(row.get("platform") or "").strip()
|
||
platform_scope = _normalize_platform_scope(str(row.get("platform_scope") or "single_platform"))
|
||
governance = _effective_policy_payload(
|
||
subject_account=account,
|
||
subject_project_id=project_id,
|
||
platform=platform,
|
||
)
|
||
profile_row = _fetch_profile_row(account, project_id) or _ensure_oneliner_profile(account, project_id)
|
||
platform_profile_row = legacy.db.fetch_one(
|
||
"SELECT * FROM platform_agent_profiles WHERE user_id = ? AND project_id = ? AND platform = ?",
|
||
(account["id"], project_id, platform),
|
||
) if platform else None
|
||
oneliner_profile_bundle = _oneliner_profile_runtime_snapshot(profile_row, account=account)
|
||
governance_snapshot = {
|
||
**governance,
|
||
"oneliner_profile": oneliner_profile_bundle,
|
||
"oneliner_profile_version": oneliner_profile_bundle.get("current_version") or {},
|
||
"platform_agent_profile": _platform_agent_runtime_snapshot(
|
||
account,
|
||
platform_profile_row,
|
||
platform=platform,
|
||
project_id=project_id,
|
||
) if platform else {},
|
||
}
|
||
plan = _parse_json(row.get("plan_json"), {})
|
||
session_id = str(row.get("session_id") or "").strip()
|
||
_touch_session_for_run(session_id, platform=platform, intent_key=str(row.get("intent_key") or "custom").strip() or "custom")
|
||
|
||
next_run_id = make_id("oline_run")
|
||
timestamp = now()
|
||
active_admin_override_notice = governance_snapshot.get("active_admin_override_notice") or {}
|
||
legacy.db.execute(
|
||
"""
|
||
INSERT INTO agent_runs (
|
||
id, user_id, project_id, session_id, source_screen, source_action_key, title, summary,
|
||
intent_key, platform, platform_scope, delivery_mode, run_status, scheduling_mode,
|
||
active_executor_key, plan_json, governance_json, result_json, status_summary,
|
||
needs_user_input, blocked_reason, active_admin_override_notice_json,
|
||
created_at, updated_at, started_at, finished_at
|
||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'needs_confirmation', ?, 'main_agent', ?, ?, '{}', ?, 1, '', ?, ?, ?, '', '')
|
||
""",
|
||
(
|
||
next_run_id,
|
||
account["id"],
|
||
project_id,
|
||
session_id,
|
||
str(row.get("source_screen") or "").strip(),
|
||
str(row.get("source_action_key") or "").strip(),
|
||
str(row.get("title") or plan.get("goal") or "主 Agent 任务").strip() or "主 Agent 任务",
|
||
str(request.reason or row.get("summary") or "").strip() or str(row.get("summary") or "").strip(),
|
||
str(row.get("intent_key") or "custom").strip() or "custom",
|
||
platform,
|
||
platform_scope,
|
||
_normalize_delivery_mode(str(row.get("delivery_mode") or "hybrid")),
|
||
_normalize_scheduling_mode(str(row.get("scheduling_mode") or "queued")),
|
||
_dump(plan),
|
||
_dump(governance_snapshot),
|
||
"等待你确认执行计划",
|
||
_dump(active_admin_override_notice),
|
||
timestamp,
|
||
timestamp,
|
||
),
|
||
)
|
||
_log_agent_run_event(
|
||
next_run_id,
|
||
event_type="run.created",
|
||
summary=f"已创建待确认任务:{str(row.get('title') or plan.get('goal') or '主 Agent 任务').strip() or '主 Agent 任务'}",
|
||
details={
|
||
"run_status": "needs_confirmation",
|
||
"source_screen": str(row.get("source_screen") or "").strip(),
|
||
"source_action_key": str(row.get("source_action_key") or "").strip(),
|
||
"platform": platform,
|
||
"platform_scope": platform_scope,
|
||
},
|
||
)
|
||
_log_agent_run_event(
|
||
next_run_id,
|
||
event_type="run.retried",
|
||
summary=str(request.reason or "已基于上一轮主 Agent 任务重新生成待确认执行卡").strip(),
|
||
details={"retry_from_run_id": run_id, "from_status": current_status},
|
||
)
|
||
_log_agent_run_event(
|
||
run_id,
|
||
event_type="run.retried",
|
||
summary=str(request.reason or "用户要求重新发起当前主 Agent 任务").strip(),
|
||
details={"new_run_id": next_run_id, "from_status": current_status},
|
||
)
|
||
created = legacy.db.fetch_one("SELECT * FROM agent_runs WHERE id = ?", (next_run_id,))
|
||
assert created is not None
|
||
return _agent_run_payload(created)
|
||
|
||
@app.get("/v2/oneliner/runs")
|
||
def list_oneliner_runs(
|
||
project_id: str | None = Query(default=None),
|
||
limit: int = Query(default=20, ge=1, le=100),
|
||
account: dict[str, Any] = Depends(legacy.require_approved),
|
||
) -> dict[str, Any]:
|
||
project = _resolve_project_for_read(account, project_id or None) if (project_id or "").strip() else _resolve_project_for_read(account, None)
|
||
if project:
|
||
rows = legacy.db.fetch_all(
|
||
"""
|
||
SELECT * FROM agent_runs
|
||
WHERE user_id = ? AND project_id = ?
|
||
ORDER BY created_at DESC
|
||
LIMIT ?
|
||
""",
|
||
(account["id"], project["id"], limit),
|
||
)
|
||
else:
|
||
rows = legacy.db.fetch_all(
|
||
"""
|
||
SELECT * FROM agent_runs
|
||
WHERE user_id = ?
|
||
ORDER BY created_at DESC
|
||
LIMIT ?
|
||
""",
|
||
(account["id"], limit),
|
||
)
|
||
items = [_agent_run_payload(row, include_events=False) for row in rows]
|
||
return {"items": items, "count": len(items)}
|
||
|
||
@app.get("/v2/oneliner/runs/{run_id}")
|
||
def get_oneliner_run(
|
||
run_id: str,
|
||
account: dict[str, Any] = Depends(legacy.require_approved),
|
||
) -> dict[str, Any]:
|
||
row = _load_owned_agent_run(run_id, account)
|
||
row = _complete_agent_run_for_read(row)
|
||
return _agent_run_payload(row)
|
||
|
||
@app.get("/v2/oneliner/runs/{run_id}/events")
|
||
def list_oneliner_run_events(
|
||
run_id: str,
|
||
account: dict[str, Any] = Depends(legacy.require_approved),
|
||
) -> dict[str, Any]:
|
||
row = _load_owned_agent_run(run_id, account)
|
||
row = _complete_agent_run_for_read(row)
|
||
items = _list_agent_run_events(row["id"])
|
||
return {"run": _agent_run_payload(row, include_events=False), "items": items, "count": len(items)}
|
||
|
||
@app.post("/v2/oneliner/runs/{run_id}/confirm")
|
||
def confirm_oneliner_run(
|
||
run_id: str,
|
||
request: AgentRunConfirmRequest,
|
||
account: dict[str, Any] = Depends(legacy.require_approved),
|
||
) -> dict[str, Any]:
|
||
row = _load_owned_agent_run(run_id, account)
|
||
current_status = str(row.get("run_status") or "needs_confirmation")
|
||
if current_status not in {"needs_confirmation", "queued", "running"}:
|
||
raise HTTPException(status_code=409, detail="Run can no longer be confirmed")
|
||
if current_status == "needs_confirmation":
|
||
_log_agent_run_event(
|
||
run_id,
|
||
event_type="run.confirmed",
|
||
summary=request.reason.strip() or "用户已确认执行计划",
|
||
details={"reason": request.reason.strip()},
|
||
)
|
||
next_status = "queued" if _has_other_active_runs(account_id=account["id"], project_id=row.get("project_id", ""), run_id=run_id) else "running"
|
||
timestamp = now()
|
||
started_at = row.get("started_at", "")
|
||
event_type = "run.queued"
|
||
event_summary = "已进入主 Agent 等待队列"
|
||
status_summary = "等待主 Agent 执行"
|
||
if next_status == "running":
|
||
started_at = timestamp
|
||
event_type = "run.started"
|
||
event_summary = "主 Agent 已开始执行"
|
||
status_summary = "主 Agent 正在执行"
|
||
legacy.db.execute(
|
||
"""
|
||
UPDATE agent_runs
|
||
SET run_status = ?, status_summary = ?, needs_user_input = 0, updated_at = ?, started_at = ?
|
||
WHERE id = ?
|
||
""",
|
||
(next_status, status_summary, timestamp, started_at, run_id),
|
||
)
|
||
_log_agent_run_event(
|
||
run_id,
|
||
event_type=event_type,
|
||
summary=event_summary,
|
||
details={"run_status": next_status},
|
||
)
|
||
updated = legacy.db.fetch_one("SELECT * FROM agent_runs WHERE id = ?", (run_id,))
|
||
assert updated is not None
|
||
return _agent_run_payload(updated)
|
||
|
||
@app.post("/v2/oneliner/runs/{run_id}/cancel")
|
||
def cancel_oneliner_run(
|
||
run_id: str,
|
||
request: AgentRunCancelRequest,
|
||
account: dict[str, Any] = Depends(legacy.require_approved),
|
||
) -> dict[str, Any]:
|
||
row = _load_owned_agent_run(run_id, account)
|
||
current_status = str(row.get("run_status") or "")
|
||
if current_status not in {"needs_confirmation", "queued"}:
|
||
raise HTTPException(status_code=409, detail="Run can no longer be cancelled")
|
||
timestamp = now()
|
||
legacy.db.execute(
|
||
"""
|
||
UPDATE agent_runs
|
||
SET run_status = 'cancelled', status_summary = ?, needs_user_input = 0, updated_at = ?, finished_at = ?
|
||
WHERE id = ?
|
||
""",
|
||
(request.reason.strip() or "任务已取消", timestamp, timestamp, run_id),
|
||
)
|
||
_log_agent_run_event(
|
||
run_id,
|
||
event_type="run.cancelled",
|
||
summary=request.reason.strip() or "用户取消了当前任务",
|
||
details={"from_status": current_status},
|
||
)
|
||
updated = legacy.db.fetch_one("SELECT * FROM agent_runs WHERE id = ?", (run_id,))
|
||
assert updated is not None
|
||
return _agent_run_payload(updated)
|
||
|
||
@app.get("/v2/oneliner/profile")
|
||
def get_oneliner_profile(
|
||
project_id: str | None = Query(default=None),
|
||
account: dict[str, Any] = Depends(legacy.require_approved),
|
||
) -> dict[str, Any]:
|
||
project = _resolve_project(account, project_id or None)
|
||
row = _ensure_oneliner_profile(account, project["id"])
|
||
return _oneliner_profile_bundle(row, account=account)
|
||
|
||
@app.put("/v2/oneliner/profile")
|
||
def put_oneliner_profile(
|
||
request: OneLinerProfileRequest,
|
||
account: dict[str, Any] = Depends(legacy.require_approved),
|
||
) -> dict[str, Any]:
|
||
project = _resolve_project(account, request.project_id or None)
|
||
assistant = _resolve_assistant(account, request.assistant_id or None, project["id"])
|
||
existing = _ensure_oneliner_profile(account, project["id"])
|
||
timestamp = now()
|
||
update_sql = """
|
||
UPDATE oneliner_profiles
|
||
SET assistant_id = ?, display_name = ?, long_term_goal = ?, notes = ?, default_platform = ?, config_json = ?, updated_at = ?
|
||
WHERE id = ?
|
||
"""
|
||
update_params = (
|
||
(assistant or {}).get("id", ""),
|
||
request.display_name.strip() or "OneLiner",
|
||
request.long_term_goal.strip(),
|
||
request.notes.strip(),
|
||
_safe_platform(request.default_platform or existing.get("default_platform") or "douyin"),
|
||
_dump(request.config),
|
||
timestamp,
|
||
existing["id"],
|
||
)
|
||
if (assistant or {}).get("id", ""):
|
||
legacy.db.execute(update_sql, update_params)
|
||
else:
|
||
with legacy.db.session() as conn:
|
||
conn.execute("PRAGMA foreign_keys=OFF")
|
||
conn.execute(update_sql, update_params)
|
||
conn.execute("PRAGMA foreign_keys=ON")
|
||
row = legacy.db.fetch_one("SELECT * FROM oneliner_profiles WHERE id = ?", (existing["id"],))
|
||
assert row is not None
|
||
version = _create_oneliner_profile_version(
|
||
row,
|
||
actor_user_id=account["id"],
|
||
source_type="user_update",
|
||
reason=request.reason.strip() or "更新 OneLiner 主配置",
|
||
)
|
||
_log_oneliner_profile_audit(
|
||
profile_id=row["id"],
|
||
version_id=version["id"],
|
||
actor_user_id=account["id"],
|
||
action_key="update-oneliner-profile",
|
||
summary=f"已更新 OneLiner「{row.get('display_name', 'OneLiner')}」主配置",
|
||
details={"project_id": project["id"], "rollback_to_version_id": "", "assistant_id": row.get("assistant_id", "")},
|
||
)
|
||
return _oneliner_profile_bundle(row, account=account)
|
||
|
||
@app.get("/v2/oneliner/profile/versions")
|
||
def list_oneliner_profile_versions(
|
||
project_id: str | None = Query(default=None),
|
||
account: dict[str, Any] = Depends(legacy.require_approved),
|
||
) -> dict[str, Any]:
|
||
project = _resolve_project(account, project_id or None)
|
||
row = _ensure_oneliner_profile(account, project["id"])
|
||
_ensure_oneliner_profile_version_seed(row, actor_user_id=account["id"])
|
||
items = _list_oneliner_profile_versions(row)
|
||
return {"items": items, "count": len(items), "current_version": items[0] if items else None}
|
||
|
||
@app.get("/v2/oneliner/profile/audits")
|
||
def list_oneliner_profile_audits(
|
||
project_id: str | None = Query(default=None),
|
||
limit: int = Query(default=12, ge=1, le=50),
|
||
account: dict[str, Any] = Depends(legacy.require_approved),
|
||
) -> dict[str, Any]:
|
||
project = _resolve_project(account, project_id or None)
|
||
row = _ensure_oneliner_profile(account, project["id"])
|
||
_ensure_oneliner_profile_version_seed(row, actor_user_id=account["id"])
|
||
items = _list_oneliner_profile_audits(row, limit=limit)
|
||
return {"items": items, "count": len(items)}
|
||
|
||
@app.post("/v2/oneliner/profile/rollback")
|
||
def rollback_oneliner_profile(
|
||
request: OneLinerProfileRollbackRequest,
|
||
account: dict[str, Any] = Depends(legacy.require_approved),
|
||
) -> dict[str, Any]:
|
||
project = _resolve_project(account, request.project_id or None)
|
||
row = _ensure_oneliner_profile(account, project["id"])
|
||
_ensure_oneliner_profile_version_seed(row, actor_user_id=account["id"])
|
||
target_version = legacy.db.fetch_one(
|
||
"""
|
||
SELECT * FROM oneliner_profile_versions
|
||
WHERE id = ? AND profile_id = ?
|
||
""",
|
||
(request.version_id, row["id"]),
|
||
)
|
||
if not target_version:
|
||
raise HTTPException(status_code=404, detail="OneLiner profile version not found")
|
||
assistant = _resolve_assistant(account, target_version.get("assistant_id", ""), project["id"])
|
||
timestamp = now()
|
||
update_sql = """
|
||
UPDATE oneliner_profiles
|
||
SET assistant_id = ?, display_name = ?, long_term_goal = ?, notes = ?, default_platform = ?, config_json = ?, updated_at = ?
|
||
WHERE id = ?
|
||
"""
|
||
update_params = (
|
||
(assistant or {}).get("id", ""),
|
||
target_version.get("display_name", "OneLiner"),
|
||
target_version.get("long_term_goal", ""),
|
||
target_version.get("notes", ""),
|
||
_safe_platform(target_version.get("default_platform") or row.get("default_platform") or "douyin"),
|
||
target_version.get("config_json", "{}"),
|
||
timestamp,
|
||
row["id"],
|
||
)
|
||
if (assistant or {}).get("id", ""):
|
||
legacy.db.execute(update_sql, update_params)
|
||
else:
|
||
with legacy.db.session() as conn:
|
||
conn.execute("PRAGMA foreign_keys=OFF")
|
||
conn.execute(update_sql, update_params)
|
||
conn.execute("PRAGMA foreign_keys=ON")
|
||
updated_row = legacy.db.fetch_one("SELECT * FROM oneliner_profiles WHERE id = ?", (row["id"],))
|
||
assert updated_row is not None
|
||
version = _create_oneliner_profile_version(
|
||
updated_row,
|
||
actor_user_id=account["id"],
|
||
source_type="user_rollback",
|
||
reason=request.reason.strip() or f"回滚到版本 {target_version.get('version_no') or request.version_id}",
|
||
rollback_from_version_id=target_version["id"],
|
||
)
|
||
_log_oneliner_profile_audit(
|
||
profile_id=updated_row["id"],
|
||
version_id=version["id"],
|
||
actor_user_id=account["id"],
|
||
action_key="rollback-oneliner-profile",
|
||
summary=f"已回滚 OneLiner 主配置到版本 {target_version.get('version_no') or request.version_id}",
|
||
details={"project_id": project["id"], "rollback_to_version_id": target_version["id"]},
|
||
)
|
||
return _oneliner_profile_bundle(updated_row, account=account)
|
||
|
||
@app.get("/v2/oneliner/sessions")
|
||
def list_oneliner_sessions(
|
||
project_id: str | None = Query(default=None),
|
||
limit: int = Query(default=20, ge=1, le=100),
|
||
account: dict[str, Any] = Depends(legacy.require_approved),
|
||
) -> dict[str, Any]:
|
||
project = _resolve_project(account, project_id or None)
|
||
rows = legacy.db.fetch_all(
|
||
"""
|
||
SELECT * FROM oneliner_sessions
|
||
WHERE user_id = ? AND project_id = ?
|
||
ORDER BY updated_at DESC
|
||
LIMIT ?
|
||
""",
|
||
(account["id"], project["id"], limit),
|
||
)
|
||
items = [_session_payload(row) for row in rows]
|
||
return {"items": items, "count": len(items)}
|
||
|
||
@app.post("/v2/oneliner/sessions")
|
||
async def create_oneliner_session(
|
||
request: OneLinerSessionCreateRequest,
|
||
account: dict[str, Any] = Depends(legacy.require_approved),
|
||
) -> dict[str, Any]:
|
||
project = _resolve_project(account, request.project_id or None)
|
||
profile = _ensure_oneliner_profile(account, project["id"])
|
||
session_id = make_id("oline")
|
||
timestamp = now()
|
||
preferred_platform = _safe_platform(request.preferred_platform or profile.get("default_platform") or "douyin")
|
||
title = request.title.strip() or "新的 OneLiner 会话"
|
||
legacy.db.execute(
|
||
"""
|
||
INSERT INTO oneliner_sessions (
|
||
id, user_id, project_id, profile_id, title, status, preferred_platform,
|
||
last_platform, last_intent_key, last_message_at, created_at, updated_at
|
||
) VALUES (?, ?, ?, ?, ?, 'active', ?, '', '', ?, ?, ?)
|
||
""",
|
||
(
|
||
session_id,
|
||
account["id"],
|
||
project["id"],
|
||
profile["id"],
|
||
title,
|
||
preferred_platform,
|
||
timestamp,
|
||
timestamp,
|
||
timestamp,
|
||
),
|
||
)
|
||
session_row = legacy.db.fetch_one("SELECT * FROM oneliner_sessions WHERE id = ?", (session_id,))
|
||
if request.initial_message.strip():
|
||
await post_oneliner_message(
|
||
session_id,
|
||
OneLinerMessageRequest(
|
||
content=request.initial_message,
|
||
project_id=project["id"],
|
||
platform=preferred_platform,
|
||
),
|
||
account,
|
||
)
|
||
session_row = legacy.db.fetch_one("SELECT * FROM oneliner_sessions WHERE id = ?", (session_id,))
|
||
return _session_payload(session_row)
|
||
|
||
@app.get("/v2/oneliner/sessions/{session_id}/messages")
|
||
def list_oneliner_messages(
|
||
session_id: str,
|
||
account: dict[str, Any] = Depends(legacy.require_approved),
|
||
) -> dict[str, Any]:
|
||
session = _load_owned_session(session_id, account)
|
||
rows = legacy.db.fetch_all(
|
||
"SELECT * FROM oneliner_messages WHERE session_id = ? ORDER BY created_at ASC",
|
||
(session["id"],),
|
||
)
|
||
items = [_message_payload(row) for row in rows]
|
||
return {"session": _session_payload(session), "items": items, "count": len(items)}
|
||
|
||
@app.post("/v2/oneliner/sessions/{session_id}/messages")
|
||
async def post_oneliner_message(
|
||
session_id: str,
|
||
request: OneLinerMessageRequest,
|
||
account: dict[str, Any] = Depends(legacy.require_approved),
|
||
) -> dict[str, Any]:
|
||
session = _load_owned_session(session_id, account)
|
||
project = _resolve_project(account, request.project_id or session.get("project_id") or None)
|
||
plan = await _plan_oneliner_request(
|
||
account,
|
||
project_id=project["id"],
|
||
message=request.content,
|
||
platform_hint=request.platform or session.get("preferred_platform") or "",
|
||
)
|
||
user_message = _insert_message(session["id"], account["id"], "user", request.content.strip(), {}, {})
|
||
remembered = _remember_message_preference(account, project_id=project["id"], plan=plan, message=request.content)
|
||
result = await _generate_oneliner_reply(account, project_id=project["id"], message=request.content, plan=plan)
|
||
if remembered:
|
||
result["remembered_memory"] = remembered
|
||
assistant_message = _insert_message(session["id"], account["id"], "assistant", result["summary_text"], plan, result)
|
||
session_title = session.get("title") or request.content.strip()[:28] or "新的 OneLiner 会话"
|
||
timestamp = now()
|
||
legacy.db.execute(
|
||
"""
|
||
UPDATE oneliner_sessions
|
||
SET title = ?, last_platform = ?, last_intent_key = ?, last_message_at = ?, updated_at = ?
|
||
WHERE id = ?
|
||
""",
|
||
(
|
||
session_title,
|
||
plan.get("platform", ""),
|
||
plan.get("intent_key", ""),
|
||
timestamp,
|
||
timestamp,
|
||
session["id"],
|
||
),
|
||
)
|
||
updated_session = legacy.db.fetch_one("SELECT * FROM oneliner_sessions WHERE id = ?", (session["id"],))
|
||
return {
|
||
"session": _session_payload(updated_session),
|
||
"user_message": _message_payload(user_message),
|
||
"assistant_message": _message_payload(assistant_message),
|
||
"plan": plan,
|
||
"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/oneliner/action-registry")
|
||
def list_oneliner_action_registry(
|
||
project_id: str | None = Query(default=None),
|
||
account: dict[str, Any] = Depends(legacy.require_approved),
|
||
) -> dict[str, Any]:
|
||
project = _resolve_project(account, project_id or None)
|
||
items = _list_action_registry(account, project_id=project["id"])
|
||
return {"items": items, "count": len(items)}
|
||
|
||
@app.put("/v2/oneliner/action-registry/{action_key}")
|
||
def update_oneliner_action_registry(
|
||
action_key: str,
|
||
request: OneLinerActionDefinitionRequest,
|
||
project_id: str | None = Query(default=None),
|
||
account: dict[str, Any] = Depends(legacy.require_approved),
|
||
) -> dict[str, Any]:
|
||
project = _resolve_project(account, project_id or None)
|
||
return _upsert_action_definition(account, project_id=project["id"], action_key=action_key, request=request)
|
||
|
||
@app.get("/v2/platform-agents")
|
||
def list_platform_agents(
|
||
project_id: str | None = Query(default=None),
|
||
account: dict[str, Any] = Depends(legacy.require_approved),
|
||
) -> dict[str, Any]:
|
||
project = _resolve_project(account, project_id or None)
|
||
items = _list_platform_profiles(account, project["id"])
|
||
return {"items": items, "count": len(items)}
|
||
|
||
@app.put("/v2/platform-agents/{platform}/profile")
|
||
def update_platform_agent(
|
||
platform: str,
|
||
request: PlatformAgentProfileRequest,
|
||
account: dict[str, Any] = Depends(legacy.require_approved),
|
||
) -> dict[str, Any]:
|
||
normalized_platform = _safe_platform(platform)
|
||
return _upsert_platform_profile(account, normalized_platform, request)
|
||
|
||
@app.get("/v2/platform-agents/{platform}/profile/versions")
|
||
def list_platform_agent_profile_versions(
|
||
platform: str,
|
||
project_id: str | None = Query(default=None),
|
||
account: dict[str, Any] = Depends(legacy.require_approved),
|
||
) -> dict[str, Any]:
|
||
project = _resolve_project(account, project_id or None)
|
||
normalized_platform = _safe_platform(platform)
|
||
row = legacy.db.fetch_one(
|
||
"SELECT * FROM platform_agent_profiles WHERE user_id = ? AND project_id = ? AND platform = ?",
|
||
(account["id"], project["id"], normalized_platform),
|
||
)
|
||
if not row:
|
||
return {"items": [], "count": 0, "current_version": None}
|
||
_ensure_platform_agent_profile_version_seed(row, actor_user_id=account["id"])
|
||
items = _list_platform_agent_profile_versions(row)
|
||
return {"items": items, "count": len(items), "current_version": items[0] if items else None}
|
||
|
||
@app.get("/v2/platform-agents/{platform}/profile/audits")
|
||
def list_platform_agent_profile_audits(
|
||
platform: str,
|
||
project_id: str | None = Query(default=None),
|
||
limit: int = Query(default=12, ge=1, le=50),
|
||
account: dict[str, Any] = Depends(legacy.require_approved),
|
||
) -> dict[str, Any]:
|
||
project = _resolve_project(account, project_id or None)
|
||
normalized_platform = _safe_platform(platform)
|
||
row = legacy.db.fetch_one(
|
||
"SELECT * FROM platform_agent_profiles WHERE user_id = ? AND project_id = ? AND platform = ?",
|
||
(account["id"], project["id"], normalized_platform),
|
||
)
|
||
if not row:
|
||
return {"items": [], "count": 0}
|
||
_ensure_platform_agent_profile_version_seed(row, actor_user_id=account["id"])
|
||
items = _list_platform_agent_profile_audits(row, limit=limit)
|
||
return {"items": items, "count": len(items)}
|
||
|
||
@app.post("/v2/platform-agents/{platform}/profile/rollback")
|
||
def rollback_platform_agent_profile(
|
||
platform: str,
|
||
request: PlatformAgentProfileRollbackRequest,
|
||
account: dict[str, Any] = Depends(legacy.require_approved),
|
||
) -> dict[str, Any]:
|
||
project = _resolve_project(account, request.project_id or None)
|
||
normalized_platform = _safe_platform(platform)
|
||
row = legacy.db.fetch_one(
|
||
"SELECT * FROM platform_agent_profiles WHERE user_id = ? AND project_id = ? AND platform = ?",
|
||
(account["id"], project["id"], normalized_platform),
|
||
)
|
||
if not row:
|
||
raise HTTPException(status_code=404, detail="Platform agent profile not found")
|
||
_ensure_platform_agent_profile_version_seed(row, actor_user_id=account["id"])
|
||
target_version = legacy.db.fetch_one(
|
||
"""
|
||
SELECT * FROM platform_agent_profile_versions
|
||
WHERE id = ? AND profile_id = ?
|
||
""",
|
||
(request.version_id, row["id"]),
|
||
)
|
||
if not target_version:
|
||
raise HTTPException(status_code=404, detail="Platform agent profile version not found")
|
||
assistant = _resolve_assistant(account, target_version.get("assistant_id", ""), project["id"])
|
||
timestamp = now()
|
||
update_sql = """
|
||
UPDATE platform_agent_profiles
|
||
SET assistant_id = ?, name = ?, mission = ?, notes = ?, status = ?, config_json = ?, updated_at = ?
|
||
WHERE id = ?
|
||
"""
|
||
update_params = (
|
||
(assistant or {}).get("id", ""),
|
||
target_version.get("name", f"{legacy.platform_label(normalized_platform)} Agent"),
|
||
target_version.get("mission", ""),
|
||
target_version.get("notes", ""),
|
||
target_version.get("status", "active"),
|
||
target_version.get("config_json", "{}"),
|
||
timestamp,
|
||
row["id"],
|
||
)
|
||
if (assistant or {}).get("id", ""):
|
||
legacy.db.execute(update_sql, update_params)
|
||
else:
|
||
with legacy.db.session() as conn:
|
||
conn.execute("PRAGMA foreign_keys=OFF")
|
||
conn.execute(update_sql, update_params)
|
||
conn.execute("PRAGMA foreign_keys=ON")
|
||
updated_row = legacy.db.fetch_one("SELECT * FROM platform_agent_profiles WHERE id = ?", (row["id"],))
|
||
assert updated_row is not None
|
||
version = _create_platform_agent_profile_version(
|
||
updated_row,
|
||
actor_user_id=account["id"],
|
||
source_type="user_rollback",
|
||
reason=request.reason.strip() or f"回滚到版本 {target_version.get('version_no') or request.version_id}",
|
||
rollback_from_version_id=target_version["id"],
|
||
)
|
||
_log_platform_agent_profile_audit(
|
||
profile_id=updated_row["id"],
|
||
version_id=version["id"],
|
||
actor_user_id=account["id"],
|
||
action_key="rollback-platform-agent-profile",
|
||
summary=f"已回滚 {legacy.platform_label(normalized_platform)} Agent 配置到版本 {target_version.get('version_no') or request.version_id}",
|
||
details={"project_id": project["id"], "platform": normalized_platform, "rollback_to_version_id": target_version["id"]},
|
||
)
|
||
return _platform_agent_payload(account, updated_row, platform=normalized_platform, project_id=project["id"])
|
||
|
||
@app.get("/v2/platform-agents/{platform}/memories")
|
||
def list_platform_memories(
|
||
platform: str,
|
||
project_id: str | None = Query(default=None),
|
||
account: dict[str, Any] = Depends(legacy.require_approved),
|
||
) -> dict[str, Any]:
|
||
project = _resolve_project(account, project_id or None)
|
||
normalized_platform = _safe_platform(platform)
|
||
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
|
||
""",
|
||
(account["id"], project["id"], normalized_platform),
|
||
)
|
||
items = [_memory_payload(row) for row in rows]
|
||
return {"items": items, "count": len(items)}
|
||
|
||
@app.post("/v2/platform-agents/{platform}/memories")
|
||
def upsert_platform_memory(
|
||
platform: str,
|
||
request: AgentMemoryUpsertRequest,
|
||
account: dict[str, Any] = Depends(legacy.require_approved),
|
||
) -> dict[str, Any]:
|
||
normalized_platform = _safe_platform(platform)
|
||
return _upsert_memory(account, agent_scope="platform", platform=normalized_platform, request=request)
|
||
|
||
@app.get("/v2/platform-agents/{platform}/skills")
|
||
def list_platform_skills(
|
||
platform: str,
|
||
project_id: str | None = Query(default=None),
|
||
account: dict[str, Any] = Depends(legacy.require_approved),
|
||
) -> dict[str, Any]:
|
||
project = _resolve_project(account, project_id or None)
|
||
normalized_platform = _safe_platform(platform)
|
||
rows = legacy.db.fetch_all(
|
||
"""
|
||
SELECT * FROM agent_skills
|
||
WHERE user_id = ? AND project_id = ? AND agent_scope = 'platform' AND platform = ?
|
||
ORDER BY updated_at DESC
|
||
""",
|
||
(account["id"], project["id"], normalized_platform),
|
||
)
|
||
items = [_skill_payload(row) for row in rows]
|
||
return {"items": items, "count": len(items)}
|
||
|
||
@app.post("/v2/platform-agents/{platform}/skills")
|
||
def create_platform_skill(
|
||
platform: str,
|
||
request: AgentSkillUpsertRequest,
|
||
account: dict[str, Any] = Depends(legacy.require_approved),
|
||
) -> dict[str, Any]:
|
||
normalized_platform = _safe_platform(platform)
|
||
return _upsert_skill(account, agent_scope="platform", platform=normalized_platform, request=request)
|
||
|
||
@app.patch("/v2/platform-agents/{platform}/skills/{skill_id}")
|
||
def update_platform_skill(
|
||
platform: str,
|
||
skill_id: str,
|
||
request: AgentSkillUpsertRequest,
|
||
account: dict[str, Any] = Depends(legacy.require_approved),
|
||
) -> dict[str, Any]:
|
||
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/platform-agents/{platform}/skills/{skill_id}/versions")
|
||
def list_platform_skill_versions(
|
||
platform: str,
|
||
skill_id: str,
|
||
project_id: str | None = Query(default=None),
|
||
account: dict[str, Any] = Depends(legacy.require_approved),
|
||
) -> dict[str, Any]:
|
||
project = _resolve_project(account, project_id or None)
|
||
normalized_platform = _safe_platform(platform)
|
||
items = _list_skill_versions(account, platform=normalized_platform, project_id=project["id"], skill_id=skill_id)
|
||
return {"items": items, "count": len(items)}
|
||
|
||
@app.post("/v2/platform-agents/{platform}/skills/{skill_id}/rollback")
|
||
def rollback_platform_skill(
|
||
platform: str,
|
||
skill_id: str,
|
||
request: PlatformSkillRollbackRequest,
|
||
account: dict[str, Any] = Depends(legacy.require_approved),
|
||
) -> dict[str, Any]:
|
||
normalized_platform = _safe_platform(platform)
|
||
return _rollback_platform_skill(account, platform=normalized_platform, skill_id=skill_id, request=request)
|
||
|
||
@app.get("/v2/oneliner/governance/effective")
|
||
def get_effective_oneliner_governance(
|
||
project_id: str | None = Query(default=None),
|
||
platform: str | None = Query(default=None),
|
||
account: dict[str, Any] = Depends(legacy.require_approved),
|
||
) -> dict[str, Any]:
|
||
project = _resolve_project_for_read(account, project_id or None)
|
||
return _effective_policy_payload(
|
||
subject_account=account,
|
||
subject_project_id=(project or {}).get("id", ""),
|
||
platform=platform or "",
|
||
)
|
||
|
||
@app.get("/v2/oneliner/governance/user/global")
|
||
def get_user_global_policy(
|
||
project_id: str | None = Query(default=None),
|
||
account: dict[str, Any] = Depends(legacy.require_approved),
|
||
) -> dict[str, Any]:
|
||
project = _resolve_project_for_read(account, project_id or None)
|
||
resolved_project_id = (project or {}).get("id", "")
|
||
scope_row = _policy_scope_row(
|
||
scope_kind="user_global",
|
||
subject_user_id=account["id"],
|
||
subject_project_id=resolved_project_id,
|
||
)
|
||
payload = _bundle_with_versions(
|
||
scope_row,
|
||
fallback_kind="user_global",
|
||
fallback_user_id=account["id"],
|
||
fallback_project_id=resolved_project_id,
|
||
active_version_only=True,
|
||
)
|
||
payload["effective_policy"] = _effective_policy_payload(
|
||
subject_account=account,
|
||
subject_project_id=resolved_project_id,
|
||
platform="",
|
||
)["effective_policy"]
|
||
return payload
|
||
|
||
@app.put("/v2/oneliner/governance/user/global")
|
||
def put_user_global_policy(
|
||
request: AgentPolicyUpsertRequest,
|
||
account: dict[str, Any] = Depends(legacy.require_approved),
|
||
) -> dict[str, Any]:
|
||
project = _resolve_project(account, request.project_id or None)
|
||
scope_row = _ensure_policy_scope(
|
||
scope_kind="user_global",
|
||
subject_user_id=account["id"],
|
||
subject_project_id=project["id"],
|
||
title=request.title,
|
||
summary=request.summary,
|
||
)
|
||
payload = _create_policy_version(
|
||
scope_row,
|
||
actor_user_id=account["id"],
|
||
title=request.title,
|
||
summary=request.summary,
|
||
policy=request.policy,
|
||
effect_mode=request.effect_mode,
|
||
starts_at=request.starts_at,
|
||
ends_at=request.ends_at,
|
||
config=request.config,
|
||
reason=request.reason,
|
||
source_type="user",
|
||
)
|
||
payload["effective_policy"] = _effective_policy_payload(
|
||
subject_account=account,
|
||
subject_project_id=project["id"],
|
||
platform="",
|
||
)["effective_policy"]
|
||
payload["audit"] = _log_policy_audit(
|
||
scope_id=payload["scope"]["id"],
|
||
version_id=(payload.get("current_version") or {}).get("id", ""),
|
||
actor_user_id=account["id"],
|
||
action_key="publish-user-global-policy",
|
||
summary=f"已更新用户全局策略:{payload['scope']['title']}",
|
||
details={"project_id": project["id"]},
|
||
)
|
||
return payload
|
||
|
||
@app.get("/v2/oneliner/governance/user/global/versions")
|
||
def list_user_global_policy_versions(
|
||
project_id: str | None = Query(default=None),
|
||
account: dict[str, Any] = Depends(legacy.require_approved),
|
||
) -> dict[str, Any]:
|
||
project = _resolve_project_for_read(account, project_id or None)
|
||
scope_row = _policy_scope_row(
|
||
scope_kind="user_global",
|
||
subject_user_id=account["id"],
|
||
subject_project_id=(project or {}).get("id", ""),
|
||
)
|
||
items = _list_policy_versions(scope_row)
|
||
return {"items": items, "count": len(items)}
|
||
|
||
@app.get("/v2/oneliner/governance/user/audits")
|
||
def list_user_policy_audits(
|
||
project_id: str | None = Query(default=None),
|
||
platform: str = Query(default=""),
|
||
limit: int = Query(default=20, ge=1, le=100),
|
||
account: dict[str, Any] = Depends(legacy.require_approved),
|
||
) -> dict[str, Any]:
|
||
project = _resolve_project_for_read(account, project_id or None)
|
||
resolved_project_id = (project or {}).get("id", "")
|
||
normalized_platform = _normalize_policy_platform(platform)
|
||
where_clauses = [
|
||
"WHERE scope.subject_user_id = ?",
|
||
"AND (scope.subject_project_id = ? OR scope.subject_project_id = '')",
|
||
]
|
||
params: list[Any] = [account["id"], resolved_project_id]
|
||
if normalized_platform:
|
||
where_clauses.append("AND (scope.platform = '' OR scope.platform = ?)")
|
||
params.append(normalized_platform)
|
||
items = _fetch_policy_audit_records(" ".join(where_clauses), tuple(params), limit=limit, public_view=True)
|
||
return {"items": items, "count": len(items)}
|
||
|
||
@app.post("/v2/oneliner/governance/user/global/rollback")
|
||
def rollback_user_global_policy(
|
||
request: AgentPolicyRollbackRequest,
|
||
account: dict[str, Any] = Depends(legacy.require_approved),
|
||
) -> dict[str, Any]:
|
||
project = _resolve_project(account, request.project_id or None)
|
||
scope_row = _policy_scope_row(
|
||
scope_kind="user_global",
|
||
subject_user_id=account["id"],
|
||
subject_project_id=project["id"],
|
||
)
|
||
if not scope_row:
|
||
raise HTTPException(status_code=404, detail="Policy scope not found")
|
||
payload = _rollback_policy_scope(
|
||
scope_row,
|
||
actor_user_id=account["id"],
|
||
version_id=request.version_id,
|
||
reason=request.reason,
|
||
source_type="user_rollback",
|
||
)
|
||
payload["effective_policy"] = _effective_policy_payload(
|
||
subject_account=account,
|
||
subject_project_id=project["id"],
|
||
platform="",
|
||
)["effective_policy"]
|
||
payload["audit"] = _log_policy_audit(
|
||
scope_id=payload["scope"]["id"],
|
||
version_id=(payload.get("current_version") or {}).get("id", ""),
|
||
actor_user_id=account["id"],
|
||
action_key="rollback-user-global-policy",
|
||
summary=f"已回滚用户全局策略:{payload['scope']['title']}",
|
||
details={"project_id": project["id"], "rollback_to_version_id": request.version_id},
|
||
)
|
||
return payload
|
||
|
||
@app.get("/v2/oneliner/governance/user/platforms/{platform}")
|
||
def get_user_platform_policy(
|
||
platform: str,
|
||
project_id: str | None = Query(default=None),
|
||
account: dict[str, Any] = Depends(legacy.require_approved),
|
||
) -> dict[str, Any]:
|
||
project = _resolve_project_for_read(account, project_id or None)
|
||
resolved_project_id = (project or {}).get("id", "")
|
||
normalized_platform = _normalize_policy_platform(platform)
|
||
scope_row = _policy_scope_row(
|
||
scope_kind="user_platform",
|
||
subject_user_id=account["id"],
|
||
subject_project_id=resolved_project_id,
|
||
platform=normalized_platform,
|
||
)
|
||
payload = _bundle_with_versions(
|
||
scope_row,
|
||
fallback_kind="user_platform",
|
||
fallback_platform=normalized_platform,
|
||
fallback_user_id=account["id"],
|
||
fallback_project_id=resolved_project_id,
|
||
active_version_only=True,
|
||
)
|
||
payload["effective_policy"] = _effective_policy_payload(
|
||
subject_account=account,
|
||
subject_project_id=resolved_project_id,
|
||
platform=normalized_platform,
|
||
)["effective_policy"]
|
||
return payload
|
||
|
||
@app.put("/v2/oneliner/governance/user/platforms/{platform}")
|
||
def put_user_platform_policy(
|
||
platform: str,
|
||
request: AgentPolicyUpsertRequest,
|
||
account: dict[str, Any] = Depends(legacy.require_approved),
|
||
) -> dict[str, Any]:
|
||
project = _resolve_project(account, request.project_id or None)
|
||
normalized_platform = _normalize_policy_platform(platform)
|
||
scope_row = _ensure_policy_scope(
|
||
scope_kind="user_platform",
|
||
subject_user_id=account["id"],
|
||
subject_project_id=project["id"],
|
||
platform=normalized_platform,
|
||
title=request.title,
|
||
summary=request.summary,
|
||
)
|
||
payload = _create_policy_version(
|
||
scope_row,
|
||
actor_user_id=account["id"],
|
||
title=request.title,
|
||
summary=request.summary,
|
||
policy=request.policy,
|
||
effect_mode=request.effect_mode,
|
||
starts_at=request.starts_at,
|
||
ends_at=request.ends_at,
|
||
config=request.config,
|
||
reason=request.reason,
|
||
source_type="user",
|
||
)
|
||
payload["effective_policy"] = _effective_policy_payload(
|
||
subject_account=account,
|
||
subject_project_id=project["id"],
|
||
platform=normalized_platform,
|
||
)["effective_policy"]
|
||
payload["audit"] = _log_policy_audit(
|
||
scope_id=payload["scope"]["id"],
|
||
version_id=(payload.get("current_version") or {}).get("id", ""),
|
||
actor_user_id=account["id"],
|
||
action_key="publish-user-platform-policy",
|
||
summary=f"已更新 {legacy.platform_label(normalized_platform)} 用户平台策略",
|
||
details={"project_id": project["id"], "platform": normalized_platform},
|
||
)
|
||
return payload
|
||
|
||
@app.get("/v2/oneliner/governance/user/platforms/{platform}/versions")
|
||
def list_user_platform_policy_versions(
|
||
platform: str,
|
||
project_id: str | None = Query(default=None),
|
||
account: dict[str, Any] = Depends(legacy.require_approved),
|
||
) -> dict[str, Any]:
|
||
project = _resolve_project_for_read(account, project_id or None)
|
||
normalized_platform = _normalize_policy_platform(platform)
|
||
scope_row = _policy_scope_row(
|
||
scope_kind="user_platform",
|
||
subject_user_id=account["id"],
|
||
subject_project_id=(project or {}).get("id", ""),
|
||
platform=normalized_platform,
|
||
)
|
||
items = _list_policy_versions(scope_row)
|
||
return {"items": items, "count": len(items)}
|
||
|
||
@app.post("/v2/oneliner/governance/user/platforms/{platform}/rollback")
|
||
def rollback_user_platform_policy(
|
||
platform: str,
|
||
request: AgentPolicyRollbackRequest,
|
||
account: dict[str, Any] = Depends(legacy.require_approved),
|
||
) -> dict[str, Any]:
|
||
project = _resolve_project(account, request.project_id or None)
|
||
normalized_platform = _normalize_policy_platform(platform)
|
||
scope_row = _policy_scope_row(
|
||
scope_kind="user_platform",
|
||
subject_user_id=account["id"],
|
||
subject_project_id=project["id"],
|
||
platform=normalized_platform,
|
||
)
|
||
if not scope_row:
|
||
raise HTTPException(status_code=404, detail="Policy scope not found")
|
||
payload = _rollback_policy_scope(
|
||
scope_row,
|
||
actor_user_id=account["id"],
|
||
version_id=request.version_id,
|
||
reason=request.reason,
|
||
source_type="user_rollback",
|
||
)
|
||
payload["effective_policy"] = _effective_policy_payload(
|
||
subject_account=account,
|
||
subject_project_id=project["id"],
|
||
platform=normalized_platform,
|
||
)["effective_policy"]
|
||
payload["audit"] = _log_policy_audit(
|
||
scope_id=payload["scope"]["id"],
|
||
version_id=(payload.get("current_version") or {}).get("id", ""),
|
||
actor_user_id=account["id"],
|
||
action_key="rollback-user-platform-policy",
|
||
summary=f"已回滚 {legacy.platform_label(normalized_platform)} 用户平台策略",
|
||
details={"project_id": project["id"], "platform": normalized_platform, "rollback_to_version_id": request.version_id},
|
||
)
|
||
return payload
|
||
|
||
@app.get("/v2/admin/oneliner/governance/system/main-agent")
|
||
def get_system_main_policy(admin: dict[str, Any] = Depends(legacy.require_super_admin)) -> dict[str, Any]:
|
||
_ = admin
|
||
scope_row = _policy_scope_row(scope_kind="system_main")
|
||
return _bundle_with_versions(scope_row, fallback_kind="system_main", active_version_only=True)
|
||
|
||
@app.put("/v2/admin/oneliner/governance/system/main-agent")
|
||
def put_system_main_policy(
|
||
request: AgentPolicyUpsertRequest,
|
||
admin: dict[str, Any] = Depends(legacy.require_super_admin),
|
||
) -> dict[str, Any]:
|
||
scope_row = _ensure_policy_scope(
|
||
scope_kind="system_main",
|
||
title=request.title,
|
||
summary=request.summary,
|
||
)
|
||
payload = _create_policy_version(
|
||
scope_row,
|
||
actor_user_id=admin["id"],
|
||
title=request.title,
|
||
summary=request.summary,
|
||
policy=request.policy,
|
||
effect_mode=request.effect_mode,
|
||
starts_at=request.starts_at,
|
||
ends_at=request.ends_at,
|
||
config=request.config,
|
||
reason=request.reason,
|
||
source_type="system",
|
||
)
|
||
payload["audit"] = _log_policy_audit(
|
||
scope_id=payload["scope"]["id"],
|
||
version_id=(payload.get("current_version") or {}).get("id", ""),
|
||
actor_user_id=admin["id"],
|
||
action_key="publish-system-main-policy",
|
||
summary=f"已发布系统主 Agent 策略:{payload['scope']['title']}",
|
||
)
|
||
return payload
|
||
|
||
@app.get("/v2/admin/oneliner/governance/system/main-agent/versions")
|
||
def list_system_main_policy_versions(admin: dict[str, Any] = Depends(legacy.require_super_admin)) -> dict[str, Any]:
|
||
_ = admin
|
||
scope_row = _policy_scope_row(scope_kind="system_main")
|
||
items = _list_policy_versions(scope_row)
|
||
return {"items": items, "count": len(items)}
|
||
|
||
@app.post("/v2/admin/oneliner/governance/system/main-agent/rollback")
|
||
def rollback_system_main_policy(
|
||
request: AgentPolicyRollbackRequest,
|
||
admin: dict[str, Any] = Depends(legacy.require_super_admin),
|
||
) -> dict[str, Any]:
|
||
scope_row = _policy_scope_row(scope_kind="system_main")
|
||
if not scope_row:
|
||
raise HTTPException(status_code=404, detail="Policy scope not found")
|
||
payload = _rollback_policy_scope(
|
||
scope_row,
|
||
actor_user_id=admin["id"],
|
||
version_id=request.version_id,
|
||
reason=request.reason,
|
||
source_type="admin_rollback",
|
||
)
|
||
payload["audit"] = _log_policy_audit(
|
||
scope_id=payload["scope"]["id"],
|
||
version_id=(payload.get("current_version") or {}).get("id", ""),
|
||
actor_user_id=admin["id"],
|
||
action_key="rollback-system-main-policy",
|
||
summary=f"已回滚系统主 Agent 策略:{payload['scope']['title']}",
|
||
details={"rollback_to_version_id": request.version_id},
|
||
)
|
||
return payload
|
||
|
||
@app.get("/v2/admin/oneliner/governance/system/platforms/{platform}")
|
||
def get_system_platform_policy(
|
||
platform: str,
|
||
admin: dict[str, Any] = Depends(legacy.require_super_admin),
|
||
) -> dict[str, Any]:
|
||
_ = admin
|
||
normalized_platform = _normalize_policy_platform(platform)
|
||
scope_row = _policy_scope_row(scope_kind="system_platform", platform=normalized_platform)
|
||
return _bundle_with_versions(
|
||
scope_row,
|
||
fallback_kind="system_platform",
|
||
fallback_platform=normalized_platform,
|
||
active_version_only=True,
|
||
)
|
||
|
||
@app.put("/v2/admin/oneliner/governance/system/platforms/{platform}")
|
||
def put_system_platform_policy(
|
||
platform: str,
|
||
request: AgentPolicyUpsertRequest,
|
||
admin: dict[str, Any] = Depends(legacy.require_super_admin),
|
||
) -> dict[str, Any]:
|
||
normalized_platform = _normalize_policy_platform(platform)
|
||
scope_row = _ensure_policy_scope(
|
||
scope_kind="system_platform",
|
||
platform=normalized_platform,
|
||
title=request.title,
|
||
summary=request.summary,
|
||
)
|
||
payload = _create_policy_version(
|
||
scope_row,
|
||
actor_user_id=admin["id"],
|
||
title=request.title,
|
||
summary=request.summary,
|
||
policy=request.policy,
|
||
effect_mode=request.effect_mode,
|
||
starts_at=request.starts_at,
|
||
ends_at=request.ends_at,
|
||
config=request.config,
|
||
reason=request.reason,
|
||
source_type="system",
|
||
)
|
||
payload["audit"] = _log_policy_audit(
|
||
scope_id=payload["scope"]["id"],
|
||
version_id=(payload.get("current_version") or {}).get("id", ""),
|
||
actor_user_id=admin["id"],
|
||
action_key="publish-system-platform-policy",
|
||
summary=f"已发布 {legacy.platform_label(normalized_platform)} 系统平台策略",
|
||
details={"platform": normalized_platform},
|
||
)
|
||
return payload
|
||
|
||
@app.get("/v2/admin/oneliner/governance/system/platforms/{platform}/versions")
|
||
def list_system_platform_policy_versions(
|
||
platform: str,
|
||
admin: dict[str, Any] = Depends(legacy.require_super_admin),
|
||
) -> dict[str, Any]:
|
||
_ = admin
|
||
normalized_platform = _normalize_policy_platform(platform)
|
||
scope_row = _policy_scope_row(scope_kind="system_platform", platform=normalized_platform)
|
||
items = _list_policy_versions(scope_row)
|
||
return {"items": items, "count": len(items)}
|
||
|
||
@app.post("/v2/admin/oneliner/governance/system/platforms/{platform}/rollback")
|
||
def rollback_system_platform_policy(
|
||
platform: str,
|
||
request: AgentPolicyRollbackRequest,
|
||
admin: dict[str, Any] = Depends(legacy.require_super_admin),
|
||
) -> dict[str, Any]:
|
||
normalized_platform = _normalize_policy_platform(platform)
|
||
scope_row = _policy_scope_row(scope_kind="system_platform", platform=normalized_platform)
|
||
if not scope_row:
|
||
raise HTTPException(status_code=404, detail="Policy scope not found")
|
||
payload = _rollback_policy_scope(
|
||
scope_row,
|
||
actor_user_id=admin["id"],
|
||
version_id=request.version_id,
|
||
reason=request.reason,
|
||
source_type="admin_rollback",
|
||
)
|
||
payload["audit"] = _log_policy_audit(
|
||
scope_id=payload["scope"]["id"],
|
||
version_id=(payload.get("current_version") or {}).get("id", ""),
|
||
actor_user_id=admin["id"],
|
||
action_key="rollback-system-platform-policy",
|
||
summary=f"已回滚 {legacy.platform_label(normalized_platform)} 系统平台策略",
|
||
details={"platform": normalized_platform, "rollback_to_version_id": request.version_id},
|
||
)
|
||
return payload
|
||
|
||
@app.get("/v2/admin/oneliner/governance/directory")
|
||
def get_admin_governance_directory(
|
||
admin: dict[str, Any] = Depends(legacy.require_super_admin),
|
||
) -> dict[str, Any]:
|
||
_ = admin
|
||
return _governance_directory_payload()
|
||
|
||
@app.get("/v2/admin/oneliner/governance/audits")
|
||
def list_admin_governance_audits(
|
||
target_user_id: str = Query(default=""),
|
||
target_project_id: str = Query(default=""),
|
||
platform: str = Query(default=""),
|
||
include_system: bool = Query(default=False),
|
||
limit: int = Query(default=30, ge=1, le=100),
|
||
admin: dict[str, Any] = Depends(legacy.require_super_admin),
|
||
) -> dict[str, Any]:
|
||
_ = admin
|
||
normalized_platform = _normalize_policy_platform(platform)
|
||
clauses: list[str] = []
|
||
params: list[Any] = []
|
||
|
||
if target_user_id.strip():
|
||
target_parts = ["(scope.subject_user_id = ?"]
|
||
params.append(target_user_id.strip())
|
||
if target_project_id.strip():
|
||
target_parts.append("AND (scope.subject_project_id = ? OR scope.subject_project_id = '')")
|
||
params.append(target_project_id.strip())
|
||
if normalized_platform:
|
||
target_parts.append("AND (scope.platform = '' OR scope.platform = ?)")
|
||
params.append(normalized_platform)
|
||
target_parts.append(")")
|
||
clauses.append(" ".join(target_parts))
|
||
|
||
if include_system:
|
||
if normalized_platform:
|
||
clauses.append("(scope.scope_kind = 'system_main' OR (scope.scope_kind = 'system_platform' AND scope.platform = ?))")
|
||
params.append(normalized_platform)
|
||
else:
|
||
clauses.append("(scope.scope_kind IN ('system_main', 'system_platform'))")
|
||
|
||
where_sql = "WHERE 1 = 1" if not clauses else "WHERE " + " OR ".join(f"({clause})" for clause in clauses)
|
||
items = _fetch_policy_audit_records(where_sql, tuple(params), limit=limit)
|
||
return {"items": items, "count": len(items)}
|
||
|
||
@app.get("/v2/admin/oneliner/governance/overrides")
|
||
def get_admin_override_policy(
|
||
target_user_id: str = Query(default=""),
|
||
target_project_id: str = Query(default=""),
|
||
platform: str | None = Query(default=None),
|
||
admin: dict[str, Any] = Depends(legacy.require_super_admin),
|
||
) -> dict[str, Any]:
|
||
_ = admin
|
||
if not target_user_id.strip():
|
||
raise HTTPException(status_code=400, detail="target_user_id is required")
|
||
subject_account = _load_policy_subject_account(target_user_id.strip())
|
||
subject_project_id = ""
|
||
if target_project_id.strip():
|
||
subject_project_id = _load_policy_subject_project(user_id=subject_account["id"], project_id=target_project_id.strip())["id"]
|
||
normalized_platform = _normalize_policy_platform(platform)
|
||
scope_row = _policy_scope_row(
|
||
scope_kind="admin_override",
|
||
subject_user_id=subject_account["id"],
|
||
subject_project_id=subject_project_id,
|
||
platform=normalized_platform,
|
||
)
|
||
payload = _bundle_with_versions(
|
||
scope_row,
|
||
fallback_kind="admin_override",
|
||
fallback_platform=normalized_platform,
|
||
fallback_user_id=subject_account["id"],
|
||
fallback_project_id=subject_project_id,
|
||
active_version_only=True,
|
||
)
|
||
payload.update(
|
||
_effective_policy_payload(
|
||
subject_account=subject_account,
|
||
subject_project_id=subject_project_id,
|
||
platform=normalized_platform,
|
||
)
|
||
)
|
||
return payload
|
||
|
||
@app.post("/v2/admin/oneliner/governance/overrides")
|
||
def put_admin_override_policy(
|
||
request: AgentPolicyUpsertRequest,
|
||
admin: dict[str, Any] = Depends(legacy.require_super_admin),
|
||
) -> dict[str, Any]:
|
||
if not request.target_user_id.strip():
|
||
raise HTTPException(status_code=400, detail="target_user_id is required")
|
||
subject_account = _load_policy_subject_account(request.target_user_id.strip())
|
||
subject_project_id = ""
|
||
if request.target_project_id.strip():
|
||
subject_project_id = _load_policy_subject_project(user_id=subject_account["id"], project_id=request.target_project_id.strip())["id"]
|
||
normalized_platform = _normalize_policy_platform(request.platform)
|
||
scope_row = _ensure_policy_scope(
|
||
scope_kind="admin_override",
|
||
subject_user_id=subject_account["id"],
|
||
subject_project_id=subject_project_id,
|
||
platform=normalized_platform,
|
||
title=request.title,
|
||
summary=request.summary,
|
||
)
|
||
payload = _create_policy_version(
|
||
scope_row,
|
||
actor_user_id=admin["id"],
|
||
title=request.title,
|
||
summary=request.summary,
|
||
policy=request.policy,
|
||
effect_mode=request.effect_mode,
|
||
starts_at=request.starts_at,
|
||
ends_at=request.ends_at,
|
||
config=request.config,
|
||
reason=request.reason,
|
||
source_type="admin_override",
|
||
)
|
||
payload.update(
|
||
_effective_policy_payload(
|
||
subject_account=subject_account,
|
||
subject_project_id=subject_project_id,
|
||
platform=normalized_platform,
|
||
)
|
||
)
|
||
payload["audit"] = _log_policy_audit(
|
||
scope_id=payload["scope"]["id"],
|
||
version_id=(payload.get("current_version") or {}).get("id", ""),
|
||
actor_user_id=admin["id"],
|
||
action_key="publish-admin-override-policy",
|
||
summary=f"已为 {subject_account.get('username') or subject_account['id']} 发布管理员覆盖策略",
|
||
details={"target_user_id": subject_account["id"], "target_project_id": subject_project_id, "platform": normalized_platform},
|
||
)
|
||
return payload
|
||
|
||
@app.get("/v2/admin/oneliner/governance/overrides/versions")
|
||
def list_admin_override_versions(
|
||
target_user_id: str = Query(default=""),
|
||
target_project_id: str = Query(default=""),
|
||
platform: str | None = Query(default=None),
|
||
admin: dict[str, Any] = Depends(legacy.require_super_admin),
|
||
) -> dict[str, Any]:
|
||
_ = admin
|
||
if not target_user_id.strip():
|
||
raise HTTPException(status_code=400, detail="target_user_id is required")
|
||
subject_account = _load_policy_subject_account(target_user_id.strip())
|
||
subject_project_id = ""
|
||
if target_project_id.strip():
|
||
subject_project_id = _load_policy_subject_project(user_id=subject_account["id"], project_id=target_project_id.strip())["id"]
|
||
normalized_platform = _normalize_policy_platform(platform)
|
||
scope_row = _policy_scope_row(
|
||
scope_kind="admin_override",
|
||
subject_user_id=subject_account["id"],
|
||
subject_project_id=subject_project_id,
|
||
platform=normalized_platform,
|
||
)
|
||
items = _list_policy_versions(scope_row)
|
||
return {"items": items, "count": len(items)}
|
||
|
||
@app.post("/v2/admin/oneliner/governance/overrides/rollback")
|
||
def rollback_admin_override_policy(
|
||
request: AgentPolicyRollbackRequest,
|
||
admin: dict[str, Any] = Depends(legacy.require_super_admin),
|
||
) -> dict[str, Any]:
|
||
if not request.target_user_id.strip():
|
||
raise HTTPException(status_code=400, detail="target_user_id is required")
|
||
subject_account = _load_policy_subject_account(request.target_user_id.strip())
|
||
subject_project_id = ""
|
||
if request.target_project_id.strip():
|
||
subject_project_id = _load_policy_subject_project(user_id=subject_account["id"], project_id=request.target_project_id.strip())["id"]
|
||
normalized_platform = _normalize_policy_platform(request.platform)
|
||
scope_row = _policy_scope_row(
|
||
scope_kind="admin_override",
|
||
subject_user_id=subject_account["id"],
|
||
subject_project_id=subject_project_id,
|
||
platform=normalized_platform,
|
||
)
|
||
if not scope_row:
|
||
raise HTTPException(status_code=404, detail="Policy scope not found")
|
||
payload = _rollback_policy_scope(
|
||
scope_row,
|
||
actor_user_id=admin["id"],
|
||
version_id=request.version_id,
|
||
reason=request.reason,
|
||
source_type="admin_rollback",
|
||
)
|
||
payload.update(
|
||
_effective_policy_payload(
|
||
subject_account=subject_account,
|
||
subject_project_id=subject_project_id,
|
||
platform=normalized_platform,
|
||
)
|
||
)
|
||
payload["audit"] = _log_policy_audit(
|
||
scope_id=payload["scope"]["id"],
|
||
version_id=(payload.get("current_version") or {}).get("id", ""),
|
||
actor_user_id=admin["id"],
|
||
action_key="rollback-admin-override-policy",
|
||
summary=f"已回滚 {subject_account.get('username') or subject_account['id']} 的管理员覆盖策略",
|
||
details={"target_user_id": subject_account["id"], "target_project_id": subject_project_id, "platform": normalized_platform, "rollback_to_version_id": request.version_id},
|
||
)
|
||
return payload
|
||
|
||
@app.get("/v2/tenant/quota")
|
||
def get_tenant_quota(
|
||
project_id: str | None = Query(default=None),
|
||
account: dict[str, Any] = Depends(legacy.require_approved),
|
||
) -> dict[str, Any]:
|
||
project = _resolve_project(account, project_id or None)
|
||
return _get_tenant_quota(account, project_id=project["id"])
|
||
|
||
@app.put("/v2/tenant/quota")
|
||
def put_tenant_quota(
|
||
request: TenantQuotaRequest,
|
||
project_id: str | None = Query(default=None),
|
||
account: dict[str, Any] = Depends(legacy.require_approved),
|
||
) -> dict[str, Any]:
|
||
project = _resolve_project(account, project_id or None)
|
||
existing = _get_tenant_quota_row(account, project_id=project["id"])
|
||
timestamp = now()
|
||
request_config = dict(request.config or {})
|
||
requested_package_label = _normalize_package_label(request.package_label or request_config.get("package_label"))
|
||
package_label = requested_package_label if requested_package_label in TENANT_QUOTA_PACKAGE_PRESETS else "custom"
|
||
warn_threshold_value = _clamp_warn_threshold(
|
||
request_config.get("warn_threshold", 0.8),
|
||
TENANT_QUOTA_PACKAGE_PRESETS.get(package_label, {}).get("warn_threshold", 0.8),
|
||
)
|
||
normalized_config = dict(request_config)
|
||
normalized_config["package_label"] = package_label
|
||
normalized_config["warn_threshold"] = warn_threshold_value
|
||
quota_values = _tenant_quota_values(request, package_label)
|
||
if existing:
|
||
legacy.db.execute(
|
||
"""
|
||
UPDATE tenant_quota_profiles
|
||
SET monthly_budget_cents = ?, storage_limit_bytes = ?, analysis_quota = ?, copy_quota = ?,
|
||
ai_video_quota = ?, real_cut_quota = ?, recorder_quota = ?, enabled = ?, config_json = ?, updated_at = ?
|
||
WHERE id = ?
|
||
""",
|
||
(
|
||
quota_values["monthly_budget_cents"],
|
||
quota_values["storage_limit_bytes"],
|
||
quota_values["analysis_quota"],
|
||
quota_values["copy_quota"],
|
||
quota_values["ai_video_quota"],
|
||
quota_values["real_cut_quota"],
|
||
quota_values["recorder_quota"],
|
||
1 if request.enabled else 0,
|
||
_dump(normalized_config),
|
||
timestamp,
|
||
existing["id"],
|
||
),
|
||
)
|
||
else:
|
||
quota_id = make_id("quota")
|
||
legacy.db.execute(
|
||
"""
|
||
INSERT INTO tenant_quota_profiles (
|
||
id, user_id, project_id, monthly_budget_cents, storage_limit_bytes, analysis_quota, copy_quota,
|
||
ai_video_quota, real_cut_quota, recorder_quota, enabled, config_json, created_at, updated_at
|
||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||
""",
|
||
(
|
||
quota_id,
|
||
account["id"],
|
||
project["id"],
|
||
quota_values["monthly_budget_cents"],
|
||
quota_values["storage_limit_bytes"],
|
||
quota_values["analysis_quota"],
|
||
quota_values["copy_quota"],
|
||
quota_values["ai_video_quota"],
|
||
quota_values["real_cut_quota"],
|
||
quota_values["recorder_quota"],
|
||
1 if request.enabled else 0,
|
||
_dump(normalized_config),
|
||
timestamp,
|
||
timestamp,
|
||
),
|
||
)
|
||
return _get_tenant_quota(account, project_id=project["id"])
|
||
|
||
@app.get("/v2/tenant/usage")
|
||
def get_tenant_usage(
|
||
project_id: str | None = Query(default=None),
|
||
account: dict[str, Any] = Depends(legacy.require_approved),
|
||
) -> dict[str, Any]:
|
||
project = _resolve_project(account, project_id or None)
|
||
return _tenant_usage_summary(account, project_id=project["id"])
|
||
|
||
@app.get("/v2/admin/ops/overview")
|
||
def admin_ops_overview(admin: dict[str, Any] = Depends(legacy.require_super_admin)) -> dict[str, Any]:
|
||
return _admin_ops_overview_payload(admin)
|
||
|
||
@app.get("/v2/admin/ops/fix-runs")
|
||
def admin_ops_fix_runs(admin: dict[str, Any] = Depends(legacy.require_super_admin)) -> dict[str, Any]:
|
||
rows = legacy.db.fetch_all(
|
||
"SELECT * FROM admin_ops_fix_runs ORDER BY updated_at DESC LIMIT 50"
|
||
)
|
||
items = [_fix_run_payload(row) for row in rows]
|
||
return {"items": items, "count": len(items)}
|
||
|
||
@app.post("/v2/admin/ops/incidents/scan")
|
||
def admin_ops_scan(admin: dict[str, Any] = Depends(legacy.require_super_admin)) -> dict[str, Any]:
|
||
return _scan_admin_incidents(admin)
|
||
|
||
@app.post("/v2/admin/ops/incidents/{incident_id}/repair-plan")
|
||
def create_admin_repair_plan(
|
||
incident_id: str,
|
||
request: AdminFixPlanRequest,
|
||
admin: dict[str, Any] = Depends(legacy.require_super_admin),
|
||
) -> dict[str, Any]:
|
||
incident_row = legacy.db.fetch_one("SELECT * FROM admin_ops_incidents WHERE id = ?", (incident_id,))
|
||
if not incident_row:
|
||
raise HTTPException(status_code=404, detail="Incident not found")
|
||
incident = _incident_payload(incident_row)
|
||
payload = _create_fix_run(
|
||
admin,
|
||
incident=incident,
|
||
scope=request.scope.strip() or "plan",
|
||
notes=request.notes,
|
||
)
|
||
payload["audit"] = _log_admin_audit_event(
|
||
actor_user_id=admin["id"],
|
||
incident_id=incident_id,
|
||
action_key="repair-plan",
|
||
status="planned",
|
||
summary=f"已为事件「{incident.get('title') or incident_id}」生成修复计划。",
|
||
details={"fix_run_id": payload["id"], "scope": payload.get("plan_scope", "plan")},
|
||
)
|
||
return payload
|
||
|
||
@app.patch("/v2/admin/ops/incidents/{incident_id}")
|
||
def review_admin_incident(
|
||
incident_id: str,
|
||
request: AdminIncidentReviewRequest,
|
||
admin: dict[str, Any] = Depends(legacy.require_super_admin),
|
||
) -> dict[str, Any]:
|
||
current = legacy.db.fetch_one("SELECT * FROM admin_ops_incidents WHERE id = ?", (incident_id,))
|
||
if not current:
|
||
raise HTTPException(status_code=404, detail="Incident not found")
|
||
timestamp = now()
|
||
legacy.db.execute(
|
||
"""
|
||
UPDATE admin_ops_incidents
|
||
SET status = ?, reviewed_by = ?, review_notes = ?, updated_at = ?
|
||
WHERE id = ?
|
||
""",
|
||
(
|
||
request.status.strip() or "reviewed",
|
||
admin["id"],
|
||
request.review_notes.strip(),
|
||
timestamp,
|
||
incident_id,
|
||
),
|
||
)
|
||
row = legacy.db.fetch_one("SELECT * FROM admin_ops_incidents WHERE id = ?", (incident_id,))
|
||
payload = _incident_payload(row)
|
||
payload["audit"] = _log_admin_audit_event(
|
||
actor_user_id=admin["id"],
|
||
incident_id=incident_id,
|
||
action_key="review",
|
||
status=payload.get("status", "reviewed"),
|
||
summary=f"事件「{payload.get('title', incident_id)}」已更新为 {payload.get('status', 'reviewed')}。",
|
||
details={
|
||
"review_notes": request.review_notes.strip(),
|
||
"severity": payload.get("severity", ""),
|
||
"source_type": payload.get("source_type", ""),
|
||
},
|
||
)
|
||
return payload
|
||
|
||
@app.post("/v2/admin/ops/fix-runs/{run_id}/audit")
|
||
def audit_admin_fix_run(
|
||
run_id: str,
|
||
request: AdminFixRunReviewRequest,
|
||
admin: dict[str, Any] = Depends(legacy.require_super_admin),
|
||
) -> dict[str, Any]:
|
||
current = legacy.db.fetch_one("SELECT * FROM admin_ops_fix_runs WHERE id = ?", (run_id,))
|
||
if not current:
|
||
raise HTTPException(status_code=404, detail="Fix run not found")
|
||
incident_row = legacy.db.fetch_one("SELECT * FROM admin_ops_incidents WHERE id = ?", (current["incident_id"],))
|
||
review_status = request.review_status.strip() or "approved"
|
||
timestamp = now()
|
||
next_status = "audited" if review_status in {"approved", "rejected"} else "watching"
|
||
legacy.db.execute(
|
||
"""
|
||
UPDATE admin_ops_fix_runs
|
||
SET audit_status = ?, review_notes = ?, status = ?, updated_at = ?
|
||
WHERE id = ?
|
||
""",
|
||
(
|
||
review_status,
|
||
request.review_notes.strip(),
|
||
next_status,
|
||
timestamp,
|
||
run_id,
|
||
),
|
||
)
|
||
if incident_row:
|
||
incident_status = "resolved" if review_status == "approved" else "watching" if review_status == "watching" else "rejected"
|
||
legacy.db.execute(
|
||
"""
|
||
UPDATE admin_ops_incidents
|
||
SET status = ?, reviewed_by = ?, review_notes = ?, updated_at = ?
|
||
WHERE id = ?
|
||
""",
|
||
(
|
||
incident_status,
|
||
admin["id"],
|
||
request.review_notes.strip(),
|
||
timestamp,
|
||
current["incident_id"],
|
||
),
|
||
)
|
||
row = legacy.db.fetch_one("SELECT * FROM admin_ops_fix_runs WHERE id = ?", (run_id,))
|
||
payload = _fix_run_payload(row)
|
||
payload["audit"] = _log_admin_audit_event(
|
||
actor_user_id=admin["id"],
|
||
incident_id=current["incident_id"],
|
||
action_key="fix-run-audit",
|
||
status=review_status,
|
||
summary=f"修复计划 {run_id} 已审计为 {review_status}。",
|
||
details={"review_notes": request.review_notes.strip()},
|
||
)
|
||
return payload
|