Skip to content

Commit f3ed733

Browse files
feat: port AppMappings static table, AppMappingOverridesConfig, CommandPatternConfig (remote regex overrides), and normalizeText to JS
1 parent 096598f commit f3ed733

1 file changed

Lines changed: 229 additions & 2 deletions

File tree

index.html

Lines changed: 229 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1561,6 +1561,132 @@
15611561
_loadExecutionPolicyOverrides();
15621562
_loadErrorClassificationOverrides();
15631563
_loadCustomActionTypeOverrides();
1564+
_loadAppMappingOverrides();
1565+
_loadCommandPatternOverrides();
1566+
}
1567+
1568+
1569+
/* ════════════════════════════════════════════════════════
1570+
APP MAPPINGS – ported from AppMappings.kt (static table)
1571+
Provides a fast, native-call-free resolution for well-known apps.
1572+
Anything NOT in this table still falls back to the native
1573+
Bridge.openAppByNameOrPackage(), which fuzzy-matches against the
1574+
device's actually-installed apps via PackageManager (JS cannot
1575+
enumerate installed apps - that part must stay native).
1576+
════════════════════════════════════════════════════════ */
1577+
const _appDefinitions = [
1578+
{ name: "whatsapp", pkg: "com.whatsapp", variations: ["whats app","whats","wa"] },
1579+
{ name: "facebook", pkg: "com.facebook.katana", variations: ["fb","face book"] },
1580+
{ name: "messenger", pkg: "com.facebook.orca", variations: [] },
1581+
{ name: "instagram", pkg: "com.instagram.android", variations: ["insta","ig"] },
1582+
{ name: "youtube", pkg: "com.google.android.youtube", variations: ["yt","you tube"] },
1583+
{ name: "twitter", pkg: "com.twitter.android", variations: ["x","tweet"], aliases: ["x"] },
1584+
{ name: "telegram", pkg: "org.telegram.messenger", variations: ["tg"] },
1585+
{ name: "tiktok", pkg: "com.zhiliaoapp.musically", variations: ["tik tok"] },
1586+
{ name: "snapchat", pkg: "com.snapchat.android", variations: ["snap"] },
1587+
{ name: "netflix", pkg: "com.netflix.mediaclient", variations: ["nflx"] },
1588+
{ name: "spotify", pkg: "com.spotify.music", variations: ["music"] },
1589+
{ name: "chrome", pkg: "com.android.chrome", variations: ["google chrome","browser"] },
1590+
{ name: "gmail", pkg: "com.google.android.gm", variations: ["google mail","email","mail"] },
1591+
{ name: "maps", pkg: "com.google.android.apps.maps", variations: ["google maps","navigation"] },
1592+
{ name: "calendar", pkg: "com.google.android.calendar", variations: ["google calendar"] },
1593+
{ name: "photos", pkg: "com.google.android.apps.photos", variations: ["google photos","gallery"] },
1594+
{ name: "drive", pkg: "com.google.android.apps.docs", variations: ["google drive"] },
1595+
{ name: "docs", pkg: "com.google.android.apps.docs.editors.docs", variations: ["google docs","documents"] },
1596+
{ name: "sheets", pkg: "com.google.android.apps.docs.editors.sheets", variations: ["google sheets","spreadsheets"] },
1597+
{ name: "slides", pkg: "com.google.android.apps.docs.editors.slides", variations: ["google slides","presentations"] },
1598+
{ name: "keep", pkg: "com.google.android.keep", variations: ["google keep","notes"] },
1599+
{ name: "amazon", pkg: "com.amazon.mShop.android.shopping", variations: ["amazon shopping","shop"] },
1600+
{ name: "uber", pkg: "com.ubercab", variations: ["uber driver"] },
1601+
{ name: "lyft", pkg: "me.lyft.android", variations: ["ride"] },
1602+
{ name: "doordash", pkg: "com.dd.doordash", variations: ["food delivery"] },
1603+
{ name: "ubereats", pkg: "com.ubercab.eats", variations: ["uber eats","food"] },
1604+
{ name: "grubhub", pkg: "com.grubhub.android", variations: ["food delivery"] },
1605+
{ name: "instacart", pkg: "com.instacart.client", variations: ["grocery"] },
1606+
{ name: "zoom", pkg: "us.zoom.videomeetings", variations: ["zoom meeting"] },
1607+
{ name: "teams", pkg: "com.microsoft.teams", variations: ["microsoft teams"] },
1608+
{ name: "skype", pkg: "com.skype.raider", variations: ["microsoft skype"] },
1609+
{ name: "outlook", pkg: "com.microsoft.office.outlook", variations: ["microsoft outlook","email"] },
1610+
{ name: "word", pkg: "com.microsoft.office.word", variations: ["microsoft word","documents"] },
1611+
{ name: "excel", pkg: "com.microsoft.office.excel", variations: ["microsoft excel","spreadsheets"] },
1612+
{ name: "powerpoint", pkg: "com.microsoft.office.powerpoint", variations: ["microsoft powerpoint","presentations"] },
1613+
{ name: "onedrive", pkg: "com.microsoft.skydrive", variations: ["microsoft onedrive","cloud"] },
1614+
{ name: "onenote", pkg: "com.microsoft.office.onenote", variations: ["microsoft onenote","notes"] },
1615+
{ name: "linkedin", pkg: "com.linkedin.android", variations: ["linked in"] },
1616+
{ name: "pinterest", pkg: "com.pinterest", variations: ["pin"] },
1617+
{ name: "reddit", pkg: "com.reddit.frontpage", variations: ["reddit app"] },
1618+
{ name: "discord", pkg: "com.discord", variations: ["chat"] },
1619+
{ name: "slack", pkg: "com.Slack", variations: ["work chat"] },
1620+
{ name: "twitch", pkg: "tv.twitch.android.app", variations: ["streaming"] },
1621+
{ name: "venmo", pkg: "com.venmo", variations: ["payment"] },
1622+
{ name: "paypal", pkg: "com.paypal.android.p2pmobile", variations: ["payment"] },
1623+
{ name: "cashapp", pkg: "com.squareup.cash", variations: ["cash app","payment"] },
1624+
{ name: "zelle", pkg: "com.zellepay.zelle", variations: ["payment"] },
1625+
{ name: "robinhood", pkg: "com.robinhood.android", variations: ["stocks"] },
1626+
{ name: "coinbase", pkg: "com.coinbase.android", variations: ["crypto"] },
1627+
{ name: "binance", pkg: "com.binance.dev", variations: ["crypto"] },
1628+
{ name: "wechat", pkg: "com.tencent.mm", variations: ["we chat"] },
1629+
{ name: "line", pkg: "jp.naver.line.android", variations: ["line messenger"] },
1630+
{ name: "viber", pkg: "com.viber.voip", variations: ["viber messenger"] },
1631+
{ name: "signal", pkg: "org.thoughtcrime.securesms", variations: ["signal messenger"] },
1632+
{ name: "threema", pkg: "ch.threema.app", variations: ["threema messenger"] },
1633+
{ name: "settings", pkg: "com.android.settings", variations: ["system settings","preferences"] },
1634+
];
1635+
1636+
/* ── AppMappingOverridesConfig (remote JSON, same file/pattern) ─ */
1637+
let _appMappingOverrides = { matchThreshold: 70, apps: [] };
1638+
1639+
function _loadAppMappingOverrides() {
1640+
fetch('app-mappings-overrides.json', { cache: 'no-store' })
1641+
.then(r => r.ok ? r.text() : null)
1642+
.then(json => {
1643+
if (!json) return;
1644+
try {
1645+
const obj = JSON.parse(json);
1646+
const threshold = Number(obj.matchThreshold);
1647+
_appMappingOverrides.matchThreshold =
1648+
(!isNaN(threshold) && threshold >= 0 && threshold <= 100) ? threshold : 70;
1649+
_appMappingOverrides.apps = Array.isArray(obj.apps)
1650+
? obj.apps
1651+
.filter(a => a && typeof a.canonicalName === 'string' && a.canonicalName.trim()
1652+
&& typeof a.packageName === 'string' && a.packageName.trim())
1653+
.map(a => ({
1654+
name: a.canonicalName.trim(),
1655+
pkg: a.packageName.trim(),
1656+
variations: Array.isArray(a.variations) ? a.variations.filter(v => typeof v === 'string' && v.trim()) : [],
1657+
aliases: Array.isArray(a.aliasesForPackageLookup) ? a.aliasesForPackageLookup.filter(v => typeof v === 'string' && v.trim()) : [],
1658+
}))
1659+
: [];
1660+
} catch(e) { console.warn('app-mappings-overrides.json parse error', e); }
1661+
})
1662+
.catch(() => { /* optional file */ });
1663+
}
1664+
1665+
/**
1666+
* Fast, native-call-free lookup: normalized name/variation/alias → package name.
1667+
* Overrides win on a canonicalName clash (merged on top of built-ins), mirroring
1668+
* AppMappings.kt's `builtIn + overrides` behavior.
1669+
*/
1670+
function _resolveKnownAppPackage(rawName) {
1671+
const normalize = (s) => (s || '').toLowerCase().trim().replace(/[^a-z0-9]/g, '');
1672+
const norm = normalize(rawName);
1673+
if (!norm) return null;
1674+
if (norm.includes('.')) return norm; // already looks like a package name
1675+
1676+
const table = new Map();
1677+
for (const def of _appDefinitions) {
1678+
table.set(normalize(def.name), def.pkg);
1679+
(def.aliases || []).forEach(a => table.set(normalize(a), def.pkg));
1680+
(def.variations || []).forEach(v => table.set(normalize(v), def.pkg));
1681+
}
1682+
// Overrides win on clash
1683+
for (const def of _appMappingOverrides.apps) {
1684+
table.set(normalize(def.name), def.pkg);
1685+
(def.aliases || []).forEach(a => table.set(normalize(a), def.pkg));
1686+
(def.variations || []).forEach(v => table.set(normalize(v), def.pkg));
1687+
}
1688+
1689+
return table.get(norm) || null;
15641690
}
15651691

15661692
/* ── PENDING STATE ───────────────────────────────────────────── */
@@ -1975,7 +2101,86 @@
19752101
COMMAND PARSER
19762102
Ported from CommandParser.kt – parses AI responses for action commands.
19772103
════════════════════════════════════════════════════════ */
1978-
function parseCommandsFromText(text) {
2104+
/**
2105+
* Mirrors CommandParser.kt's normalizeText(): collapses whitespace runs and
2106+
* normalizes line breaks before pattern matching, so multi-line / oddly
2107+
* formatted AI responses are recognized the same way as the native parser was.
2108+
*/
2109+
function _normalizeCommandText(text) {
2110+
return text.replace(/\s+/g, ' ').replace(/\r\n|\r/g, '\n').trim();
2111+
}
2112+
2113+
/* ── CommandPatternConfig (remote regex overrides for EXISTING command types) ── */
2114+
// Maps a CommandType name (as used in command-patterns.json) to a builder function
2115+
// compatible with the JS command shape used by _executeSingleCommand/_cmdToString.
2116+
const _COMMAND_TYPE_BUILDERS = {
2117+
CLICK_BUTTON: (m) => ({ type: 'CLICK', text: m[1] }),
2118+
LONG_CLICK_BUTTON: (m) => ({ type: 'LONG_CLICK', text: m[1] }),
2119+
TAP_COORDINATES: (m) => ({ type: 'TAP', x: m[1], y: m[2] }),
2120+
TAKE_SCREENSHOT: () => ({ type: 'SCREENSHOT' }),
2121+
COMPLETED: () => ({ type: 'COMPLETED' }),
2122+
WAIT: (m) => ({ type: 'WAIT', seconds: parseInt(m[1], 10) }),
2123+
PRESS_HOME: () => ({ type: 'HOME' }),
2124+
PRESS_BACK: () => ({ type: 'BACK' }),
2125+
SHOW_RECENT_APPS: () => ({ type: 'RECENT_APPS' }),
2126+
SCROLL_DOWN: () => ({ type: 'SCROLL_DOWN' }),
2127+
SCROLL_UP: () => ({ type: 'SCROLL_UP' }),
2128+
SCROLL_LEFT: () => ({ type: 'SCROLL_LEFT' }),
2129+
SCROLL_RIGHT: () => ({ type: 'SCROLL_RIGHT' }),
2130+
SCROLL_DOWN_FROM_COORDINATES: (m) => ({ type: 'SCROLL_DOWN_C', x: m[1], y: m[2], dist: m[3], dur: parseInt(m[4], 10) }),
2131+
SCROLL_UP_FROM_COORDINATES: (m) => ({ type: 'SCROLL_UP_C', x: m[1], y: m[2], dist: m[3], dur: parseInt(m[4], 10) }),
2132+
SCROLL_LEFT_FROM_COORDINATES: (m) => ({ type: 'SCROLL_LEFT_C', x: m[1], y: m[2], dist: m[3], dur: parseInt(m[4], 10) }),
2133+
SCROLL_RIGHT_FROM_COORDINATES: (m) => ({ type: 'SCROLL_RIGHT_C', x: m[1], y: m[2], dist: m[3], dur: parseInt(m[4], 10) }),
2134+
OPEN_APP: (m) => ({ type: 'OPEN_APP', app: m[1] }),
2135+
WRITE_TEXT: (m) => ({ type: 'WRITE_TEXT', text: m[1] }),
2136+
USE_HIGH_REASONING_MODEL: () => null, // model switching ignored in JS, same as built-in patterns
2137+
USE_LOW_REASONING_MODEL: () => null,
2138+
PRESS_ENTER_KEY: () => ({ type: 'ENTER' }),
2139+
RETRIEVE: (m) => ({ type: 'RETRIEVE', title: m[1] }),
2140+
TERMUX_COMMAND: (m) => ({ type: 'TERMUX', command: m[2] ? m[2].replace(/\\(['"])/g, '$1') : m[1] }),
2141+
PINCH_GESTURE: (m) => ({ type: 'PINCH', cx: m[1], cy: m[2], sd: m[3], ed: m[4], dur: parseInt(m[5], 10) }),
2142+
COPY_TO_CLIPBOARD: (m) => ({ type: 'COPY_CLIP', text: m[1] }),
2143+
};
2144+
2145+
// Remote pattern overrides installed via command-patterns.json - same file/contract as the
2146+
// native CommandPatternConfig. Each entry attaches a new regex to an EXISTING command type's
2147+
// builder above; it can never introduce new logic, matching the native safety boundary.
2148+
let _remoteCommandPatterns = []; // [{id, commandType, regex}]
2149+
2150+
function _loadCommandPatternOverrides() {
2151+
fetch('command-patterns.json', { cache: 'no-store' })
2152+
.then(r => r.ok ? r.text() : null)
2153+
.then(json => {
2154+
if (!json) return;
2155+
try {
2156+
const arr = JSON.parse(json);
2157+
const parsed = [];
2158+
if (Array.isArray(arr)) {
2159+
for (let i = 0; i < arr.length; i++) {
2160+
const entry = arr[i];
2161+
if (!entry || typeof entry.regex !== 'string' || !entry.regex.trim()) continue;
2162+
const typeName = entry.commandType;
2163+
if (!_COMMAND_TYPE_BUILDERS[typeName]) {
2164+
console.warn('Skipping command-patterns.json override: unknown commandType', typeName);
2165+
continue;
2166+
}
2167+
try {
2168+
parsed.push({
2169+
id: entry.id || ('remote_' + i),
2170+
commandType: typeName,
2171+
regex: new RegExp(entry.regex, 'gi'),
2172+
});
2173+
} catch(e) { console.warn('Invalid regex in command-patterns.json for', typeName, e); }
2174+
}
2175+
}
2176+
_remoteCommandPatterns = parsed;
2177+
} catch(e) { console.warn('command-patterns.json parse error', e); }
2178+
})
2179+
.catch(() => { /* optional file */ });
2180+
}
2181+
2182+
function parseCommandsFromText(rawText) {
2183+
const text = _normalizeCommandText(rawText);
19792184
const commands = [];
19802185
const seenSingletons = new Set();
19812186

@@ -2078,6 +2283,21 @@
20782283
}
20792284
}
20802285

2286+
// Remote pattern overrides (command-patterns.json) - extra regex syntaxes for EXISTING
2287+
// command types, using the same builder functions as the built-in patterns above.
2288+
for (const override of _remoteCommandPatterns) {
2289+
const builder = _COMMAND_TYPE_BUILDERS[override.commandType];
2290+
if (!builder) continue;
2291+
override.regex.lastIndex = 0;
2292+
let m;
2293+
while ((m = override.regex.exec(text)) !== null) {
2294+
const cmd = builder(m);
2295+
if (cmd === null) continue;
2296+
const single = (override.commandType === 'TAKE_SCREENSHOT' || override.commandType === 'COMPLETED');
2297+
allMatches.push({ start: m.index, end: m.index + m[0].length, cmd, type: override.commandType, single });
2298+
}
2299+
}
2300+
20812301
// Sort by position in text
20822302
allMatches.sort((a, b) => a.start - b.start);
20832303

@@ -2156,7 +2376,14 @@
21562376
case 'SCROLL_UP_C': Bridge.scrollUpFromCoordinates(cmd.x, cmd.y, cmd.dist, cmd.dur); break;
21572377
case 'SCROLL_LEFT_C': Bridge.scrollLeftFromCoordinates(cmd.x, cmd.y, cmd.dist, cmd.dur); break;
21582378
case 'SCROLL_RIGHT_C': Bridge.scrollRightFromCoordinates(cmd.x, cmd.y, cmd.dist, cmd.dur); break;
2159-
case 'OPEN_APP': Bridge.openAppByNameOrPackage(cmd.app); break;
2379+
case 'OPEN_APP': {
2380+
// Fast path: resolve well-known apps (AppMappings.kt table + remote overrides) without
2381+
// a native PackageManager round-trip. Anything unrecognized still falls back to the
2382+
// native fuzzy-match against actually-installed apps (JS cannot enumerate those).
2383+
const resolvedPkg = _resolveKnownAppPackage(cmd.app);
2384+
Bridge.openAppByNameOrPackage(resolvedPkg || cmd.app);
2385+
break;
2386+
}
21602387
case 'PINCH': Bridge.pinchGesture(cmd.cx, cmd.cy, cmd.sd, cmd.ed, cmd.dur); break;
21612388
case 'COPY_CLIP': Bridge.copyToClipboard(cmd.text); break;
21622389
case 'TOAST': Bridge.showToast(cmd.msg, false); break;

0 commit comments

Comments
 (0)