Refresh stale device capability timestamps
This commit is contained in:
@@ -1742,6 +1742,36 @@ function normalizeDeviceCapabilities(
|
||||
};
|
||||
}
|
||||
|
||||
function refreshDeviceCapabilityLastSeenAt(
|
||||
raw: DeviceCapabilitiesInput | undefined,
|
||||
fallbackLastSeenAt: string,
|
||||
preserveExistingLastSeenAt = true,
|
||||
): DeviceCapabilitiesInput | undefined {
|
||||
if (!raw) {
|
||||
return raw;
|
||||
}
|
||||
return {
|
||||
gui: raw.gui
|
||||
? {
|
||||
...raw.gui,
|
||||
lastSeenAt:
|
||||
preserveExistingLastSeenAt
|
||||
? trimToDefined(raw.gui.lastSeenAt) ?? fallbackLastSeenAt
|
||||
: fallbackLastSeenAt,
|
||||
}
|
||||
: raw.gui,
|
||||
cli: raw.cli
|
||||
? {
|
||||
...raw.cli,
|
||||
lastSeenAt:
|
||||
preserveExistingLastSeenAt
|
||||
? trimToDefined(raw.cli.lastSeenAt) ?? fallbackLastSeenAt
|
||||
: fallbackLastSeenAt,
|
||||
}
|
||||
: raw.cli,
|
||||
};
|
||||
}
|
||||
|
||||
function normalizePreferredExecutionMode(value: unknown): DeviceExecutionMode {
|
||||
return value === "gui" ? "gui" : "cli";
|
||||
}
|
||||
@@ -7273,6 +7303,7 @@ export async function updateDevice(deviceId: string, payload: DeviceUpdatePayloa
|
||||
const device = await mutateState((state) => {
|
||||
const nextDevice = state.devices.find((item) => item.id === deviceId);
|
||||
if (!nextDevice) throw new Error("DEVICE_NOT_FOUND");
|
||||
const nextLastSeenAt = nowIso();
|
||||
|
||||
if (payload.name) nextDevice.name = payload.name.trim();
|
||||
if (payload.avatar) nextDevice.avatar = payload.avatar.trim().slice(0, 2) || nextDevice.avatar;
|
||||
@@ -7283,21 +7314,23 @@ export async function updateDevice(deviceId: string, payload: DeviceUpdatePayloa
|
||||
if (payload.projects) {
|
||||
nextDevice.projects = payload.projects.filter(Boolean);
|
||||
}
|
||||
if (payload.capabilities) {
|
||||
nextDevice.capabilities = normalizeDeviceCapabilities(
|
||||
{
|
||||
gui: payload.capabilities.gui ?? nextDevice.capabilities?.gui,
|
||||
cli: payload.capabilities.cli ?? nextDevice.capabilities?.cli,
|
||||
},
|
||||
trimToDefined(payload.capabilities.gui?.lastSeenAt) ??
|
||||
trimToDefined(payload.capabilities.cli?.lastSeenAt) ??
|
||||
nextDevice.lastSeenAt,
|
||||
);
|
||||
}
|
||||
nextDevice.capabilities = normalizeDeviceCapabilities(
|
||||
refreshDeviceCapabilityLastSeenAt(
|
||||
payload.capabilities
|
||||
? {
|
||||
gui: payload.capabilities.gui ?? nextDevice.capabilities?.gui,
|
||||
cli: payload.capabilities.cli ?? nextDevice.capabilities?.cli,
|
||||
}
|
||||
: nextDevice.capabilities,
|
||||
nextLastSeenAt,
|
||||
Boolean(payload.capabilities),
|
||||
),
|
||||
nextLastSeenAt,
|
||||
);
|
||||
if (payload.preferredExecutionMode !== undefined) {
|
||||
nextDevice.preferredExecutionMode = normalizePreferredExecutionMode(payload.preferredExecutionMode);
|
||||
}
|
||||
nextDevice.lastSeenAt = nowIso();
|
||||
nextDevice.lastSeenAt = nextLastSeenAt;
|
||||
return nextDevice;
|
||||
});
|
||||
publishBossEvent("devices.updated", { deviceId });
|
||||
@@ -7635,6 +7668,7 @@ export async function upsertDeviceHeartbeat(payload: {
|
||||
if (device.token && payload.token && device.token !== payload.token && !claimedEnrollment) {
|
||||
throw new Error("DEVICE_TOKEN_MISMATCH");
|
||||
}
|
||||
const nextLastSeenAt = nowIso();
|
||||
device.name = payload.name;
|
||||
device.avatar = payload.avatar;
|
||||
device.account = payload.account;
|
||||
@@ -7643,12 +7677,16 @@ export async function upsertDeviceHeartbeat(payload: {
|
||||
device.projects = payload.projects;
|
||||
device.quota5h = payload.quota5h;
|
||||
device.quota7d = payload.quota7d;
|
||||
device.lastSeenAt = nowIso();
|
||||
device.lastSeenAt = nextLastSeenAt;
|
||||
device.endpoint = payload.endpoint ?? device.endpoint;
|
||||
device.token = claimedEnrollment?.token ?? payload.token ?? device.token;
|
||||
device.capabilities = normalizeDeviceCapabilities(
|
||||
payload.capabilities ?? device.capabilities,
|
||||
device.lastSeenAt,
|
||||
refreshDeviceCapabilityLastSeenAt(
|
||||
payload.capabilities ?? device.capabilities,
|
||||
nextLastSeenAt,
|
||||
Boolean(payload.capabilities),
|
||||
),
|
||||
nextLastSeenAt,
|
||||
);
|
||||
if (device.preferredExecutionMode === undefined && payload.preferredExecutionMode !== undefined) {
|
||||
device.preferredExecutionMode = normalizePreferredExecutionMode(payload.preferredExecutionMode);
|
||||
|
||||
@@ -240,3 +240,56 @@ test("device heartbeat does not overwrite the preferred execution mode chosen in
|
||||
assert.ok(device);
|
||||
assert.equal(device.preferredExecutionMode, "gui");
|
||||
});
|
||||
|
||||
test("device heartbeat without capability payload refreshes stale gui cli lastSeenAt", async () => {
|
||||
await setup();
|
||||
|
||||
const staleCapabilityTime = "2026-03-25T11:52:00+08:00";
|
||||
const staleState = await readState();
|
||||
await writeState({
|
||||
...staleState,
|
||||
devices: staleState.devices.map((device) =>
|
||||
device.id === "mac-studio"
|
||||
? {
|
||||
...device,
|
||||
lastSeenAt: staleCapabilityTime,
|
||||
capabilities: {
|
||||
gui: {
|
||||
...device.capabilities?.gui,
|
||||
connected: true,
|
||||
lastSeenAt: staleCapabilityTime,
|
||||
lastActiveProjectId: "master-agent",
|
||||
},
|
||||
cli: {
|
||||
...device.capabilities?.cli,
|
||||
connected: true,
|
||||
lastSeenAt: staleCapabilityTime,
|
||||
lastActiveProjectId: "master-agent",
|
||||
},
|
||||
},
|
||||
}
|
||||
: device,
|
||||
),
|
||||
});
|
||||
|
||||
await upsertDeviceHeartbeat({
|
||||
deviceId: "mac-studio",
|
||||
name: "Mac Studio",
|
||||
avatar: "M",
|
||||
account: "17600003315",
|
||||
status: "online",
|
||||
quota5h: 72,
|
||||
quota7d: 86,
|
||||
preferredExecutionMode: "cli",
|
||||
projects: ["硬件审计协作"],
|
||||
endpoint: "mac://kris.local",
|
||||
});
|
||||
|
||||
const state = await readState();
|
||||
const device = state.devices.find((item) => item.id === "mac-studio");
|
||||
|
||||
assert.ok(device);
|
||||
assert.notEqual(device.lastSeenAt, staleCapabilityTime);
|
||||
assert.equal(device.capabilities?.gui.lastSeenAt, device.lastSeenAt);
|
||||
assert.equal(device.capabilities?.cli.lastSeenAt, device.lastSeenAt);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user