neoagent 2.3.1-beta.88 → 2.3.1-beta.89

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -32,7 +32,6 @@
32
32
 
33
33
  <title>NeoAgent</title>
34
34
  <link rel="manifest" href="manifest.json">
35
- <script src="https://cdn.mxpnl.com/libs/mixpanel-2-latest.min.js"></script>
36
35
  </head>
37
36
  <body>
38
37
  <script src="flutter_bootstrap.js" async></script>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neoagent",
3
- "version": "2.3.1-beta.88",
3
+ "version": "2.3.1-beta.89",
4
4
  "description": "Proactive personal AI agent with no limits",
5
5
  "license": "MIT",
6
6
  "main": "server/index.js",
@@ -1 +1 @@
1
- fc11155a7630763ef2acb1831b5bf274
1
+ 565ea9475bb1a5c2efca6dc427d87b38
@@ -37,6 +37,6 @@ _flutter.buildConfig = {"engineRevision":"42d3d75a56efe1a2e9902f52dc8006099c45d9
37
37
 
38
38
  _flutter.loader.load({
39
39
  serviceWorkerSettings: {
40
- serviceWorkerVersion: "1255209520" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */
40
+ serviceWorkerVersion: "2533429045" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */
41
41
  }
42
42
  });
@@ -32,7 +32,6 @@
32
32
 
33
33
  <title>NeoAgent</title>
34
34
  <link rel="manifest" href="manifest.json">
35
- <script src="https://cdn.mxpnl.com/libs/mixpanel-2-latest.min.js"></script>
36
35
  </head>
37
36
  <body>
38
37
  <script src="flutter_bootstrap.js" async></script>
@@ -128078,7 +128078,7 @@ r===$&&A.b()
128078
128078
  o.push(A.ih(p,A.iW(!1,new A.a3(B.tI,A.dT(new A.cI(B.hb,new A.a5t(r,p),p),p,p),p),!1,B.I,!0),p,p,0,0,0,p))}r=!1
128079
128079
  if(!s.ay)if(!s.ch){r=s.e
128080
128080
  r===$&&A.b()
128081
- r=B.b.t("mp5m4j1a-50b102d").length!==0&&r.b}if(r){r=s.d
128081
+ r=B.b.t("mp5t17sf-b851db3").length!==0&&r.b}if(r){r=s.d
128082
128082
  r===$&&A.b()
128083
128083
  r=r.V&&!r.a0?84:0
128084
128084
  q=s.e
@@ -132854,7 +132854,7 @@ $S:273}
132854
132854
  A.Y8.prototype={}
132855
132855
  A.R7.prototype={
132856
132856
  mJ(a){var s=this
132857
- if(B.b.t("mp5m4j1a-50b102d").length===0||s.a!=null)return
132857
+ if(B.b.t("mp5t17sf-b851db3").length===0||s.a!=null)return
132858
132858
  s.zY()
132859
132859
  s.a=A.pR(B.PA,new A.b4b(s))},
132860
132860
  zY(){var s=0,r=A.l(t.H),q,p=2,o=[],n=this,m,l,k,j,i,h,g,f
@@ -132872,7 +132872,7 @@ if(!t.f.b(k)){s=1
132872
132872
  break}i=J.Z(k,"buildId")
132873
132873
  h=i==null?null:B.b.t(J.r(i))
132874
132874
  j=h==null?"":h
132875
- if(J.bk(j)===0||J.d(j,"mp5m4j1a-50b102d")){s=1
132875
+ if(J.bk(j)===0||J.d(j,"mp5t17sf-b851db3")){s=1
132876
132876
  break}n.b=!0
132877
132877
  n.J()
132878
132878
  p=2
@@ -132889,7 +132889,7 @@ case 2:return A.i(o.at(-1),r)}})
132889
132889
  return A.k($async$zY,r)},
132890
132890
  v0(){var s=0,r=A.l(t.H),q,p=2,o=[],n=this,m,l,k,j,i,h,g,f,e,d,c,b,a,a0,a1
132891
132891
  var $async$v0=A.h(function(a2,a3){if(a2===1){o.push(a3)
132892
- s=p}for(;;)switch(s){case 0:if(B.b.t("mp5m4j1a-50b102d").length===0||n.c){s=1
132892
+ s=p}for(;;)switch(s){case 0:if(B.b.t("mp5t17sf-b851db3").length===0||n.c){s=1
132893
132893
  break}n.c=!0
132894
132894
  n.J()
132895
132895
  p=4
@@ -53,9 +53,16 @@ for (const dir of [ANDROID_ROOT, SDK_ROOT, EMULATOR_HOME, ARTIFACTS_DIR, SCREENS
53
53
 
54
54
  function ensureEmulatorAdvancedFeaturesFile() {
55
55
  try {
56
- if (fs.existsSync(EMULATOR_ADVANCED_FEATURES_FILE)) {
57
- fs.rmSync(EMULATOR_ADVANCED_FEATURES_FILE, { force: true });
58
- }
56
+ fs.mkdirSync(path.dirname(EMULATOR_ADVANCED_FEATURES_FILE), { recursive: true });
57
+ fs.writeFileSync(
58
+ EMULATOR_ADVANCED_FEATURES_FILE,
59
+ [
60
+ 'QuickbootFileBacked=off',
61
+ 'QuickbootSupport=off',
62
+ '',
63
+ ].join('\n'),
64
+ 'utf8',
65
+ );
59
66
  } catch {}
60
67
  }
61
68
 
@@ -200,6 +207,15 @@ function tailFile(filePath, maxLines = 40) {
200
207
  }
201
208
  }
202
209
 
210
+ function isLikelyPng(buffer) {
211
+ return Buffer.isBuffer(buffer)
212
+ && buffer.length > 24
213
+ && buffer[0] === 0x89
214
+ && buffer[1] === 0x50
215
+ && buffer[2] === 0x4e
216
+ && buffer[3] === 0x47;
217
+ }
218
+
203
219
  function commandExists(command) {
204
220
  const probe = spawnSync('bash', ['-lc', `command -v "${command}"`], { encoding: 'utf8' });
205
221
  return probe.status === 0;
@@ -271,6 +287,9 @@ function emulatorGpuMode() {
271
287
  if (process.platform === 'darwin' && process.arch === 'arm64') {
272
288
  return 'swiftshader_indirect';
273
289
  }
290
+ if (process.platform === 'linux' && process.arch === 'arm64') {
291
+ return 'swiftshader_indirect';
292
+ }
274
293
  return 'auto';
275
294
  }
276
295
 
@@ -292,6 +311,8 @@ function isRecoverableEmulatorStartError(message) {
292
311
  return (
293
312
  value.includes('failed to restore previous context') ||
294
313
  value.includes('emulator exited before boot completed') ||
314
+ value.includes('android framework did not become ready') ||
315
+ value.includes('package manager service did not become ready') ||
295
316
  value.includes('failed to process .ini file') ||
296
317
  value.includes('error while loading state for instance')
297
318
  );
@@ -437,10 +458,10 @@ function hostAndroidSdkRoot() {
437
458
  return null;
438
459
  }
439
460
 
440
- const SHARED_ANDROID_SDK_ROOT = hostAndroidSdkRoot();
461
+ const SHARED_ANDROID_SDK_ROOT = null;
441
462
 
442
463
  function activeAndroidSdkRoot() {
443
- return SHARED_ANDROID_SDK_ROOT || SDK_ROOT;
464
+ return SDK_ROOT;
444
465
  }
445
466
 
446
467
  function sharedAndroidSdkReady() {
@@ -458,20 +479,6 @@ function adbBinary() {
458
479
  if (process.env.ANDROID_ADB_PATH) {
459
480
  return process.env.ANDROID_ADB_PATH;
460
481
  }
461
- if (SHARED_ANDROID_SDK_ROOT) {
462
- const sharedAdb = path.join(SHARED_ANDROID_SDK_ROOT, 'platform-tools', process.platform === 'win32' ? 'adb.exe' : 'adb');
463
- if (isExecutable(sharedAdb)) {
464
- return sharedAdb;
465
- }
466
- }
467
- const hostAdb = hostAdbBinary();
468
- if (hostAdb) {
469
- return hostAdb;
470
- }
471
- const distroAdb = systemAdbBinary();
472
- if (process.platform === 'linux' && process.arch === 'arm64' && distroAdb && isExecutable(distroAdb)) {
473
- return distroAdb;
474
- }
475
482
  return path.join(SDK_ROOT, 'platform-tools', process.platform === 'win32' ? 'adb.exe' : 'adb');
476
483
  }
477
484
 
@@ -819,17 +826,8 @@ async function installPlatformToolsArchive(metadata) {
819
826
  }
820
827
 
821
828
  function shouldInstallPlatformToolsArchive() {
822
- if (SHARED_ANDROID_SDK_ROOT) {
823
- return false;
824
- }
825
- if (hostAdbBinary()) {
826
- return false;
827
- }
828
- const distroAdb = systemAdbBinary();
829
- if (process.platform === 'linux' && process.arch === 'arm64' && distroAdb && isExecutable(distroAdb)) {
830
- return false;
831
- }
832
- return true;
829
+ const localAdb = path.join(SDK_ROOT, 'platform-tools', process.platform === 'win32' ? 'adb.exe' : 'adb');
830
+ return !isExecutable(localAdb);
833
831
  }
834
832
 
835
833
  async function installSystemImageArchive(metadata, packageName) {
@@ -947,27 +945,30 @@ function parseInstalledSystemImages() {
947
945
  return parseSystemImageCandidates(matches);
948
946
  }
949
947
 
948
+ function chooseStableRuntimeSystemImage(candidates, currentPackage) {
949
+ const normalizedCurrent = String(currentPackage || '').trim();
950
+ const pool = Array.isArray(candidates)
951
+ ? candidates.filter((item) => item && item.packageName && isValidInstalledSystemImage(item.packageName))
952
+ : [];
953
+ if (pool.length === 0) return null;
954
+ const ranked = rankSystemImagePool(pool);
955
+ const recommended = ranked.find((item) =>
956
+ item.arch === 'arm64-v8a'
957
+ && item.stable
958
+ && item.apiLevel >= 33
959
+ ) || ranked[0];
960
+ if (!recommended) return null;
961
+ if (normalizedCurrent && recommended.packageName === normalizedCurrent) {
962
+ return null;
963
+ }
964
+ return recommended;
965
+ }
966
+
950
967
  function rankSystemImagePool(pool) {
951
968
  const preferredMatches = pool.filter((candidate) => candidate.tagScore > 0);
952
969
  const rankedPool = preferredMatches.length > 0 ? preferredMatches : pool;
953
- const appleSiliconPreferredApis = process.platform === 'darwin' && process.arch === 'arm64'
954
- ? [30, 31, 35, 36]
955
- : [];
956
- const preferredApiRank = (apiLevel) => appleSiliconPreferredApis.indexOf(Number(apiLevel || 0));
957
970
 
958
971
  rankedPool.sort((a, b) =>
959
- (
960
- (() => {
961
- const aRank = preferredApiRank(a.apiLevel);
962
- const bRank = preferredApiRank(b.apiLevel);
963
- if (aRank !== -1 || bRank !== -1) {
964
- if (aRank === -1) return 1;
965
- if (bRank === -1) return -1;
966
- return aRank - bRank;
967
- }
968
- return 0;
969
- })()
970
- ) ||
971
972
  Number(b.stable) - Number(a.stable) ||
972
973
  b.tagScore - a.tagScore ||
973
974
  b.apiLevel - a.apiLevel ||
@@ -1080,6 +1081,18 @@ function systemImagePackageToRelativeDir(packageName) {
1080
1081
  return `${parts.join('/')}/`;
1081
1082
  }
1082
1083
 
1084
+ function isValidInstalledSystemImage(packageName) {
1085
+ const relativeDir = systemImagePackageToRelativeDir(packageName);
1086
+ if (!relativeDir) return false;
1087
+ const root = path.join(activeAndroidSdkRoot(), relativeDir);
1088
+ const required = [
1089
+ path.join(root, 'package.xml'),
1090
+ path.join(root, 'system.img'),
1091
+ path.join(root, 'userdata.img'),
1092
+ ];
1093
+ return required.every((filePath) => fs.existsSync(filePath));
1094
+ }
1095
+
1083
1096
  function systemImagePackageToAbi(packageName) {
1084
1097
  const parts = String(packageName || '').split(';').filter(Boolean);
1085
1098
  if (parts.length !== 4 || parts[0] !== 'system-images') {
@@ -1387,19 +1400,35 @@ class AndroidController {
1387
1400
  chooseLatestSystemImage(available);
1388
1401
  const selectedImage = rankSystemImagePool([preferredInstalled, preferredAvailable].filter(Boolean))[0] || preferredInstalled || preferredAvailable;
1389
1402
  const stateApiLevel = Number(state.apiLevel || 0) || 0;
1390
-
1391
- if (!shouldForceSdkRefresh() && sharedAndroidSdkReady() && selectedImage) {
1403
+ const legacyLinuxArm64Image =
1404
+ process.platform === 'linux'
1405
+ && process.arch === 'arm64'
1406
+ && /system-images;android-30;default;arm64-v8a/i.test(String(state.systemImage || ''));
1407
+ const migrationTargetImage =
1408
+ process.platform === 'linux' && process.arch === 'arm64'
1409
+ ? (
1410
+ rankSystemImagePool(
1411
+ [preferredAvailable, preferredInstalled]
1412
+ .filter(Boolean)
1413
+ .filter((image) => image.arch === 'arm64-v8a' && image.stable && image.apiLevel >= 33)
1414
+ )[0] || null
1415
+ )
1416
+ : null;
1417
+ const effectiveSelectedImage = migrationTargetImage || selectedImage;
1418
+ const selectedImageInvalid = effectiveSelectedImage?.packageName && !isValidInstalledSystemImage(effectiveSelectedImage.packageName);
1419
+
1420
+ if (!shouldForceSdkRefresh() && !legacyLinuxArm64Image && !selectedImageInvalid && sharedAndroidSdkReady() && effectiveSelectedImage) {
1392
1421
  const stateAligned =
1393
- selectedImage.packageName === state.systemImage &&
1394
- selectedImage.apiLevel === stateApiLevel &&
1395
- selectedImage.arch === state.systemImageArch &&
1422
+ effectiveSelectedImage.packageName === state.systemImage &&
1423
+ effectiveSelectedImage.apiLevel === stateApiLevel &&
1424
+ effectiveSelectedImage.arch === state.systemImageArch &&
1396
1425
  state.avdName === this.avdName;
1397
1426
 
1398
1427
  if (stateAligned) {
1399
1428
  return;
1400
1429
  }
1401
1430
 
1402
- if (selectedImage === preferredInstalled && selectedImage.packageName) {
1431
+ if (effectiveSelectedImage === preferredInstalled && effectiveSelectedImage.packageName) {
1403
1432
  const changeSummary = describeAutoFixChanges(
1404
1433
  {
1405
1434
  avdName: state.avdName || null,
@@ -1409,9 +1438,9 @@ class AndroidController {
1409
1438
  },
1410
1439
  {
1411
1440
  avdName: this.avdName,
1412
- systemImage: selectedImage.packageName,
1413
- apiLevel: selectedImage.apiLevel,
1414
- systemImageArch: selectedImage.arch,
1441
+ systemImage: effectiveSelectedImage.packageName,
1442
+ apiLevel: effectiveSelectedImage.apiLevel,
1443
+ systemImageArch: effectiveSelectedImage.arch,
1415
1444
  },
1416
1445
  ['avdName', 'systemImage', 'apiLevel', 'systemImageArch']
1417
1446
  );
@@ -1423,9 +1452,9 @@ class AndroidController {
1423
1452
  avdName: this.avdName,
1424
1453
  serial: null,
1425
1454
  emulatorPid: null,
1426
- systemImage: selectedImage.packageName,
1427
- apiLevel: selectedImage.apiLevel,
1428
- systemImageArch: selectedImage.arch,
1455
+ systemImage: effectiveSelectedImage.packageName,
1456
+ apiLevel: effectiveSelectedImage.apiLevel,
1457
+ systemImageArch: effectiveSelectedImage.arch,
1429
1458
  });
1430
1459
  return;
1431
1460
  }
@@ -1451,12 +1480,12 @@ class AndroidController {
1451
1480
  const runtimeNeedsRefresh =
1452
1481
  state.systemImageArch !== desiredArch ||
1453
1482
  !installedEmulatorMatchesHost();
1454
- if (!shouldForceSdkRefresh()) {
1455
- if (!runtimeNeedsRefresh && selectedImage && selectedImage === preferredInstalled) {
1483
+ if (!shouldForceSdkRefresh() && !legacyLinuxArm64Image && !selectedImageInvalid) {
1484
+ if (!runtimeNeedsRefresh && effectiveSelectedImage && effectiveSelectedImage === preferredInstalled) {
1456
1485
  const stateNeedsRefresh =
1457
- selectedImage.packageName !== state.systemImage ||
1458
- selectedImage.apiLevel !== stateApiLevel ||
1459
- selectedImage.arch !== state.systemImageArch ||
1486
+ effectiveSelectedImage.packageName !== state.systemImage ||
1487
+ effectiveSelectedImage.apiLevel !== stateApiLevel ||
1488
+ effectiveSelectedImage.arch !== state.systemImageArch ||
1460
1489
  state.avdName !== this.avdName;
1461
1490
  if (stateNeedsRefresh) {
1462
1491
  const changeSummary = describeAutoFixChanges(
@@ -1468,9 +1497,9 @@ class AndroidController {
1468
1497
  },
1469
1498
  {
1470
1499
  avdName: this.avdName,
1471
- systemImage: selectedImage.packageName,
1472
- apiLevel: selectedImage.apiLevel,
1473
- systemImageArch: selectedImage.arch,
1500
+ systemImage: effectiveSelectedImage.packageName,
1501
+ apiLevel: effectiveSelectedImage.apiLevel,
1502
+ systemImageArch: effectiveSelectedImage.arch,
1474
1503
  },
1475
1504
  ['avdName', 'systemImage', 'apiLevel', 'systemImageArch']
1476
1505
  );
@@ -1482,9 +1511,9 @@ class AndroidController {
1482
1511
  avdName: this.avdName,
1483
1512
  serial: null,
1484
1513
  emulatorPid: null,
1485
- systemImage: selectedImage.packageName,
1486
- apiLevel: selectedImage.apiLevel,
1487
- systemImageArch: selectedImage.arch,
1514
+ systemImage: effectiveSelectedImage.packageName,
1515
+ apiLevel: effectiveSelectedImage.apiLevel,
1516
+ systemImageArch: effectiveSelectedImage.arch,
1488
1517
  });
1489
1518
  }
1490
1519
  return;
@@ -1493,8 +1522,8 @@ class AndroidController {
1493
1522
  !runtimeNeedsRefresh &&
1494
1523
  state.bootstrapped === true &&
1495
1524
  state.systemImage &&
1496
- selectedImage &&
1497
- selectedImage.packageName === state.systemImage
1525
+ effectiveSelectedImage &&
1526
+ effectiveSelectedImage.packageName === state.systemImage
1498
1527
  ) {
1499
1528
  return;
1500
1529
  }
@@ -1506,18 +1535,19 @@ class AndroidController {
1506
1535
  await installPlatformToolsArchive(metadata);
1507
1536
  }
1508
1537
  await installEmulatorArchive(metadata);
1509
- if (!selectedImage) {
1538
+ if (!effectiveSelectedImage) {
1510
1539
  throw new Error(formatSystemImageError(available));
1511
1540
  }
1512
- if (selectedImage?.packageName) {
1513
- await installSystemImageArchive(systemImageMetadata, selectedImage.packageName);
1541
+ if (effectiveSelectedImage?.packageName) {
1542
+ await installSystemImageArchive(systemImageMetadata, effectiveSelectedImage.packageName);
1514
1543
  }
1515
1544
  this.#appendState({
1516
1545
  bootstrapped: true,
1517
1546
  avdName: this.avdName,
1518
- systemImage: selectedImage.packageName,
1519
- apiLevel: selectedImage.apiLevel,
1520
- systemImageArch: selectedImage.arch,
1547
+ systemImage: effectiveSelectedImage.packageName,
1548
+ apiLevel: effectiveSelectedImage.apiLevel,
1549
+ systemImageArch: effectiveSelectedImage.arch,
1550
+ avdSystemImage: null,
1521
1551
  });
1522
1552
  }
1523
1553
 
@@ -1585,8 +1615,22 @@ class AndroidController {
1585
1615
  await this.ensureBootstrapped();
1586
1616
 
1587
1617
  const state = this.#readState();
1588
- const pkg = state.systemImage;
1618
+ let pkg = state.systemImage;
1589
1619
  if (!pkg) throw new Error('Android system image not installed');
1620
+ if (process.platform === 'linux' && process.arch === 'arm64') {
1621
+ const installedCandidates = parseInstalledSystemImages();
1622
+ const migratedImage = chooseStableRuntimeSystemImage(installedCandidates, pkg);
1623
+ if (migratedImage) {
1624
+ pkg = migratedImage.packageName;
1625
+ this.#appendState({
1626
+ systemImage: pkg,
1627
+ apiLevel: migratedImage.apiLevel,
1628
+ systemImageArch: migratedImage.arch,
1629
+ avdSystemImage: null,
1630
+ lastLogLine: `Migrated Android runtime image to ${pkg} for stability.`,
1631
+ });
1632
+ }
1633
+ }
1590
1634
  const avdDir = path.join(AVD_HOME, `${this.avdName}.avd`);
1591
1635
  const configPath = path.join(avdDir, 'config.ini');
1592
1636
  const avdExists = fs.existsSync(configPath);
@@ -1723,6 +1767,8 @@ class AndroidController {
1723
1767
  `sdcard.size=${DEFAULT_SDCARD_SIZE_BYTES}`,
1724
1768
  'runtime.network.latency=none',
1725
1769
  'runtime.network.speed=full',
1770
+ 'fastboot.forceColdBoot=yes',
1771
+ 'fastboot.forceFastBoot=no',
1726
1772
  'vm.heapSize=256',
1727
1773
  `tag.display=${tagDisplay}`,
1728
1774
  `tag.id=${tagId}`,
@@ -1746,6 +1792,8 @@ class AndroidController {
1746
1792
  content = updateIniValue(content, 'sdcard.size', DEFAULT_SDCARD_SIZE_BYTES);
1747
1793
  content = updateIniValue(content, 'hw.ramSize', DEFAULT_RAM_SIZE_MB);
1748
1794
  content = updateIniValue(content, 'hw.gpu.mode', emulatorGpuMode());
1795
+ content = updateIniValue(content, 'fastboot.forceColdBoot', 'yes');
1796
+ content = updateIniValue(content, 'fastboot.forceFastBoot', 'no');
1749
1797
  content = updateIniValue(content, 'PlayStore.enabled', String(this.#readState()?.systemImage || '').includes('playstore'));
1750
1798
  fs.writeFileSync(configPath, content);
1751
1799
  }
@@ -1783,6 +1831,21 @@ class AndroidController {
1783
1831
  } catch {}
1784
1832
  }
1785
1833
 
1834
+ async #forceRecreateAvdForRecovery() {
1835
+ const avdDir = path.join(AVD_HOME, `${this.avdName}.avd`);
1836
+ await this.stopEmulator().catch(() => {});
1837
+ fs.rmSync(avdDir, { recursive: true, force: true });
1838
+ fs.rmSync(path.join(AVD_HOME, `${this.avdName}.ini`), { force: true });
1839
+ this.#appendState({
1840
+ avdSystemImage: null,
1841
+ serial: null,
1842
+ emulatorPid: null,
1843
+ lastLogLine: 'Recreating AVD after failed framework boot.',
1844
+ });
1845
+ await this.ensureAvd();
1846
+ this.#cleanupAvdTransientState();
1847
+ }
1848
+
1786
1849
  async listDevices(options = {}) {
1787
1850
  if (options.ensureBootstrapped !== false) {
1788
1851
  await this.ensureBootstrapped();
@@ -1921,6 +1984,11 @@ class AndroidController {
1921
1984
  this.#cleanupAvdTransientState();
1922
1985
  return this.#startEmulatorBlocking({ ...options, _recoveredOnce: true });
1923
1986
  }
1987
+ if (!options._recreatedAvdOnce && isRecoverableEmulatorStartError(lastLine)) {
1988
+ console.warn(`[Android] Emulator recovery escalation: recreating AVD and retrying once: ${lastLine}`);
1989
+ await this.#forceRecreateAvdForRecovery();
1990
+ return this.#startEmulatorBlocking({ ...options, _recoveredOnce: true, _recreatedAvdOnce: true });
1991
+ }
1924
1992
  this.markBootstrapFailure(lastLine);
1925
1993
  throw new Error(lastLine);
1926
1994
  }
@@ -2059,6 +2127,7 @@ class AndroidController {
2059
2127
  let reconnectCounter = 0;
2060
2128
  let missingPidSince = null;
2061
2129
  let firstOnlineAt = null;
2130
+ let offlineSince = null;
2062
2131
 
2063
2132
  while (Date.now() < deadline) {
2064
2133
  const serial = await this.getPrimarySerial();
@@ -2083,22 +2152,44 @@ class AndroidController {
2083
2152
  `${quoteShell(adbBinary())} -s ${quoteShell(serial)} shell echo ready`,
2084
2153
  { timeout: 10000 },
2085
2154
  );
2155
+ const packageServiceProbe = await this.#runAllowFailure(
2156
+ `${quoteShell(adbBinary())} -s ${quoteShell(serial)} shell service check package`,
2157
+ { timeout: 10000 },
2158
+ );
2159
+ const pmProbe = await this.#runAllowFailure(
2160
+ `${quoteShell(adbBinary())} -s ${quoteShell(serial)} shell pm path android`,
2161
+ { timeout: 10000 },
2162
+ );
2086
2163
 
2087
2164
  const bootValue = String(bootCompleted.stdout || '').trim();
2088
2165
  const devBootValue = String(devBootComplete.stdout || '').trim();
2089
2166
  const bootAnimValue = String(bootAnim.stdout || '').trim().toLowerCase();
2090
2167
  const shellReady = String(shellProbe.stdout || '').trim() === 'ready';
2168
+ const packageServiceReady = /found/i.test(String(packageServiceProbe.stdout || ''));
2169
+ const packageManagerReady = /^package:/m.test(String(pmProbe.stdout || '').trim());
2091
2170
  if (
2092
- bootValue === '1' ||
2093
- devBootValue === '1' ||
2094
- (shellReady && (bootAnimValue === 'stopped' || bootAnimValue === ''))
2171
+ packageServiceReady
2172
+ && packageManagerReady
2173
+ && (
2174
+ bootValue === '1'
2175
+ || devBootValue === '1'
2176
+ || (shellReady && (bootAnimValue === 'stopped' || bootAnimValue === ''))
2177
+ )
2095
2178
  ) {
2096
2179
  return serial;
2097
2180
  }
2098
- if (shellReady && firstOnlineAt && Date.now() - firstOnlineAt >= 45000) {
2099
- return serial;
2181
+ if (
2182
+ firstOnlineAt
2183
+ && Date.now() - firstOnlineAt > 120000
2184
+ && (
2185
+ !packageServiceReady
2186
+ || !packageManagerReady
2187
+ )
2188
+ ) {
2189
+ throw new Error('Android framework did not become ready (package manager service did not become ready).');
2100
2190
  }
2101
2191
  missingPidSince = null;
2192
+ offlineSince = null;
2102
2193
  } else {
2103
2194
  firstOnlineAt = null;
2104
2195
  const state = this.#readState();
@@ -2115,6 +2206,23 @@ class AndroidController {
2115
2206
  }
2116
2207
  } else {
2117
2208
  missingPidSince = null;
2209
+ const devices = await this.listDevices({ ensureBootstrapped: false }).catch(() => []);
2210
+ const hasOfflineEmulator = devices.some((device) => device.emulator && device.status === 'offline');
2211
+ if (hasOfflineEmulator) {
2212
+ if (!offlineSince) {
2213
+ offlineSince = Date.now();
2214
+ }
2215
+ if (Date.now() - offlineSince > 30000) {
2216
+ await this.#runAllowFailure(`${quoteShell(adbBinary())} kill-server`, { timeout: 10000 });
2217
+ await sleep(600);
2218
+ await this.#runAllowFailure(`${quoteShell(adbBinary())} start-server`, { timeout: 15000 });
2219
+ await this.#runAllowFailure(`${quoteShell(adbBinary())} reconnect`, { timeout: 15000 });
2220
+ await this.#runAllowFailure(`${quoteShell(adbBinary())} wait-for-device`, { timeout: 30000 });
2221
+ offlineSince = Date.now();
2222
+ }
2223
+ } else {
2224
+ offlineSince = null;
2225
+ }
2118
2226
  }
2119
2227
  }
2120
2228
  reconnectCounter += 1;
@@ -2184,8 +2292,17 @@ class AndroidController {
2184
2292
  return this.#run(`${quoteShell(adbBinary())} -s ${quoteShell(serial)} ${command}`, options);
2185
2293
  }
2186
2294
 
2295
+ async #prepareScreenForCapture(serial) {
2296
+ await this.#adb(serial, 'shell input keyevent 224', { timeout: 10000 }).catch(() => {});
2297
+ await this.#adb(serial, 'shell wm dismiss-keyguard', { timeout: 10000 }).catch(() => {});
2298
+ await this.#adb(serial, 'shell input keyevent 82', { timeout: 10000 }).catch(() => {});
2299
+ await this.#adb(serial, 'shell input keyevent 3', { timeout: 10000 }).catch(() => {});
2300
+ await sleep(350);
2301
+ }
2302
+
2187
2303
  async screenshot(options = {}) {
2188
2304
  const serial = options.serial || await this.ensureDevice();
2305
+ await this.#prepareScreenForCapture(serial).catch(() => {});
2189
2306
  let artifactRecord = null;
2190
2307
  let filename = `android_${Date.now()}.png`;
2191
2308
  let fullPath = path.join(SCREENSHOTS_DIR, filename);
@@ -2203,7 +2320,32 @@ class AndroidController {
2203
2320
  fullPath = artifactRecord.storagePath;
2204
2321
  filename = path.basename(fullPath);
2205
2322
  }
2206
- await this.#adb(serial, `exec-out screencap -p > ${quoteShell(fullPath)}`, { timeout: 30000 });
2323
+ let captured = false;
2324
+ const localTmp = path.join(TMP_DIR, `shot-${Date.now()}-${Math.random().toString(16).slice(2)}.png`);
2325
+ const remoteTmp = `/sdcard/neoagent-shot-${Date.now()}.png`;
2326
+ for (let attempt = 0; attempt < 3 && !captured; attempt += 1) {
2327
+ try {
2328
+ if (attempt === 0) {
2329
+ await this.#adb(serial, `exec-out screencap -p > ${quoteShell(fullPath)}`, { timeout: 30000 });
2330
+ } else {
2331
+ await this.#adb(serial, `shell screencap -p ${quoteShell(remoteTmp)}`, { timeout: 30000 });
2332
+ await this.#adb(serial, `pull ${quoteShell(remoteTmp)} ${quoteShell(localTmp)}`, { timeout: 30000 });
2333
+ if (fs.existsSync(localTmp)) {
2334
+ fs.copyFileSync(localTmp, fullPath);
2335
+ }
2336
+ }
2337
+ const data = fs.readFileSync(fullPath);
2338
+ captured = isLikelyPng(data);
2339
+ } catch {}
2340
+ if (!captured) {
2341
+ await sleep(500);
2342
+ }
2343
+ }
2344
+ fs.rmSync(localTmp, { force: true });
2345
+ await this.#adb(serial, `shell rm -f ${quoteShell(remoteTmp)}`, { timeout: 10000 }).catch(() => {});
2346
+ if (!captured) {
2347
+ throw new Error('Failed to capture a valid Android screenshot.');
2348
+ }
2207
2349
  if (artifactRecord) {
2208
2350
  this.artifactStore.finalizeFile(artifactRecord.artifactId, fullPath);
2209
2351
  }
@@ -2561,9 +2703,36 @@ class AndroidController {
2561
2703
  }
2562
2704
 
2563
2705
  async listApps(args = {}) {
2564
- const serial = await this.ensureDevice();
2706
+ let serial = null;
2707
+ try {
2708
+ serial = await this.ensureDevice();
2709
+ } catch (error) {
2710
+ return {
2711
+ success: false,
2712
+ serial: null,
2713
+ count: 0,
2714
+ packages: [],
2715
+ error: String(error?.message || 'Android device is not ready.'),
2716
+ };
2717
+ }
2565
2718
  const cmd = args.includeSystem === true ? 'shell pm list packages' : 'shell pm list packages -3';
2566
- const out = await this.#adb(serial, cmd, { timeout: 30000 });
2719
+ let out = '';
2720
+ try {
2721
+ out = await this.#adb(serial, cmd, { timeout: 30000 });
2722
+ } catch (error) {
2723
+ await sleep(1000);
2724
+ try {
2725
+ out = await this.#adb(serial, cmd, { timeout: 30000 });
2726
+ } catch (retryError) {
2727
+ return {
2728
+ success: false,
2729
+ serial,
2730
+ count: 0,
2731
+ packages: [],
2732
+ error: String(retryError?.message || error?.message || 'Failed to list Android apps.'),
2733
+ };
2734
+ }
2735
+ }
2567
2736
  const packages = out
2568
2737
  .split('\n')
2569
2738
  .map((line) => line.trim())
@@ -2642,6 +2811,8 @@ class AndroidController {
2642
2811
 
2643
2812
  async getStatus() {
2644
2813
  const state = this.#readState();
2814
+ const bootstrapWorkerPid = Number(state.bootstrapWorkerPid || 0) || null;
2815
+ const bootstrapWorkerAlive = isProcessAlive(bootstrapWorkerPid);
2645
2816
  const devices = isExecutable(adbBinary())
2646
2817
  ? await this.listDevices({ ensureBootstrapped: false }).catch(() => [])
2647
2818
  : [];
@@ -2674,8 +2845,8 @@ class AndroidController {
2674
2845
  }
2675
2846
  return {
2676
2847
  bootstrapped: state.bootstrapped === true,
2677
- starting: state.starting === true || this.startPromise != null,
2678
- startupPhase: state.startupPhase || null,
2848
+ starting: state.starting === true || this.startPromise != null || bootstrapWorkerAlive,
2849
+ startupPhase: state.startupPhase || (bootstrapWorkerAlive ? 'Preparing Android runtime' : null),
2679
2850
  startRequestedAt: state.startRequestedAt || null,
2680
2851
  lastStartError: state.lastStartError || null,
2681
2852
  sdkRoot: activeAndroidSdkRoot(),
@@ -2686,7 +2857,7 @@ class AndroidController {
2686
2857
  serial: state.serial,
2687
2858
  serialOwnedByCurrentUser,
2688
2859
  emulatorPid: state.emulatorPid,
2689
- bootstrapWorkerPid: Number(state.bootstrapWorkerPid || 0) || null,
2860
+ bootstrapWorkerPid,
2690
2861
  systemImage: state.systemImage || null,
2691
2862
  systemImageArch: state.systemImageArch || null,
2692
2863
  preferredSystemImageArchs: systemImageArchCandidates(),
@@ -193,6 +193,14 @@ class VmBrowserProvider {
193
193
  } catch {}
194
194
  }
195
195
  if (!file?.content) {
196
+ if (typeof result.screenshotPath === 'string' && result.screenshotPath.startsWith('/screenshots/')) {
197
+ return {
198
+ ...result,
199
+ screenshotPath: null,
200
+ artifactId: result.artifactId || null,
201
+ fullPath: result.fullPath || null,
202
+ };
203
+ }
196
204
  return result;
197
205
  }
198
206