fix: relax workbench live status timeout

This commit is contained in:
lyon
2026-05-25 23:40:52 +08:00
parent 817f2d41db
commit 18a9a5de05
3 changed files with 73 additions and 13 deletions
@@ -608,6 +608,11 @@ function runStaticSmoke() {
evidence: ["API 正常", "API 错误", "等待验证", "只读模式", "hwlab-cloud-api /health/live", "/v1/agent/chat", "/v1/m3/io", "raw degraded kept as internalRawStatuses"]
});
addCheck(checks, blockers, "workbench-live-surface-timeout-contract", hasWorkbenchLiveSurfaceTimeoutContract(files), "Workbench live status uses a dedicated live-surface timeout instead of the 4500ms light API budget.", {
blocker: "runtime_blocker",
evidence: ["DEFAULT_LIVE_SURFACE_TIMEOUT_MS=12000", "liveSurfaceFetch", "liveSurfaceRpc", "/v1/m3/status", "system.health"]
});
addCheck(checks, blockers, "code-agent-chat-primary-flow", hasCodeAgentChatContract(files), "Center Agent conversation sends to the controlled Code Agent chat endpoint and no longer uses 添加草稿 as the primary flow.", {
blocker: "runtime_blocker",
evidence: ["command-send", "agent-chat-status", "/v1/agent/chat", "conversationId/messageId/status/timestamps/error.message"]
@@ -2641,6 +2646,25 @@ function hasCodeAgentLongTimeoutContract(files) {
);
}
function hasWorkbenchLiveSurfaceTimeoutContract(files) {
const app = files.app;
return (
/DEFAULT_API_TIMEOUT_MS\s*=\s*4500/u.test(app) &&
/DEFAULT_LIVE_SURFACE_TIMEOUT_MS\s*=\s*12000/u.test(app) &&
/LIVE_SURFACE_TIMEOUT_MS\s*=\s*resolveTimeoutMs\("liveSurfaceTimeoutMs"/u.test(app) &&
/timeoutMs:\s*LIVE_SURFACE_TIMEOUT_MS/u.test(functionBody(app, "liveSurfaceFetch")) &&
/timeoutName:\s*`工作台实况 \$\{path\}`/u.test(functionBody(app, "liveSurfaceFetch")) &&
/callRpc\(method,\s*params,\s*\{/u.test(functionBody(app, "liveSurfaceRpc")) &&
/timeoutMs:\s*LIVE_SURFACE_TIMEOUT_MS/u.test(functionBody(app, "liveSurfaceRpc")) &&
/timeoutMs:\s*options\?\.timeoutMs \?\? API_TIMEOUT_MS/u.test(functionBody(app, "callRpc")) &&
/liveSurfaceFetch\("\/health\/live"\)/u.test(functionBody(app, "loadLiveSurface")) &&
/liveSurfaceFetch\("\/v1\/m3\/status"\)/u.test(functionBody(app, "loadLiveSurface")) &&
/liveSurfaceRpc\("system\.health"\)/u.test(functionBody(app, "loadLiveSurface")) &&
/timeoutMs:\s*LIVE_SURFACE_TIMEOUT_MS/u.test(functionBody(app, "loadGateDiagnostics")) &&
/timeoutName:\s*"工作台 M3 状态刷新"/u.test(functionBody(app, "runM3IoAction"))
);
}
function hasCodeAgentTraceReplayDisclosure({ app, styles }) {
const tracePanelBody = functionBody(app, "messageTracePanel");
const traceToolbarBody = functionBody(app, "messageTraceToolbar");
+36 -13
View File
@@ -18,6 +18,7 @@ import {
} from "./code-agent-m3-evidence.mjs";
const DEFAULT_API_TIMEOUT_MS = 4500;
const DEFAULT_LIVE_SURFACE_TIMEOUT_MS = 12000;
const DEFAULT_CODE_AGENT_TIMEOUT_MS = 600000;
const DEFAULT_CODE_AGENT_SUBMIT_TIMEOUT_MS = 60000;
const DEFAULT_CODE_AGENT_CANCEL_TIMEOUT_MS = 30000;
@@ -28,6 +29,7 @@ const TRACE_STREAM_FALLBACK_MS = 2500;
const TRACE_POLL_INTERVAL_MS = 1000;
const FULL_TRACE_REPLAY_TIMEOUT_MS = 15000;
const API_TIMEOUT_MS = resolveTimeoutMs("apiTimeoutMs", DEFAULT_API_TIMEOUT_MS, { min: 500, max: 30000 });
const LIVE_SURFACE_TIMEOUT_MS = resolveTimeoutMs("liveSurfaceTimeoutMs", DEFAULT_LIVE_SURFACE_TIMEOUT_MS, { min: 5000, max: 60000 });
let CODE_AGENT_TIMEOUT_MS = resolveUserCodeAgentTimeoutMs();
let GATEWAY_SHELL_TIMEOUT_MS = resolveUserGatewayShellTimeoutMs();
const CODE_AGENT_SUBMIT_TIMEOUT_MS = resolveTimeoutMs("codeAgentSubmitTimeoutMs", DEFAULT_CODE_AGENT_SUBMIT_TIMEOUT_MS, { min: configuredTimeoutMinMs("codeAgentSubmitTimeoutMs", 5000), max: 120000 });
@@ -1593,15 +1595,15 @@ function wait(ms) {
async function loadLiveSurface() {
const projectId = gateSummary.topology.projectId;
const [healthLive, liveBuilds, restIndex, m3Control, m3Status, health, adapter, audit, evidence] = await Promise.all([
fetchJson("/health/live"),
fetchJson("/v1/live-builds"),
fetchJson("/v1"),
fetchJson("/v1/m3/io"),
fetchJson("/v1/m3/status"),
callRpc("system.health"),
callRpc("cloud.adapter.describe"),
callRpc("audit.event.query", { projectId, limit: 6 }),
callRpc("evidence.record.query", { projectId, limit: 6 })
liveSurfaceFetch("/health/live"),
liveSurfaceFetch("/v1/live-builds"),
liveSurfaceFetch("/v1"),
liveSurfaceFetch("/v1/m3/io"),
liveSurfaceFetch("/v1/m3/status"),
liveSurfaceRpc("system.health"),
liveSurfaceRpc("cloud.adapter.describe"),
liveSurfaceRpc("audit.event.query", { projectId, limit: 6 }),
liveSurfaceRpc("evidence.record.query", { projectId, limit: 6 })
]);
return {
@@ -1618,13 +1620,27 @@ async function loadLiveSurface() {
};
}
function liveSurfaceFetch(path) {
return fetchJson(path, {
timeoutMs: LIVE_SURFACE_TIMEOUT_MS,
timeoutName: `工作台实况 ${path}`
});
}
function liveSurfaceRpc(method, params = {}) {
return callRpc(method, params, {
timeoutMs: LIVE_SURFACE_TIMEOUT_MS,
timeoutName: `工作台实况 /json-rpc ${method}`
});
}
async function loadGateDiagnostics() {
if (state.gateDiagnostics.loading) return;
state.gateDiagnostics.loading = true;
state.gateDiagnostics.error = null;
renderGateTable();
const response = await fetchJson("/v1/diagnostics/gate", {
timeoutMs: API_TIMEOUT_MS,
timeoutMs: LIVE_SURFACE_TIMEOUT_MS,
timeoutName: "内部复核 live 聚合"
});
state.gateDiagnostics.loading = false;
@@ -1733,7 +1749,7 @@ function readActivityRef(activityRef, fallbackMs = Date.now()) {
};
}
async function callRpc(method, params = {}) {
async function callRpc(method, params = {}, options = {}) {
const id = nextProtocolId("req");
const traceId = nextProtocolId("trc");
const response = await fetchJson("/json-rpc", {
@@ -1751,7 +1767,9 @@ async function callRpc(method, params = {}) {
serviceId: "hwlab-cloud-web",
environment: "dev"
}
})
}),
timeoutMs: options?.timeoutMs ?? API_TIMEOUT_MS,
timeoutName: options?.timeoutName ?? `/json-rpc ${method}`
});
if (!response.ok) {
@@ -1837,6 +1855,8 @@ async function runM3IoAction(action) {
"X-Trace-Id": traceId,
"X-Actor-Id": "usr_hwlab_cloud_web"
},
timeoutMs: LIVE_SURFACE_TIMEOUT_MS,
timeoutName: `工作台 M3 IO ${action}`,
body: JSON.stringify(body)
});
@@ -1857,7 +1877,10 @@ async function runM3IoAction(action) {
sourceKind: response.data?.evidenceState?.status === "green" ? "DEV-LIVE" : "BLOCKED"
};
}
const refreshedStatus = await fetchJson("/v1/m3/status");
const refreshedStatus = await fetchJson("/v1/m3/status", {
timeoutMs: LIVE_SURFACE_TIMEOUT_MS,
timeoutName: "工作台 M3 状态刷新"
});
if (refreshedStatus.ok) {
state.m3Control.status = refreshedStatus.data;
}
+13
View File
@@ -401,6 +401,8 @@ assert.match(app, /renderRecords\(state\.liveSurface\)/);
assert.match(app, /traceId: error\.traceId \|\| traceId/);
assert.match(app, /error\.traceId = (?:traceId|response\.data\?\.traceId \|\| traceId)/);
assert.match(app, /DEFAULT_API_TIMEOUT_MS\s*=\s*4500/);
assert.match(app, /DEFAULT_LIVE_SURFACE_TIMEOUT_MS\s*=\s*12000/);
assert.match(app, /LIVE_SURFACE_TIMEOUT_MS\s*=\s*resolveTimeoutMs\("liveSurfaceTimeoutMs"/);
assert.match(app, /DEFAULT_CODE_AGENT_TIMEOUT_MS\s*=\s*600000/);
assert.match(app, /DEFAULT_CODE_AGENT_SUBMIT_TIMEOUT_MS\s*=\s*60000/);
assert.match(app, /CODE_AGENT_TIMEOUT_STORAGE_KEY/);
@@ -414,6 +416,11 @@ assert.match(styles, /\.agent-timeout-control\s*{/);
assert.match(app, /activityRef:\s*\(\)\s*=>\s*state\.currentRequest/);
assert.match(functionBody(app, "fetchJson"), /timeoutMs\s*=\s*API_TIMEOUT_MS/);
assert.match(functionBody(app, "fetchJson"), /无新事件/);
assert.match(functionBody(app, "liveSurfaceFetch"), /timeoutMs:\s*LIVE_SURFACE_TIMEOUT_MS/);
assert.match(functionBody(app, "liveSurfaceRpc"), /timeoutMs:\s*LIVE_SURFACE_TIMEOUT_MS/);
assert.match(functionBody(app, "loadLiveSurface"), /liveSurfaceFetch\("\/v1\/m3\/status"\)/);
assert.match(functionBody(app, "loadLiveSurface"), /liveSurfaceRpc\("system\.health"\)/);
assert.match(functionBody(app, "callRpc"), /timeoutMs:\s*options\?\.timeoutMs \?\? API_TIMEOUT_MS/);
for (const hardwareTerm of [
"Gateway 在线",
"BOX 在线",
@@ -1014,11 +1021,17 @@ assert.match(app, /createdAt/);
assert.match(app, /updatedAt/);
assert.match(app, /error\?\.message/);
assert.match(app, /DEFAULT_API_TIMEOUT_MS\s*=\s*4500/);
assert.match(app, /DEFAULT_LIVE_SURFACE_TIMEOUT_MS\s*=\s*12000/);
assert.match(app, /LIVE_SURFACE_TIMEOUT_MS\s*=\s*resolveTimeoutMs\("liveSurfaceTimeoutMs"/);
assert.match(app, /DEFAULT_CODE_AGENT_TIMEOUT_MS\s*=\s*600000/);
assert.match(app, /CODE_AGENT_SUBMIT_TIMEOUT_MS/);
assert.match(app, /CODE_AGENT_TIMEOUT_MS/);
assert.match(functionBody(app, "sendAgentMessage"), /timeoutMs:\s*CODE_AGENT_SUBMIT_TIMEOUT_MS/);
assert.match(functionBody(app, "waitForAgentMessageResult"), /CODE_AGENT_TIMEOUT_MS/);
assert.match(functionBody(app, "liveSurfaceFetch"), /timeoutMs:\s*LIVE_SURFACE_TIMEOUT_MS/);
assert.match(functionBody(app, "liveSurfaceRpc"), /timeoutMs:\s*LIVE_SURFACE_TIMEOUT_MS/);
assert.match(functionBody(app, "loadLiveSurface"), /liveSurfaceFetch\("\/health\/live"\)/);
assert.match(functionBody(app, "loadLiveSurface"), /liveSurfaceRpc\("system\.health"\)/);
assert.match(app, /HWLAB_CLOUD_WEB_CONFIG/);
assert.match(app, /sourceKind:\s*"PENDING"/);
assert.match(app, /旧 4500ms/);