refactor(agent): delete preprocessor.ts (replaced by parser.ts)

This commit is contained in:
Sanju Sivalingam
2026-02-18 00:28:50 +05:30
parent d5c3466554
commit 3769b21ed1
6 changed files with 190 additions and 232 deletions

View File

@@ -1,218 +0,0 @@
/**
* Goal preprocessor for DroidClaw agent loop.
*
* Intercepts simple goals (like "open youtube") and executes direct
* actions before the LLM loop starts. This avoids wasting 20 steps
* on what should be a 2-step task, especially with weaker LLMs that
* navigate via UI instead of using programmatic launch commands.
*/
import { sessions } from "../ws/sessions.js";
import { db } from "../db.js";
import { device as deviceTable } from "../schema.js";
import { eq } from "drizzle-orm";
// ─── Installed App type ──────────────────────────────────────
interface InstalledApp {
packageName: string;
label: string;
}
// ─── Fallback App Name → Package Name Map ────────────────────
// Used only when device has no installed apps data in DB.
const FALLBACK_PACKAGES: Record<string, string> = {
youtube: "com.google.android.youtube",
gmail: "com.google.android.gm",
chrome: "com.android.chrome",
maps: "com.google.android.apps.maps",
photos: "com.google.android.apps.photos",
drive: "com.google.android.apps.docs",
calendar: "com.google.android.calendar",
contacts: "com.google.android.contacts",
messages: "com.google.android.apps.messaging",
phone: "com.google.android.dialer",
clock: "com.google.android.deskclock",
calculator: "com.google.android.calculator",
camera: "com.android.camera",
settings: "com.android.settings",
files: "com.google.android.apps.nbu.files",
play: "com.android.vending",
"play store": "com.android.vending",
"google play": "com.android.vending",
whatsapp: "com.whatsapp",
telegram: "org.telegram.messenger",
instagram: "com.instagram.android",
facebook: "com.facebook.katana",
twitter: "com.twitter.android",
x: "com.twitter.android",
spotify: "com.spotify.music",
netflix: "com.netflix.mediaclient",
tiktok: "com.zhiliaoapp.musically",
snapchat: "com.snapchat.android",
reddit: "com.reddit.frontpage",
discord: "com.discord",
slack: "com.Slack",
zoom: "us.zoom.videomeetings",
teams: "com.microsoft.teams",
outlook: "com.microsoft.office.outlook",
"google meet": "com.google.android.apps.tachyon",
meet: "com.google.android.apps.tachyon",
keep: "com.google.android.keep",
notes: "com.google.android.keep",
sheets: "com.google.android.apps.docs.editors.sheets",
docs: "com.google.android.apps.docs.editors.docs",
slides: "com.google.android.apps.docs.editors.slides",
translate: "com.google.android.apps.translate",
weather: "com.google.android.apps.weather",
news: "com.google.android.apps.magazines",
podcasts: "com.google.android.apps.podcasts",
fitbit: "com.fitbit.FitbitMobile",
uber: "com.ubercab",
lyft: "me.lyft.android",
amazon: "com.amazon.mShop.android.shopping",
ebay: "com.ebay.mobile",
linkedin: "com.linkedin.android",
pinterest: "com.pinterest",
twitch: "tv.twitch.android.app",
};
// ─── Goal Pattern Matching ───────────────────────────────────
interface PreprocessResult {
/** Whether the preprocessor handled the goal */
handled: boolean;
/** Command sent to device (if any) */
command?: Record<string, unknown>;
/** Updated goal text for the LLM (optional) */
refinedGoal?: string;
}
/**
* Build a label→packageName lookup from the device's installed apps.
* Keys are lowercase app labels (e.g. "youtube", "play store").
*/
function buildInstalledAppMap(apps: InstalledApp[]): Record<string, string> {
const map: Record<string, string> = {};
for (const app of apps) {
map[app.label.toLowerCase()] = app.packageName;
}
return map;
}
/**
* Try to find an app name at the start of a goal string.
* Checks device's installed apps first, then falls back to hardcoded map.
* Returns the package name and remaining text, or null.
*/
function matchAppName(
lower: string,
installedApps: InstalledApp[]
): { pkg: string; appName: string; rest: string } | null {
// Build combined lookup: installed apps take priority over fallback
const installedMap = buildInstalledAppMap(installedApps);
const combined: Record<string, string> = { ...FALLBACK_PACKAGES, ...installedMap };
// Try longest app names first (e.g. "google meet" before "meet")
const sorted = Object.keys(combined).sort((a, b) => b.length - a.length);
for (const name of sorted) {
// Match: "open <app> [app] and <rest>" or "open <app> [app]"
const pattern = new RegExp(
`^(?:open|launch|start|go to)\\s+(?:the\\s+)?${escapeRegex(name)}(?:\\s+app)?(?:\\s+(?:and|then)\\s+(.+))?$`
);
const m = lower.match(pattern);
if (m) {
return { pkg: combined[name], appName: name, rest: m[1]?.trim() ?? "" };
}
}
return null;
}
function escapeRegex(s: string): string {
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
/**
* Fetch installed apps from the device record in DB.
*/
async function fetchInstalledApps(persistentDeviceId: string): Promise<InstalledApp[]> {
try {
const rows = await db
.select({ info: deviceTable.deviceInfo })
.from(deviceTable)
.where(eq(deviceTable.id, persistentDeviceId))
.limit(1);
const info = rows[0]?.info as Record<string, unknown> | null;
return (info?.installedApps as InstalledApp[]) ?? [];
} catch {
return [];
}
}
/**
* Attempt to preprocess a goal before the LLM loop.
*
* Three outcomes:
* 1. { handled: true, refinedGoal: undefined } — goal fully handled (pure "open X")
* 2. { handled: true, refinedGoal: "..." } — app launched, LLM continues with refined goal
* 3. { handled: false } — preprocessor can't help, LLM gets full goal
*/
export async function preprocessGoal(
deviceId: string,
goal: string,
persistentDeviceId?: string
): Promise<PreprocessResult> {
const lower = goal.toLowerCase().trim();
// Fetch device's actual installed apps for accurate package resolution
const installedApps = persistentDeviceId ? await fetchInstalledApps(persistentDeviceId) : [];
// ── Pattern: "open <app> [and <remaining>]" ───────────────
const appMatch = matchAppName(lower, installedApps);
if (appMatch) {
try {
await sessions.sendCommand(deviceId, {
type: "launch",
packageName: appMatch.pkg,
});
if (appMatch.rest) {
// Compound goal: app launched, pass remaining instructions to LLM
console.log(`[Preprocessor] Launched ${appMatch.pkg}, refined goal: ${appMatch.rest}`);
return {
handled: true,
command: { type: "launch", packageName: appMatch.pkg },
refinedGoal: appMatch.rest,
};
}
// Pure "open X" — fully handled
console.log(`[Preprocessor] Launched ${appMatch.pkg} for goal: ${goal}`);
return { handled: true, command: { type: "launch", packageName: appMatch.pkg } };
} catch (err) {
console.warn(`[Preprocessor] Failed to launch ${appMatch.pkg}: ${err}`);
// Fall through to LLM
}
}
// ── Pattern: "open <url>" or "go to <url>" ────────────────
const urlMatch = lower.match(
/^(?:open|go to|visit|navigate to)\s+(https?:\/\/\S+)$/
);
if (urlMatch) {
const url = urlMatch[1];
try {
await sessions.sendCommand(deviceId, { type: "open_url", url });
console.log(`[Preprocessor] Opened URL: ${url}`);
return { handled: true, command: { type: "open_url", url } };
} catch (err) {
console.warn(`[Preprocessor] Failed to open URL: ${err}`);
}
}
return { handled: false };
}