feat: add oneliner policy history controls

This commit is contained in:
kris
2026-03-29 16:42:12 +08:00
parent cb17fb0760
commit 26f86f8484
6 changed files with 719 additions and 11 deletions

View File

@@ -59,6 +59,9 @@ const appState = {
userCurrentPlatformPolicy: null,
adminSystemMainPolicy: null,
adminSystemPlatformPolicies: [],
adminGovernanceDirectory: null,
adminOverrideTarget: null,
adminOverridePolicy: null,
tenantQuota: null,
tenantUsage: null,
adminOpsOverview: null,
@@ -1261,6 +1264,9 @@ async function logoutSession() {
appState.userCurrentPlatformPolicy = null;
appState.adminSystemMainPolicy = null;
appState.adminSystemPlatformPolicies = [];
appState.adminGovernanceDirectory = null;
appState.adminOverrideTarget = null;
appState.adminOverridePolicy = null;
appState.tenantQuota = null;
appState.tenantUsage = null;
appState.adminOpsOverview = null;
@@ -1312,12 +1318,14 @@ async function loadAgentControlSurfaces(projectId = "") {
const supportsUserPlatformPolicy = backendSupports("/v2/oneliner/governance/user/platforms/{platform}");
const supportsAdminSystemMainPolicy = backendSupports("/v2/admin/oneliner/governance/system/main-agent");
const supportsAdminSystemPlatformPolicy = backendSupports("/v2/admin/oneliner/governance/system/platforms/{platform}");
const supportsAdminGovernanceDirectory = backendSupports("/v2/admin/oneliner/governance/directory");
const supportsAdminOverridePolicy = backendSupports("/v2/admin/oneliner/governance/overrides");
const supportsAdminOps = backendSupports("/v2/admin/ops/overview");
const supportsAdminFixRuns = backendSupports("/v2/admin/ops/fix-runs");
const supportsTenantQuota = backendSupports("/v2/tenant/quota");
const supportsTenantUsage = backendSupports("/v2/tenant/usage");
const [profile, sessionsPayload, actionRegistryPayload, platformAgentsPayload, governanceEffective, userGlobalPolicy, userCurrentPlatformPolicy, adminSystemMainPolicy, adminSystemPlatformPolicies, tenantQuota, tenantUsage, adminOpsOverview, adminFixRunsPayload] = await Promise.all([
const [profile, sessionsPayload, actionRegistryPayload, platformAgentsPayload, governanceEffective, userGlobalPolicy, userCurrentPlatformPolicy, adminSystemMainPolicy, adminSystemPlatformPolicies, adminGovernanceDirectory, tenantQuota, tenantUsage, adminOpsOverview, adminFixRunsPayload] = await Promise.all([
supportsOneLinerProfile
? storyforgeFetch(`/v2/oneliner/profile?project_id=${encodeURIComponent(normalizedProjectId)}`).catch(() => null)
: Promise.resolve(null),
@@ -1347,6 +1355,9 @@ async function loadAgentControlSurfaces(projectId = "") {
storyforgeFetch(`/v2/admin/oneliner/governance/system/platforms/${encodeURIComponent(item.value)}`).catch(() => null)
))
: Promise.resolve([]),
supportsAdminGovernanceDirectory && isSuperAdmin()
? storyforgeFetch("/v2/admin/oneliner/governance/directory").catch(() => ({ items: [] }))
: Promise.resolve({ items: [] }),
supportsTenantQuota
? storyforgeFetch(`/v2/tenant/quota?project_id=${encodeURIComponent(normalizedProjectId)}`).catch(() => null)
: Promise.resolve(null),
@@ -1373,6 +1384,24 @@ async function loadAgentControlSurfaces(projectId = "") {
appState.userCurrentPlatformPolicy = userCurrentPlatformPolicy;
appState.adminSystemMainPolicy = adminSystemMainPolicy;
appState.adminSystemPlatformPolicies = safeArray(adminSystemPlatformPolicies);
appState.adminGovernanceDirectory = safeArray(adminGovernanceDirectory?.items || adminGovernanceDirectory);
if (isSuperAdmin() && supportsAdminOverridePolicy && appState.adminGovernanceDirectory.length) {
const existingTarget = appState.adminOverrideTarget || {};
const targetUserId = String(existingTarget.targetUserId || existingTarget.target_user_id || appState.adminGovernanceDirectory[0]?.id || "");
const targetUser = appState.adminGovernanceDirectory.find((item) => item.id === targetUserId) || appState.adminGovernanceDirectory[0] || null;
const targetProjects = safeArray(targetUser?.projects);
const targetProjectId = String(existingTarget.targetProjectId || existingTarget.target_project_id || targetProjects[0]?.id || "");
const targetPlatform = normalizePlatformValue(existingTarget.platform || governancePlatform, "douyin");
appState.adminOverrideTarget = {
targetUserId,
targetProjectId,
platform: targetPlatform
};
appState.adminOverridePolicy = await storyforgeFetch(`/v2/admin/oneliner/governance/overrides?target_user_id=${encodeURIComponent(targetUserId)}&target_project_id=${encodeURIComponent(targetProjectId)}&platform=${encodeURIComponent(targetPlatform)}`).catch(() => null);
} else {
appState.adminOverrideTarget = null;
appState.adminOverridePolicy = null;
}
appState.tenantQuota = tenantQuota;
appState.tenantUsage = tenantUsage;
appState.adminOpsOverview = adminOpsOverview;
@@ -3203,9 +3232,86 @@ function summarizePolicyHighlights(policy = {}, platform = "") {
return items.slice(0, 4);
}
function renderGovernanceSummaryCard({ title, subtitle, effective, primaryAction = "", primaryLabel = "编辑策略", secondaryAction = "", secondaryLabel = "", secondaryPlatform = "" }) {
function getGovernanceDirectoryItems() {
return safeArray(appState.adminGovernanceDirectory?.items || appState.adminGovernanceDirectory);
}
function parseAdminOverrideScopeValue(value) {
const normalized = String(value || "").trim();
if (!normalized) return { targetUserId: "", targetProjectId: "" };
if (normalized.startsWith("project:")) {
const parts = normalized.slice("project:".length).split("|");
return { targetUserId: parts[0] || "", targetProjectId: parts[1] || "" };
}
if (normalized.startsWith("user:")) {
return { targetUserId: normalized.slice("user:".length), targetProjectId: "" };
}
return { targetUserId: normalized, targetProjectId: "" };
}
function getAdminOverrideTargetOptions(directoryItems = getGovernanceDirectoryItems()) {
return safeArray(directoryItems).flatMap((account) => {
const accountLabel = account.display_name || account.username || account.id;
const projects = safeArray(account.projects);
return [
{ value: `user:${account.id}`, label: `${accountLabel} · 全部项目` },
...projects.map((project) => ({
value: `project:${account.id}|${project.id}`,
label: `${accountLabel} / ${project.name || project.id}`
}))
];
});
}
function normalizeAdminOverrideTarget(target, directoryItems = getGovernanceDirectoryItems(), fallbackPlatform = "") {
const items = safeArray(directoryItems);
if (!items.length) {
return { targetUserId: "", targetProjectId: "", platform: normalizePlatformValue(fallbackPlatform, "douyin") };
}
const preferred = target && target.targetUserId
? items.find((item) => item.id === target.targetUserId)
: items.find((item) => item.role !== "super_admin") || items[0];
const preferredProjects = safeArray(preferred?.projects);
const project =
target?.targetProjectId
? preferredProjects.find((item) => item.id === target.targetProjectId)
: preferredProjects[0] || null;
return {
targetUserId: preferred?.id || "",
targetProjectId: project?.id || "",
platform: target?.platform === "" ? "" : normalizePlatformValue(target?.platform || fallbackPlatform, "douyin")
};
}
function findGovernanceDirectoryAccount(userId) {
return getGovernanceDirectoryItems().find((item) => item.id === userId) || null;
}
function findGovernanceDirectoryProject(userId, projectId) {
const account = findGovernanceDirectoryAccount(userId);
return safeArray(account?.projects).find((item) => item.id === projectId) || null;
}
function getAdminOverrideTargetSummary(target = appState.adminOverrideTarget) {
const normalized = target || {};
const account = findGovernanceDirectoryAccount(normalized.targetUserId || "");
const project = normalized.targetProjectId ? findGovernanceDirectoryProject(normalized.targetUserId || "", normalized.targetProjectId) : null;
return {
account,
project,
accountLabel: account?.display_name || account?.username || normalized.targetUserId || "未选择用户",
projectLabel: project?.name || (normalized.targetProjectId ? normalized.targetProjectId : "全部项目"),
platformLabel: normalized.platform ? platformLabel(normalized.platform) : "全部平台"
};
}
function renderGovernanceSummaryCard({ title, subtitle, effective, primaryAction = "", primaryLabel = "编辑策略", secondaryAction = "", secondaryLabel = "", secondaryPlatform = "", actions = null }) {
const layers = safeArray(effective?.layers);
const highlights = summarizePolicyHighlights(effective?.effective_policy || {}, effective?.platform || secondaryPlatform || "");
const resolvedActions = safeArray(actions?.length ? actions : [
primaryAction ? { action: primaryAction, label: primaryLabel } : null,
secondaryAction ? { action: secondaryAction, label: secondaryLabel, platform: secondaryPlatform } : null
].filter(Boolean));
return `
<div class="task-item compact">
<h4>${escapeHtml(title)}</h4>
@@ -3214,10 +3320,13 @@ function renderGovernanceSummaryCard({ title, subtitle, effective, primaryAction
${layers.map((layer) => `<span class="tag ${layer.scope_kind === "admin_override" ? "orange" : "blue"}">${escapeHtml(policyScopeTagLabel(layer.scope_kind, layer.scope?.platform || effective?.platform || ""))}</span>`).join("") || `<span class="tag">尚未发布</span>`}
${highlights.map((item) => `<span class="tag green">${escapeHtml(item)}</span>`).join("")}
</div>
${(primaryAction || secondaryAction) ? `
${resolvedActions.length ? `
<div class="task-meta" style="margin-top:10px;">
${primaryAction ? `<span class="tag clickable-tag" data-action="${escapeHtml(primaryAction)}">${escapeHtml(primaryLabel)}</span>` : ""}
${secondaryAction ? `<span class="tag clickable-tag" data-action="${escapeHtml(secondaryAction)}" ${secondaryPlatform ? `data-platform="${escapeHtml(secondaryPlatform)}"` : ""}>${escapeHtml(secondaryLabel)}</span>` : ""}
${resolvedActions.map((item) => `
<span class="tag clickable-tag" data-action="${escapeHtml(item.action || "")}" ${item.platform ? `data-platform="${escapeHtml(item.platform)}"` : ""}>
${escapeHtml(item.label || "查看")}
</span>
`).join("")}
</div>
` : ""}
</div>
@@ -3228,6 +3337,8 @@ function renderAdminGovernanceSummaryPanel() {
const systemMain = appState.adminSystemMainPolicy;
const systemPlatforms = safeArray(appState.adminSystemPlatformPolicies);
const configuredPlatforms = systemPlatforms.filter((item) => item?.current_version);
const targetSummary = getAdminOverrideTargetSummary();
const overrideBundle = appState.adminOverridePolicy;
return `
<div class="panel pad" style="box-shadow:none; margin-bottom:18px;">
<div class="panel-head">
@@ -3248,6 +3359,20 @@ function renderAdminGovernanceSummaryPanel() {
<div class="entity-meta">
<span class="tag ${systemMain?.current_version ? "green" : "orange"}">${escapeHtml(systemMain?.current_version ? `版本 ${formatNumber(systemMain.current_version.version_no)}` : "未发布")}</span>
<span class="tag">历史 ${escapeHtml(formatNumber(systemMain?.versions?.count || 0))}</span>
<span class="tag clickable-tag" data-action="open-system-main-policy-history">历史与回滚</span>
</div>
</div>
<div class="entity-card pad">
<div class="cell-title">管理员覆盖</div>
<div class="cell-desc">${escapeHtml(overrideBundle?.current_version?.summary || `${targetSummary.accountLabel} / ${targetSummary.projectLabel} / ${targetSummary.platformLabel}`)}</div>
<div class="entity-meta">
<span class="tag blue">${escapeHtml(targetSummary.accountLabel)}</span>
<span class="tag">${escapeHtml(targetSummary.projectLabel)}</span>
<span class="tag">${escapeHtml(targetSummary.platformLabel)}</span>
<span class="tag ${overrideBundle?.current_version ? "orange" : "blue"}">${escapeHtml(overrideBundle?.current_version ? `版本 ${formatNumber(overrideBundle.current_version.version_no)}` : "未发布")}</span>
<span class="tag clickable-tag" data-action="open-admin-override-target">切换目标</span>
<span class="tag clickable-tag" data-action="open-admin-override-policy">编辑覆盖</span>
<span class="tag clickable-tag" data-action="open-admin-override-history">历史与回滚</span>
</div>
</div>
${ACTIVE_PLATFORMS.map((platformItem) => {
@@ -3260,6 +3385,7 @@ function renderAdminGovernanceSummaryPanel() {
<span class="tag ${item?.current_version ? "green" : "blue"}">${escapeHtml(item?.current_version ? `版本 ${formatNumber(item.current_version.version_no)}` : "沿用系统默认")}</span>
<span class="tag">历史 ${escapeHtml(formatNumber(item?.versions?.count || 0))}</span>
<span class="tag clickable-tag" data-action="open-system-platform-policy" data-platform="${escapeHtml(platformItem.value)}">编辑</span>
<span class="tag clickable-tag" data-action="open-system-platform-policy-history" data-platform="${escapeHtml(platformItem.value)}">历史与回滚</span>
</div>
</div>
`;
@@ -4755,11 +4881,26 @@ function renderPlaybookScreen() {
title: "我的策略与历史",
subtitle: appState.userGlobalPolicy?.current_version?.summary || "你和主 Agent 的策略对话,会先沉淀成用户全局策略,再按需要下放到单平台。",
effective: appState.onelinerGovernanceEffective,
primaryAction: "open-user-global-policy",
primaryLabel: `编辑全局策略 · 历史 ${formatNumber(appState.userGlobalPolicy?.versions?.count || 0)}`,
secondaryAction: "open-user-platform-policy",
secondaryLabel: `编辑当前平台策略 · 历史 ${formatNumber(appState.userCurrentPlatformPolicy?.versions?.count || 0)}`,
secondaryPlatform: appState.onelinerGovernanceEffective?.platform || appState.onelinerProfile?.default_platform || getPreferredPlatform()
actions: [
{
action: "open-user-global-policy",
label: `编辑全局策略 · 历史 ${formatNumber(appState.userGlobalPolicy?.versions?.count || 0)}`
},
{
action: "open-user-global-policy-history",
label: "查看全局历史"
},
{
action: "open-user-platform-policy",
label: `编辑当前平台策略 · 历史 ${formatNumber(appState.userCurrentPlatformPolicy?.versions?.count || 0)}`,
platform: appState.onelinerGovernanceEffective?.platform || appState.onelinerProfile?.default_platform || getPreferredPlatform()
},
{
action: "open-user-platform-policy-history",
label: "查看当前平台历史",
platform: appState.onelinerGovernanceEffective?.platform || appState.onelinerProfile?.default_platform || getPreferredPlatform()
}
]
})}
</div>
<div class="panel pad" style="box-shadow:none; margin-top:18px;">
@@ -6409,6 +6550,82 @@ function renderPolicyVersionSummary(bundle, emptyText) {
`;
}
function getAdminGovernanceDirectoryItems() {
return safeArray(appState.adminGovernanceDirectory);
}
function findAdminGovernanceDirectoryItem(targetUserId) {
return getAdminGovernanceDirectoryItems().find((item) => item.id === targetUserId) || null;
}
function getAdminOverrideTargetState() {
const directoryItems = getAdminGovernanceDirectoryItems();
const existing = appState.adminOverrideTarget || {};
const targetUserId = String(existing.targetUserId || existing.target_user_id || directoryItems[0]?.id || "");
const targetUser = findAdminGovernanceDirectoryItem(targetUserId) || directoryItems[0] || null;
const targetProjects = safeArray(targetUser?.projects);
const targetProjectId = String(existing.targetProjectId || existing.target_project_id || targetProjects[0]?.id || "");
return {
targetUserId,
targetProjectId,
platform: normalizePlatformValue(existing.platform || getPreferredPlatform(), "douyin")
};
}
function formatAdminGovernanceTargetLabel(target) {
const directoryItem = findAdminGovernanceDirectoryItem(target?.targetUserId || target?.target_user_id || "");
const project = safeArray(directoryItem?.projects).find((item) => item.id === (target?.targetProjectId || target?.target_project_id || ""));
const userLabel = directoryItem ? `${directoryItem.display_name || directoryItem.username || directoryItem.id}${directoryItem.role ? ` · ${directoryItem.role}` : ""}` : "未选择目标";
const projectLabel = project ? project.name || project.id : "默认用户全局";
return `${userLabel} / ${projectLabel}`;
}
function getAdminGovernanceDirectoryUserOptions() {
return getAdminGovernanceDirectoryItems().map((item) => ({
value: item.id,
label: `${item.display_name || item.username || item.id}${item.project_count ? ` · ${formatNumber(item.project_count)} 项目` : ""}`
}));
}
function getAdminGovernanceDirectoryProjectOptions(targetUserId) {
const directoryItem = findAdminGovernanceDirectoryItem(targetUserId);
return safeArray(directoryItem?.projects).map((item) => ({
value: item.id,
label: item.name || item.id
}));
}
function renderPolicyVersionsHtml(items, emptyText = "暂无历史版本。") {
const versions = safeArray(items);
if (!versions.length) {
return `<div class="task-item compact"><h4>还没有历史版本</h4><p>${escapeHtml(emptyText)}</p></div>`;
}
return versions.slice(0, 8).map((version) => `
<div class="task-item compact">
<h4>${escapeHtml(version.title || `版本 ${formatNumber(version.version_no || 0)}`)}</h4>
<p>${escapeHtml(version.summary || "没有补充摘要。")}</p>
<div class="task-meta">
<span class="tag blue">版本 ${escapeHtml(formatNumber(version.version_no || 0))}</span>
${version.created_at ? `<span class="tag">${escapeHtml(formatDateTime(version.created_at))}</span>` : ""}
${version.rollback_from_version_id ? `<span class="tag orange">回滚生成</span>` : ""}
</div>
</div>
`).join("");
}
async function loadPolicyVersions(url) {
const payload = await storyforgeFetch(url).catch(() => ({ items: [] }));
const items = safeArray(payload?.items || payload);
return { items, count: Number(payload?.count || items.length) };
}
function buildPolicyVersionOptions(history) {
return safeArray(history?.items).map((item) => ({
value: item.id,
label: `v${formatNumber(item.version_no || 0)} · ${item.title || brief(item.summary || item.id, 24)}`
}));
}
function openUserGlobalPolicyAction() {
const project = requireSelectedProject();
const bundle = appState.userGlobalPolicy || {};
@@ -6478,6 +6695,77 @@ function openUserPlatformPolicyAction(platform) {
});
}
async function openUserGlobalPolicyHistoryAction() {
const project = requireSelectedProject();
const history = await loadPolicyVersions(`/v2/oneliner/governance/user/global/versions?project_id=${encodeURIComponent(project.id)}`);
const selectedVersionId = history.items[0]?.id || "";
const versionOptions = buildPolicyVersionOptions(history);
openActionModal({
title: "我的全局策略历史",
description: "查看你自己的全局策略版本,并从历史里选择一个版本回滚。回滚不会改旧记录,而是会生成一个新的生效版本。",
submitLabel: "回滚到所选版本",
hideSubmit: !selectedVersionId,
fields: [
{ type: "html", label: "当前版本", html: renderPolicyVersionSummary(appState.userGlobalPolicy || {}, "你还没有发布自己的全局策略。") },
{ type: "html", label: "历史版本", html: renderPolicyVersionsHtml(history.items, "你的全局策略还没有历史版本。") },
...(selectedVersionId ? [
{ name: "versionId", label: "回滚版本", type: "select", value: selectedVersionId, options: versionOptions },
{ name: "reason", label: "回滚原因", type: "textarea", rows: 3, value: "", placeholder: "例如:恢复到更稳妥的首页动作和语气策略" }
] : [])
],
onSubmit: async (values) => {
const saved = await storyforgeFetch("/v2/oneliner/governance/user/global/rollback", {
method: "POST",
body: {
project_id: project.id,
version_id: values.versionId || selectedVersionId,
reason: values.reason || ""
}
});
appState.userGlobalPolicy = saved;
await loadAgentControlSurfaces(project.id);
rememberAction("我的全局策略已回滚", `已生成回滚版本 ${saved.current_version?.version_no || "所选版本"}`, "green", saved);
renderAll();
}
});
}
async function openUserPlatformPolicyHistoryAction(platform) {
const normalizedPlatform = normalizePlatformValue(platform || getPreferredPlatform(), "douyin");
const project = requireSelectedProject();
const history = await loadPolicyVersions(`/v2/oneliner/governance/user/platforms/${encodeURIComponent(normalizedPlatform)}/versions?project_id=${encodeURIComponent(project.id)}`);
const selectedVersionId = history.items[0]?.id || "";
const versionOptions = buildPolicyVersionOptions(history);
openActionModal({
title: `${platformLabel(normalizedPlatform)} 平台策略历史`,
description: "查看该平台的个人策略版本,并从历史里选择一个版本回滚。回滚只影响当前平台,不会改动其他平台。",
submitLabel: "回滚到所选版本",
hideSubmit: !selectedVersionId,
fields: [
{ type: "html", label: "当前版本", html: renderPolicyVersionSummary(appState.userCurrentPlatformPolicy || {}, `你还没有发布 ${platformLabel(normalizedPlatform)} 平台策略。`) },
{ type: "html", label: "历史版本", html: renderPolicyVersionsHtml(history.items, `${platformLabel(normalizedPlatform)} 还没有历史版本。`) },
...(selectedVersionId ? [
{ name: "versionId", label: "回滚版本", type: "select", value: selectedVersionId, options: versionOptions },
{ name: "reason", label: "回滚原因", type: "textarea", rows: 3, value: "", placeholder: "例如:恢复到更适合这个平台的拆解方式" }
] : [])
],
onSubmit: async (values) => {
const saved = await storyforgeFetch(`/v2/oneliner/governance/user/platforms/${encodeURIComponent(normalizedPlatform)}/rollback`, {
method: "POST",
body: {
project_id: project.id,
version_id: values.versionId || selectedVersionId,
reason: values.reason || ""
}
});
appState.userCurrentPlatformPolicy = saved;
await loadAgentControlSurfaces(project.id);
rememberAction(`${platformLabel(normalizedPlatform)} 平台策略已回滚`, `已生成回滚版本 ${saved.current_version?.version_no || "所选版本"}`, "green", saved);
renderAll();
}
});
}
function openSystemMainPolicyAction() {
const projectId = getOneLinerProjectId();
const bundle = appState.adminSystemMainPolicy || {};
@@ -6548,6 +6836,165 @@ function openSystemPlatformPolicyAction(platform) {
});
}
async function openAdminOverrideTargetAction() {
const current = getAdminOverrideTargetState();
const directoryItems = getAdminGovernanceDirectoryItems();
openActionModal({
title: "选择管理员覆盖目标",
description: "先选中要覆盖的用户、项目和平台,再去编辑覆盖策略或查看历史。",
submitLabel: "保存目标",
fields: [
{ type: "html", label: "当前目标", html: renderPolicyVersionSummary(appState.adminOverridePolicy || {}, `当前目标是 ${formatAdminGovernanceTargetLabel(current)}`) },
{ name: "targetUserId", label: "目标用户", type: "select", value: current.targetUserId, options: getAdminGovernanceDirectoryUserOptions() },
{ name: "targetProjectId", label: "目标项目", type: "select", value: current.targetProjectId, options: [{ value: "", label: "用户全局" }, ...getAdminGovernanceDirectoryProjectOptions(current.targetUserId)] },
{ name: "platform", label: "平台", type: "select", value: current.platform, options: getPlatformOptions() },
{ type: "html", label: "目录提示", html: directoryItems.length ? `<div class="task-item compact"><h4>可选目标</h4><p>当前目录里有 ${escapeHtml(formatNumber(directoryItems.length))} 位已审核账号。</p></div>` : `<div class="task-item compact"><h4>目录为空</h4><p>后端还没有返回可选账号。</p></div>` }
],
onSubmit: async (values) => {
appState.adminOverrideTarget = {
targetUserId: String(values.targetUserId || ""),
targetProjectId: String(values.targetProjectId || ""),
platform: normalizePlatformValue(values.platform || "douyin", "douyin")
};
await loadAgentControlSurfaces(getOneLinerProjectId());
rememberAction("管理员覆盖目标已更新", `当前目标已切换到 ${formatAdminGovernanceTargetLabel(appState.adminOverrideTarget)}`, "green");
renderAll();
}
});
}
function openAdminOverridePolicyAction() {
const target = getAdminOverrideTargetState();
const bundle = appState.adminOverridePolicy || {};
const current = bundle.current_version || {};
openActionModal({
title: "编辑管理员覆盖策略",
description: "这层策略只作用于当前选中的目标,会叠加在用户策略和系统默认之上。",
submitLabel: "保存覆盖策略",
fields: [
{ type: "html", label: "当前版本", html: renderPolicyVersionSummary(bundle, `当前还没有为 ${formatAdminGovernanceTargetLabel(target)} 发布覆盖策略。`) },
{ name: "title", label: "策略标题", value: current.title || `管理员覆盖:${formatAdminGovernanceTargetLabel(target)}`, placeholder: "例如:重点账号短期放量覆盖" },
{ name: "summary", label: "摘要", type: "textarea", rows: 3, value: current.summary || "", placeholder: "写清楚这层覆盖是为了什么目标" },
{ name: "policyJson", label: "策略 JSON", type: "textarea", rows: 8, value: JSON.stringify(current.policy || {}, null, 2), placeholder: "{\"guardrails\":{\"require_admin_review\":true}}" },
{ name: "reason", label: "变更原因", type: "textarea", rows: 3, value: "", placeholder: "例如:对该账号/项目临时放宽首页动作数量" }
],
onSubmit: async (values) => {
const saved = await storyforgeFetch("/v2/admin/oneliner/governance/overrides", {
method: "POST",
body: {
target_user_id: target.targetUserId,
target_project_id: target.targetProjectId,
platform: target.platform,
title: values.title || `管理员覆盖:${formatAdminGovernanceTargetLabel(target)}`,
summary: values.summary || "",
policy: parsePolicyJsonField(values.policyJson, "管理员覆盖策略 JSON"),
reason: values.reason || ""
}
});
appState.adminOverridePolicy = saved;
await loadAgentControlSurfaces(getOneLinerProjectId());
rememberAction("管理员覆盖策略已保存", `已为 ${formatAdminGovernanceTargetLabel(target)} 发布版本 ${saved.current_version?.version_no || 1}`, "green", saved);
renderAll();
}
});
}
async function openAdminOverrideHistoryAction() {
const target = getAdminOverrideTargetState();
const history = await loadPolicyVersions(`/v2/admin/oneliner/governance/overrides/versions?target_user_id=${encodeURIComponent(target.targetUserId)}&target_project_id=${encodeURIComponent(target.targetProjectId)}&platform=${encodeURIComponent(target.platform)}`);
const selectedVersionId = history.items[0]?.id || "";
openActionModal({
title: "管理员覆盖历史",
description: "查看当前目标的管理员覆盖版本,并从历史里选择一个版本回滚。",
submitLabel: "回滚到所选版本",
fields: [
{ type: "html", label: "当前目标", html: renderPolicyVersionSummary(appState.adminOverridePolicy || {}, `当前查看的是 ${formatAdminGovernanceTargetLabel(target)} 的覆盖历史。`) },
{ type: "html", label: "历史版本", html: renderPolicyVersionsHtml(history.items, "当前目标还没有历史版本。") },
{ name: "versionId", label: "回滚版本", type: "select", value: selectedVersionId, options: safeArray(history.items).map((item) => ({ value: item.id, label: `v${formatNumber(item.version_no || 0)} · ${item.title || brief(item.summary || item.id, 24)}` })) },
{ name: "reason", label: "回滚原因", type: "textarea", rows: 3, value: "", placeholder: "例如:这版覆盖太激进,需要恢复到上一版" }
],
onSubmit: async (values) => {
const saved = await storyforgeFetch("/v2/admin/oneliner/governance/overrides/rollback", {
method: "POST",
body: {
target_user_id: target.targetUserId,
target_project_id: target.targetProjectId,
platform: target.platform,
version_id: values.versionId || selectedVersionId,
reason: values.reason || ""
}
});
appState.adminOverridePolicy = saved;
await loadAgentControlSurfaces(getOneLinerProjectId());
rememberAction("管理员覆盖已回滚", `已回滚到版本 ${saved.current_version?.version_no || "所选版本"}`, "green", saved);
renderAll();
}
});
}
async function openSystemMainPolicyHistoryAction() {
const history = await loadPolicyVersions("/v2/admin/oneliner/governance/system/main-agent/versions");
const selectedVersionId = history.items[0]?.id || "";
openActionModal({
title: "系统主 Agent 历史",
description: "查看系统主 Agent 的历史版本,并选择某个版本回滚。",
submitLabel: "回滚到所选版本",
fields: [
{ type: "html", label: "当前版本", html: renderPolicyVersionSummary(appState.adminSystemMainPolicy || {}, "系统主 Agent 还没有历史版本。") },
{ type: "html", label: "历史版本", html: renderPolicyVersionsHtml(history.items, "系统主 Agent 还没有历史版本。") },
{ name: "versionId", label: "回滚版本", type: "select", value: selectedVersionId, options: safeArray(history.items).map((item) => ({ value: item.id, label: `v${formatNumber(item.version_no || 0)} · ${item.title || brief(item.summary || item.id, 24)}` })) },
{ name: "reason", label: "回滚原因", type: "textarea", rows: 3, value: "", placeholder: "例如:恢复到上一版系统主 Agent 策略" }
],
onSubmit: async (values) => {
const saved = await storyforgeFetch("/v2/admin/oneliner/governance/system/main-agent/rollback", {
method: "POST",
body: {
version_id: values.versionId || selectedVersionId,
reason: values.reason || ""
}
});
appState.adminSystemMainPolicy = saved;
await loadAgentControlSurfaces(getOneLinerProjectId());
rememberAction("系统主 Agent 已回滚", `已回滚到版本 ${saved.current_version?.version_no || "所选版本"}`, "green", saved);
renderAll();
}
});
}
async function openSystemPlatformPolicyHistoryAction(platform) {
const normalizedPlatform = normalizePlatformValue(platform || getPreferredPlatform(), "douyin");
const history = await loadPolicyVersions(`/v2/admin/oneliner/governance/system/platforms/${encodeURIComponent(normalizedPlatform)}/versions`);
const selectedVersionId = history.items[0]?.id || "";
const bundle = safeArray(appState.adminSystemPlatformPolicies).find((item) => item?.scope?.platform === normalizedPlatform) || {};
openActionModal({
title: `${platformLabel(normalizedPlatform)} 系统平台历史`,
description: "查看该平台的系统默认策略历史,并选择某个版本回滚。",
submitLabel: "回滚到所选版本",
fields: [
{ type: "html", label: "当前版本", html: renderPolicyVersionSummary(bundle, `当前 ${platformLabel(normalizedPlatform)} 还没有系统平台历史版本。`) },
{ type: "html", label: "历史版本", html: renderPolicyVersionsHtml(history.items, `${platformLabel(normalizedPlatform)} 还没有历史版本。`) },
{ name: "versionId", label: "回滚版本", type: "select", value: selectedVersionId, options: safeArray(history.items).map((item) => ({ value: item.id, label: `v${formatNumber(item.version_no || 0)} · ${item.title || brief(item.summary || item.id, 24)}` })) },
{ name: "reason", label: "回滚原因", type: "textarea", rows: 3, value: "", placeholder: "例如:恢复到上一版平台默认方法论" }
],
onSubmit: async (values) => {
const saved = await storyforgeFetch(`/v2/admin/oneliner/governance/system/platforms/${encodeURIComponent(normalizedPlatform)}/rollback`, {
method: "POST",
body: {
version_id: values.versionId || selectedVersionId,
reason: values.reason || ""
}
});
appState.adminSystemPlatformPolicies = safeArray(appState.adminSystemPlatformPolicies)
.filter((item) => item?.scope?.platform !== normalizedPlatform)
.concat(saved)
.sort((a, b) => String(a?.scope?.platform || "").localeCompare(String(b?.scope?.platform || "")));
await loadAgentControlSurfaces(getOneLinerProjectId());
rememberAction(`${platformLabel(normalizedPlatform)} 系统平台策略已回滚`, `已回滚到版本 ${saved.current_version?.version_no || "所选版本"}`, "green", saved);
renderAll();
}
});
}
function openPlatformAgentProfileAction(platform) {
const project = requireSelectedProject();
const agents = safeArray(appState.platformAgents);
@@ -8316,18 +8763,46 @@ document.addEventListener("click", async (event) => {
openUserGlobalPolicyAction();
return;
}
if (name === "open-user-global-policy-history") {
await openUserGlobalPolicyHistoryAction();
return;
}
if (name === "open-user-platform-policy") {
openUserPlatformPolicyAction(action.dataset.platform || "");
return;
}
if (name === "open-user-platform-policy-history") {
await openUserPlatformPolicyHistoryAction(action.dataset.platform || "");
return;
}
if (name === "open-system-main-policy") {
openSystemMainPolicyAction();
return;
}
if (name === "open-system-main-policy-history") {
await openSystemMainPolicyHistoryAction();
return;
}
if (name === "open-system-platform-policy") {
openSystemPlatformPolicyAction(action.dataset.platform || "");
return;
}
if (name === "open-system-platform-policy-history") {
await openSystemPlatformPolicyHistoryAction(action.dataset.platform || "");
return;
}
if (name === "open-admin-override-target") {
await openAdminOverrideTargetAction();
return;
}
if (name === "open-admin-override-policy") {
openAdminOverridePolicyAction();
return;
}
if (name === "open-admin-override-history") {
await openAdminOverrideHistoryAction();
return;
}
if (name === "select-oneliner-session") {
appState.selectedOnelinerSessionId = action.dataset.sessionId || "";
await loadOneLinerMessages(appState.selectedOnelinerSessionId);

View File

@@ -105,6 +105,8 @@ test("agent control surfaces load governance endpoints for user and admin summar
assert.match(source, /\/v2\/oneliner\/governance\/user\/platforms\/\$\{encodeURIComponent\(governancePlatform\)\}/);
assert.match(source, /\/v2\/admin\/oneliner\/governance\/system\/main-agent/);
assert.match(source, /\/v2\/admin\/oneliner\/governance\/system\/platforms\/\$\{encodeURIComponent\(item\.value\)\}/);
assert.match(source, /\/v2\/admin\/oneliner\/governance\/directory/);
assert.match(source, /\/v2\/admin\/oneliner\/governance\/overrides/);
});
test("oneliner meta and action handlers expose governance entry points", () => {
@@ -140,3 +142,31 @@ test("system governance saves refresh control surfaces after persisting", () =>
assert.match(platform, /appState\.adminSystemPlatformPolicies = safeArray\(appState\.adminSystemPlatformPolicies\)/);
assert.match(platform, /await loadAgentControlSurfaces\(projectId\);/);
});
test("governance UI exposes admin override target picker and history rollback entrypoints", () => {
const admin = extractBetween(APP, "function renderAdminGovernanceSummaryPanel()", "function renderPlatformAgentPanel()");
const actions = extractBetween(APP, "document.addEventListener(\"click\", async (event) => {", "document.addEventListener(\"submit\", async (event) => {");
assert.match(admin, /open-admin-override-target/);
assert.match(admin, /open-admin-override-policy/);
assert.match(admin, /open-admin-override-history/);
assert.match(admin, /open-system-main-policy-history/);
assert.match(admin, /open-system-platform-policy-history/);
assert.match(actions, /name === "open-admin-override-target"/);
assert.match(actions, /name === "open-admin-override-policy"/);
assert.match(actions, /name === "open-admin-override-history"/);
assert.match(actions, /name === "open-system-main-policy-history"/);
assert.match(actions, /name === "open-system-platform-policy-history"/);
});
test("user governance UI exposes personal history and rollback entrypoints", () => {
const playbook = extractBetween(APP, "function renderPlaybookScreen()", "function renderProductionScreen()");
const actions = extractBetween(APP, "document.addEventListener(\"click\", async (event) => {", "document.addEventListener(\"submit\", async (event) => {");
assert.match(playbook, /open-user-global-policy-history/);
assert.match(playbook, /open-user-platform-policy-history/);
assert.match(actions, /name === "open-user-global-policy-history"/);
assert.match(actions, /name === "open-user-platform-policy-history"/);
});