fix: remove frontend json-rpc probe (#1344)
This commit is contained in:
@@ -12,9 +12,11 @@ export async function createCloudApiBunServer(options: any = {}) {
|
||||
|
||||
const host = options.host ?? "0.0.0.0";
|
||||
const port = options.port ?? 0;
|
||||
const idleTimeout = parsePositiveInteger(options.idleTimeout ?? env.HWLAB_CLOUD_API_IDLE_TIMEOUT_SECONDS, 120);
|
||||
const server = Bun.serve({
|
||||
hostname: host,
|
||||
port,
|
||||
idleTimeout,
|
||||
async fetch(request, bunServer) {
|
||||
const url = new URL(request.url);
|
||||
if (url.pathname === "/v1/hwpod-node/ws") {
|
||||
@@ -64,6 +66,12 @@ async function proxyHttpToInnerServer(request: Request, innerPort: number) {
|
||||
});
|
||||
}
|
||||
|
||||
function parsePositiveInteger(value: unknown, fallback: number) {
|
||||
const parsed = Number(value);
|
||||
if (!Number.isFinite(parsed) || parsed <= 0) return fallback;
|
||||
return Math.floor(parsed);
|
||||
}
|
||||
|
||||
function json(body: any, status = 200) {
|
||||
return new Response(`${JSON.stringify(body)}\n`, {
|
||||
status,
|
||||
|
||||
@@ -4,7 +4,6 @@ const POST_PROXY_ROUTES = new Set([
|
||||
"/auth/login",
|
||||
"/auth/register",
|
||||
"/auth/logout",
|
||||
"/json-rpc",
|
||||
"/v1/agent/chat",
|
||||
"/v1/agent/chat/cancel",
|
||||
"/v1/agent/chat/steer",
|
||||
|
||||
@@ -6,13 +6,6 @@ import { promisify } from "node:util";
|
||||
|
||||
import { isCloudWebSseRoute, proxyCloudApiRequest, upstreamRequestHeaders } from "./cloud-web-proxy.mjs";
|
||||
import { cloudWebProxyRoutePolicy } from "./cloud-web-routes.mjs";
|
||||
|
||||
const readOnlyRpcMethods = new Set([
|
||||
"system.health",
|
||||
"cloud.adapter.describe",
|
||||
"audit.event.query",
|
||||
"evidence.record.query"
|
||||
]);
|
||||
const gzipAsync = promisify(gzip);
|
||||
const GZIP_MIN_BYTES = 1024;
|
||||
const CLIENT_DISCONNECT_ERROR_CODES = new Set([
|
||||
@@ -127,7 +120,7 @@ function isCloudWebClientRouteRequest(request, routePath) {
|
||||
if (request.method !== "GET" && request.method !== "HEAD") return false;
|
||||
if (!routePath || routePath === "/") return true;
|
||||
if (routePath.startsWith("/auth/") || routePath === "/auth") return false;
|
||||
if (routePath.startsWith("/v1/") || routePath === "/v1" || routePath === "/json-rpc") return false;
|
||||
if (routePath.startsWith("/v1/") || routePath === "/v1") return false;
|
||||
if (STATIC_ASSET_EXTENSION_PATTERN.test(path.basename(routePath))) return false;
|
||||
const accept = String(request.headers.accept || "").toLowerCase();
|
||||
return !accept || accept.includes("text/html") || accept.includes("*/*");
|
||||
@@ -364,27 +357,6 @@ export async function proxyCloudApi({ request, response, url, cloudApiBaseUrl, c
|
||||
body = await readRequestBody(request);
|
||||
}
|
||||
|
||||
if (url.pathname === "/json-rpc") {
|
||||
try {
|
||||
const envelope = body ? JSON.parse(body) : {};
|
||||
if (!readOnlyRpcMethods.has(envelope.method)) {
|
||||
sendJson(response, 403, {
|
||||
error: "readonly_rpc_required",
|
||||
serviceId,
|
||||
method: envelope.method || null
|
||||
});
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
sendJson(response, 400, {
|
||||
error: "invalid_json",
|
||||
serviceId,
|
||||
reason: error.message
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const target = new URL(url.pathname + url.search, cloudApiBaseUrl);
|
||||
try {
|
||||
await requestUpstream({ target, request, response, body: request.method === "GET" ? "" : body, url, cloudApiProxyTimeoutMs });
|
||||
|
||||
@@ -31,12 +31,6 @@ test("cloud web route policy proxies public Code Agent chat without gating other
|
||||
publicRoute: false,
|
||||
routeKey: "POST /v1/m3/io"
|
||||
});
|
||||
assert.deepEqual(cloudWebProxyRoutePolicy("POST", "/json-rpc"), {
|
||||
proxy: true,
|
||||
authRequired: true,
|
||||
publicRoute: false,
|
||||
routeKey: "POST /json-rpc"
|
||||
});
|
||||
assert.deepEqual(cloudWebProxyRoutePolicy("GET", "/v1/agent/traces/trc_live"), {
|
||||
proxy: true,
|
||||
authRequired: false,
|
||||
|
||||
@@ -7,8 +7,8 @@ import { liveCall } from "../src/stores/workbench.ts";
|
||||
|
||||
test("R3 probe summary exposes React WorkbenchProbe rows", () => {
|
||||
const summary = summarizeProbeRows(liveSurface());
|
||||
assert.equal(summary.rows.length, 4);
|
||||
assert.equal(summary.okCount, 4);
|
||||
assert.equal(summary.rows.length, 3);
|
||||
assert.equal(summary.okCount, 3);
|
||||
assert.equal(summary.tone, "ok");
|
||||
assert.ok(summary.rows.some((row) => row.path === "/health/live" && row.detail.includes("revision=c0cbdbb38d")));
|
||||
});
|
||||
@@ -53,7 +53,6 @@ function liveSurface(): LiveSurface {
|
||||
healthLive: liveProbe,
|
||||
health: ok({ serviceId: "hwlab-edge-proxy", status: "ok", ready: true, revision: "c0cbdbb38de77f1ecc9d1222438a13294d81d4dd" }),
|
||||
restIndex: ok({ serviceId: "hwlab-cloud-api", status: "ok", ready: true, revision: "c0cbdbb38de77f1ecc9d1222438a13294d81d4dd" }),
|
||||
adapter: ok({ serviceId: "hwlab-cloud-api", status: "ok", ready: true, revision: "c0cbdbb38de77f1ecc9d1222438a13294d81d4dd" }),
|
||||
hwpodSpecs: ok<HwpodSpecsResponse>({ ok: true, status: "completed", count: 1, availableCount: 0, nodeOpsContractVersion: "hwpod-node-ops-v1", specs: [{ name: "d601-f103-v2", hwpodId: "d601-f103-v2", nodeId: "node-d601-f103-v2", workspacePath: "F:\\Work\\D601-HWLAB", source: { caseId: "d601-f103-v2-compile" }, availability: { ok: false, status: "blocked", checkedAt: "2026-06-13T17:59:15.785Z", results: [{ opId: "op_01_workspace_ls", op: "workspace.ls", ok: false, status: "blocked", blocker: { code: "hwpod_node_unavailable", layer: "hwpod-node", retryable: true, summary: "no outbound WebSocket hwpod-node is connected" } }] } }] }),
|
||||
hwpodNodeOps: ok<HwpodNodeOpsResponse>({ ok: true, status: "ready", contractVersion: "hwpod-node-ops-v1", route: "/v1/hwpod-node-ops", supportedOps: ["node.health", "workspace.ls", "debug.build"], websocket: { connectedCount: 0, pendingCount: 0, nodes: [] } }),
|
||||
liveBuilds: ok({ status: "ok", counts: { total: 2, withBuildTime: 0, unavailable: 2, external: 0 }, services: [{ serviceId: "hwlab-cloud-api", status: "build_time_unavailable", build: { createdAt: null, unavailableReason: "构建时间不可用:live tag 与 deploy metadata 不匹配", metadataSource: "live-health" }, image: { tag: "env-abc", digest: "sha256:abcdef0123456789" }, commit: { id: "c0cbdbb38de77f1ecc9d1222438a13294d81d4dd" }, revision: "c0cbdbb38de77f1ecc9d1222438a13294d81d4dd" }, { serviceId: "hwlab-cloud-web", status: "build_time_unavailable", build: { createdAt: null, unavailableReason: "构建时间不可用:live tag 与 deploy metadata 不匹配" }, image: { tag: "env-web" }, commit: { id: "c0cbdbb38de77f1ecc9d1222438a13294d81d4dd" }, revision: "c0cbdbb38de77f1ecc9d1222438a13294d81d4dd" }] }),
|
||||
|
||||
@@ -37,7 +37,6 @@ export const api = {
|
||||
healthLive: (): Promise<ApiResult<LiveProbePayload>> => fetchJson("/health/live", { timeoutMs: 12000, timeoutName: "health/live" }),
|
||||
health: (): Promise<ApiResult<LiveProbePayload>> => fetchJson("/health", { timeoutMs: 12000, timeoutName: "health" }),
|
||||
restIndex: (): Promise<ApiResult<LiveProbePayload>> => fetchJson("/v1", { timeoutMs: 12000, timeoutName: "REST index" }),
|
||||
adapter: (): Promise<ApiResult<LiveProbePayload>> => fetchJson("/json-rpc", { method: "POST", body: JSON.stringify({ jsonrpc: "2.0", id: "cloud-web-system-health", method: "system.health", params: {} }), timeoutMs: 12000, timeoutName: "adapter" }),
|
||||
liveBuilds: (): Promise<ApiResult<LiveBuildsPayload>> => fetchJson("/v1/live-builds", { timeoutMs: 12000, timeoutName: "live builds" }),
|
||||
skills: (): Promise<ApiResult<SkillsResponse>> => fetchJson("/v1/skills", { timeoutMs: 12000, timeoutName: "skills" })
|
||||
};
|
||||
|
||||
@@ -14,6 +14,7 @@ const expanded = ref(false);
|
||||
const events = computed(() => Array.isArray(props.trace?.events) ? props.trace.events : []);
|
||||
const eventCount = computed(() => props.trace?.eventCount ?? events.value.length);
|
||||
const readableRows = computed(() => traceDisplayRows(traceRecord(props.trace), events.value as Record<string, unknown>[]));
|
||||
const emptyTraceLabel = computed(() => props.trace?.fullTraceLoaded === true ? "思考中..." : "加载中...");
|
||||
const toolPreviewLimit = 140;
|
||||
|
||||
watch(() => [eventCount.value, props.trace?.status, following.value, expanded.value], async () => {
|
||||
@@ -117,7 +118,7 @@ function traceRecord(trace: RunnerTrace | null | undefined): Record<string, unkn
|
||||
</template>
|
||||
</li>
|
||||
</ol>
|
||||
<LoadingState v-if="readableRows.length === 0" class="trace-empty" label="思考中..." compact />
|
||||
<LoadingState v-if="readableRows.length === 0" class="trace-empty" :label="emptyTraceLabel" compact />
|
||||
</div>
|
||||
</details>
|
||||
</section>
|
||||
|
||||
@@ -89,12 +89,11 @@ export interface HwpodNodeOpsSummary {
|
||||
const PROBE_DEFINITIONS = Object.freeze([
|
||||
{ key: "cloud-api-live", label: "Cloud API 实况身份", method: "GET", path: "/health/live" },
|
||||
{ key: "cloud-api-health", label: "Cloud API health", method: "GET", path: "/health" },
|
||||
{ key: "rest-index", label: "REST index", method: "GET", path: "/v1" },
|
||||
{ key: "adapter", label: "Codex app-server adapter", method: "POST", path: "/json-rpc" }
|
||||
{ key: "rest-index", label: "REST index", method: "GET", path: "/v1" }
|
||||
]);
|
||||
|
||||
export function summarizeProbeRows(live: LiveSurface | null): ProbeSummary {
|
||||
const results = [live?.healthLive, live?.health, live?.restIndex, live?.adapter];
|
||||
const results = [live?.healthLive, live?.health, live?.restIndex];
|
||||
const rows = PROBE_DEFINITIONS.map((definition, index) => rowFromProbe(definition, results[index] ?? null));
|
||||
const okCount = rows.filter((row) => row.status === "ok").length;
|
||||
const tone: Tone = okCount === rows.length ? "ok" : rows.some((row) => row.status === "blocked" || row.status === "error") ? "blocked" : "pending";
|
||||
|
||||
@@ -86,16 +86,15 @@ export const useWorkbenchStore = defineStore("workbench", () => {
|
||||
}
|
||||
|
||||
async function refreshLive(): Promise<void> {
|
||||
const [healthLive, health, restIndex, adapter, hwpodSpecs, hwpodNodeOps, liveBuilds] = await Promise.all([
|
||||
const [healthLive, health, restIndex, hwpodSpecs, hwpodNodeOps, liveBuilds] = await Promise.all([
|
||||
liveCall("health/live", api.healthLive),
|
||||
liveCall("health", api.health),
|
||||
liveCall("REST index", api.restIndex),
|
||||
liveCall("adapter", api.adapter),
|
||||
liveCall("hwpod specs", api.hwpod.specs),
|
||||
liveCall("hwpod node ops", api.hwpod.nodeOpsHealth),
|
||||
liveCall("live builds", api.liveBuilds)
|
||||
]);
|
||||
live.value = { healthLive, health, restIndex, adapter, hwpodSpecs, hwpodNodeOps, liveBuilds, loadedAt: new Date().toISOString() };
|
||||
live.value = { healthLive, health, restIndex, hwpodSpecs, hwpodNodeOps, liveBuilds, loadedAt: new Date().toISOString() };
|
||||
}
|
||||
|
||||
async function createSession(): Promise<void> {
|
||||
|
||||
@@ -53,7 +53,6 @@ export interface LiveSurface {
|
||||
healthLive: ApiResult<LiveProbePayload>;
|
||||
health: ApiResult<LiveProbePayload>;
|
||||
restIndex: ApiResult<LiveProbePayload>;
|
||||
adapter: ApiResult<LiveProbePayload>;
|
||||
hwpodSpecs: ApiResult<HwpodSpecsResponse>;
|
||||
hwpodNodeOps: ApiResult<HwpodNodeOpsResponse>;
|
||||
liveBuilds: ApiResult<LiveBuildsPayload>;
|
||||
|
||||
Reference in New Issue
Block a user