@midscene/ios 1.8.4-beta-20260521065716.0 → 1.8.4

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.
package/dist/es/bin.mjs CHANGED
@@ -635,6 +635,10 @@ class IOSWebDriverClient extends WebDriverClient {
635
635
  debugIOS(`Failed to apply iOS session configuration: ${error}`);
636
636
  }
637
637
  }
638
+ async setupExistingSession() {
639
+ this.ensureSession();
640
+ await this.setupIOSSession();
641
+ }
638
642
  async executeRequest(method, endpoint, data) {
639
643
  return this.makeRequest(method, this.buildSessionEndpoint(endpoint), data);
640
644
  }
@@ -732,7 +736,10 @@ class IOSDevice {
732
736
  debugDevice(`Connecting to iOS device: ${this.deviceId}`);
733
737
  try {
734
738
  await this.wdaManager.start();
735
- await this.wdaBackend.createSession();
739
+ if (this.options?.sessionId) {
740
+ debugDevice(`Using existing WDA session: ${this.options.sessionId}`);
741
+ await this.wdaBackend.setupExistingSession();
742
+ } else await this.wdaBackend.createSession();
736
743
  const deviceInfo = await this.wdaBackend.getDeviceInfo();
737
744
  if (deviceInfo?.udid) {
738
745
  this.deviceId = deviceInfo.udid;
@@ -1207,7 +1214,10 @@ ScreenSize: ${size.width}x${size.height} (DPR: ${size.scale})
1207
1214
  const mjpegPort = options?.wdaMjpegPort ?? DEFAULT_WDA_MJPEG_PORT;
1208
1215
  this.wdaBackend = new IOSWebDriverClient({
1209
1216
  port: wdaPort,
1210
- host: wdaHost
1217
+ host: wdaHost,
1218
+ ...options?.sessionId ? {
1219
+ sessionId: options.sessionId
1220
+ } : {}
1211
1221
  });
1212
1222
  this.wdaManager = WDAManager.getInstance(wdaPort, wdaHost);
1213
1223
  this.mjpegStreamUrl = `http://${wdaHost}:${mjpegPort}`;
@@ -1334,6 +1344,22 @@ async function agentFromWebDriverAgent(opts) {
1334
1344
  await device.connect();
1335
1345
  return new IOSAgent(device, opts);
1336
1346
  }
1347
+ async function probeWdaReady(host, port, timeoutMs = 800) {
1348
+ const controller = new AbortController();
1349
+ const timer = setTimeout(()=>controller.abort(), timeoutMs);
1350
+ try {
1351
+ const res = await fetch(`http://${host}:${port}/status`, {
1352
+ signal: controller.signal
1353
+ });
1354
+ if (!res.ok) return false;
1355
+ const body = await res.json().catch(()=>null);
1356
+ return body?.value?.ready === true;
1357
+ } catch {
1358
+ return false;
1359
+ } finally{
1360
+ clearTimeout(timer);
1361
+ }
1362
+ }
1337
1363
  const iosPlaygroundPlatform = definePlaygroundPlatform({
1338
1364
  id: 'ios',
1339
1365
  title: 'Midscene iOS Playground',
@@ -1344,10 +1370,12 @@ const iosPlaygroundPlatform = definePlaygroundPlatform({
1344
1370
  if (availablePlaygroundPort !== PLAYGROUND_SERVER_PORT) console.log(`⚠️ Port ${PLAYGROUND_SERVER_PORT} is busy, using port ${availablePlaygroundPort} instead`);
1345
1371
  const sessionManager = {
1346
1372
  async getSetupSchema () {
1373
+ const wdaReady = await probeWdaReady('localhost', DEFAULT_WDA_PORT);
1347
1374
  return {
1348
1375
  title: 'Connect WebDriverAgent',
1349
1376
  description: 'Provide the WebDriverAgent host and port that are already running for your selected iPhone or simulator.',
1350
1377
  primaryActionLabel: 'Create Agent',
1378
+ autoSubmitWhenReady: wdaReady,
1351
1379
  fields: [
1352
1380
  {
1353
1381
  key: 'host',
@@ -1364,6 +1392,13 @@ const iosPlaygroundPlatform = definePlaygroundPlatform({
1364
1392
  required: true,
1365
1393
  defaultValue: DEFAULT_WDA_PORT,
1366
1394
  placeholder: DEFAULT_WDA_PORT.toString()
1395
+ },
1396
+ {
1397
+ key: 'sessionId',
1398
+ label: 'WebDriverAgent session ID',
1399
+ type: 'text',
1400
+ required: false,
1401
+ placeholder: 'Existing session ID'
1367
1402
  }
1368
1403
  ]
1369
1404
  };
@@ -1372,9 +1407,13 @@ const iosPlaygroundPlatform = definePlaygroundPlatform({
1372
1407
  const host = 'string' == typeof input?.host && input.host.trim() ? input.host.trim().replace(/^https?:\/\//, '') : 'localhost';
1373
1408
  const port = 'number' == typeof input?.port ? input.port : Number.parseInt(String(input?.port ?? DEFAULT_WDA_PORT), 10);
1374
1409
  if (Number.isNaN(port) || port < 1 || port > 65535) throw new Error(`Invalid WebDriverAgent port: ${String(input?.port)}`);
1410
+ const sessionId = 'string' == typeof input?.sessionId && input.sessionId.trim() ? input.sessionId.trim() : void 0;
1375
1411
  const connectAgent = async ()=>agentFromWebDriverAgent({
1376
1412
  wdaHost: host,
1377
- wdaPort: port
1413
+ wdaPort: port,
1414
+ ...sessionId ? {
1415
+ sessionId
1416
+ } : {}
1378
1417
  });
1379
1418
  const agent = await connectAgent();
1380
1419
  const deviceInfo = await agent.interface.getConnectedDeviceInfo?.();
@@ -1389,6 +1428,9 @@ const iosPlaygroundPlatform = definePlaygroundPlatform({
1389
1428
  metadata: {
1390
1429
  wdaHost: host,
1391
1430
  wdaPort: port,
1431
+ ...sessionId ? {
1432
+ sessionId
1433
+ } : {},
1392
1434
  ...deviceInfo ? {
1393
1435
  deviceInfo
1394
1436
  } : {}
package/dist/es/cli.mjs CHANGED
@@ -634,6 +634,10 @@ class IOSWebDriverClient extends WebDriverClient {
634
634
  debugIOS(`Failed to apply iOS session configuration: ${error}`);
635
635
  }
636
636
  }
637
+ async setupExistingSession() {
638
+ this.ensureSession();
639
+ await this.setupIOSSession();
640
+ }
637
641
  async executeRequest(method, endpoint, data) {
638
642
  return this.makeRequest(method, this.buildSessionEndpoint(endpoint), data);
639
643
  }
@@ -731,7 +735,10 @@ class IOSDevice {
731
735
  debugDevice(`Connecting to iOS device: ${this.deviceId}`);
732
736
  try {
733
737
  await this.wdaManager.start();
734
- await this.wdaBackend.createSession();
738
+ if (this.options?.sessionId) {
739
+ debugDevice(`Using existing WDA session: ${this.options.sessionId}`);
740
+ await this.wdaBackend.setupExistingSession();
741
+ } else await this.wdaBackend.createSession();
735
742
  const deviceInfo = await this.wdaBackend.getDeviceInfo();
736
743
  if (deviceInfo?.udid) {
737
744
  this.deviceId = deviceInfo.udid;
@@ -1206,7 +1213,10 @@ ScreenSize: ${size.width}x${size.height} (DPR: ${size.scale})
1206
1213
  const mjpegPort = options?.wdaMjpegPort ?? DEFAULT_WDA_MJPEG_PORT;
1207
1214
  this.wdaBackend = new IOSWebDriverClient({
1208
1215
  port: wdaPort,
1209
- host: wdaHost
1216
+ host: wdaHost,
1217
+ ...options?.sessionId ? {
1218
+ sessionId: options.sessionId
1219
+ } : {}
1210
1220
  });
1211
1221
  this.wdaManager = WDAManager.getInstance(wdaPort, wdaHost);
1212
1222
  this.mjpegStreamUrl = `http://${wdaHost}:${mjpegPort}`;
@@ -1348,9 +1358,20 @@ const iosInitArgShape = {
1348
1358
  deviceId: z.string().optional().describe('iOS device UDID (optional when WDA auto-detect is sufficient)'),
1349
1359
  wdaHost: z.string().optional().describe('WebDriverAgent host, defaults to localhost'),
1350
1360
  wdaPort: z.number().optional().describe('WebDriverAgent port'),
1361
+ sessionId: z.string().optional().describe('Existing WebDriverAgent session ID to reuse'),
1351
1362
  useWDA: z.boolean().optional().describe('Whether to reuse an existing WebDriverAgent session'),
1352
1363
  wdaMjpegPort: z.number().optional().describe('WebDriverAgent MJPEG streaming port')
1353
1364
  };
1365
+ function getTargetIdentity(initArgs) {
1366
+ if (initArgs?.deviceId) return initArgs.sessionId ? `${initArgs.deviceId}-session-${initArgs.sessionId}` : initArgs.deviceId;
1367
+ if (initArgs?.wdaHost || initArgs?.wdaPort || initArgs?.sessionId) {
1368
+ const wdaHost = initArgs.wdaHost ?? 'localhost';
1369
+ const wdaPort = initArgs.wdaPort ?? 'default';
1370
+ const sessionSegment = initArgs.sessionId ? `-session-${initArgs.sessionId}` : '';
1371
+ return `wda-${wdaHost}-${wdaPort}${sessionSegment}`;
1372
+ }
1373
+ return 'auto';
1374
+ }
1354
1375
  class IOSMidsceneTools extends BaseMidsceneTools {
1355
1376
  getCliReportSessionName() {
1356
1377
  return 'midscene-ios';
@@ -1389,7 +1410,7 @@ class IOSMidsceneTools extends BaseMidsceneTools {
1389
1410
  cli: this.getAgentInitArgCliMetadata(),
1390
1411
  handler: async (args)=>{
1391
1412
  const initArgs = this.extractAgentInitParam(args);
1392
- const identity = initArgs?.deviceId ?? (initArgs?.wdaHost || initArgs?.wdaPort ? `wda-${initArgs.wdaHost ?? 'localhost'}-${initArgs.wdaPort ?? 'default'}` : 'auto');
1413
+ const identity = getTargetIdentity(initArgs);
1393
1414
  const reportSession = this.createNewCliReportSession(identity);
1394
1415
  this.commitCliReportSession(reportSession);
1395
1416
  if (this.agent) {
@@ -1436,7 +1457,7 @@ class IOSMidsceneTools extends BaseMidsceneTools {
1436
1457
  const tools = new IOSMidsceneTools();
1437
1458
  runToolsCLI(tools, 'midscene-ios', {
1438
1459
  stripPrefix: 'ios_',
1439
- version: "1.8.4-beta-20260521065716.0",
1460
+ version: "1.8.4",
1440
1461
  extraCommands: createReportCliCommands()
1441
1462
  }).catch((e)=>{
1442
1463
  process.exit(reportCLIError(e));
package/dist/es/index.mjs CHANGED
@@ -455,6 +455,10 @@ class IOSWebDriverClient extends WebDriverClient {
455
455
  debugIOS(`Failed to apply iOS session configuration: ${error}`);
456
456
  }
457
457
  }
458
+ async setupExistingSession() {
459
+ this.ensureSession();
460
+ await this.setupIOSSession();
461
+ }
458
462
  async executeRequest(method, endpoint, data) {
459
463
  return this.makeRequest(method, this.buildSessionEndpoint(endpoint), data);
460
464
  }
@@ -552,7 +556,10 @@ class IOSDevice {
552
556
  debugDevice(`Connecting to iOS device: ${this.deviceId}`);
553
557
  try {
554
558
  await this.wdaManager.start();
555
- await this.wdaBackend.createSession();
559
+ if (this.options?.sessionId) {
560
+ debugDevice(`Using existing WDA session: ${this.options.sessionId}`);
561
+ await this.wdaBackend.setupExistingSession();
562
+ } else await this.wdaBackend.createSession();
556
563
  const deviceInfo = await this.wdaBackend.getDeviceInfo();
557
564
  if (deviceInfo?.udid) {
558
565
  this.deviceId = deviceInfo.udid;
@@ -1027,7 +1034,10 @@ ScreenSize: ${size.width}x${size.height} (DPR: ${size.scale})
1027
1034
  const mjpegPort = options?.wdaMjpegPort ?? DEFAULT_WDA_MJPEG_PORT;
1028
1035
  this.wdaBackend = new IOSWebDriverClient({
1029
1036
  port: wdaPort,
1030
- host: wdaHost
1037
+ host: wdaHost,
1038
+ ...options?.sessionId ? {
1039
+ sessionId: options.sessionId
1040
+ } : {}
1031
1041
  });
1032
1042
  this.wdaManager = WDAManager.getInstance(wdaPort, wdaHost);
1033
1043
  this.mjpegStreamUrl = `http://${wdaHost}:${mjpegPort}`;
@@ -1353,9 +1363,20 @@ const iosInitArgShape = {
1353
1363
  deviceId: z.string().optional().describe('iOS device UDID (optional when WDA auto-detect is sufficient)'),
1354
1364
  wdaHost: z.string().optional().describe('WebDriverAgent host, defaults to localhost'),
1355
1365
  wdaPort: z.number().optional().describe('WebDriverAgent port'),
1366
+ sessionId: z.string().optional().describe('Existing WebDriverAgent session ID to reuse'),
1356
1367
  useWDA: z.boolean().optional().describe('Whether to reuse an existing WebDriverAgent session'),
1357
1368
  wdaMjpegPort: z.number().optional().describe('WebDriverAgent MJPEG streaming port')
1358
1369
  };
1370
+ function getTargetIdentity(initArgs) {
1371
+ if (initArgs?.deviceId) return initArgs.sessionId ? `${initArgs.deviceId}-session-${initArgs.sessionId}` : initArgs.deviceId;
1372
+ if (initArgs?.wdaHost || initArgs?.wdaPort || initArgs?.sessionId) {
1373
+ const wdaHost = initArgs.wdaHost ?? 'localhost';
1374
+ const wdaPort = initArgs.wdaPort ?? 'default';
1375
+ const sessionSegment = initArgs.sessionId ? `-session-${initArgs.sessionId}` : '';
1376
+ return `wda-${wdaHost}-${wdaPort}${sessionSegment}`;
1377
+ }
1378
+ return 'auto';
1379
+ }
1359
1380
  class IOSMidsceneTools extends BaseMidsceneTools {
1360
1381
  getCliReportSessionName() {
1361
1382
  return 'midscene-ios';
@@ -1394,7 +1415,7 @@ class IOSMidsceneTools extends BaseMidsceneTools {
1394
1415
  cli: this.getAgentInitArgCliMetadata(),
1395
1416
  handler: async (args)=>{
1396
1417
  const initArgs = this.extractAgentInitParam(args);
1397
- const identity = initArgs?.deviceId ?? (initArgs?.wdaHost || initArgs?.wdaPort ? `wda-${initArgs.wdaHost ?? 'localhost'}-${initArgs.wdaPort ?? 'default'}` : 'auto');
1418
+ const identity = getTargetIdentity(initArgs);
1398
1419
  const reportSession = this.createNewCliReportSession(identity);
1399
1420
  this.commitCliReportSession(reportSession);
1400
1421
  if (this.agent) {
@@ -1484,6 +1505,22 @@ async function checkIOSEnvironment() {
1484
1505
  };
1485
1506
  }
1486
1507
  }
1508
+ async function probeWdaReady(host, port, timeoutMs = 800) {
1509
+ const controller = new AbortController();
1510
+ const timer = setTimeout(()=>controller.abort(), timeoutMs);
1511
+ try {
1512
+ const res = await fetch(`http://${host}:${port}/status`, {
1513
+ signal: controller.signal
1514
+ });
1515
+ if (!res.ok) return false;
1516
+ const body = await res.json().catch(()=>null);
1517
+ return body?.value?.ready === true;
1518
+ } catch {
1519
+ return false;
1520
+ } finally{
1521
+ clearTimeout(timer);
1522
+ }
1523
+ }
1487
1524
  const iosPlaygroundPlatform = definePlaygroundPlatform({
1488
1525
  id: 'ios',
1489
1526
  title: 'Midscene iOS Playground',
@@ -1494,10 +1531,12 @@ const iosPlaygroundPlatform = definePlaygroundPlatform({
1494
1531
  if (availablePlaygroundPort !== PLAYGROUND_SERVER_PORT) console.log(`⚠️ Port ${PLAYGROUND_SERVER_PORT} is busy, using port ${availablePlaygroundPort} instead`);
1495
1532
  const sessionManager = {
1496
1533
  async getSetupSchema () {
1534
+ const wdaReady = await probeWdaReady('localhost', DEFAULT_WDA_PORT);
1497
1535
  return {
1498
1536
  title: 'Connect WebDriverAgent',
1499
1537
  description: 'Provide the WebDriverAgent host and port that are already running for your selected iPhone or simulator.',
1500
1538
  primaryActionLabel: 'Create Agent',
1539
+ autoSubmitWhenReady: wdaReady,
1501
1540
  fields: [
1502
1541
  {
1503
1542
  key: 'host',
@@ -1514,6 +1553,13 @@ const iosPlaygroundPlatform = definePlaygroundPlatform({
1514
1553
  required: true,
1515
1554
  defaultValue: DEFAULT_WDA_PORT,
1516
1555
  placeholder: DEFAULT_WDA_PORT.toString()
1556
+ },
1557
+ {
1558
+ key: 'sessionId',
1559
+ label: 'WebDriverAgent session ID',
1560
+ type: 'text',
1561
+ required: false,
1562
+ placeholder: 'Existing session ID'
1517
1563
  }
1518
1564
  ]
1519
1565
  };
@@ -1522,9 +1568,13 @@ const iosPlaygroundPlatform = definePlaygroundPlatform({
1522
1568
  const host = 'string' == typeof input?.host && input.host.trim() ? input.host.trim().replace(/^https?:\/\//, '') : 'localhost';
1523
1569
  const port = 'number' == typeof input?.port ? input.port : Number.parseInt(String(input?.port ?? DEFAULT_WDA_PORT), 10);
1524
1570
  if (Number.isNaN(port) || port < 1 || port > 65535) throw new Error(`Invalid WebDriverAgent port: ${String(input?.port)}`);
1571
+ const sessionId = 'string' == typeof input?.sessionId && input.sessionId.trim() ? input.sessionId.trim() : void 0;
1525
1572
  const connectAgent = async ()=>agentFromWebDriverAgent({
1526
1573
  wdaHost: host,
1527
- wdaPort: port
1574
+ wdaPort: port,
1575
+ ...sessionId ? {
1576
+ sessionId
1577
+ } : {}
1528
1578
  });
1529
1579
  const agent = await connectAgent();
1530
1580
  const deviceInfo = await agent.interface.getConnectedDeviceInfo?.();
@@ -1539,6 +1589,9 @@ const iosPlaygroundPlatform = definePlaygroundPlatform({
1539
1589
  metadata: {
1540
1590
  wdaHost: host,
1541
1591
  wdaPort: port,
1592
+ ...sessionId ? {
1593
+ sessionId
1594
+ } : {},
1542
1595
  ...deviceInfo ? {
1543
1596
  deviceInfo
1544
1597
  } : {}
@@ -634,6 +634,10 @@ class IOSWebDriverClient extends WebDriverClient {
634
634
  debugIOS(`Failed to apply iOS session configuration: ${error}`);
635
635
  }
636
636
  }
637
+ async setupExistingSession() {
638
+ this.ensureSession();
639
+ await this.setupIOSSession();
640
+ }
637
641
  async executeRequest(method, endpoint, data) {
638
642
  return this.makeRequest(method, this.buildSessionEndpoint(endpoint), data);
639
643
  }
@@ -731,7 +735,10 @@ class IOSDevice {
731
735
  debugDevice(`Connecting to iOS device: ${this.deviceId}`);
732
736
  try {
733
737
  await this.wdaManager.start();
734
- await this.wdaBackend.createSession();
738
+ if (this.options?.sessionId) {
739
+ debugDevice(`Using existing WDA session: ${this.options.sessionId}`);
740
+ await this.wdaBackend.setupExistingSession();
741
+ } else await this.wdaBackend.createSession();
735
742
  const deviceInfo = await this.wdaBackend.getDeviceInfo();
736
743
  if (deviceInfo?.udid) {
737
744
  this.deviceId = deviceInfo.udid;
@@ -1206,7 +1213,10 @@ ScreenSize: ${size.width}x${size.height} (DPR: ${size.scale})
1206
1213
  const mjpegPort = options?.wdaMjpegPort ?? DEFAULT_WDA_MJPEG_PORT;
1207
1214
  this.wdaBackend = new IOSWebDriverClient({
1208
1215
  port: wdaPort,
1209
- host: wdaHost
1216
+ host: wdaHost,
1217
+ ...options?.sessionId ? {
1218
+ sessionId: options.sessionId
1219
+ } : {}
1210
1220
  });
1211
1221
  this.wdaManager = WDAManager.getInstance(wdaPort, wdaHost);
1212
1222
  this.mjpegStreamUrl = `http://${wdaHost}:${mjpegPort}`;
@@ -1348,9 +1358,20 @@ const iosInitArgShape = {
1348
1358
  deviceId: z.string().optional().describe('iOS device UDID (optional when WDA auto-detect is sufficient)'),
1349
1359
  wdaHost: z.string().optional().describe('WebDriverAgent host, defaults to localhost'),
1350
1360
  wdaPort: z.number().optional().describe('WebDriverAgent port'),
1361
+ sessionId: z.string().optional().describe('Existing WebDriverAgent session ID to reuse'),
1351
1362
  useWDA: z.boolean().optional().describe('Whether to reuse an existing WebDriverAgent session'),
1352
1363
  wdaMjpegPort: z.number().optional().describe('WebDriverAgent MJPEG streaming port')
1353
1364
  };
1365
+ function getTargetIdentity(initArgs) {
1366
+ if (initArgs?.deviceId) return initArgs.sessionId ? `${initArgs.deviceId}-session-${initArgs.sessionId}` : initArgs.deviceId;
1367
+ if (initArgs?.wdaHost || initArgs?.wdaPort || initArgs?.sessionId) {
1368
+ const wdaHost = initArgs.wdaHost ?? 'localhost';
1369
+ const wdaPort = initArgs.wdaPort ?? 'default';
1370
+ const sessionSegment = initArgs.sessionId ? `-session-${initArgs.sessionId}` : '';
1371
+ return `wda-${wdaHost}-${wdaPort}${sessionSegment}`;
1372
+ }
1373
+ return 'auto';
1374
+ }
1354
1375
  class IOSMidsceneTools extends BaseMidsceneTools {
1355
1376
  getCliReportSessionName() {
1356
1377
  return 'midscene-ios';
@@ -1389,7 +1410,7 @@ class IOSMidsceneTools extends BaseMidsceneTools {
1389
1410
  cli: this.getAgentInitArgCliMetadata(),
1390
1411
  handler: async (args)=>{
1391
1412
  const initArgs = this.extractAgentInitParam(args);
1392
- const identity = initArgs?.deviceId ?? (initArgs?.wdaHost || initArgs?.wdaPort ? `wda-${initArgs.wdaHost ?? 'localhost'}-${initArgs.wdaPort ?? 'default'}` : 'auto');
1413
+ const identity = getTargetIdentity(initArgs);
1393
1414
  const reportSession = this.createNewCliReportSession(identity);
1394
1415
  this.commitCliReportSession(reportSession);
1395
1416
  if (this.agent) {
@@ -1440,7 +1461,7 @@ class IOSMCPServer extends BaseMCPServer {
1440
1461
  constructor(toolsManager){
1441
1462
  super({
1442
1463
  name: '@midscene/ios-mcp',
1443
- version: "1.8.4-beta-20260521065716.0",
1464
+ version: "1.8.4",
1444
1465
  description: 'Control the iOS device using natural language commands'
1445
1466
  }, toolsManager);
1446
1467
  }
package/dist/lib/bin.js CHANGED
@@ -660,6 +660,10 @@ class IOSWebDriverClient extends webdriver_namespaceObject.WebDriverClient {
660
660
  debugIOS(`Failed to apply iOS session configuration: ${error}`);
661
661
  }
662
662
  }
663
+ async setupExistingSession() {
664
+ this.ensureSession();
665
+ await this.setupIOSSession();
666
+ }
663
667
  async executeRequest(method, endpoint, data) {
664
668
  return this.makeRequest(method, this.buildSessionEndpoint(endpoint), data);
665
669
  }
@@ -757,7 +761,10 @@ class IOSDevice {
757
761
  debugDevice(`Connecting to iOS device: ${this.deviceId}`);
758
762
  try {
759
763
  await this.wdaManager.start();
760
- await this.wdaBackend.createSession();
764
+ if (this.options?.sessionId) {
765
+ debugDevice(`Using existing WDA session: ${this.options.sessionId}`);
766
+ await this.wdaBackend.setupExistingSession();
767
+ } else await this.wdaBackend.createSession();
761
768
  const deviceInfo = await this.wdaBackend.getDeviceInfo();
762
769
  if (deviceInfo?.udid) {
763
770
  this.deviceId = deviceInfo.udid;
@@ -1232,7 +1239,10 @@ ScreenSize: ${size.width}x${size.height} (DPR: ${size.scale})
1232
1239
  const mjpegPort = options?.wdaMjpegPort ?? DEFAULT_WDA_MJPEG_PORT;
1233
1240
  this.wdaBackend = new IOSWebDriverClient({
1234
1241
  port: wdaPort,
1235
- host: wdaHost
1242
+ host: wdaHost,
1243
+ ...options?.sessionId ? {
1244
+ sessionId: options.sessionId
1245
+ } : {}
1236
1246
  });
1237
1247
  this.wdaManager = webdriver_namespaceObject.WDAManager.getInstance(wdaPort, wdaHost);
1238
1248
  this.mjpegStreamUrl = `http://${wdaHost}:${mjpegPort}`;
@@ -1359,6 +1369,22 @@ async function agentFromWebDriverAgent(opts) {
1359
1369
  await device.connect();
1360
1370
  return new IOSAgent(device, opts);
1361
1371
  }
1372
+ async function probeWdaReady(host, port, timeoutMs = 800) {
1373
+ const controller = new AbortController();
1374
+ const timer = setTimeout(()=>controller.abort(), timeoutMs);
1375
+ try {
1376
+ const res = await fetch(`http://${host}:${port}/status`, {
1377
+ signal: controller.signal
1378
+ });
1379
+ if (!res.ok) return false;
1380
+ const body = await res.json().catch(()=>null);
1381
+ return body?.value?.ready === true;
1382
+ } catch {
1383
+ return false;
1384
+ } finally{
1385
+ clearTimeout(timer);
1386
+ }
1387
+ }
1362
1388
  const iosPlaygroundPlatform = (0, playground_namespaceObject.definePlaygroundPlatform)({
1363
1389
  id: 'ios',
1364
1390
  title: 'Midscene iOS Playground',
@@ -1369,10 +1395,12 @@ const iosPlaygroundPlatform = (0, playground_namespaceObject.definePlaygroundPla
1369
1395
  if (availablePlaygroundPort !== constants_namespaceObject.PLAYGROUND_SERVER_PORT) console.log(`⚠️ Port ${constants_namespaceObject.PLAYGROUND_SERVER_PORT} is busy, using port ${availablePlaygroundPort} instead`);
1370
1396
  const sessionManager = {
1371
1397
  async getSetupSchema () {
1398
+ const wdaReady = await probeWdaReady('localhost', constants_namespaceObject.DEFAULT_WDA_PORT);
1372
1399
  return {
1373
1400
  title: 'Connect WebDriverAgent',
1374
1401
  description: 'Provide the WebDriverAgent host and port that are already running for your selected iPhone or simulator.',
1375
1402
  primaryActionLabel: 'Create Agent',
1403
+ autoSubmitWhenReady: wdaReady,
1376
1404
  fields: [
1377
1405
  {
1378
1406
  key: 'host',
@@ -1389,6 +1417,13 @@ const iosPlaygroundPlatform = (0, playground_namespaceObject.definePlaygroundPla
1389
1417
  required: true,
1390
1418
  defaultValue: constants_namespaceObject.DEFAULT_WDA_PORT,
1391
1419
  placeholder: constants_namespaceObject.DEFAULT_WDA_PORT.toString()
1420
+ },
1421
+ {
1422
+ key: 'sessionId',
1423
+ label: 'WebDriverAgent session ID',
1424
+ type: 'text',
1425
+ required: false,
1426
+ placeholder: 'Existing session ID'
1392
1427
  }
1393
1428
  ]
1394
1429
  };
@@ -1397,9 +1432,13 @@ const iosPlaygroundPlatform = (0, playground_namespaceObject.definePlaygroundPla
1397
1432
  const host = 'string' == typeof input?.host && input.host.trim() ? input.host.trim().replace(/^https?:\/\//, '') : 'localhost';
1398
1433
  const port = 'number' == typeof input?.port ? input.port : Number.parseInt(String(input?.port ?? constants_namespaceObject.DEFAULT_WDA_PORT), 10);
1399
1434
  if (Number.isNaN(port) || port < 1 || port > 65535) throw new Error(`Invalid WebDriverAgent port: ${String(input?.port)}`);
1435
+ const sessionId = 'string' == typeof input?.sessionId && input.sessionId.trim() ? input.sessionId.trim() : void 0;
1400
1436
  const connectAgent = async ()=>agentFromWebDriverAgent({
1401
1437
  wdaHost: host,
1402
- wdaPort: port
1438
+ wdaPort: port,
1439
+ ...sessionId ? {
1440
+ sessionId
1441
+ } : {}
1403
1442
  });
1404
1443
  const agent = await connectAgent();
1405
1444
  const deviceInfo = await agent.interface.getConnectedDeviceInfo?.();
@@ -1414,6 +1453,9 @@ const iosPlaygroundPlatform = (0, playground_namespaceObject.definePlaygroundPla
1414
1453
  metadata: {
1415
1454
  wdaHost: host,
1416
1455
  wdaPort: port,
1456
+ ...sessionId ? {
1457
+ sessionId
1458
+ } : {},
1417
1459
  ...deviceInfo ? {
1418
1460
  deviceInfo
1419
1461
  } : {}
package/dist/lib/cli.js CHANGED
@@ -658,6 +658,10 @@ class IOSWebDriverClient extends webdriver_namespaceObject.WebDriverClient {
658
658
  debugIOS(`Failed to apply iOS session configuration: ${error}`);
659
659
  }
660
660
  }
661
+ async setupExistingSession() {
662
+ this.ensureSession();
663
+ await this.setupIOSSession();
664
+ }
661
665
  async executeRequest(method, endpoint, data) {
662
666
  return this.makeRequest(method, this.buildSessionEndpoint(endpoint), data);
663
667
  }
@@ -755,7 +759,10 @@ class IOSDevice {
755
759
  debugDevice(`Connecting to iOS device: ${this.deviceId}`);
756
760
  try {
757
761
  await this.wdaManager.start();
758
- await this.wdaBackend.createSession();
762
+ if (this.options?.sessionId) {
763
+ debugDevice(`Using existing WDA session: ${this.options.sessionId}`);
764
+ await this.wdaBackend.setupExistingSession();
765
+ } else await this.wdaBackend.createSession();
759
766
  const deviceInfo = await this.wdaBackend.getDeviceInfo();
760
767
  if (deviceInfo?.udid) {
761
768
  this.deviceId = deviceInfo.udid;
@@ -1230,7 +1237,10 @@ ScreenSize: ${size.width}x${size.height} (DPR: ${size.scale})
1230
1237
  const mjpegPort = options?.wdaMjpegPort ?? DEFAULT_WDA_MJPEG_PORT;
1231
1238
  this.wdaBackend = new IOSWebDriverClient({
1232
1239
  port: wdaPort,
1233
- host: wdaHost
1240
+ host: wdaHost,
1241
+ ...options?.sessionId ? {
1242
+ sessionId: options.sessionId
1243
+ } : {}
1234
1244
  });
1235
1245
  this.wdaManager = webdriver_namespaceObject.WDAManager.getInstance(wdaPort, wdaHost);
1236
1246
  this.mjpegStreamUrl = `http://${wdaHost}:${mjpegPort}`;
@@ -1372,9 +1382,20 @@ const iosInitArgShape = {
1372
1382
  deviceId: core_namespaceObject.z.string().optional().describe('iOS device UDID (optional when WDA auto-detect is sufficient)'),
1373
1383
  wdaHost: core_namespaceObject.z.string().optional().describe('WebDriverAgent host, defaults to localhost'),
1374
1384
  wdaPort: core_namespaceObject.z.number().optional().describe('WebDriverAgent port'),
1385
+ sessionId: core_namespaceObject.z.string().optional().describe('Existing WebDriverAgent session ID to reuse'),
1375
1386
  useWDA: core_namespaceObject.z.boolean().optional().describe('Whether to reuse an existing WebDriverAgent session'),
1376
1387
  wdaMjpegPort: core_namespaceObject.z.number().optional().describe('WebDriverAgent MJPEG streaming port')
1377
1388
  };
1389
+ function getTargetIdentity(initArgs) {
1390
+ if (initArgs?.deviceId) return initArgs.sessionId ? `${initArgs.deviceId}-session-${initArgs.sessionId}` : initArgs.deviceId;
1391
+ if (initArgs?.wdaHost || initArgs?.wdaPort || initArgs?.sessionId) {
1392
+ const wdaHost = initArgs.wdaHost ?? 'localhost';
1393
+ const wdaPort = initArgs.wdaPort ?? 'default';
1394
+ const sessionSegment = initArgs.sessionId ? `-session-${initArgs.sessionId}` : '';
1395
+ return `wda-${wdaHost}-${wdaPort}${sessionSegment}`;
1396
+ }
1397
+ return 'auto';
1398
+ }
1378
1399
  class IOSMidsceneTools extends base_tools_namespaceObject.BaseMidsceneTools {
1379
1400
  getCliReportSessionName() {
1380
1401
  return 'midscene-ios';
@@ -1413,7 +1434,7 @@ class IOSMidsceneTools extends base_tools_namespaceObject.BaseMidsceneTools {
1413
1434
  cli: this.getAgentInitArgCliMetadata(),
1414
1435
  handler: async (args)=>{
1415
1436
  const initArgs = this.extractAgentInitParam(args);
1416
- const identity = initArgs?.deviceId ?? (initArgs?.wdaHost || initArgs?.wdaPort ? `wda-${initArgs.wdaHost ?? 'localhost'}-${initArgs.wdaPort ?? 'default'}` : 'auto');
1437
+ const identity = getTargetIdentity(initArgs);
1417
1438
  const reportSession = this.createNewCliReportSession(identity);
1418
1439
  this.commitCliReportSession(reportSession);
1419
1440
  if (this.agent) {
@@ -1460,7 +1481,7 @@ class IOSMidsceneTools extends base_tools_namespaceObject.BaseMidsceneTools {
1460
1481
  const tools = new IOSMidsceneTools();
1461
1482
  (0, cli_namespaceObject.runToolsCLI)(tools, 'midscene-ios', {
1462
1483
  stripPrefix: 'ios_',
1463
- version: "1.8.4-beta-20260521065716.0",
1484
+ version: "1.8.4",
1464
1485
  extraCommands: (0, core_namespaceObject.createReportCliCommands)()
1465
1486
  }).catch((e)=>{
1466
1487
  process.exit((0, cli_namespaceObject.reportCLIError)(e));