Skip to content

Commit 3aa5cf3

Browse files
author
Shaw
committed
ci: stabilize mobile release builds
1 parent e01090b commit 3aa5cf3

5 files changed

Lines changed: 160 additions & 9 deletions

File tree

packages/app-core/scripts/run-mobile-build.mjs

Lines changed: 150 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1409,6 +1409,26 @@ function removeApplicationComponentClassBlock(xml, className) {
14091409
return xml.replace(pairedRe, "\n").replace(selfClosingRe, "\n");
14101410
}
14111411

1412+
function removeXmlCommentsContaining(xml, markers) {
1413+
let patched = xml;
1414+
for (const marker of markers) {
1415+
const escapedMarker = escapeRegExp(marker);
1416+
patched = patched.replace(
1417+
new RegExp(`\\n?\\s*<!--[\\s\\S]*?${escapedMarker}[\\s\\S]*?-->\\s*`, "g"),
1418+
"\n",
1419+
);
1420+
}
1421+
return patched;
1422+
}
1423+
1424+
function ensureManifestApplicationClosedBeforeTopLevelEntries(xml) {
1425+
if (xml.includes("</application>")) return xml;
1426+
return xml.replace(
1427+
/\n\s*(<(?:uses-permission|uses-feature)\b)/,
1428+
"\n </application>\n\n $1",
1429+
);
1430+
}
1431+
14121432
function removeStaleAndroidJavaSourceRoots(dstJava) {
14131433
const candidates = [
14141434
"ai.elizaos.app",
@@ -4323,6 +4343,124 @@ public class AgentPlugin extends Plugin {
43234343
`;
43244344
}
43254345

4346+
function cloudSafeTasksWorkerJava(androidPackage) {
4347+
return `package ${androidPackage};
4348+
4349+
import android.content.Context;
4350+
import android.content.SharedPreferences;
4351+
import android.util.Log;
4352+
4353+
import androidx.annotation.NonNull;
4354+
import androidx.work.Worker;
4355+
import androidx.work.WorkerParameters;
4356+
4357+
import java.io.IOException;
4358+
import java.io.OutputStream;
4359+
import java.net.HttpURLConnection;
4360+
import java.net.URL;
4361+
import java.nio.charset.StandardCharsets;
4362+
4363+
import org.json.JSONException;
4364+
import org.json.JSONObject;
4365+
4366+
public class ElizaTasksWorker extends Worker {
4367+
4368+
private static final String TAG = "ElizaTasksWorker";
4369+
private static final String CAPACITOR_PREFS_GROUP = "CapacitorStorage";
4370+
private static final String KEY_DEVICE_SECRET = "eliza:device-secret";
4371+
private static final String KEY_AGENT_BASE = "eliza:agent-base";
4372+
private static final String WAKE_PATH = "/api/internal/wake";
4373+
private static final int CONNECT_TIMEOUT_MS = 5_000;
4374+
private static final int READ_TIMEOUT_MS = 25_000;
4375+
private static final long DEADLINE_MS = 25_000L;
4376+
4377+
public ElizaTasksWorker(@NonNull Context context, @NonNull WorkerParameters params) {
4378+
super(context, params);
4379+
}
4380+
4381+
@NonNull
4382+
@Override
4383+
public Result doWork() {
4384+
Context context = getApplicationContext();
4385+
SharedPreferences prefs = context.getSharedPreferences(
4386+
CAPACITOR_PREFS_GROUP,
4387+
Context.MODE_PRIVATE
4388+
);
4389+
4390+
String deviceSecret = prefs.getString(KEY_DEVICE_SECRET, null);
4391+
String agentBase = prefs.getString(KEY_AGENT_BASE, null);
4392+
if (deviceSecret == null || deviceSecret.isEmpty() || agentBase == null || agentBase.isEmpty()) {
4393+
Log.w(TAG, "cloud wake credentials are not provisioned; skipping");
4394+
return Result.failure();
4395+
}
4396+
4397+
String body;
4398+
try {
4399+
JSONObject json = new JSONObject();
4400+
json.put("kind", "refresh");
4401+
json.put("deadlineMs", System.currentTimeMillis() + DEADLINE_MS);
4402+
body = json.toString();
4403+
} catch (JSONException e) {
4404+
Log.e(TAG, "failed to serialize wake body", e);
4405+
return Result.failure();
4406+
}
4407+
4408+
String endpoint = trimTrailingSlash(agentBase) + WAKE_PATH;
4409+
HttpURLConnection conn = null;
4410+
try {
4411+
URL url = new URL(endpoint);
4412+
if (!"https".equalsIgnoreCase(url.getProtocol())) {
4413+
Log.w(TAG, "cloud wake requires https agent base");
4414+
return Result.failure();
4415+
}
4416+
conn = (HttpURLConnection) url.openConnection();
4417+
conn.setRequestMethod("POST");
4418+
conn.setConnectTimeout(CONNECT_TIMEOUT_MS);
4419+
conn.setReadTimeout(READ_TIMEOUT_MS);
4420+
conn.setDoOutput(true);
4421+
conn.setUseCaches(false);
4422+
conn.setRequestProperty("Content-Type", "application/json");
4423+
conn.setRequestProperty("Authorization", "Bearer " + deviceSecret);
4424+
4425+
try (OutputStream out = conn.getOutputStream()) {
4426+
out.write(body.getBytes(StandardCharsets.UTF_8));
4427+
out.flush();
4428+
}
4429+
4430+
int status = conn.getResponseCode();
4431+
if (status >= 200 && status < 300) {
4432+
Log.i(TAG, "cloud wake delivered ok status=" + status);
4433+
return Result.success();
4434+
}
4435+
if (status == HttpURLConnection.HTTP_UNAUTHORIZED
4436+
|| (status >= 400 && status < 500 && status != HttpURLConnection.HTTP_CLIENT_TIMEOUT)) {
4437+
Log.w(TAG, "cloud wake rejected with permanent status=" + status + "; not retrying");
4438+
return Result.failure();
4439+
}
4440+
Log.w(TAG, "cloud wake transient failure status=" + status + "; will retry");
4441+
return Result.retry();
4442+
} catch (IOException e) {
4443+
Log.w(TAG, "cloud wake network failure; will retry", e);
4444+
return Result.retry();
4445+
} finally {
4446+
if (conn != null) {
4447+
conn.disconnect();
4448+
}
4449+
}
4450+
}
4451+
4452+
private static String trimTrailingSlash(String value) {
4453+
if (value == null) return "";
4454+
int end = value.length();
4455+
while (end > 0 && value.charAt(end - 1) == '/') {
4456+
end--;
4457+
}
4458+
return value.substring(0, end);
4459+
}
4460+
}
4461+
`;
4462+
}
4463+
43264464
function rewriteCloudJavaSources(javaRoots, androidPackage) {
43274465
let touched = 0;
43284466
for (const root of javaRoots) {
@@ -4337,14 +4475,23 @@ function rewriteCloudJavaSources(javaRoots, androidPackage) {
43374475
touched += 1;
43384476
}
43394477
const agentPlugin = path.join(root, "AgentPlugin.java");
4340-
if (fs.existsSync(agentPlugin)) {
4478+
if (fs.existsSync(mainActivity) || fs.existsSync(agentPlugin)) {
43414479
fs.writeFileSync(
43424480
agentPlugin,
43434481
cloudSafeAgentPluginJava(androidPackage),
43444482
"utf8",
43454483
);
43464484
touched += 1;
43474485
}
4486+
const tasksWorker = path.join(root, "ElizaTasksWorker.java");
4487+
if (fs.existsSync(tasksWorker)) {
4488+
fs.writeFileSync(
4489+
tasksWorker,
4490+
cloudSafeTasksWorkerJava(androidPackage),
4491+
"utf8",
4492+
);
4493+
touched += 1;
4494+
}
43484495
const nativeBridge = path.join(root, "ElizaNativeBridge.java");
43494496
if (fs.existsSync(nativeBridge)) {
43504497
fs.rmSync(nativeBridge);
@@ -4715,6 +4862,8 @@ function stripAndroidForCloud() {
47154862
);
47164863
xml = removeApplicationComponentClassBlock(xml, component);
47174864
}
4865+
xml = removeXmlCommentsContaining(xml, ANDROID_CLOUD_STRIPPED_COMPONENTS);
4866+
xml = ensureManifestApplicationClosedBeforeTopLevelEntries(xml);
47184867

47194868
for (const perm of ANDROID_CLOUD_STRIPPED_PERMISSIONS) {
47204869
const escaped = escapeRegExp(`android.permission.${perm}`);

packages/app-core/src/platform/ios-runtime-bridge.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
// Preferences so the simulator host can read it. The probe runs once per
77
// page load and is a no-op when the flag is absent.
88
import { Preferences } from "@capacitor/preferences";
9-
import { primeIosFullBunRuntime } from "@elizaos/ui";
9+
import { primeIosFullBunRuntime } from "../api/ios-local-agent-transport";
1010

1111
export const IOS_FULL_BUN_SMOKE_REQUEST_KEY =
1212
"eliza:ios-full-bun-smoke:request";

packages/app/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@
5050
"cap:sync:android": "node scripts/ensure-capacitor-platform.mjs android && capacitor sync android",
5151
"cap:open:ios": "node scripts/ensure-capacitor-platform.mjs ios && capacitor open ios",
5252
"cap:open:android": "node scripts/ensure-capacitor-platform.mjs android && capacitor open android",
53-
"build:web": "bun --bun vite build",
53+
"build:web": "node ../../node_modules/@elizaos/vitest-vite/bin/vite.js build",
5454
"predev": "node ../shared-brand/scripts/sync-to-public.mjs ./public",
5555
"prebuild": "node ../shared-brand/scripts/sync-to-public.mjs ./public"
5656
},

packages/app/src/main.tsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,13 @@ import {
1313
DesktopTrayRuntime,
1414
DetachedShellRoot,
1515
} from "@elizaos/app-core";
16+
import {
17+
type IosLocalAgentNativeRequestOptions,
18+
type IosLocalAgentNativeRequestResult,
19+
installIosLocalAgentFetchBridge,
20+
installIosLocalAgentNativeRequestBridge,
21+
primeIosFullBunRuntime,
22+
} from "@elizaos/app-core/api/ios-local-agent-transport";
1623
import { Agent } from "@elizaos/capacitor-agent";
1724
import { Desktop } from "@elizaos/capacitor-desktop";
1825
import type { DeviceBridgeClient } from "@elizaos/capacitor-llama";
@@ -56,15 +63,11 @@ import {
5663
getBootConfig,
5764
getWindowNavigationPath,
5865
IOS_LOCAL_AGENT_IPC_BASE,
59-
type IosLocalAgentNativeRequestOptions,
60-
type IosLocalAgentNativeRequestResult,
6166
initializeCapacitorBridge,
6267
initializeStorageBridge,
6368
installAndroidNativeAgentFetchBridge,
6469
installDesktopPermissionsClientPatch,
6570
installForceFreshOnboardingClientPatch,
66-
installIosLocalAgentFetchBridge,
67-
installIosLocalAgentNativeRequestBridge,
6871
installLocalProviderCloudPreferencePatch,
6972
isAppWindowRoute,
7073
isDetachedWindowShell,
@@ -79,7 +82,6 @@ import {
7982
type NetworkStatusChangeDetail,
8083
normalizeMobileRuntimeMode,
8184
preSeedAndroidLocalRuntimeIfFresh,
82-
primeIosFullBunRuntime,
8385
resolveWindowShellRoute,
8486
routeOnboardingDeepLink,
8587
SHARE_TARGET_EVENT,

packages/ui/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// imported by Node-side plugin loaders without forcing a CSS evaluation
33
// (Node refuses ".css" extensions). Renderers must opt-in explicitly.
44

5+
export { resolveAppBranding } from "@elizaos/shared";
56
export * from "./App";
67
export type {
78
AppLaunchDiagnostic,
@@ -282,7 +283,6 @@ export {
282283
getByPath,
283284
interpolateString,
284285
parseAllowedHostEnv,
285-
resolveAppBranding,
286286
resolveCharacterCatalog,
287287
resolveDynamic,
288288
resolveFields,

0 commit comments

Comments
 (0)