fix: keep workbench failures inside app shell
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 22:00:51 +08:00
parent ea643aad63
commit 9317d4c0a5
3 changed files with 43 additions and 20 deletions

View File

@@ -31,6 +31,7 @@
- `OneLiner 会话 / 运行详情 / 治理控制面 / integrations / live-recorder` 这些固定接口也已经切成 live-first请求失败才降级不再先被陈旧 capability 表拦住。
- 任务恢复链会优先真实调用 `/v2/explore/jobs/{job_id}/retry`,只有接口真的不存在时才回退到手动恢复模板。
- `找对标 / 跟踪账号` 里一批已经失效的 “当前平台待接入” 按钮禁用与入口分支已删除,当前 active 平台都直接走真实路由,失败时再给真实反馈。
- 工作台前端已经清掉浏览器 `alert` 弹窗,缺对象、权限不足、刷新失败和加载失败都会回到站内反馈,不再把用户从当前流程里打断出去。
### NAS 联调与回归
@@ -38,7 +39,7 @@
- Web: `http://192.168.31.188:19192/`
- Collector: `http://192.168.31.188:19193/healthz`
- 当前基线通过:
- 前端测试 `60/60`
- 前端测试 `61/61`
- `bash scripts/check_repo_baseline.sh`
- `bash scripts/smoke_fnos_storyforge_lan.sh`

View File

@@ -9201,7 +9201,8 @@ async function openPlatformAgentDetailAction(platform) {
const normalizedPlatform = normalizePlatformValue(platform, getPreferredPlatform());
const profile = safeArray(appState.platformAgents).find((item) => item.platform === normalizedPlatform) || null;
if (!profile) {
alert("没有找到这平台 Agent");
rememberAction("平台 Agent 不存在", "当前没有找到这平台 Agent,请先刷新当前页面。", "orange");
renderAll();
return;
}
const [memoriesPayload, skillsPayload] = await Promise.all([
@@ -9379,7 +9380,8 @@ function openActionRegistryEditAction(actionKey) {
const project = requireSelectedProject();
const actionDef = safeArray(appState.onelinerActionRegistry).find((item) => item.action_key === actionKey) || null;
if (!actionDef) {
alert("没有找到这条动作定义。");
rememberAction("动作定义不存在", "当前没有找到这条 OneLiner 动作定义,请先刷新当前页面。", "orange");
renderAll();
return;
}
openActionModal({
@@ -9495,7 +9497,8 @@ function openCreateAssistantAction() {
function openEditAssistantAction(assistantId = "") {
const assistant = safeArray(appState.dashboard?.assistants).find((item) => item.id === assistantId) || getSelectedAssistant();
if (!assistant) {
alert("请先选择一个 Agent");
rememberAction("请先选择 Agent", "当前还没有可编辑的 Agent先在项目里选择或创建一个。", "orange");
renderAll();
return;
}
const modelOptions = getModelOptions();
@@ -9718,12 +9721,14 @@ async function scanAdminOpsAction() {
function openAdminIncidentReviewAction(incidentId) {
if (!isSuperAdmin()) {
alert("只有平台管理者才能审计处理故障事件。");
rememberAction("权限不足", "只有平台管理者才能审计处理故障事件。", "orange");
renderAll();
return;
}
const incident = safeArray(appState.adminOpsOverview?.incidents).find((item) => item.id === incidentId);
if (!incident) {
alert("没有找到这条故障事件。");
rememberAction("故障事件不存在", "当前没有找到这条故障事件,请先重新扫描。", "orange");
renderAll();
return;
}
openActionModal({
@@ -9776,12 +9781,14 @@ function openAdminIncidentReviewAction(incidentId) {
function openAdminRepairPlanAction(incidentId) {
if (!isSuperAdmin()) {
alert("只有平台管理者才能生成修复计划。");
rememberAction("权限不足", "只有平台管理者才能生成修复计划。", "orange");
renderAll();
return;
}
const incident = safeArray(appState.adminOpsOverview?.incidents).find((item) => item.id === incidentId);
if (!incident) {
alert("没有找到这条故障事件。");
rememberAction("故障事件不存在", "当前没有找到这条故障事件,请先重新扫描。", "orange");
renderAll();
return;
}
openActionModal({
@@ -9810,12 +9817,14 @@ function openAdminRepairPlanAction(incidentId) {
function openAdminFixRunDetailAction(runId) {
if (!isSuperAdmin()) {
alert("只有平台管理者才能查看修复计划。");
rememberAction("权限不足", "只有平台管理者才能查看修复计划。", "orange");
renderAll();
return;
}
const run = safeArray(appState.adminFixRuns.length ? appState.adminFixRuns : appState.adminOpsOverview?.recent_fix_runs).find((item) => item.id === runId);
if (!run) {
alert("没有找到这条修复计划。");
rememberAction("修复计划不存在", "当前没有找到这条修复计划,请先刷新管理员配置台。", "orange");
renderAll();
return;
}
openActionModal({
@@ -9857,12 +9866,14 @@ function openAdminFixRunDetailAction(runId) {
function openAdminFixRunAuditAction(runId) {
if (!isSuperAdmin()) {
alert("只有平台管理者才能审计修复计划。");
rememberAction("权限不足", "只有平台管理者才能审计修复计划。", "orange");
renderAll();
return;
}
const run = safeArray(appState.adminFixRuns.length ? appState.adminFixRuns : appState.adminOpsOverview?.recent_fix_runs).find((item) => item.id === runId);
if (!run) {
alert("没有找到这条修复计划。");
rememberAction("修复计划不存在", "当前没有找到这条修复计划,请先刷新管理员配置台。", "orange");
renderAll();
return;
}
openActionModal({
@@ -10033,7 +10044,8 @@ function openRecoverJobAction(jobId) {
const job = safeArray(appState.dashboard?.recent_jobs).find((item) => item.id === jobId)
|| (appState.lastJobDetail?.job?.id === jobId ? appState.lastJobDetail.job : null);
if (!job) {
alert("没有找到这条任务。");
rememberAction("任务不存在", "当前没有找到这条任务,请先刷新生产中心。", "orange");
renderAll();
return;
}
const recovery = getJobRecoverability(job);
@@ -10350,7 +10362,8 @@ function openLiveRecorderCreateAction() {
function openLiveRecorderSourceAction(sourceId) {
const source = safeArray(appState.liveRecorderSources).find((item) => item.id === sourceId);
if (!source) {
alert("没有找到这条录制源。");
rememberAction("录制源不存在", "当前没有找到这条录制源,请先刷新录制维护。", "orange");
renderAll();
return;
}
const currentProject = getSelectedProject() || safeArray(appState.dashboard?.projects).find((item) => item.id === source.project_id) || appState.dashboard?.projects?.[0] || null;
@@ -10435,7 +10448,8 @@ function openLiveRecorderImportAction() {
async function toggleLiveRecorderSourceAction(sourceId, nextEnabled) {
const source = safeArray(appState.liveRecorderSources).find((item) => item.id === sourceId);
if (!source) {
alert("没有找到这条录制源。");
rememberAction("录制源不存在", "当前没有找到这条录制源,请先刷新录制维护。", "orange");
renderAll();
return;
}
setBusy(true, nextEnabled ? "正在启用录制源..." : "正在停用录制源...");
@@ -10456,7 +10470,8 @@ async function toggleLiveRecorderSourceAction(sourceId, nextEnabled) {
async function deleteLiveRecorderSourceAction(sourceId) {
const source = safeArray(appState.liveRecorderSources).find((item) => item.id === sourceId);
if (!source) {
alert("没有找到这条录制源。");
rememberAction("录制源不存在", "当前没有找到这条录制源,请先刷新录制维护。", "orange");
renderAll();
return;
}
if (!window.confirm(`确认删除「${source.title || source.source_url || "录制源"}」吗?删除后需要重新导入。`)) {
@@ -10660,7 +10675,6 @@ document.addEventListener("click", async (event) => {
const reason = action.dataset.disabledReason || action.title || "当前动作暂不可用";
rememberAction("动作已拦截", reason, "orange");
renderAll();
alert(reason);
return;
}
if (name === "auth-refresh" || name === "refresh-data") {
@@ -10678,7 +10692,7 @@ document.addEventListener("click", async (event) => {
if (name === "auth-refresh" && message) {
message.textContent = formatActionErrorMessage(error, "自动连接失败");
} else {
alert("刷新数据失败: " + error.message);
presentActionFailure(error, "刷新数据失败");
}
} finally {
setBusy(false, "");
@@ -11064,7 +11078,8 @@ document.addEventListener("click", async (event) => {
if (name === "open-review-edit") {
const review = getReviewById(action.dataset.reviewId || "");
if (!review) {
alert("复盘记录不存在,请先刷新页面");
rememberAction("复盘记录不存在", "当前没有找到这条复盘记录,请先刷新当前页面。", "orange");
renderAll();
return;
}
openReviewAction({ review });
@@ -11196,7 +11211,7 @@ document.addEventListener("click", async (event) => {
}
} catch (error) {
if (requestToken === appState.selectedAccountRequestToken) {
alert("加载对标详情失败: " + error.message);
presentActionFailure(error, "加载对标详情失败");
}
} finally {
if (requestToken === appState.selectedAccountRequestToken) {

View File

@@ -782,3 +782,10 @@ test("oneliner panel auto-polls active runs while the floating panel stays open"
assert.match(APP, /setTimeout\(async \(\) => \{/);
assert.match(APP, /await hydrateSelectedOneLinerRun\(\);/);
});
test("workbench interaction flow avoids browser alerts and keeps failures inside the product shell", () => {
const clicks = extractBetween(APP, "document.addEventListener(\"click\", async (event) => {", "document.addEventListener(\"submit\", async (event) => {");
assert.doesNotMatch(APP, /alert\(/);
assert.match(clicks, /name === "show-disabled-reason"[\s\S]*rememberAction\("动作已拦截"/);
assert.match(clicks, /name === "auth-refresh" \|\| name === "refresh-data"[\s\S]*presentActionFailure\(error, "刷新数据失败"\)/);
});