fix: harden code agent timeout discovery path
This commit is contained in:
@@ -4,6 +4,10 @@ import { createCloudApiServer } from "../../internal/cloud/server.mjs";
|
||||
const host = process.env.HWLAB_CLOUD_API_HOST || "0.0.0.0";
|
||||
const port = Number.parseInt(process.env.HWLAB_CLOUD_API_PORT || process.env.PORT || "6667", 10);
|
||||
const commitId = process.env.HWLAB_COMMIT_ID || process.env.HWLAB_GIT_SHA;
|
||||
const codeAgentTimeoutMs = parseTimeout(process.env.HWLAB_CODE_AGENT_TIMEOUT_MS, 120000, {
|
||||
min: 1000,
|
||||
max: 300000
|
||||
});
|
||||
|
||||
if (!process.env.HWLAB_IMAGE && commitId) {
|
||||
process.env.HWLAB_IMAGE = `ghcr.io/pikastech/hwlab-cloud-api:${commitId.slice(0, 7)}`;
|
||||
@@ -13,7 +17,15 @@ if (!process.env.HWLAB_IMAGE_TAG && commitId) {
|
||||
process.env.HWLAB_IMAGE_TAG = commitId.slice(0, 7);
|
||||
}
|
||||
|
||||
const server = createCloudApiServer();
|
||||
const server = createCloudApiServer({
|
||||
codeAgentTimeoutMs
|
||||
});
|
||||
|
||||
function parseTimeout(value, fallback, { min, max }) {
|
||||
const parsed = Number.parseInt(value || "", 10);
|
||||
if (!Number.isInteger(parsed)) return fallback;
|
||||
return Math.min(Math.max(parsed, min), max);
|
||||
}
|
||||
|
||||
server.listen(port, host, () => {
|
||||
const address = server.address();
|
||||
|
||||
+4
-1
@@ -204,6 +204,7 @@
|
||||
"HWLAB_M3_PATCH_PANEL_URL": "http://hwlab-patch-panel.hwlab-dev.svc.cluster.local:7301",
|
||||
"HWLAB_CODE_AGENT_PROVIDER": "openai",
|
||||
"HWLAB_CODE_AGENT_MODEL": "gpt-5.5",
|
||||
"HWLAB_CODE_AGENT_TIMEOUT_MS": "120000",
|
||||
"HWLAB_CODE_AGENT_OPENAI_BASE_URL": "http://172.26.26.227:17680/v1/responses",
|
||||
"HWLAB_CODE_AGENT_HWLAB_API_BASE_URL": "http://hwlab-cloud-api.hwlab-dev.svc.cluster.local:6667",
|
||||
"HWLAB_CODE_AGENT_CODEX_STDIO_ENABLED": "1",
|
||||
@@ -226,6 +227,7 @@
|
||||
"env": {
|
||||
"HWLAB_ENVIRONMENT": "dev",
|
||||
"HWLAB_API_BASE_URL": "http://hwlab-cloud-api.hwlab-dev.svc.cluster.local:6667",
|
||||
"HWLAB_CLOUD_WEB_PROXY_TIMEOUT_MS": "150000",
|
||||
"HWLAB_COMMIT_ID": "9190860",
|
||||
"HWLAB_IMAGE": "127.0.0.1:5000/hwlab/hwlab-cloud-web:9190860",
|
||||
"HWLAB_IMAGE_TAG": "9190860"
|
||||
@@ -362,7 +364,8 @@
|
||||
"replicas": 1,
|
||||
"env": {
|
||||
"HWLAB_EDGE_LISTEN": "0.0.0.0:6667",
|
||||
"HWLAB_EDGE_UPSTREAM": "http://hwlab-cloud-api.hwlab-dev.svc.cluster.local:6667"
|
||||
"HWLAB_EDGE_UPSTREAM": "http://hwlab-cloud-api.hwlab-dev.svc.cluster.local:6667",
|
||||
"HWLAB_EDGE_PROXY_TIMEOUT_MS": "150000"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -113,6 +113,10 @@
|
||||
"name": "HWLAB_CODE_AGENT_MODEL",
|
||||
"value": "gpt-5.5"
|
||||
},
|
||||
{
|
||||
"name": "HWLAB_CODE_AGENT_TIMEOUT_MS",
|
||||
"value": "120000"
|
||||
},
|
||||
{
|
||||
"name": "HWLAB_CODE_AGENT_OPENAI_BASE_URL",
|
||||
"value": "http://172.26.26.227:17680/v1/responses"
|
||||
@@ -219,6 +223,10 @@
|
||||
"name": "HWLAB_API_BASE_URL",
|
||||
"value": "http://hwlab-cloud-api.hwlab-dev.svc.cluster.local:6667"
|
||||
},
|
||||
{
|
||||
"name": "HWLAB_CLOUD_WEB_PROXY_TIMEOUT_MS",
|
||||
"value": "150000"
|
||||
},
|
||||
{
|
||||
"name": "HWLAB_COMMIT_ID",
|
||||
"value": "9190860"
|
||||
@@ -791,6 +799,10 @@
|
||||
{
|
||||
"name": "HWLAB_EDGE_UPSTREAM",
|
||||
"value": "http://hwlab-cloud-api.hwlab-dev.svc.cluster.local:6667"
|
||||
},
|
||||
{
|
||||
"name": "HWLAB_EDGE_PROXY_TIMEOUT_MS",
|
||||
"value": "150000"
|
||||
}
|
||||
],
|
||||
"readinessProbe": {
|
||||
|
||||
@@ -1268,6 +1268,8 @@ test("cloud api /v1/agent/chat parse and params errors use structured blocker en
|
||||
assert.equal(parse.body.error.traceId, "trc_parse_error");
|
||||
assert.equal(parse.body.error.route, "/v1/agent/chat");
|
||||
assert.match(parse.body.error.userMessage, /JSON/u);
|
||||
assert.equal(Object.hasOwn(parse.body, "reply"), false);
|
||||
assert.equal(JSON.stringify(parse.body).includes("sk-"), false);
|
||||
|
||||
const invalid = await postAgentRaw(port, JSON.stringify([]), { "x-trace-id": "trc_invalid_params" });
|
||||
assert.equal(invalid.status, 400);
|
||||
@@ -2155,6 +2157,9 @@ test("cloud api /v1/agent/chat reports OpenAI provider 502 and 503 as failed blo
|
||||
assert.match(payload.error.userMessage, /provider/u);
|
||||
assert.equal(payload.error.providerStatus, status);
|
||||
assert.match(payload.error.message, new RegExp(`HTTP ${status}`));
|
||||
assert.equal(payload.error.blocker.traceId, `trc_server-test-agent-chat-provider-${status}`);
|
||||
assert.equal(payload.error.blocker.retryable, true);
|
||||
assert.equal(payload.error.blocker.capabilityLevel, "blocked");
|
||||
assert.equal(Object.hasOwn(payload, "reply"), false);
|
||||
} finally {
|
||||
await new Promise((resolve, reject) => {
|
||||
|
||||
@@ -106,6 +106,7 @@ export function listen(server, { serviceId, host, port }) {
|
||||
|
||||
export function proxyHttpRequest({ request, response, upstream, timeoutMs = DEFAULT_PROXY_TIMEOUT_MS }) {
|
||||
const target = new URL(request.url || "/", upstream);
|
||||
const traceId = request.headers["x-trace-id"] ?? null;
|
||||
let timedOut = false;
|
||||
const timeout = setTimeout(() => {
|
||||
timedOut = true;
|
||||
@@ -136,17 +137,37 @@ export function proxyHttpRequest({ request, response, upstream, timeoutMs = DEFA
|
||||
return;
|
||||
}
|
||||
settled = true;
|
||||
const code = timedOut ? "proxy_timeout" : "upstream_unavailable";
|
||||
const category = timedOut ? "timeout" : "proxy";
|
||||
const userMessage = timedOut
|
||||
? `Code Agent 代理等待上游超过 ${timeoutMs}ms;输入已保留,可稍后重试。`
|
||||
: "Code Agent 代理暂时无法连接上游;输入已保留,可稍后重试。";
|
||||
sendJson(response, 502, {
|
||||
status: "failed",
|
||||
error: {
|
||||
code: timedOut ? "proxy_timeout" : "upstream_unavailable",
|
||||
category: timedOut ? "timeout" : "proxy",
|
||||
message: error.message,
|
||||
timeoutMs: timedOut ? timeoutMs : null
|
||||
code,
|
||||
layer: "proxy",
|
||||
category,
|
||||
retryable: true,
|
||||
userMessage,
|
||||
message: userMessage,
|
||||
timeoutMs: timedOut ? timeoutMs : null,
|
||||
traceId,
|
||||
route: request.url || null,
|
||||
blocker: {
|
||||
code,
|
||||
layer: "proxy",
|
||||
category,
|
||||
retryable: true,
|
||||
summary: userMessage,
|
||||
userMessage,
|
||||
traceId,
|
||||
route: request.url || null
|
||||
}
|
||||
},
|
||||
upstream,
|
||||
traceId: request.headers["x-trace-id"] ?? null,
|
||||
message: error.message
|
||||
traceId,
|
||||
message: userMessage
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -81,10 +81,17 @@ test("dev entrypoint proxy returns 502 when hard timeout expires", async () => {
|
||||
const payload = await response.json();
|
||||
assert.equal(payload.status, "failed");
|
||||
assert.equal(payload.error.code, "proxy_timeout");
|
||||
assert.equal(payload.error.layer, "proxy");
|
||||
assert.equal(payload.error.category, "timeout");
|
||||
assert.equal(payload.error.retryable, true);
|
||||
assert.equal(payload.error.timeoutMs, 50);
|
||||
assert.equal(payload.error.traceId, "trc_dev-entrypoint-proxy-timeout");
|
||||
assert.match(payload.error.userMessage, /超过 50ms/u);
|
||||
assert.match(payload.error.message, /输入已保留/u);
|
||||
assert.equal(payload.error.blocker.code, "proxy_timeout");
|
||||
assert.equal(payload.error.blocker.retryable, true);
|
||||
assert.equal(payload.traceId, "trc_dev-entrypoint-proxy-timeout");
|
||||
assert.match(payload.message, /timed out after 50ms/u);
|
||||
assert.match(payload.message, /Code Agent 代理等待上游超过 50ms/u);
|
||||
} finally {
|
||||
await close(proxy);
|
||||
await close(upstream);
|
||||
|
||||
@@ -671,18 +671,39 @@ async function proxyCloudApi(request, response, url) {
|
||||
response.end(upstream.body);
|
||||
} catch (error) {
|
||||
const timedOut = /timed out after \d+ms/iu.test(error.message);
|
||||
const code = timedOut ? "cloud_api_proxy_timeout" : "cloud_api_proxy_failed";
|
||||
const category = timedOut ? "timeout" : "proxy";
|
||||
const traceId = request.headers["x-trace-id"] || null;
|
||||
const userMessage = timedOut
|
||||
? "Code Agent 代理等待 cloud-api 超过 " + cloudApiProxyTimeoutMs + "ms;输入已保留,可稍后重试。"
|
||||
: "Code Agent 代理暂时无法连接 cloud-api;输入已保留,可稍后重试。";
|
||||
sendJson(response, 502, {
|
||||
status: "failed",
|
||||
error: {
|
||||
code: timedOut ? "cloud_api_proxy_timeout" : "cloud_api_proxy_failed",
|
||||
category: timedOut ? "timeout" : "proxy",
|
||||
message: error.message,
|
||||
timeoutMs: timedOut ? cloudApiProxyTimeoutMs : null
|
||||
code,
|
||||
layer: "proxy",
|
||||
category,
|
||||
retryable: true,
|
||||
userMessage,
|
||||
message: userMessage,
|
||||
timeoutMs: timedOut ? cloudApiProxyTimeoutMs : null,
|
||||
traceId,
|
||||
route: url.pathname,
|
||||
blocker: {
|
||||
code,
|
||||
layer: "proxy",
|
||||
category,
|
||||
retryable: true,
|
||||
summary: userMessage,
|
||||
userMessage,
|
||||
traceId,
|
||||
route: url.pathname
|
||||
}
|
||||
},
|
||||
serviceId,
|
||||
target: target.href.replace(/\/\/[^/@]*@/u, "//***@"),
|
||||
traceId: request.headers["x-trace-id"] || null,
|
||||
reason: error.message
|
||||
traceId,
|
||||
reason: userMessage
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,9 +227,12 @@ function validateSource(ctx, manifest) {
|
||||
const tunnelClient = servicesById.get("hwlab-tunnel-client");
|
||||
const cli = servicesById.get("hwlab-cli");
|
||||
const patchPanel = servicesById.get("hwlab-patch-panel");
|
||||
const cloudWeb = servicesById.get("hwlab-cloud-web");
|
||||
const edgeProxy = servicesById.get("hwlab-edge-proxy");
|
||||
expectEqual(ctx, cloudApi?.env?.HWLAB_PUBLIC_ENDPOINT, endpoints.api?.url, "$.services.hwlab-cloud-api.env.HWLAB_PUBLIC_ENDPOINT", "cloud API public endpoint env");
|
||||
validateCloudApiDbSource(ctx, cloudApi?.env ?? {});
|
||||
validateCloudApiCodeAgentSource(ctx, cloudApi?.env ?? {});
|
||||
validateCodeAgentProxyTimeoutSource(ctx, { cloudWeb, edgeProxy });
|
||||
validateM3PatchPanelSource(ctx, patchPanel);
|
||||
expectEqual(ctx, cli?.env?.HWLAB_CLI_ENDPOINT, endpoints.api?.url, "$.services.hwlab-cli.env.HWLAB_CLI_ENDPOINT", "CLI endpoint env");
|
||||
expectEqual(ctx, tunnelClient?.env?.HWLAB_FRP_PUBLIC_PORT, String(expectedPorts.api), "$.services.hwlab-tunnel-client.env.HWLAB_FRP_PUBLIC_PORT", "tunnel API public port env");
|
||||
@@ -374,6 +377,7 @@ function validateCloudApiCodeAgentSource(ctx, env) {
|
||||
const contract = DEV_CODE_AGENT_PROVIDER_CONTRACT;
|
||||
expectEqual(ctx, env.HWLAB_CODE_AGENT_PROVIDER, contract.provider, "$.services.hwlab-cloud-api.env.HWLAB_CODE_AGENT_PROVIDER", "cloud API Code Agent provider");
|
||||
expectEqual(ctx, env.HWLAB_CODE_AGENT_MODEL, contract.model, "$.services.hwlab-cloud-api.env.HWLAB_CODE_AGENT_MODEL", "cloud API Code Agent model");
|
||||
expectEqual(ctx, env.HWLAB_CODE_AGENT_TIMEOUT_MS, "120000", "$.services.hwlab-cloud-api.env.HWLAB_CODE_AGENT_TIMEOUT_MS", "cloud API Code Agent hard timeout budget");
|
||||
expectEqual(
|
||||
ctx,
|
||||
env.HWLAB_CODE_AGENT_OPENAI_BASE_URL,
|
||||
@@ -406,6 +410,11 @@ function validateCloudApiCodeAgentSource(ctx, env) {
|
||||
expectEqual(ctx, env.OPENAI_API_KEY, codeAgentSecretRefPlaceholder(), "$.services.hwlab-cloud-api.env.OPENAI_API_KEY", "cloud API Code Agent OpenAI Secret reference");
|
||||
}
|
||||
|
||||
function validateCodeAgentProxyTimeoutSource(ctx, { cloudWeb, edgeProxy }) {
|
||||
expectEqual(ctx, cloudWeb?.env?.HWLAB_CLOUD_WEB_PROXY_TIMEOUT_MS, "150000", "$.services.hwlab-cloud-web.env.HWLAB_CLOUD_WEB_PROXY_TIMEOUT_MS", "Cloud Web same-origin Code Agent proxy timeout budget");
|
||||
expectEqual(ctx, edgeProxy?.env?.HWLAB_EDGE_PROXY_TIMEOUT_MS, "150000", "$.services.hwlab-edge-proxy.env.HWLAB_EDGE_PROXY_TIMEOUT_MS", "edge proxy Code Agent timeout budget");
|
||||
}
|
||||
|
||||
function renderPlan(source) {
|
||||
const proxyPorts = [...new Set(source.proxies.map((proxy) => Number(proxy.remotePort)))].sort((a, b) => a - b);
|
||||
return {
|
||||
@@ -505,6 +514,7 @@ function validateK3sArtifacts(ctx, source, rendered, services, workloads, health
|
||||
|
||||
validateCloudApiDbOptionalAliasArtifacts(ctx, services, workloads);
|
||||
validateCloudApiCodeAgentArtifacts(ctx, workloads);
|
||||
validateCodeAgentProxyTimeoutArtifacts(ctx, workloads);
|
||||
|
||||
expectEqual(ctx, healthContract.data?.endpoint, source.endpoints.api?.url, "deploy/k8s/dev/health-contract.yaml.data.endpoint", "health contract endpoint");
|
||||
expectEqual(ctx, masterEdge.endpoint, source.endpoints.api?.url, "deploy/master-edge/health-contract.json.endpoint", "master edge endpoint");
|
||||
@@ -543,6 +553,7 @@ function validateCloudApiCodeAgentArtifacts(ctx, workloads) {
|
||||
const env = new Map((container?.env ?? []).map((entry) => [entry.name, entry]));
|
||||
expectEqual(ctx, env.get("HWLAB_CODE_AGENT_PROVIDER")?.value, contract.provider, "deploy/k8s/base/workloads.yaml.hwlab-cloud-api.env.HWLAB_CODE_AGENT_PROVIDER", "cloud API workload Code Agent provider");
|
||||
expectEqual(ctx, env.get("HWLAB_CODE_AGENT_MODEL")?.value, contract.model, "deploy/k8s/base/workloads.yaml.hwlab-cloud-api.env.HWLAB_CODE_AGENT_MODEL", "cloud API workload Code Agent model");
|
||||
expectEqual(ctx, env.get("HWLAB_CODE_AGENT_TIMEOUT_MS")?.value, "120000", "deploy/k8s/base/workloads.yaml.hwlab-cloud-api.env.HWLAB_CODE_AGENT_TIMEOUT_MS", "cloud API workload Code Agent hard timeout budget");
|
||||
expectEqual(
|
||||
ctx,
|
||||
env.get("HWLAB_CODE_AGENT_OPENAI_BASE_URL")?.value,
|
||||
@@ -576,6 +587,14 @@ function validateCloudApiCodeAgentArtifacts(ctx, workloads) {
|
||||
expectEqual(ctx, env.get(secretRef.env)?.valueFrom?.secretKeyRef?.key, secretRef.secretKey, "deploy/k8s/base/workloads.yaml.hwlab-cloud-api.env.OPENAI_API_KEY.secretKeyRef.key", "cloud API workload Code Agent Secret key");
|
||||
}
|
||||
|
||||
function validateCodeAgentProxyTimeoutArtifacts(ctx, workloads) {
|
||||
const workloadItems = mapByServiceId(listItems(workloads));
|
||||
const cloudWebEnv = new Map((workloadItems.get("hwlab-cloud-web")?.spec?.template?.spec?.containers?.[0]?.env ?? []).map((entry) => [entry.name, entry.value]));
|
||||
const edgeProxyEnv = new Map((workloadItems.get("hwlab-edge-proxy")?.spec?.template?.spec?.containers?.[0]?.env ?? []).map((entry) => [entry.name, entry.value]));
|
||||
expectEqual(ctx, cloudWebEnv.get("HWLAB_CLOUD_WEB_PROXY_TIMEOUT_MS"), "150000", "deploy/k8s/base/workloads.yaml.hwlab-cloud-web.env.HWLAB_CLOUD_WEB_PROXY_TIMEOUT_MS", "Cloud Web workload same-origin Code Agent proxy timeout budget");
|
||||
expectEqual(ctx, edgeProxyEnv.get("HWLAB_EDGE_PROXY_TIMEOUT_MS"), "150000", "deploy/k8s/base/workloads.yaml.hwlab-edge-proxy.env.HWLAB_EDGE_PROXY_TIMEOUT_MS", "edge proxy workload Code Agent timeout budget");
|
||||
}
|
||||
|
||||
async function buildPlan() {
|
||||
const ctx = { checks: 0, diagnostics: [] };
|
||||
const [manifest, services, workloads, healthContract, masterEdge, frpc, frps] = await Promise.all([
|
||||
|
||||
@@ -604,7 +604,7 @@ function runStaticSmoke() {
|
||||
evidence: codeAgentQuickPromptFixtures.flatMap((prompt) => [prompt.id, prompt.label, prompt.prompt])
|
||||
});
|
||||
|
||||
addCheck(checks, blockers, "code-agent-long-timeout-contract", hasCodeAgentLongTimeoutContract(files.app), "Code Agent chat uses a dedicated long timeout and does not reuse the old 4500ms API timeout.", {
|
||||
addCheck(checks, blockers, "code-agent-long-timeout-contract", hasCodeAgentLongTimeoutContract(files), "Code Agent chat uses a dedicated long timeout and does not reuse the old 4500ms API timeout.", {
|
||||
blocker: "runtime_blocker",
|
||||
evidence: ["DEFAULT_API_TIMEOUT_MS=4500 for light probes", `DEFAULT_CODE_AGENT_TIMEOUT_MS=${codeAgentLongTimeoutMs}`, "sendAgentMessage passes timeoutMs"]
|
||||
});
|
||||
@@ -1689,7 +1689,8 @@ function readStaticFiles() {
|
||||
runtime: readText("web/hwlab-cloud-web/runtime.mjs"),
|
||||
panel: readText("web/hwlab-cloud-web/workbench-hardware-panel.mjs"),
|
||||
codeAgentM3Evidence: readText("web/hwlab-cloud-web/code-agent-m3-evidence.mjs"),
|
||||
help: readText("web/hwlab-cloud-web/help.md")
|
||||
help: readText("web/hwlab-cloud-web/help.md"),
|
||||
artifactPublisher: readText("scripts/dev-artifact-publish.mjs")
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2561,7 +2562,8 @@ function hasCodeAgentCompletedEvidenceVisibility({ app, codeAgentM3Evidence }) {
|
||||
);
|
||||
}
|
||||
|
||||
function hasCodeAgentLongTimeoutContract(app) {
|
||||
function hasCodeAgentLongTimeoutContract(files) {
|
||||
const app = files.app;
|
||||
return (
|
||||
/DEFAULT_API_TIMEOUT_MS\s*=\s*4500/u.test(app) &&
|
||||
/DEFAULT_CODE_AGENT_TIMEOUT_MS\s*=\s*150000/u.test(app) &&
|
||||
@@ -2569,7 +2571,11 @@ function hasCodeAgentLongTimeoutContract(app) {
|
||||
/timeoutMs\s*=\s*API_TIMEOUT_MS/u.test(functionBody(app, "fetchJson")) &&
|
||||
/timeoutName\s*=\s*path/u.test(functionBody(app, "fetchJson")) &&
|
||||
/HWLAB_CLOUD_WEB_CONFIG/u.test(app) &&
|
||||
/codeAgentTimeoutMs/u.test(app)
|
||||
/codeAgentTimeoutMs/u.test(app) &&
|
||||
/Code Agent 代理等待 cloud-api 超过/u.test(files.artifactPublisher ?? "") &&
|
||||
/输入已保留,可稍后重试/u.test(files.artifactPublisher ?? "") &&
|
||||
/blocker:\s*\{/u.test(files.artifactPublisher ?? "") &&
|
||||
/retryable:\s*true/u.test(files.artifactPublisher ?? "")
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -934,7 +934,12 @@ assert.match(artifactPublisher, /"evidence\.record\.query"/);
|
||||
assert.match(artifactPublisher, /readonly_rpc_required/);
|
||||
assert.match(artifactPublisher, /cloud_api_proxy_failed/);
|
||||
assert.match(artifactPublisher, /cloud_api_proxy_timeout/);
|
||||
assert.match(artifactPublisher, /traceId:\s*request\.headers\["x-trace-id"\]/);
|
||||
assert.match(artifactPublisher, /Code Agent 代理等待 cloud-api 超过/);
|
||||
assert.match(artifactPublisher, /输入已保留,可稍后重试/);
|
||||
assert.match(artifactPublisher, /retryable:\s*true/);
|
||||
assert.match(artifactPublisher, /blocker:\s*\{/);
|
||||
assert.match(artifactPublisher, /const traceId = request\.headers\["x-trace-id"\] \|\| null/);
|
||||
assert.match(artifactPublisher, /traceId,\s*\n\s*route: url\.pathname/);
|
||||
assert.match(artifactPublisher, /url\.pathname\.replace\(\/\\\/\+\$\/,\s*""\) \|\| "\/"/);
|
||||
assert.match(artifactPublisher, /routePath === "\/gate"/);
|
||||
assert.match(artifactPublisher, /routePath === "\/diagnostics\/gate"/);
|
||||
|
||||
Reference in New Issue
Block a user