fix: prefer live workbench endpoints
Some checks failed
StoryForge CI / Baseline checks (push) Has been cancelled
StoryForge CI / Backend tests (push) Has been cancelled
StoryForge CI / Web tests (push) Has been cancelled

This commit is contained in:
kris
2026-03-30 21:46:24 +08:00
parent 67bddcf4b3
commit aa2893b392
3 changed files with 59 additions and 82 deletions

View File

@@ -28,6 +28,8 @@
- `OneLiner 动作注册表 / 平台 Agent / 租户额度 / 复盘` 已按 live collector 实际能力展示,不再误导成“还没接”。
- `额度``复盘` 页面首屏已改成围绕 live 数据的任务页,直接展示风险、主要消耗、高频结论和下一步动作。
- `跟踪已读 / 批量跟踪同步 / 单账号跟踪同步 / 高分作品分析 / 平台技能验收` 已改成“真实调用优先”,避免旧 capability 口径把已接好的接口误判成未接入。
- `OneLiner 会话 / 运行详情 / 治理控制面 / integrations / live-recorder` 这些固定接口也已经切成 live-first请求失败才降级不再先被陈旧 capability 表拦住。
- 任务恢复链会优先真实调用 `/v2/explore/jobs/{job_id}/retry`,只有接口真的不存在时才回退到手动恢复模板。
### NAS 联调与回归
@@ -35,7 +37,7 @@
- Web: `http://192.168.31.188:19192/`
- Collector: `http://192.168.31.188:19193/healthz`
- 当前基线通过:
- 前端测试 `59/59`
- 前端测试 `60/60`
- `bash scripts/check_repo_baseline.sh`
- `bash scripts/smoke_fnos_storyforge_lan.sh`

View File

@@ -1722,7 +1722,7 @@ async function loadStorageStatus(projectId = "") {
async function hydrateSelectedOneLinerRun() {
const runId = appState.selectedOnelinerRunId || "";
if (!runId || !backendSupports("/v2/oneliner/runs/{run_id}")) {
if (!runId) {
return null;
}
const detail = await storyforgeFetch(`/v2/oneliner/runs/${encodeURIComponent(runId)}`).catch(() => null);
@@ -1742,74 +1742,33 @@ async function hydrateSelectedOneLinerRun() {
async function loadAgentControlSurfaces(projectId = "") {
const normalizedProjectId = projectId || getOneLinerProjectId();
const governancePlatform = normalizePlatformValue(getPreferredPlatform(), "douyin");
const supportsOneLinerProfile = backendSupports("/v2/oneliner/profile");
const supportsOneLinerSessions = backendSupports("/v2/oneliner/sessions");
const supportsOneLinerRuns = backendSupports("/v2/oneliner/runs");
const supportsActionRegistry = backendSupports("/v2/oneliner/action-registry");
const supportsPlatformAgents = backendSupports("/v2/platform-agents");
const supportsGovernanceEffective = backendSupports("/v2/oneliner/governance/effective");
const supportsUserGlobalPolicy = backendSupports("/v2/oneliner/governance/user/global");
const supportsUserPlatformPolicy = backendSupports("/v2/oneliner/governance/user/platforms/{platform}");
const supportsUserPolicyAudits = backendSupports("/v2/oneliner/governance/user/audits");
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 supportsAdminGovernanceAudits = backendSupports("/v2/admin/oneliner/governance/audits");
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, runsPayload, actionRegistryPayload, platformAgentsPayload, governanceEffective, userGlobalPolicy, userCurrentPlatformPolicy, userPolicyAuditsPayload, adminSystemMainPolicy, adminSystemPlatformPolicies, adminGovernanceDirectory, tenantQuota, tenantUsage, adminOpsOverview, adminFixRunsPayload] = await Promise.all([
supportsOneLinerProfile
? storyforgeFetch(`/v2/oneliner/profile?project_id=${encodeURIComponent(normalizedProjectId)}`).catch(() => null)
: Promise.resolve(null),
supportsOneLinerSessions
? storyforgeFetch(`/v2/oneliner/sessions?project_id=${encodeURIComponent(normalizedProjectId)}`).catch(() => ({ items: [] }))
: Promise.resolve({ items: [] }),
supportsOneLinerRuns
? storyforgeFetch(`/v2/oneliner/runs?project_id=${encodeURIComponent(normalizedProjectId)}`).catch(() => ({ items: [] }))
: Promise.resolve({ items: [] }),
supportsActionRegistry
? storyforgeFetch(`/v2/oneliner/action-registry?project_id=${encodeURIComponent(normalizedProjectId)}`).catch(() => ({ items: [] }))
: Promise.resolve({ items: [] }),
supportsPlatformAgents
? storyforgeFetch(`/v2/platform-agents?project_id=${encodeURIComponent(normalizedProjectId)}`).catch(() => ({ items: [] }))
: Promise.resolve({ items: [] }),
supportsGovernanceEffective
? storyforgeFetch(`/v2/oneliner/governance/effective?project_id=${encodeURIComponent(normalizedProjectId)}&platform=${encodeURIComponent(governancePlatform)}`).catch(() => null)
: Promise.resolve(null),
supportsUserGlobalPolicy
? storyforgeFetch(`/v2/oneliner/governance/user/global?project_id=${encodeURIComponent(normalizedProjectId)}`).catch(() => null)
: Promise.resolve(null),
supportsUserPlatformPolicy
? storyforgeFetch(`/v2/oneliner/governance/user/platforms/${encodeURIComponent(governancePlatform)}?project_id=${encodeURIComponent(normalizedProjectId)}`).catch(() => null)
: Promise.resolve(null),
supportsUserPolicyAudits
? storyforgeFetch(`/v2/oneliner/governance/user/audits?project_id=${encodeURIComponent(normalizedProjectId)}&platform=${encodeURIComponent(governancePlatform)}`).catch(() => ({ items: [] }))
: Promise.resolve({ items: [] }),
supportsAdminSystemMainPolicy && isSuperAdmin()
storyforgeFetch(`/v2/oneliner/profile?project_id=${encodeURIComponent(normalizedProjectId)}`).catch(() => null),
storyforgeFetch(`/v2/oneliner/sessions?project_id=${encodeURIComponent(normalizedProjectId)}`).catch(() => ({ items: [] })),
storyforgeFetch(`/v2/oneliner/runs?project_id=${encodeURIComponent(normalizedProjectId)}`).catch(() => ({ items: [] })),
storyforgeFetch(`/v2/oneliner/action-registry?project_id=${encodeURIComponent(normalizedProjectId)}`).catch(() => ({ items: [] })),
storyforgeFetch(`/v2/platform-agents?project_id=${encodeURIComponent(normalizedProjectId)}`).catch(() => ({ items: [] })),
storyforgeFetch(`/v2/oneliner/governance/effective?project_id=${encodeURIComponent(normalizedProjectId)}&platform=${encodeURIComponent(governancePlatform)}`).catch(() => null),
storyforgeFetch(`/v2/oneliner/governance/user/global?project_id=${encodeURIComponent(normalizedProjectId)}`).catch(() => null),
storyforgeFetch(`/v2/oneliner/governance/user/platforms/${encodeURIComponent(governancePlatform)}?project_id=${encodeURIComponent(normalizedProjectId)}`).catch(() => null),
storyforgeFetch(`/v2/oneliner/governance/user/audits?project_id=${encodeURIComponent(normalizedProjectId)}&platform=${encodeURIComponent(governancePlatform)}`).catch(() => ({ items: [] })),
isSuperAdmin()
? storyforgeFetch("/v2/admin/oneliner/governance/system/main-agent").catch(() => null)
: Promise.resolve(null),
supportsAdminSystemPlatformPolicy && isSuperAdmin()
isSuperAdmin()
? Promise.all(ACTIVE_PLATFORMS.map((item) =>
storyforgeFetch(`/v2/admin/oneliner/governance/system/platforms/${encodeURIComponent(item.value)}`).catch(() => null)
))
: Promise.resolve([]),
supportsAdminGovernanceDirectory && isSuperAdmin()
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),
supportsTenantUsage
? storyforgeFetch(`/v2/tenant/usage?project_id=${encodeURIComponent(normalizedProjectId)}`).catch(() => null)
: Promise.resolve(null),
supportsAdminOps && isSuperAdmin()
storyforgeFetch(`/v2/tenant/quota?project_id=${encodeURIComponent(normalizedProjectId)}`).catch(() => null),
storyforgeFetch(`/v2/tenant/usage?project_id=${encodeURIComponent(normalizedProjectId)}`).catch(() => null),
isSuperAdmin()
? storyforgeFetch("/v2/admin/ops/overview").catch(() => null)
: Promise.resolve(null),
supportsAdminFixRuns && isSuperAdmin()
isSuperAdmin()
? storyforgeFetch("/v2/admin/ops/fix-runs").catch(() => ({ items: [] }))
: Promise.resolve({ items: [] })
]);
@@ -1833,7 +1792,7 @@ async function loadAgentControlSurfaces(projectId = "") {
appState.adminSystemMainPolicy = adminSystemMainPolicy;
appState.adminSystemPlatformPolicies = safeArray(adminSystemPlatformPolicies);
appState.adminGovernanceDirectory = safeArray(adminGovernanceDirectory?.items || adminGovernanceDirectory);
if (isSuperAdmin() && supportsAdminOverridePolicy && appState.adminGovernanceDirectory.length) {
if (isSuperAdmin() && appState.adminGovernanceDirectory.length) {
const existingTarget = appState.adminOverrideTarget || {};
const hasExistingProjectTarget = Object.prototype.hasOwnProperty.call(existingTarget, "targetProjectId")
|| Object.prototype.hasOwnProperty.call(existingTarget, "target_project_id");
@@ -1853,9 +1812,7 @@ async function loadAgentControlSurfaces(projectId = "") {
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);
appState.adminPolicyAudits = supportsAdminGovernanceAudits
? safeArray((await storyforgeFetch(`/v2/admin/oneliner/governance/audits?target_user_id=${encodeURIComponent(targetUserId)}&target_project_id=${encodeURIComponent(targetProjectId)}&platform=${encodeURIComponent(targetPlatform)}&include_system=1`).catch(() => ({ items: [] })))?.items || [])
: [];
appState.adminPolicyAudits = safeArray((await storyforgeFetch(`/v2/admin/oneliner/governance/audits?target_user_id=${encodeURIComponent(targetUserId)}&target_project_id=${encodeURIComponent(targetProjectId)}&platform=${encodeURIComponent(targetPlatform)}&include_system=1`).catch(() => ({ items: [] })))?.items || []);
} else {
appState.adminOverrideTarget = null;
appState.adminOverridePolicy = null;
@@ -1868,7 +1825,7 @@ async function loadAgentControlSurfaces(projectId = "") {
}
async function loadOneLinerMessages(sessionId) {
if (!sessionId || !backendSupports("/v2/oneliner/sessions/{session_id}/messages")) {
if (!sessionId) {
appState.onelinerMessages = [];
return [];
}
@@ -2498,12 +2455,6 @@ async function bootstrap() {
const runtimePlatforms = getRuntimePlatformValues();
const preferredPlatform = getCurrentPlatformValue();
setCurrentPlatform(preferredPlatform);
const supportsIntegrationHealth = backendSupports("/v2/integrations/health");
const supportsLocalModels = backendSupports("/v2/integrations/local-models");
const supportsLiveRecorderSources = backendSupports("/v2/live-recorder/sources");
const supportsLiveRecorderStatus = backendSupports("/v2/live-recorder/status");
const supportsLiveRecorderFiles = backendSupports("/v2/live-recorder/files");
const supportsLiveRecorderHealth = backendSupports("/v2/live-recorder/health");
const [contentSources, platformPayloads, reviews, integrationHealth, localModelCatalog, liveRecorderSourcesPayload] = await Promise.all([
storyforgeFetch("/v2/content-sources").catch(() => []),
Promise.all(runtimePlatforms.map(async (platform) => {
@@ -2539,16 +2490,16 @@ async function bootstrap() {
};
})),
storyforgeFetch("/v2/reviews").catch(() => []),
supportsIntegrationHealth ? storyforgeFetch("/v2/integrations/health").catch(() => null) : Promise.resolve(null),
supportsLocalModels ? storyforgeFetch("/v2/integrations/local-models").catch(() => null) : Promise.resolve(null),
supportsLiveRecorderSources ? storyforgeFetch("/v2/live-recorder/sources").catch(() => ({ items: [] })) : Promise.resolve({ items: [] })
storyforgeFetch("/v2/integrations/health").catch(() => null),
storyforgeFetch("/v2/integrations/local-models").catch(() => null),
storyforgeFetch("/v2/live-recorder/sources").catch(() => ({ items: [] }))
]);
const liveRecorderIntegration = integrationHealth?.live_recorder || null;
const canLoadLiveRecorderRuntime = Boolean(liveRecorderIntegration?.reachable);
const [liveRecorderStatus, liveRecorderFilesPayload, liveRecorderHealth] = await Promise.all([
supportsLiveRecorderStatus && canLoadLiveRecorderRuntime ? storyforgeFetch("/v2/live-recorder/status").catch(() => null) : Promise.resolve(null),
supportsLiveRecorderFiles && canLoadLiveRecorderRuntime ? storyforgeFetch("/v2/live-recorder/files?limit=16").catch(() => ({ items: [] })) : Promise.resolve({ items: [] }),
supportsLiveRecorderHealth && canLoadLiveRecorderRuntime ? storyforgeFetch("/v2/live-recorder/health").catch(() => null) : Promise.resolve(null)
canLoadLiveRecorderRuntime ? storyforgeFetch("/v2/live-recorder/status").catch(() => null) : Promise.resolve(null),
canLoadLiveRecorderRuntime ? storyforgeFetch("/v2/live-recorder/files?limit=16").catch(() => ({ items: [] })) : Promise.resolve({ items: [] }),
canLoadLiveRecorderRuntime ? storyforgeFetch("/v2/live-recorder/health").catch(() => null) : Promise.resolve(null)
]);
const mergedAccounts = safeArray(platformPayloads)
.flatMap((entry) => safeArray(entry.accounts))
@@ -7832,7 +7783,7 @@ function getJobRecoverability(job) {
};
}
if (sourceType === "upload_video") {
if (backendSupports("/v2/explore/jobs/{job_id}/retry") && uploadedPath) {
if (uploadedPath) {
return {
...base,
state: "recoverable",
@@ -8080,7 +8031,7 @@ async function recoverJobAction(jobId, options = {}) {
if (!recovery.recoverable) {
throw new Error(recovery.reason || "当前任务暂不支持恢复");
}
if (backendSupports("/v2/explore/jobs/{job_id}/retry")) {
try {
const retried = await storyforgeFetch(`/v2/explore/jobs/${encodeURIComponent(job.id)}/retry`, {
method: "POST"
});
@@ -8111,6 +8062,13 @@ async function recoverJobAction(jobId, options = {}) {
reason: "通过任务重试接口恢复"
}
};
} catch (error) {
if (!isMissingBackendCapability(error)) {
throw error;
}
if (job?.source_type === "upload_video") {
throw new Error("当前实例没有开放上传任务重试接口,暂时无法自动恢复已上传素材。");
}
}
const request = getJobRecoveryRequest(job);
const payload = await storyforgeFetch(request.endpoint, {
@@ -10748,7 +10706,7 @@ document.addEventListener("click", async (event) => {
await loadAgentControlSurfaces(appState.selectedProjectId || "");
if (appState.selectedOnelinerSessionId) {
await loadOneLinerMessages(appState.selectedOnelinerSessionId);
} else if (backendSupports("/v2/oneliner/sessions")) {
} else {
await ensureOneLinerSession();
}
}

View File

@@ -293,6 +293,10 @@ test("tracking refresh and top-video analysis flows expose async feedback inside
const tracking = extractBetween(APP, "function renderTrackingScreen()", "function renderAutomationScreen()");
const discovery = extractBetween(APP, "function renderDiscoveryOverviewSection(", "function renderDiscoveryRelationsSection(");
const accountWorkspaceLoad = extractBetween(APP, "async function loadPlatformAccount(", "async function bootstrap()");
const bootstrapSource = extractBetween(APP, "async function bootstrap()", "async function markTrackingDigestRead()");
const oneLinerHydrate = extractBetween(APP, "async function hydrateSelectedOneLinerRun()", "async function loadAgentControlSurfaces(");
const controlSurfaces = extractBetween(APP, "async function loadAgentControlSurfaces(", "async function loadOneLinerMessages(");
const oneLinerMessages = extractBetween(APP, "async function loadOneLinerMessages(", "async function ensureOneLinerSession(");
const trackingActions = extractBetween(APP, "async function markTrackingDigestRead()", "function createEmptyTrackingDigest(");
const oneLinerSession = extractBetween(APP, "async function ensureOneLinerSession()", "async function submitOneLinerMessage(");
const oneLinerRun = extractBetween(APP, "async function createOneLinerRun(", "async function confirmOneLinerRun(");
@@ -300,6 +304,9 @@ test("tracking refresh and top-video analysis flows expose async feedback inside
const skillReview = extractBetween(APP, "function openPlatformSkillReviewAction(", "function openPlatformSkillRollbackAction(");
const agentDetail = extractBetween(APP, "async function openPlatformAgentDetailAction(", "function openPlatformSkillReviewAction(");
const topVideoAction = extractBetween(APP, "function openAnalyzeTopVideosAction()", "function openSimilaritySearchAction()");
const jobRecoverability = extractBetween(APP, "function getJobRecoverability(job) {", "function getJobRecoveryRequest(job) {");
const recoveryAction = extractBetween(APP, "async function recoverJobAction(", "function getRecoverableFailedJobs()");
const clickActions = extractBetween(APP, "document.addEventListener(\"click\", async (event) => {", "document.addEventListener(\"submit\", async (event) => {");
assert.match(APP, /function summarizeTrackingRefreshPayload\(/);
assert.match(APP, /function rememberTrackingRefreshNotice\(/);
@@ -313,6 +320,12 @@ test("tracking refresh and top-video analysis flows expose async feedback inside
assert.match(discovery, /这批结果已经回流到当前账号页/);
assert.doesNotMatch(trackingActions, /.*|.*|.*/s);
assert.doesNotMatch(accountWorkspaceLoad, /supportsAccountVideos|supportsAccountSnapshots|supportsCreatorFields|supportsAnalysisReports/);
assert.ok(!bootstrapSource.includes('backendSupports("/v2/integrations/health")'));
assert.ok(!bootstrapSource.includes('backendSupports("/v2/live-recorder/sources")'));
assert.ok(!oneLinerHydrate.includes('backendSupports("/v2/oneliner/runs/{run_id}")'));
assert.ok(!oneLinerMessages.includes('backendSupports("/v2/oneliner/sessions/{session_id}/messages")'));
assert.ok(!controlSurfaces.includes('backendSupports("/v2/oneliner/profile")'));
assert.ok(!controlSurfaces.includes('backendSupports("/v2/platform-agents")'));
assert.doesNotMatch(oneLinerSession, /当前后端还没有接入 OneLiner 会话接口/);
assert.doesNotMatch(oneLinerRun, /当前后端还没有接入主 Agent 运行层/);
assert.doesNotMatch(oneLinerAction, /当前后端还没有接入 OneLiner 动作执行器/);
@@ -320,6 +333,9 @@ test("tracking refresh and top-video analysis flows expose async feedback inside
assert.ok(!agentDetail.includes('backendSupports("/v2/platform-agents/{platform}/skills/{skill_id}/versions")'));
assert.doesNotMatch(topVideoAction, /.*/s);
assert.match(topVideoAction, /当前实例未提供/);
assert.ok(!jobRecoverability.includes('backendSupports("/v2/explore/jobs/{job_id}/retry") && uploadedPath'));
assert.ok(!recoveryAction.includes('if (backendSupports("/v2/explore/jobs/{job_id}/retry"))'));
assert.ok(!clickActions.includes('else if (backendSupports("/v2/oneliner/sessions"))'));
});
test("discovery and production screens expose compact mobile flow summaries", () => {
@@ -498,9 +514,10 @@ test("bootstrap only loads live recorder runtime endpoints when the integration
const bootstrap = extractBetween(APP, "async function bootstrap()", "async function markTrackingDigestRead()");
assert.match(bootstrap, /const liveRecorderIntegration = integrationHealth\?\.live_recorder \|\| null/);
assert.match(bootstrap, /const canLoadLiveRecorderRuntime = Boolean\(liveRecorderIntegration\?\.reachable\)/);
assert.match(bootstrap, /supportsLiveRecorderStatus && canLoadLiveRecorderRuntime/);
assert.match(bootstrap, /supportsLiveRecorderFiles && canLoadLiveRecorderRuntime/);
assert.match(bootstrap, /supportsLiveRecorderHealth && canLoadLiveRecorderRuntime/);
assert.doesNotMatch(bootstrap, /supportsLiveRecorderStatus|supportsLiveRecorderFiles|supportsLiveRecorderHealth/);
assert.match(bootstrap, /canLoadLiveRecorderRuntime \? storyforgeFetch\("\/v2\/live-recorder\/status"\)/);
assert.match(bootstrap, /canLoadLiveRecorderRuntime \? storyforgeFetch\("\/v2\/live-recorder\/files\?limit=16"\)/);
assert.match(bootstrap, /canLoadLiveRecorderRuntime \? storyforgeFetch\("\/v2\/live-recorder\/health"\)/);
});
test("oneliner submit failures stay inside the app instead of using a browser alert", () => {