fix: block code agent non-2xx readiness

This commit is contained in:
Code Queue Review
2026-05-23 02:39:41 +00:00
parent 892a0276bd
commit 6961baedb7
8 changed files with 722 additions and 157 deletions
+26 -33
View File
@@ -7,6 +7,12 @@ import { fileURLToPath } from "node:url";
import { gateSummary } from "../../web/hwlab-cloud-web/gate-summary.mjs";
import { runtime } from "../../web/hwlab-cloud-web/runtime.mjs";
import {
classifyCodeAgentBrowserJourney,
summarizeCodeAgentPayload
} from "./code-agent-response-contract.mjs";
export { classifyCodeAgentBrowserJourney };
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
const webRoot = path.join(repoRoot, "web/hwlab-cloud-web");
@@ -510,7 +516,7 @@ async function runLiveSmoke(args) {
hardwareWriteApis: false,
sourceIsDevLive: false,
liveMode: "browser-user-journey-with-code-agent-post",
retainedApiFields: ["status", "provider", "model", "backend", "traceId", "hasReply", "error.code", "error.missingEnv"],
retainedApiFields: ["status", "provider", "model", "backend", "traceId", "hasReply", "error.code", "error.missingEnv", "error.providerStatus"],
statement: "Live smoke opens the deployed workbench and sends one controlled Code Agent message; it does not call hardware write APIs and must not be used to infer M3 DEV-LIVE hardware-loop acceptance."
}
});
@@ -1667,7 +1673,7 @@ async function inspectLiveUserJourney(url) {
method: response.request().method(),
status: response.status(),
urlPath: new URL(response.url()).pathname,
body: sanitizeAgentChatBody(body)
body: sanitizeAgentChatBody(body, { httpStatus: response.status() })
});
});
@@ -1698,28 +1704,30 @@ async function inspectLiveUserJourney(url) {
}
await page.waitForTimeout(500);
const ui = await inspectJourneyUi(page);
const responseSummary = sanitizeAgentChatBody(responseBody);
const backendCompleted =
response.ok() &&
responseSummary?.status === "completed" &&
Boolean(responseSummary.provider) &&
Boolean(responseSummary.model) &&
Boolean(responseSummary.traceId);
const uiCompleted = ui.agentChatStatus === "DEV-LIVE 回复" || ui.completedMessageVisible;
const pass = backendCompleted && uiCompleted && !ui.failedMessageVisible;
const responseSummary = sanitizeAgentChatBody(responseBody, { httpStatus: response.status() });
const classification = classifyCodeAgentBrowserJourney({
responseSummary,
httpOk: response.ok(),
httpStatus: response.status(),
ui
});
const pass = classification.status === "pass";
return {
status: pass ? "pass" : "blocked",
summary: pass
? "Deployed browser journey opened the workbench, sent a Code Agent message, and observed a completed UI response."
: "Deployed browser journey did not observe a completed non-error Code Agent response.",
: classification.blocker === "provider-upstream" || classification.blocker === "provider-unavailable"
? "Deployed browser journey reached Code Agent, but the provider returned a blocked/unavailable state instead of a completed reply."
: "Deployed browser journey did not observe a completed non-error Code Agent response.",
evidence: [
`POST /v1/agent/chat HTTP ${response.status()}`,
`status=${responseSummary?.status ?? "unknown"}`,
`provider=${responseSummary?.provider ?? "unknown"}`,
`model=${responseSummary?.model ?? "unknown"}`,
`traceId=${responseSummary?.traceId ?? "unknown"}`,
`uiStatus=${ui.agentChatStatus ?? "unknown"}`
`uiStatus=${ui.agentChatStatus ?? "unknown"}`,
`blocker=${classification.blocker ?? "none"}`
],
observations: {
controls,
@@ -1729,6 +1737,7 @@ async function inspectLiveUserJourney(url) {
urlPath: new URL(response.url()).pathname
},
response: responseSummary,
classification,
ui,
networkEvents: agentResponses
}
@@ -1811,24 +1820,8 @@ async function inspectDefaultApiStatus(page) {
});
}
function sanitizeAgentChatBody(body) {
if (!body || typeof body !== "object") return null;
return {
status: typeof body.status === "string" ? body.status : null,
sourceKind: typeof body.sourceKind === "string" ? body.sourceKind : null,
evidenceLevel: typeof body.evidenceLevel === "string" ? body.evidenceLevel : null,
provider: typeof body.provider === "string" ? body.provider : null,
model: typeof body.model === "string" ? body.model : null,
backend: typeof body.backend === "string" ? body.backend : null,
traceId: typeof body.traceId === "string" ? body.traceId : null,
hasReply: Boolean(body.reply?.content),
error: body.error && typeof body.error === "object"
? {
code: typeof body.error.code === "string" ? body.error.code : null,
missingEnv: Array.isArray(body.error.missingEnv) ? body.error.missingEnv.filter((item) => typeof item === "string") : []
}
: null
};
export function sanitizeAgentChatBody(body, options = {}) {
return summarizeCodeAgentPayload(body, options);
}
export async function runDevCloudWorkbenchLocalAgentFixtureSmoke() {
@@ -1867,7 +1860,7 @@ export async function runDevCloudWorkbenchLocalAgentFixtureSmoke() {
method: response.request().method(),
status: response.status(),
urlPath: new URL(response.url()).pathname,
body: sanitizeAgentChatBody(body)
body: sanitizeAgentChatBody(body, { httpStatus: response.status() })
});
});
@@ -1894,7 +1887,7 @@ export async function runDevCloudWorkbenchLocalAgentFixtureSmoke() {
{ timeout: 8000 }
);
const ui = await inspectJourneyUi(page);
const responseSummary = sanitizeAgentChatBody(responseBody);
const responseSummary = sanitizeAgentChatBody(responseBody, { httpStatus: response.status() });
const pass =
response.ok() &&
responseSummary?.status === "completed" &&