feat: installed apps, stop goal, auth fixes, remote commands
- Android: fetch installed apps via PackageManager, send to server on connect - Android: add QUERY_ALL_PACKAGES permission for full app visibility - Android: fix duplicate Intent import, increase accessibility retry window - Android: default server URL to ws:// instead of wss:// - Server: store installed apps in device metadata JSONB - Server: inject installed apps context into LLM prompt - Server: preprocessor resolves app names from device's actual installed apps - Server: add POST /goals/stop endpoint with AbortController cancellation - Server: rewrite session middleware to direct DB token lookup - Server: goals route fetches user's saved LLM config from DB - Web: show installed apps in device detail Overview tab with search - Web: add Stop button for running goals - Web: replace API routes with remote commands (submitGoal, stopGoal) - Web: add error display for goal submission failures - Shared: add InstalledApp type and apps message to protocol
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import * as v from 'valibot';
|
||||
import { query, getRequestEvent } from '$app/server';
|
||||
import { query, command, getRequestEvent } from '$app/server';
|
||||
import { env } from '$env/dynamic/private';
|
||||
import { db } from '$lib/server/db';
|
||||
import { device, agentSession, agentStep } from '$lib/server/db/schema';
|
||||
import { eq, desc, and, count, avg, sql, inArray } from 'drizzle-orm';
|
||||
@@ -85,7 +86,8 @@ export const getDevice = query(v.string(), async (deviceId) => {
|
||||
screenHeight: (info?.screenHeight as number) ?? null,
|
||||
batteryLevel: (info?.batteryLevel as number) ?? null,
|
||||
isCharging: (info?.isCharging as boolean) ?? false,
|
||||
lastSeen: d.lastSeen?.toISOString() ?? d.createdAt.toISOString()
|
||||
lastSeen: d.lastSeen?.toISOString() ?? d.createdAt.toISOString(),
|
||||
installedApps: (info?.installedApps as Array<{ packageName: string; label: string }>) ?? []
|
||||
};
|
||||
});
|
||||
|
||||
@@ -155,3 +157,37 @@ export const listSessionSteps = query(
|
||||
return steps;
|
||||
}
|
||||
);
|
||||
|
||||
// ─── Commands (write operations) ─────────────────────────────
|
||||
|
||||
const SERVER_URL = () => env.SERVER_URL || 'http://localhost:8080';
|
||||
|
||||
/** Forward a request to the DroidClaw server with auth cookies */
|
||||
async function serverFetch(path: string, body: Record<string, unknown>) {
|
||||
const { request } = getRequestEvent();
|
||||
const res = await fetch(`${SERVER_URL()}${path}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
cookie: request.headers.get('cookie') ?? ''
|
||||
},
|
||||
body: JSON.stringify(body)
|
||||
});
|
||||
const data = await res.json().catch(() => ({ error: 'Unknown error' }));
|
||||
if (!res.ok) throw new Error(data.error ?? `Error ${res.status}`);
|
||||
return data;
|
||||
}
|
||||
|
||||
export const submitGoal = command(
|
||||
v.object({ deviceId: v.string(), goal: v.string() }),
|
||||
async ({ deviceId, goal }) => {
|
||||
return serverFetch('/goals', { deviceId, goal });
|
||||
}
|
||||
);
|
||||
|
||||
export const stopGoal = command(
|
||||
v.object({ deviceId: v.string() }),
|
||||
async ({ deviceId }) => {
|
||||
return serverFetch('/goals/stop', { deviceId });
|
||||
}
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user