fix: remove stale backend capability gating
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 20:49:43 +08:00
parent b93b32f59d
commit b0199a6b85
2 changed files with 31 additions and 41 deletions

View File

@@ -1714,10 +1714,6 @@ async function loadKnowledgeDocuments(knowledgeBases) {
} }
async function loadStorageStatus(projectId = "") { async function loadStorageStatus(projectId = "") {
if (!backendSupports("/v2/storage/status")) {
appState.storageStatus = null;
return null;
}
const suffix = projectId ? `?project_id=${encodeURIComponent(projectId)}` : ""; const suffix = projectId ? `?project_id=${encodeURIComponent(projectId)}` : "";
const payload = await storyforgeFetch(`/v2/storage/status${suffix}`).catch(() => null); const payload = await storyforgeFetch(`/v2/storage/status${suffix}`).catch(() => null);
appState.storageStatus = payload; appState.storageStatus = payload;
@@ -1884,9 +1880,6 @@ async function loadOneLinerMessages(sessionId) {
async function ensureOneLinerSession() { async function ensureOneLinerSession() {
const projectId = getOneLinerProjectId(); const projectId = getOneLinerProjectId();
if (!projectId) throw new Error("当前还没有项目OneLiner 需要先绑定项目上下文。"); if (!projectId) throw new Error("当前还没有项目OneLiner 需要先绑定项目上下文。");
if (!backendSupports("/v2/oneliner/sessions")) {
throw new Error("当前后端还没有接入 OneLiner 会话接口。");
}
let session = getCurrentOneLinerSession(); let session = getCurrentOneLinerSession();
if (!session) { if (!session) {
session = await storyforgeFetch("/v2/oneliner/sessions", { session = await storyforgeFetch("/v2/oneliner/sessions", {
@@ -1895,6 +1888,11 @@ async function ensureOneLinerSession() {
project_id: projectId, project_id: projectId,
preferred_platform: getPreferredPlatform() preferred_platform: getPreferredPlatform()
} }
}).catch((error) => {
if (isMissingBackendCapability(error)) {
throw new Error("当前实例还没有开放 OneLiner 会话接口。");
}
throw error;
}); });
appState.onelinerSessions = [session, ...safeArray(appState.onelinerSessions)]; appState.onelinerSessions = [session, ...safeArray(appState.onelinerSessions)];
appState.selectedOnelinerSessionId = session.id; appState.selectedOnelinerSessionId = session.id;
@@ -1931,9 +1929,6 @@ async function submitOneLinerMessage(content) {
} }
async function createOneLinerRun(runRequest) { async function createOneLinerRun(runRequest) {
if (!backendSupports("/v2/oneliner/runs")) {
throw new Error("当前后端还没有接入主 Agent 运行层。");
}
const projectId = getOneLinerProjectId(); const projectId = getOneLinerProjectId();
const payload = await storyforgeFetch("/v2/oneliner/runs", { const payload = await storyforgeFetch("/v2/oneliner/runs", {
method: "POST", method: "POST",
@@ -1945,6 +1940,11 @@ async function createOneLinerRun(runRequest) {
scheduling_mode: "queued", scheduling_mode: "queued",
...runRequest ...runRequest
} }
}).catch((error) => {
if (isMissingBackendCapability(error)) {
throw new Error("当前实例还没有开放主 Agent 运行层。");
}
throw error;
}); });
await loadAgentControlSurfaces(projectId); await loadAgentControlSurfaces(projectId);
appState.selectedOnelinerRunId = payload?.id || choosePreferredOneLinerRunId(appState.onelinerRuns, ""); appState.selectedOnelinerRunId = payload?.id || choosePreferredOneLinerRunId(appState.onelinerRuns, "");
@@ -2229,9 +2229,6 @@ function collectOneLinerActionPayload(action) {
} }
async function executeOneLinerAction(executorKey, options = {}) { async function executeOneLinerAction(executorKey, options = {}) {
if (!backendSupports("/v2/oneliner/actions/execute")) {
throw new Error("当前后端还没有接入 OneLiner 动作执行器。");
}
const projectId = getOneLinerProjectId(); const projectId = getOneLinerProjectId();
const session = getCurrentOneLinerSession() || await ensureOneLinerSession(); const session = getCurrentOneLinerSession() || await ensureOneLinerSession();
const payload = await storyforgeFetch("/v2/oneliner/actions/execute", { const payload = await storyforgeFetch("/v2/oneliner/actions/execute", {
@@ -2243,6 +2240,11 @@ async function executeOneLinerAction(executorKey, options = {}) {
session_id: options.sessionId || session?.id || "", session_id: options.sessionId || session?.id || "",
payload: options.payload || {} payload: options.payload || {}
} }
}).catch((error) => {
if (isMissingBackendCapability(error)) {
throw new Error("当前实例还没有开放 OneLiner 动作执行器。");
}
throw error;
}); });
await loadAgentControlSurfaces(projectId); await loadAgentControlSurfaces(projectId);
if (appState.selectedOnelinerSessionId) { if (appState.selectedOnelinerSessionId) {
@@ -2500,10 +2502,8 @@ async function bootstrap() {
const runtimePlatforms = getRuntimePlatformValues(); const runtimePlatforms = getRuntimePlatformValues();
const preferredPlatform = getCurrentPlatformValue(); const preferredPlatform = getCurrentPlatformValue();
setCurrentPlatform(preferredPlatform); setCurrentPlatform(preferredPlatform);
const supportsReviews = backendSupports("/v2/reviews");
const supportsIntegrationHealth = backendSupports("/v2/integrations/health"); const supportsIntegrationHealth = backendSupports("/v2/integrations/health");
const supportsLocalModels = backendSupports("/v2/integrations/local-models"); const supportsLocalModels = backendSupports("/v2/integrations/local-models");
const supportsStorageStatus = backendSupports("/v2/storage/status");
const supportsLiveRecorderSources = backendSupports("/v2/live-recorder/sources"); const supportsLiveRecorderSources = backendSupports("/v2/live-recorder/sources");
const supportsLiveRecorderStatus = backendSupports("/v2/live-recorder/status"); const supportsLiveRecorderStatus = backendSupports("/v2/live-recorder/status");
const supportsLiveRecorderFiles = backendSupports("/v2/live-recorder/files"); const supportsLiveRecorderFiles = backendSupports("/v2/live-recorder/files");
@@ -2545,7 +2545,7 @@ async function bootstrap() {
trackingDigest trackingDigest
}; };
})), })),
supportsReviews ? storyforgeFetch("/v2/reviews").catch(() => []) : Promise.resolve([]), storyforgeFetch("/v2/reviews").catch(() => []),
supportsIntegrationHealth ? storyforgeFetch("/v2/integrations/health").catch(() => null) : Promise.resolve(null), supportsIntegrationHealth ? storyforgeFetch("/v2/integrations/health").catch(() => null) : Promise.resolve(null),
supportsLocalModels ? storyforgeFetch("/v2/integrations/local-models").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: [] }) supportsLiveRecorderSources ? storyforgeFetch("/v2/live-recorder/sources").catch(() => ({ items: [] })) : Promise.resolve({ items: [] })
@@ -2605,11 +2605,7 @@ async function bootstrap() {
appState.liveRecorderHealth = liveRecorderHealth; appState.liveRecorderHealth = liveRecorderHealth;
appState.documents = await loadKnowledgeDocuments(dashboard.knowledge_bases); appState.documents = await loadKnowledgeDocuments(dashboard.knowledge_bases);
appState.selectedProjectId = appState.selectedProjectId || dashboard.projects?.[0]?.id || ""; appState.selectedProjectId = appState.selectedProjectId || dashboard.projects?.[0]?.id || "";
if (supportsStorageStatus) { await loadStorageStatus(appState.selectedProjectId || "");
await loadStorageStatus(appState.selectedProjectId || "");
} else {
appState.storageStatus = null;
}
await loadAgentControlSurfaces(appState.selectedProjectId || ""); await loadAgentControlSurfaces(appState.selectedProjectId || "");
if (appState.selectedOnelinerSessionId) { if (appState.selectedOnelinerSessionId) {
await loadOneLinerMessages(appState.selectedOnelinerSessionId); await loadOneLinerMessages(appState.selectedOnelinerSessionId);
@@ -3220,11 +3216,7 @@ async function applySelectedProject(projectId = "") {
appState.selectedProjectId = projectId || ""; appState.selectedProjectId = projectId || "";
setBusy(true, "正在切换项目视图..."); setBusy(true, "正在切换项目视图...");
try { try {
if (backendSupports("/v2/storage/status")) { await loadStorageStatus(appState.selectedProjectId || "");
await loadStorageStatus(appState.selectedProjectId || "");
} else {
appState.storageStatus = null;
}
await loadAgentControlSurfaces(appState.selectedProjectId || ""); await loadAgentControlSurfaces(appState.selectedProjectId || "");
if (appState.selectedOnelinerSessionId) { if (appState.selectedOnelinerSessionId) {
await loadOneLinerMessages(appState.selectedOnelinerSessionId); await loadOneLinerMessages(appState.selectedOnelinerSessionId);
@@ -3824,13 +3816,13 @@ function renderStorageStatusPanel() {
<div class="panel-head"> <div class="panel-head">
<div> <div>
<h3>存储状态</h3> <h3>存储状态</h3>
<div class="panel-subtitle">后端暂未提供 /v2/storage/status,先用任务和录像文件做本地观察</div> <div class="panel-subtitle">当前实例没有返回存储策略时,先用任务和录像文件做本地观察</div>
</div> </div>
<span class="tag blue">降级视图</span> <span class="tag blue">降级视图</span>
</div> </div>
<div class="task-item"> <div class="task-item">
<h4>未拉取到 NAS 策略</h4> <h4>未拉取到 NAS 策略</h4>
<p>后端补上 storage/status 后,这里会自动显示账号 / 项目 / 任务分层、容量和最近写入路径。</p> <p> storage/status 暂时拿不到时,这里会自动退回到任务和录像文件的降级视图。</p>
<div class="task-meta"> <div class="task-meta">
<span class="tag">最近任务 ${escapeHtml(formatNumber(projectJobCount))}</span> <span class="tag">最近任务 ${escapeHtml(formatNumber(projectJobCount))}</span>
<span class="tag">录制源 ${escapeHtml(formatNumber(liveRecorderSources.length))}</span> <span class="tag">录制源 ${escapeHtml(formatNumber(liveRecorderSources.length))}</span>
@@ -6896,14 +6888,6 @@ function renderReviewScreen() {
} }
return screenShell("发布与复盘", "先自动连接工作区。", `${button("自动连接", "open-auth", "primary")}`, renderEmptyState("复盘未加载", "自动连接成功后,这里会先用最近任务生成一版复盘入口。")); return screenShell("发布与复盘", "先自动连接工作区。", `${button("自动连接", "open-auth", "primary")}`, renderEmptyState("复盘未加载", "自动连接成功后,这里会先用最近任务生成一版复盘入口。"));
} }
if (!backendSupports("/v2/reviews")) {
return screenShell(
"发布与复盘",
"当前 live collector 还没有接入复盘读写接口。",
`${button("去生产", "goto-production", "primary")}`,
renderEmptyState("复盘能力暂未接入", "这套后端还缺 /v2/reviews当前可以继续跑生产任务等 live collector 同步后这里会自动切成真实复盘工作台。")
);
}
const project = getSelectedProject(); const project = getSelectedProject();
const completed = safeArray(appState.dashboard.recent_jobs).filter((item) => item.status === "completed").slice(0, 4); const completed = safeArray(appState.dashboard.recent_jobs).filter((item) => item.status === "completed").slice(0, 4);
const reviews = getProjectReviews(project?.id || "").slice(0, 8); const reviews = getProjectReviews(project?.id || "").slice(0, 8);
@@ -11314,11 +11298,7 @@ document.addEventListener("click", async (event) => {
appState.selectedProjectId = action.dataset.projectId || ""; appState.selectedProjectId = action.dataset.projectId || "";
setBusy(true, "正在切换项目视图..."); setBusy(true, "正在切换项目视图...");
try { try {
if (backendSupports("/v2/storage/status")) { await loadStorageStatus(appState.selectedProjectId || "");
await loadStorageStatus(appState.selectedProjectId || "");
} else {
appState.storageStatus = null;
}
await loadAgentControlSurfaces(appState.selectedProjectId || ""); await loadAgentControlSurfaces(appState.selectedProjectId || "");
if (appState.selectedOnelinerSessionId) { if (appState.selectedOnelinerSessionId) {
await loadOneLinerMessages(appState.selectedOnelinerSessionId); await loadOneLinerMessages(appState.selectedOnelinerSessionId);

View File

@@ -274,21 +274,28 @@ test("governance and quota panels use real empty-state language instead of backe
test("quota and review screens foreground live next-step guidance", () => { test("quota and review screens foreground live next-step guidance", () => {
const tenantQuota = extractBetween(APP, "function renderTenantQuotaPanel()", "function policyScopeTagLabel("); const tenantQuota = extractBetween(APP, "function renderTenantQuotaPanel()", "function policyScopeTagLabel(");
const review = extractBetween(APP, "function renderReviewScreen()", "function renderStrategyScreen()"); const review = extractBetween(APP, "function renderReviewScreen()", "function renderStrategyScreen()");
const storage = extractBetween(APP, "function renderStorageStatusPanel()", "function renderAutomationScreen()");
assert.match(tenantQuota, /先处理存储超限|先恢复额度保护|先补项目额度策略|先检查本周期消耗|先跑出第一条计量/); assert.match(tenantQuota, /先处理存储超限|先恢复额度保护|先补项目额度策略|先检查本周期消耗|先跑出第一条计量/);
assert.match(tenantQuota, /主要消耗/); assert.match(tenantQuota, /主要消耗/);
assert.match(tenantQuota, /周期 /); assert.match(tenantQuota, /周期 /);
assert.match(tenantQuota, /计量 /); assert.match(tenantQuota, /计量 /);
assert.doesNotMatch(review, /复盘能力暂未接入|还缺 \/v2\/reviews/);
assert.match(review, /先把最近完成任务写成复盘|先回看高频结论|先跑出第一条可复盘任务/); assert.match(review, /先把最近完成任务写成复盘|先回看高频结论|先跑出第一条可复盘任务/);
assert.match(review, /高频结论/); assert.match(review, /高频结论/);
assert.match(review, /已发布/); assert.match(review, /已发布/);
assert.doesNotMatch(storage, /后端暂未提供 \/v2\/storage\/status/);
assert.match(storage, /当前实例没有返回存储策略时/);
}); });
test("tracking refresh and top-video analysis flows expose async feedback inside the workbench", () => { test("tracking refresh and top-video analysis flows expose async feedback inside the workbench", () => {
const tracking = extractBetween(APP, "function renderTrackingScreen()", "function renderAutomationScreen()"); const tracking = extractBetween(APP, "function renderTrackingScreen()", "function renderAutomationScreen()");
const discovery = extractBetween(APP, "function renderDiscoveryOverviewSection(", "function renderDiscoveryRelationsSection("); const discovery = extractBetween(APP, "function renderDiscoveryOverviewSection(", "function renderDiscoveryRelationsSection(");
const trackingActions = extractBetween(APP, "async function markTrackingDigestRead()", "function createEmptyTrackingDigest("); 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(");
const oneLinerAction = extractBetween(APP, "async function executeOneLinerAction(", "function openCurrentOneLinerRunResultAction(");
const skillReview = extractBetween(APP, "function openPlatformSkillReviewAction(", "function openPlatformSkillRollbackAction("); const skillReview = extractBetween(APP, "function openPlatformSkillReviewAction(", "function openPlatformSkillRollbackAction(");
const topVideoAction = extractBetween(APP, "function openAnalyzeTopVideosAction()", "function openSimilaritySearchAction()"); const topVideoAction = extractBetween(APP, "function openAnalyzeTopVideosAction()", "function openSimilaritySearchAction()");
@@ -302,6 +309,9 @@ test("tracking refresh and top-video analysis flows expose async feedback inside
assert.match(discovery, /最近高分拆解/); assert.match(discovery, /最近高分拆解/);
assert.match(discovery, /这批结果已经回流到当前账号页/); assert.match(discovery, /这批结果已经回流到当前账号页/);
assert.doesNotMatch(trackingActions, /.*|.*|.*/s); assert.doesNotMatch(trackingActions, /.*|.*|.*/s);
assert.doesNotMatch(oneLinerSession, /当前后端还没有接入 OneLiner 会话接口/);
assert.doesNotMatch(oneLinerRun, /当前后端还没有接入主 Agent 运行层/);
assert.doesNotMatch(oneLinerAction, /当前后端还没有接入 OneLiner 动作执行器/);
assert.doesNotMatch(skillReview, /当前后端还没有接入平台技能验收接口/); assert.doesNotMatch(skillReview, /当前后端还没有接入平台技能验收接口/);
assert.doesNotMatch(topVideoAction, /.*/s); assert.doesNotMatch(topVideoAction, /.*/s);
assert.match(topVideoAction, /当前实例未提供/); assert.match(topVideoAction, /当前实例未提供/);