fix: goals route now finds devices by persistent DB ID, not connection UUID

This commit is contained in:
Sanju Sivalingam
2026-02-17 21:22:43 +05:30
parent 5b73e89217
commit fae5fd3534
4 changed files with 26 additions and 11 deletions

View File

@@ -25,7 +25,9 @@ goals.post("/", async (c) => {
return c.json({ error: "deviceId and goal are required" }, 400); return c.json({ error: "deviceId and goal are required" }, 400);
} }
const device = sessions.getDevice(body.deviceId); // Look up by connection ID first, then by persistent DB ID
const device = sessions.getDevice(body.deviceId)
?? sessions.getDeviceByPersistentId(body.deviceId);
if (!device) { if (!device) {
return c.json({ error: "device not connected" }, 404); return c.json({ error: "device not connected" }, 404);
} }
@@ -35,8 +37,9 @@ goals.post("/", async (c) => {
} }
// Prevent multiple agent loops on the same device // Prevent multiple agent loops on the same device
if (activeSessions.has(body.deviceId)) { const trackingKey = device.persistentDeviceId ?? device.deviceId;
const existing = activeSessions.get(body.deviceId)!; if (activeSessions.has(trackingKey)) {
const existing = activeSessions.get(trackingKey)!;
return c.json( return c.json(
{ error: "agent already running on this device", sessionId: existing.sessionId, goal: existing.goal }, { error: "agent already running on this device", sessionId: existing.sessionId, goal: existing.goal },
409 409
@@ -55,7 +58,8 @@ goals.post("/", async (c) => {
} }
const options: AgentLoopOptions = { const options: AgentLoopOptions = {
deviceId: body.deviceId, deviceId: device.deviceId,
persistentDeviceId: device.persistentDeviceId,
userId: user.id, userId: user.id,
goal: body.goal, goal: body.goal,
llmConfig, llmConfig,
@@ -67,20 +71,19 @@ goals.post("/", async (c) => {
const loopPromise = runAgentLoop(options); const loopPromise = runAgentLoop(options);
// Track as active until it completes // Track as active until it completes
const trackingId = body.deviceId;
const sessionPlaceholder = { sessionId: "pending", goal: body.goal }; const sessionPlaceholder = { sessionId: "pending", goal: body.goal };
activeSessions.set(trackingId, sessionPlaceholder); activeSessions.set(trackingKey, sessionPlaceholder);
loopPromise loopPromise
.then((result) => { .then((result) => {
activeSessions.delete(trackingId); activeSessions.delete(trackingKey);
console.log( console.log(
`[Agent] Completed on ${body.deviceId}: ${result.success ? "success" : "incomplete"} in ${result.stepsUsed} steps (session ${result.sessionId})` `[Agent] Completed on ${device.deviceId}: ${result.success ? "success" : "incomplete"} in ${result.stepsUsed} steps (session ${result.sessionId})`
); );
}) })
.catch((err) => { .catch((err) => {
activeSessions.delete(trackingId); activeSessions.delete(trackingKey);
console.error(`[Agent] Error on ${body.deviceId}: ${err}`); console.error(`[Agent] Error on ${device.deviceId}: ${err}`);
}); });
// We need the sessionId from the loop, but it's created inside runAgentLoop. // We need the sessionId from the loop, but it's created inside runAgentLoop.

View File

@@ -122,6 +122,7 @@ export async function handleDeviceMessage(
// Register device in session manager // Register device in session manager
sessions.addDevice({ sessions.addDevice({
deviceId, deviceId,
persistentDeviceId,
userId, userId,
ws, ws,
deviceInfo: msg.deviceInfo, deviceInfo: msg.deviceInfo,

View File

@@ -14,6 +14,7 @@ export interface WebSocketData {
/** A connected Android device */ /** A connected Android device */
export interface ConnectedDevice { export interface ConnectedDevice {
deviceId: string; deviceId: string;
persistentDeviceId?: string;
userId: string; userId: string;
ws: ServerWebSocket<WebSocketData>; ws: ServerWebSocket<WebSocketData>;
deviceInfo?: DeviceInfo; deviceInfo?: DeviceInfo;
@@ -56,6 +57,16 @@ class SessionManager {
return this.devices.get(deviceId); return this.devices.get(deviceId);
} }
/** Look up a device by its persistent DB ID (survives reconnects) */
getDeviceByPersistentId(persistentId: string): ConnectedDevice | undefined {
for (const device of this.devices.values()) {
if (device.persistentDeviceId === persistentId) {
return device;
}
}
return undefined;
}
getDevicesForUser(userId: string): ConnectedDevice[] { getDevicesForUser(userId: string): ConnectedDevice[] {
const result: ConnectedDevice[] = []; const result: ConnectedDevice[] = [];
for (const device of this.devices.values()) { for (const device of this.devices.values()) {

View File

@@ -370,7 +370,7 @@
</div> </div>
<!-- Live Steps --> <!-- Live Steps -->
{#if steps.length > 0 || runStatus === 'running'} {#if steps.length > 0 || runStatus !== 'idle'}
<div class="rounded-lg border border-neutral-200"> <div class="rounded-lg border border-neutral-200">
<div <div
class="flex items-center justify-between border-b border-neutral-200 px-5 py-3" class="flex items-center justify-between border-b border-neutral-200 px-5 py-3"