merge: 修复 HWPOD 控制台 L0/L1 问题

This commit is contained in:
root
2026-07-21 13:24:29 +02:00
9 changed files with 97 additions and 62 deletions
@@ -28,7 +28,7 @@ test("typed topology covers multi-device, offline, unconfigured, invalid, mismat
runtimeKind: "python-tkinter-gui",
installed: true,
desktopVisible: true,
capabilities: ["workspace.ls"],
capabilities: ["workspace.ls", "node.health"],
inFlightCount: 1,
maxInFlight: 1,
lastDiagnostic: { level: "ERROR", source: "ws", message: "connect failed: Bearer private-token?token=still-secret" },
@@ -44,15 +44,24 @@ test("typed topology covers multi-device, offline, unconfigured, invalid, mismat
assert.equal(devicePayload.summary.nodeCount, 3);
assert.equal(devicePayload.summary.deviceCount, 3);
assert.equal(devicePayload.summary.invalidSpecCount, 1);
assert.equal(devicePayload.items.find((item: any) => item.hwpodId === "hwpod-a").status, "busy");
assert.equal(devicePayload.items.find((item: any) => item.hwpodId === "hwpod-b").status, "capability-mismatch");
assert.deepEqual(devicePayload.items.find((item: any) => item.hwpodId === "hwpod-b").missingCapabilities, ["debug.download"]);
const deviceA = devicePayload.items.find((item: any) => item.hwpodId === "hwpod-a");
const deviceB = devicePayload.items.find((item: any) => item.hwpodId === "hwpod-b");
assert.equal(deviceA.status, "online");
assert.equal(deviceA.busy, true);
assert.equal(deviceA.available, false);
assert.equal(deviceB.status, "online");
assert.equal(deviceB.capabilityMismatch, true);
assert.deepEqual(deviceB.missingCapabilities, ["debug.download"]);
assert.deepEqual(deviceB.capabilityCoverage, { matched: 1, declared: 2, actual: 2, missing: 1, extra: 1 });
assert.equal(deviceB.blockers.some((item: any) => item.code === "hwpod_capability_mismatch"), true);
assert.equal(devicePayload.items.find((item: any) => item.hwpodId === "hwpod-c").status, "offline");
const nodePayload = buildHwpodTopologyReadModel(specs, registry, { resource: "node", observedAt: "2026-07-13T01:00:00.000Z" });
const nodeA = nodePayload.items.find((item: any) => item.nodeId === "node-a");
assert.equal(nodeA.deviceCount, 2);
assert.equal(nodeA.status, "online");
assert.equal(nodeA.inFlight.busy, true);
assert.deepEqual(nodeA.capabilityCoverage, { matched: 1, declared: 2, actual: 2, missing: 1, extra: 1 });
assert.equal(nodeA.diagnostic.message.includes("private-token"), false);
assert.equal(nodeA.diagnostic.message.includes("still-secret"), false);
assert.equal(nodeA.diagnostic.message.includes("[REDACTED]"), true);
@@ -61,7 +70,8 @@ test("typed topology covers multi-device, offline, unconfigured, invalid, mismat
assert.equal(nodeA.readiness.find((stage: any) => stage.id === "desktop-visible").nextAction, null);
assert.equal(nodeA.readiness.find((stage: any) => stage.id === "hwpod-ready").nextAction.entrypoint, "hwpod-topology");
assert.equal(nodePayload.items.find((item: any) => item.nodeId === "node-b").readiness.find((stage: any) => stage.id === "workspace-ready").blocker.code, "hwpod_node_spec_missing");
assert.equal(nodePayload.items.find((item: any) => item.nodeId === "node-b").status, "unconfigured");
assert.equal(nodePayload.items.find((item: any) => item.nodeId === "node-b").status, "online");
assert.equal(nodePayload.summary.unconfiguredNodeCount, 1);
assert.equal(nodePayload.items.find((item: any) => item.nodeId === "node-c").status, "offline");
assert.equal(nodePayload.invalidSpecs[0].blocker.code, "invalid_hwpod_spec");
});
@@ -74,11 +84,11 @@ test("typed topology keeps server-side status filtering and cursor bounded", ()
{ nodeId: "node-b", status: "online", capabilities: ["workspace.ls"], inFlightCount: 0, maxInFlight: 1 }
]
};
const first = buildHwpodTopologyReadModel(specs, registry, { resource: "device", status: "available", limit: 1 });
const first = buildHwpodTopologyReadModel(specs, registry, { resource: "device", status: "online", limit: 1 });
assert.equal(first.items.length, 1);
assert.equal(first.page.total, 2);
assert.ok(first.page.nextCursor);
const second = buildHwpodTopologyReadModel(specs, registry, { resource: "device", status: "available", limit: 1, cursor: first.page.nextCursor });
const second = buildHwpodTopologyReadModel(specs, registry, { resource: "device", status: "online", limit: 1, cursor: first.page.nextCursor });
assert.equal(second.items.length, 1);
assert.notEqual(first.items[0].hwpodId, second.items[0].hwpodId);
assert.equal(second.page.previousCursor, "b2Zmc2V0OjA");
+20 -12
View File
@@ -52,9 +52,9 @@ export function buildHwpodTopologyReadModel(specs: any[], registrySnapshot: any
nodeCount: nodes.length,
onlineNodeCount: nodes.filter((node) => node.online).length,
offlineNodeCount: nodes.filter((node) => !node.online).length,
unconfiguredNodeCount: nodes.filter((node) => node.status === "unconfigured").length,
unconfiguredNodeCount: nodes.filter((node) => node.deviceCount === 0).length,
deviceCount: devices.length,
availableDeviceCount: devices.filter((device) => device.status === "available").length,
availableDeviceCount: devices.filter((device) => device.available).length,
busyDeviceCount: devices.filter((device) => device.busy).length,
capabilityMismatchCount: devices.filter((device) => device.capabilityMismatch).length,
invalidSpecCount: invalidSpecs.length
@@ -105,12 +105,14 @@ function buildDevice(spec: any, node: any, observedAt: string) {
const declaredCapabilities = declaredCapabilitiesFor(body);
const actualCapabilities = node?.actualCapabilities ?? [];
const missingCapabilities = node ? declaredCapabilities.filter((capability) => !actualCapabilities.includes(capability)) : declaredCapabilities;
const capabilityCoverage = buildCapabilityCoverage(declaredCapabilities, actualCapabilities);
const availability = availabilitySummary(spec.availability);
const online = Boolean(node?.online);
const busy = Boolean(node?.busy);
const capabilityMismatch = online && missingCapabilities.length > 0;
const blockers = deviceBlockers({ online, busy, capabilityMismatch, missingCapabilities, availability });
const status = deviceStatus({ online, busy, capabilityMismatch, availability });
const status = online ? "online" : "offline";
const available = online && !busy && !capabilityMismatch && availability.ok === true;
return {
resourceType: "hwpod",
hwpodId: text(spec.hwpodId ?? spec.name),
@@ -120,12 +122,13 @@ function buildDevice(spec: any, node: any, observedAt: string) {
nodeName: node?.name ?? text(spec.nodeId),
status,
online,
available: status === "available",
available,
busy,
capabilityMismatch,
declaredCapabilities,
actualCapabilities,
missingCapabilities,
capabilityCoverage,
elements: hwpodElements(body),
availability,
operation: node?.latestOperation?.hwpodId === text(spec.hwpodId ?? spec.name) ? node.latestOperation : null,
@@ -144,11 +147,12 @@ function buildNode(nodeId: string, onlineNode: any, devices: any[], observedAt:
const declaredCapabilities = uniqueStrings(devices.flatMap((device) => device.declaredCapabilities));
const actualCapabilities = onlineNode?.actualCapabilities ?? [];
const missingCapabilities = onlineNode ? declaredCapabilities.filter((capability) => !actualCapabilities.includes(capability)) : declaredCapabilities;
const capabilityCoverage = buildCapabilityCoverage(declaredCapabilities, actualCapabilities);
const busy = Boolean(onlineNode?.busy);
const online = Boolean(onlineNode?.online);
const capabilityMismatch = online && missingCapabilities.length > 0;
const hasSpec = devices.length > 0;
const status = !online ? "offline" : !hasSpec ? "unconfigured" : capabilityMismatch ? "capability-mismatch" : busy ? "busy" : "online";
const status = online ? "online" : "offline";
const blockers = nodeBlockers({ online, hasSpec, busy, capabilityMismatch, missingCapabilities, devices });
const workspaceReady = devices.some((device) => device.availability.ok === true);
const hwpodReady = devices.some((device) => device.available);
@@ -175,6 +179,7 @@ function buildNode(nodeId: string, onlineNode: any, devices: any[], observedAt:
declaredCapabilities,
actualCapabilities,
missingCapabilities,
capabilityCoverage,
capabilityMismatch,
inFlight: {
count: onlineNode?.inFlightCount ?? 0,
@@ -244,13 +249,16 @@ function availabilitySummary(value: any) {
};
}
function deviceStatus(input: any) {
if (!input.online) return "offline";
if (input.capabilityMismatch) return "capability-mismatch";
if (input.busy) return "busy";
if (input.availability.ok === false) return "workspace-blocked";
if (input.availability.ok === true) return "available";
return "unprobed";
function buildCapabilityCoverage(declaredCapabilities: string[], actualCapabilities: string[]) {
const matched = declaredCapabilities.filter((capability) => actualCapabilities.includes(capability)).length;
const extra = actualCapabilities.filter((capability) => !declaredCapabilities.includes(capability)).length;
return {
matched,
declared: declaredCapabilities.length,
actual: actualCapabilities.length,
missing: declaredCapabilities.length - matched,
extra
};
}
function deviceBlockers(input: any) {