fix: extend code agent heavy request timeout
This commit is contained in:
@@ -176,9 +176,19 @@ export function runDevCloudWorkbenchStaticSmoke() {
|
||||
return runStaticSmoke();
|
||||
}
|
||||
|
||||
export async function runDevCloudWorkbenchTimeoutFixtureSmoke(options = {}) {
|
||||
return runLocalAgentFixtureSmoke({
|
||||
mode: "local-agent-timeout-fixture-browser",
|
||||
responseDelayMs: options.responseDelayMs ?? 120,
|
||||
timeoutConfigMs: options.timeoutConfigMs ?? 50,
|
||||
expectTimeout: true
|
||||
});
|
||||
}
|
||||
|
||||
export async function runDevCloudWorkbenchSmoke(argv = []) {
|
||||
const args = parseSmokeArgs(argv);
|
||||
if (args.mode === "live") return runLiveSmoke(args);
|
||||
if (args.mode === "local-agent-timeout-fixture") return runDevCloudWorkbenchTimeoutFixtureSmoke();
|
||||
if (args.mode === "local-agent-fixture") return runDevCloudWorkbenchLocalAgentFixtureSmoke();
|
||||
if (args.mode === "mobile") return runDevCloudWorkbenchMobileSmoke();
|
||||
return runStaticSmoke();
|
||||
@@ -197,6 +207,8 @@ export function parseSmokeArgs(argv) {
|
||||
args.mobile = true;
|
||||
} else if (arg === "--local-agent-fixture") {
|
||||
args.mode = "local-agent-fixture";
|
||||
} else if (arg === "--local-agent-timeout-fixture") {
|
||||
args.mode = "local-agent-timeout-fixture";
|
||||
} else if (arg === "--url") {
|
||||
index += 1;
|
||||
if (!argv[index]) throw new Error("--url requires a value");
|
||||
@@ -1269,6 +1281,12 @@ function hasCodeAgentConversationUxStates({ app, styles }) {
|
||||
/function\s+messageEvidencePanel\s*\(/u.test(app) &&
|
||||
/function\s+messageEvidenceSummary\s*\(/u.test(app) &&
|
||||
/function\s+boundedEvidenceField\s*\(/u.test(app) &&
|
||||
/DEFAULT_API_TIMEOUT_MS\s*=\s*4500/u.test(app) &&
|
||||
/DEFAULT_CODE_AGENT_TIMEOUT_MS\s*=\s*150000/u.test(app) &&
|
||||
/timeoutMs:\s*CODE_AGENT_TIMEOUT_MS/u.test(app) &&
|
||||
/HWLAB_CLOUD_WEB_CONFIG/u.test(app) &&
|
||||
/失败时会保留输入供重试/u.test(app) &&
|
||||
/Code Agent 请求超过/u.test(app) &&
|
||||
/Code Agent SOURCE 回复/u.test(app) &&
|
||||
/SOURCE fixture 只可显示为 SOURCE 回复,不能冒充 DEV-LIVE/u.test(app) &&
|
||||
/sourceKind:\s*completion\.sourceKind/u.test(submitBody) &&
|
||||
@@ -1949,26 +1967,35 @@ async function inspectJourneyControls(page) {
|
||||
}
|
||||
|
||||
async function inspectJourneyUi(page) {
|
||||
return page.evaluate(() => ({
|
||||
title: document.title,
|
||||
workspaceHidden: document.querySelector('[data-view="workspace"]')?.hidden ?? null,
|
||||
gateHidden: document.querySelector('[data-view="gate"]')?.hidden ?? null,
|
||||
helpHidden: document.querySelector('[data-view="help"]')?.hidden ?? null,
|
||||
agentChatStatus: document.querySelector("#agent-chat-status")?.textContent?.trim() ?? null,
|
||||
inputCleared: document.querySelector("#command-input")?.value === "",
|
||||
completedMessageVisible: Boolean(document.querySelector(".message-card.status-completed")),
|
||||
sourceMessageVisible: Boolean(document.querySelector(".message-card.status-source")),
|
||||
failedMessageVisible: Boolean(document.querySelector(".message-card.status-failed")),
|
||||
messageCount: document.querySelectorAll(".message-card").length,
|
||||
completedMessageHasNonSensitiveMeta: Boolean(
|
||||
[...document.querySelectorAll(".message-card.status-completed .message-meta")]
|
||||
.some((element) => /openai-responses|gpt-5\.5|trc_/u.test(element.textContent ?? ""))
|
||||
),
|
||||
sourceMessageHasNonSensitiveMeta: Boolean(
|
||||
[...document.querySelectorAll(".message-card.status-source .message-meta")]
|
||||
.some((element) => /SOURCE|source-fixture|trc_/u.test(element.textContent ?? ""))
|
||||
)
|
||||
}));
|
||||
return page.evaluate(() => {
|
||||
const failedText = [...document.querySelectorAll(".message-card.status-failed")]
|
||||
.map((element) => element.textContent ?? "")
|
||||
.join("\n");
|
||||
const commandValue = document.querySelector("#command-input")?.value ?? "";
|
||||
return {
|
||||
title: document.title,
|
||||
workspaceHidden: document.querySelector('[data-view="workspace"]')?.hidden ?? null,
|
||||
gateHidden: document.querySelector('[data-view="gate"]')?.hidden ?? null,
|
||||
helpHidden: document.querySelector('[data-view="help"]')?.hidden ?? null,
|
||||
agentChatStatus: document.querySelector("#agent-chat-status")?.textContent?.trim() ?? null,
|
||||
inputCleared: commandValue === "",
|
||||
retryInputPreserved: commandValue === "请用一句话说明当前 HWLAB 工作台可以做什么。",
|
||||
completedMessageVisible: Boolean(document.querySelector(".message-card.status-completed")),
|
||||
sourceMessageVisible: Boolean(document.querySelector(".message-card.status-source")),
|
||||
failedMessageVisible: Boolean(document.querySelector(".message-card.status-failed")),
|
||||
failedMessageHasTrace: /trace=trc_/u.test(failedText),
|
||||
failedMessageHasChineseTimeout: /请求超过|请求超时|保留输入|重试/u.test(failedText),
|
||||
messageCount: document.querySelectorAll(".message-card").length,
|
||||
completedMessageHasNonSensitiveMeta: Boolean(
|
||||
[...document.querySelectorAll(".message-card.status-completed .message-meta")]
|
||||
.some((element) => /openai-responses|gpt-5\.5|trc_/u.test(element.textContent ?? ""))
|
||||
),
|
||||
sourceMessageHasNonSensitiveMeta: Boolean(
|
||||
[...document.querySelectorAll(".message-card.status-source .message-meta")]
|
||||
.some((element) => /SOURCE|source-fixture|trc_/u.test(element.textContent ?? ""))
|
||||
)
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
async function inspectDefaultApiStatus(page) {
|
||||
@@ -2012,6 +2039,19 @@ function blockedAgentChatResponse(code) {
|
||||
}
|
||||
|
||||
export async function runDevCloudWorkbenchLocalAgentFixtureSmoke() {
|
||||
return runLocalAgentFixtureSmoke({
|
||||
mode: "local-agent-fixture-browser",
|
||||
responseDelayMs: 0,
|
||||
expectTimeout: false
|
||||
});
|
||||
}
|
||||
|
||||
async function runLocalAgentFixtureSmoke({
|
||||
mode,
|
||||
responseDelayMs = 0,
|
||||
timeoutConfigMs = null,
|
||||
expectTimeout = false
|
||||
} = {}) {
|
||||
let chromium;
|
||||
try {
|
||||
({ chromium } = await import("playwright"));
|
||||
@@ -2019,7 +2059,7 @@ export async function runDevCloudWorkbenchLocalAgentFixtureSmoke() {
|
||||
return {
|
||||
status: "skip",
|
||||
task: "DC-DCSN-P0-2026-003",
|
||||
mode: "local-agent-fixture-browser",
|
||||
mode,
|
||||
evidenceLevel: "SOURCE",
|
||||
devLive: false,
|
||||
summary: `Local Code Agent browser fixture smoke skipped because Playwright is unavailable: ${error.message}`,
|
||||
@@ -2029,11 +2069,22 @@ export async function runDevCloudWorkbenchLocalAgentFixtureSmoke() {
|
||||
};
|
||||
}
|
||||
|
||||
const server = await startStaticWebServer({ agentFixture: true });
|
||||
const server = await startStaticWebServer({ agentFixture: true, agentDelayMs: responseDelayMs });
|
||||
let browser;
|
||||
try {
|
||||
browser = await chromium.launch({ headless: true });
|
||||
const page = await browser.newPage({ viewport: { width: 1366, height: 768 } });
|
||||
if (timeoutConfigMs !== null) {
|
||||
await page.addInitScript((timeoutMs) => {
|
||||
globalThis.HWLAB_CLOUD_WEB_CONFIG = {
|
||||
...(globalThis.HWLAB_CLOUD_WEB_CONFIG ?? {}),
|
||||
timeouts: {
|
||||
...(globalThis.HWLAB_CLOUD_WEB_CONFIG?.timeouts ?? {}),
|
||||
codeAgentTimeoutMs: timeoutMs
|
||||
}
|
||||
};
|
||||
}, timeoutConfigMs);
|
||||
}
|
||||
const agentResponses = [];
|
||||
page.on("response", async (response) => {
|
||||
if (!response.url().includes("/v1/agent/chat")) return;
|
||||
@@ -2061,30 +2112,50 @@ export async function runDevCloudWorkbenchLocalAgentFixtureSmoke() {
|
||||
const controls = await inspectJourneyControls(page);
|
||||
const apiStatus = await inspectDefaultApiStatus(page);
|
||||
await page.locator("#command-input").fill("请用一句话说明当前 HWLAB 工作台可以做什么。");
|
||||
const responsePromise = page.waitForResponse(
|
||||
const responsePromise = expectTimeout ? null : page.waitForResponse(
|
||||
(response) => response.url().includes("/v1/agent/chat") && response.request().method() === "POST",
|
||||
{ timeout: 15000 }
|
||||
);
|
||||
await page.locator("#command-send").click();
|
||||
const response = await responsePromise;
|
||||
const responseBody = await response.json().catch(() => null);
|
||||
await page.waitForFunction(
|
||||
() => document.querySelector("#agent-chat-status")?.textContent?.trim() === "SOURCE 回复",
|
||||
null,
|
||||
{ timeout: 8000 }
|
||||
);
|
||||
let response = null;
|
||||
let responseBody = null;
|
||||
if (expectTimeout) {
|
||||
await page.waitForFunction(
|
||||
() => document.querySelector("#agent-chat-status")?.textContent?.trim() === "发送失败",
|
||||
null,
|
||||
{ timeout: 8000 }
|
||||
);
|
||||
} else {
|
||||
response = await responsePromise;
|
||||
responseBody = await response.json().catch(() => null);
|
||||
await page.waitForFunction(
|
||||
() => document.querySelector("#agent-chat-status")?.textContent?.trim() === "SOURCE 回复",
|
||||
null,
|
||||
{ timeout: 8000 }
|
||||
);
|
||||
}
|
||||
const ui = await inspectJourneyUi(page);
|
||||
const responseSummary = sanitizeAgentChatBody(responseBody, { httpStatus: response.status() });
|
||||
const pass =
|
||||
response.ok() &&
|
||||
responseSummary?.status === "completed" &&
|
||||
responseSummary?.sourceKind === "SOURCE" &&
|
||||
responseSummary?.hasReply === true &&
|
||||
ui.agentChatStatus === "SOURCE 回复" &&
|
||||
ui.sourceMessageVisible &&
|
||||
ui.sourceMessageHasNonSensitiveMeta &&
|
||||
!ui.completedMessageVisible &&
|
||||
!ui.failedMessageVisible;
|
||||
const responseSummary = response ? sanitizeAgentChatBody(responseBody, { httpStatus: response.status() }) : null;
|
||||
const pass = expectTimeout
|
||||
? (
|
||||
ui.agentChatStatus === "发送失败" &&
|
||||
ui.failedMessageVisible &&
|
||||
ui.retryInputPreserved &&
|
||||
ui.failedMessageHasTrace &&
|
||||
ui.failedMessageHasChineseTimeout &&
|
||||
!ui.completedMessageVisible
|
||||
)
|
||||
: (
|
||||
response.ok() &&
|
||||
responseSummary?.status === "completed" &&
|
||||
responseSummary?.sourceKind === "SOURCE" &&
|
||||
responseSummary?.hasReply === true &&
|
||||
ui.agentChatStatus === "SOURCE 回复" &&
|
||||
ui.sourceMessageVisible &&
|
||||
ui.sourceMessageHasNonSensitiveMeta &&
|
||||
!ui.completedMessageVisible &&
|
||||
!ui.failedMessageVisible
|
||||
);
|
||||
const checks = [
|
||||
{
|
||||
id: "local-agent-fixture-controls",
|
||||
@@ -2099,9 +2170,11 @@ export async function runDevCloudWorkbenchLocalAgentFixtureSmoke() {
|
||||
observations: apiStatus
|
||||
},
|
||||
{
|
||||
id: "local-agent-fixture-replied-state",
|
||||
id: expectTimeout ? "local-agent-timeout-fixture-failed-state" : "local-agent-fixture-replied-state",
|
||||
status: pass ? "pass" : "blocked",
|
||||
summary: "Local SOURCE fixture sends input and reaches a user-visible replied state without marking the fixture as DEV-LIVE.",
|
||||
summary: expectTimeout
|
||||
? "Local SOURCE fixture proves a bounded Code Agent timeout stays failed/BLOCKED in the UI, preserves trace evidence, and keeps the user's input for retry."
|
||||
: "Local SOURCE fixture sends input and reaches a user-visible replied state without marking the fixture as DEV-LIVE.",
|
||||
observations: {
|
||||
response: responseSummary,
|
||||
ui,
|
||||
@@ -2120,12 +2193,14 @@ export async function runDevCloudWorkbenchLocalAgentFixtureSmoke() {
|
||||
return {
|
||||
status: blockers.length === 0 ? "pass" : "blocked",
|
||||
task: "DC-DCSN-P0-2026-003",
|
||||
mode: "local-agent-fixture-browser",
|
||||
mode,
|
||||
url: server.url,
|
||||
generatedAt: new Date().toISOString(),
|
||||
evidenceLevel: "SOURCE",
|
||||
devLive: false,
|
||||
summary: "Local browser smoke uses a sanitized SOURCE fixture for /v1/agent/chat; it proves the UI conversation path only and does not claim DEV-LIVE.",
|
||||
summary: expectTimeout
|
||||
? "Local browser smoke uses a sanitized slow SOURCE fixture for /v1/agent/chat; it proves timeout UI classification only and does not claim DEV-LIVE."
|
||||
: "Local browser smoke uses a sanitized SOURCE fixture for /v1/agent/chat; it proves the UI conversation path only and does not claim DEV-LIVE.",
|
||||
checks,
|
||||
blockers,
|
||||
safety: {
|
||||
@@ -2139,7 +2214,7 @@ export async function runDevCloudWorkbenchLocalAgentFixtureSmoke() {
|
||||
return {
|
||||
status: "blocked",
|
||||
task: "DC-DCSN-P0-2026-003",
|
||||
mode: "local-agent-fixture-browser",
|
||||
mode,
|
||||
url: server.url,
|
||||
generatedAt: new Date().toISOString(),
|
||||
evidenceLevel: "SOURCE",
|
||||
@@ -2149,7 +2224,7 @@ export async function runDevCloudWorkbenchLocalAgentFixtureSmoke() {
|
||||
blockers: [
|
||||
{
|
||||
type: "observability_blocker",
|
||||
scope: "local-agent-fixture-browser",
|
||||
scope: mode,
|
||||
status: "open",
|
||||
summary: error.message
|
||||
}
|
||||
@@ -2336,7 +2411,7 @@ export async function runDevCloudWorkbenchMobileSmoke() {
|
||||
async function startStaticWebServer(options = {}) {
|
||||
const server = http.createServer(async (request, response) => {
|
||||
const url = new URL(request.url ?? "/", "http://127.0.0.1");
|
||||
if (options.agentFixture && await handleLocalAgentFixtureApi({ request, response, url })) {
|
||||
if (options.agentFixture && await handleLocalAgentFixtureApi({ request, response, url, options })) {
|
||||
return;
|
||||
}
|
||||
let pathname = decodeURIComponent(url.pathname);
|
||||
@@ -2369,7 +2444,7 @@ async function startStaticWebServer(options = {}) {
|
||||
};
|
||||
}
|
||||
|
||||
async function handleLocalAgentFixtureApi({ request, response, url }) {
|
||||
async function handleLocalAgentFixtureApi({ request, response, url, options = {} }) {
|
||||
if (request.method === "GET" && (url.pathname === "/health/live" || url.pathname === "/v1")) {
|
||||
jsonResponse(response, 200, {
|
||||
serviceId: "hwlab-cloud-api",
|
||||
@@ -2418,6 +2493,7 @@ async function handleLocalAgentFixtureApi({ request, response, url }) {
|
||||
}
|
||||
if (request.method === "POST" && url.pathname === "/v1/agent/chat") {
|
||||
const body = await readJsonBody(request);
|
||||
await delay(options.agentDelayMs ?? 0);
|
||||
const timestamp = "2026-05-22T00:00:00.000Z";
|
||||
const conversationId = stringOrFallback(body?.conversationId, "cnv_source_fixture_browser");
|
||||
jsonResponse(response, 200, {
|
||||
@@ -2453,6 +2529,11 @@ async function handleLocalAgentFixtureApi({ request, response, url }) {
|
||||
return false;
|
||||
}
|
||||
|
||||
function delay(ms) {
|
||||
const normalized = Number.isFinite(Number(ms)) && Number(ms) > 0 ? Number(ms) : 0;
|
||||
return new Promise((resolve) => setTimeout(resolve, normalized));
|
||||
}
|
||||
|
||||
function localSourceFixtureAvailability() {
|
||||
return {
|
||||
endpoint: "POST /v1/agent/chat",
|
||||
@@ -2781,11 +2862,12 @@ function escapeRegExp(value) {
|
||||
export function printSmokeHelp() {
|
||||
return {
|
||||
status: "usage",
|
||||
command: "node scripts/dev-cloud-workbench-smoke.mjs --static | --mobile | --local-agent-fixture | --live --url http://74.48.78.17:16666/ [--report reports/dev-gate/dev-cloud-workbench-live.json]",
|
||||
command: "node scripts/dev-cloud-workbench-smoke.mjs --static | --mobile | --local-agent-fixture | --local-agent-timeout-fixture | --live --url http://74.48.78.17:16666/ [--report reports/dev-gate/dev-cloud-workbench-live.json]",
|
||||
notes: [
|
||||
"Static mode reads repository files and emits SOURCE-level evidence only.",
|
||||
"--mobile runs a local static 390x844 browser hit-test and still emits SOURCE-level evidence only.",
|
||||
"--local-agent-fixture runs a local browser send/reply path with a sanitized SOURCE fixture and does not claim DEV-LIVE.",
|
||||
"--local-agent-timeout-fixture runs a local slow SOURCE fixture to verify bounded timeout UI classification and retry preservation without live acceptance.",
|
||||
"Live mode opens the deployed workbench in a browser, verifies core controls, sends one Code Agent message, and retains only non-sensitive response fields."
|
||||
]
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user