fix(gateway): stop paired-device duplication and redesign the Nodes page (#102810)

* fix(gateway): prune superseded silent device pairings on auto-approve

* feat(ui): unified nodes & devices inventory with dedupe and cleanup

* docs: nodes page inventory + silent pairing supersede cleanup

* refactor(gateway): run silent-pairing prune after approval broadcast

* fix(ui): surface node list errors on the nodes inventory card

* fix(gateway): restrict pairing auto-prune to same-host silent approvals

* fix(gateway): report live device connections and guard pairing prune races

* docs(ui): document bulk-cleanup name-collision tradeoff

* fix(gateway): avoid map-spread when marking live device connections

* docs: regenerate docs map
This commit is contained in:
Peter Steinberger 2026-07-09 15:40:30 +01:00 committed by GitHub
parent 743422217e
commit 8985598302
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
25 changed files with 2112 additions and 279 deletions

View file

@ -560,7 +560,92 @@ async function createChatPickerScenario(): Promise<ControlUiMockGatewayScenario>
methodResponses: {
"usage.cost": profileUsage.cost,
"sessions.usage": profileUsage.sessions,
"device.pair.list": { paired: [], pending: [] },
"device.pair.list": {
paired: [
{
deviceId: "a1b2c3d4e5f60718293a4b5c6d7e8f90a1b2c3d4e5f60718293a4b5c6d7e8f90",
displayName: "Mac Studio",
platform: "darwin",
clientId: "node-host",
clientMode: "node",
roles: ["operator", "node"],
scopes: ["operator.admin", "operator.read", "operator.write"],
approvedVia: "trusted-cidr",
approvedAtMs: baseTime - 3_600_000,
lastSeenAtMs: baseTime - 60_000,
tokens: [
{ role: "node", scopes: [], createdAtMs: baseTime - 3_600_000 },
{
role: "operator",
scopes: ["operator.admin", "operator.read", "operator.write"],
createdAtMs: baseTime - 3_600_000,
},
],
},
{
deviceId: "0f1e2d3c4b5a69788796a5b4c3d2e1f00f1e2d3c4b5a69788796a5b4c3d2e1f0",
displayName: "Mac Studio",
platform: "darwin",
clientId: "node-host",
clientMode: "node",
roles: ["node"],
approvedVia: "trusted-cidr",
approvedAtMs: baseTime - 86_400_000,
lastSeenAtMs: baseTime - 82_800_000,
tokens: [{ role: "node", scopes: [], createdAtMs: baseTime - 86_400_000 }],
},
{
deviceId: "9988776655443322119988776655443322119988776655443322119988776655",
clientId: "cli",
clientMode: "cli",
platform: "darwin",
roles: ["operator"],
scopes: ["operator.admin", "operator.read", "operator.write"],
approvedVia: "silent",
approvedAtMs: baseTime - 7_200_000,
lastSeenAtMs: baseTime - 7_100_000,
tokens: [
{
role: "operator",
scopes: ["operator.admin", "operator.read", "operator.write"],
createdAtMs: baseTime - 7_200_000,
},
],
},
{
deviceId: "11223344556677889900aabbccddeeff11223344556677889900aabbccddeeff",
displayName: "iPhone",
platform: "iOS 26.4",
clientId: "openclaw-ios",
clientMode: "ui",
roles: ["operator", "node"],
scopes: ["operator.approvals", "operator.read", "operator.write"],
approvedVia: "bootstrap",
approvedAtMs: baseTime - 172_800_000,
lastSeenAtMs: baseTime - 3_600_000,
tokens: [
{ role: "node", scopes: [], createdAtMs: baseTime - 172_800_000 },
{
role: "operator",
scopes: ["operator.approvals", "operator.read", "operator.write"],
createdAtMs: baseTime - 172_800_000,
},
],
},
],
pending: [
{
requestId: "mock-pending-request",
deviceId: "feedfacecafebeef0123456789abcdeffeedfacecafebeef0123456789abcdef",
displayName: "MacBook Pro",
role: "operator",
roles: ["operator"],
scopes: ["operator.read", "operator.write"],
remoteIp: "192.168.1.20",
ts: baseTime - 30_000,
},
],
},
"device.pair.setupCode": {
auth: "token",
gatewayUrl: "wss://gateway.example.test",
@ -568,7 +653,54 @@ async function createChatPickerScenario(): Promise<ControlUiMockGatewayScenario>
setupCode: devicePairSetupCode,
urlSource: "mock",
},
"node.list": { nodes: [] },
"node.list": {
nodes: [
{
nodeId: "a1b2c3d4e5f60718293a4b5c6d7e8f90a1b2c3d4e5f60718293a4b5c6d7e8f90",
displayName: "Mac Studio",
platform: "darwin",
version: "2026.6.11",
connected: true,
paired: true,
approvalState: "approved",
connectedAtMs: baseTime - 60_000,
caps: ["canvas", "screen"],
commands: [
"screen.snapshot",
"system.execApprovals.get",
"system.execApprovals.set",
"system.notify",
"system.run",
"system.which",
],
},
{
nodeId: "0f1e2d3c4b5a69788796a5b4c3d2e1f00f1e2d3c4b5a69788796a5b4c3d2e1f0",
displayName: "Mac Studio",
platform: "darwin",
version: "2026.6.10",
connected: false,
paired: true,
approvalState: "approved",
lastSeenAtMs: baseTime - 82_800_000,
caps: ["canvas", "screen"],
commands: ["screen.snapshot", "system.run"],
},
{
nodeId: "11223344556677889900aabbccddeeff11223344556677889900aabbccddeeff",
displayName: "iPhone",
platform: "iOS 26.4",
version: "2026.6.11",
connected: false,
paired: true,
approvalState: "pending-reapproval",
pendingRequestId: "mock-node-reapproval",
lastSeenAtMs: baseTime - 3_600_000,
caps: ["camera", "canvas", "contacts", "device", "location"],
commands: ["camera.list", "contacts.search", "device.info", "location.get"],
},
],
},
"agents.files.get": {
cases: workspaceFileCases,
},