@@ -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 * ( < (?: u s e s - p e r m i s s i o n | u s e s - f e a t u r e ) \b ) / ,
1428+ "\n </application>\n\n $1" ,
1429+ ) ;
1430+ }
1431+
14121432function 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+
43264464function 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 } ` ) ;
0 commit comments