@xiuchang-midscene/android 2.0.3 → 2.0.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.
Files changed (24) hide show
  1. package/dist/es/cli.mjs +119 -350
  2. package/dist/es/index.mjs +117 -348
  3. package/dist/es/mcp-server.mjs +119 -350
  4. package/dist/lib/cli.js +121 -352
  5. package/dist/lib/index.js +117 -348
  6. package/dist/lib/mcp-server.js +121 -352
  7. package/dist/types/index.d.ts +1 -8
  8. package/dist/types/mcp-server.d.ts +1 -8
  9. package/package.json +3 -3
  10. package/src/app-knowledge/screenshots/fliggy/fliggy-qwen/.gitkeep +0 -0
  11. package/src/app-knowledge/screenshots/fliggy/flight-detail/.gitkeep +0 -0
  12. package/src/app-knowledge/screenshots/fliggy/flight-list/.gitkeep +0 -0
  13. package/src/app-knowledge/screenshots/fliggy/flight-order/.gitkeep +0 -0
  14. package/src/app-knowledge/screenshots/fliggy/home/home-page-annotated.jpg +0 -0
  15. package/src/app-knowledge/screenshots/fliggy/hotel-detail/.gitkeep +0 -0
  16. package/src/app-knowledge/screenshots/fliggy/hotel-list/.gitkeep +0 -0
  17. package/src/app-knowledge/screenshots/fliggy/hotel-order/.gitkeep +0 -0
  18. package/src/app-knowledge/screenshots/fliggy/search-default/search-default-annotated.jpg +0 -0
  19. package/src/app-knowledge/screenshots/fliggy/search-result/search-result-annotated.jpg +0 -0
  20. package/src/app-knowledge/screenshots/fliggy/search-result/search-result-giraffe-annotated.jpg +0 -0
  21. package/src/app-knowledge/screenshots/fliggy/search-result/search-result-quickfilter-annotated.jpg +0 -0
  22. package/src/app-knowledge/screenshots/fliggy/search-sug/search-sug-annotated.jpg +0 -0
  23. package/src/app-knowledge/screenshots/fliggy/ticket-detail/.gitkeep +0 -0
  24. package/src/app-knowledge/screenshots/fliggy/ticket-order/.gitkeep +0 -0
@@ -463,352 +463,130 @@ var __webpack_exports__ = {};
463
463
  AndroidMCPServer: ()=>AndroidMCPServer
464
464
  });
465
465
  const mcp_namespaceObject = require("@xiuchang-midscene/shared/mcp");
466
- var external_node_fs_ = __webpack_require__("node:fs");
467
- var external_node_fs_default = /*#__PURE__*/ __webpack_require__.n(external_node_fs_);
468
- var external_node_path_ = __webpack_require__("node:path");
469
- var external_node_path_default = /*#__PURE__*/ __webpack_require__.n(external_node_path_);
470
466
  const agent_namespaceObject = require("@xiuchang-midscene/core/agent");
471
467
  var logger_ = __webpack_require__("@xiuchang-midscene/shared/logger");
472
468
  const utils_namespaceObject = require("@xiuchang-midscene/shared/utils");
473
- const fliggyKnowledge = `---
474
- name: fliggy-app-guide
475
- description: >
476
- 飞猪 App 页面结构、功能入口和操作指南。适用于 iOS、Android、鸿蒙全端。
477
- 当需要在飞猪 App 上进行任何操作时,优先阅读此 Skill 了解页面结构和导航路径。
478
- 包含首页、搜索(猪搜)、飞猪千问(问一问)、结果页等核心页面的布局和操作方式。
479
- 包含门票、酒店、机票下单链路的页面描述和操作规则。
480
-
481
- Trigger keywords: 飞猪, fliggy, 猪搜, 飞猪千问, 问一问, 飞猪搜索, 飞猪app, 商卡, 结果页, 默认页, 品专, SUG, 联想词, 底纹词, 金刚词, 景点货架, POI, 门票, 酒店, 机票, 下单, 预订
482
- ---
483
-
484
- # 飞猪 App 操作指南(全端通用)
485
-
486
- > 本指南适用于 iOS、Android、鸿蒙端的飞猪 App 操作。
487
- > 在执行任何飞猪 App 相关任务前,先参考本文档了解页面结构和导航路径。
488
-
489
- ## App 版本区分
490
-
491
- - 手机上可能有两个"飞猪旅行" App:**测试包**和**正式包**
492
- - 区分方式:测试包图标右上角有"测试版"标记,正式包没有
493
- - **默认打开正式包**,除非用户明确要求打开测试包
494
-
495
- ## 首页(即"推荐"页)
496
-
497
- - 飞猪 App 首页是所有操作的默认起点
498
- - 除非用户明确要求"继续操作"或指定其他页面,否则每次任务都先回到首页
499
-
500
- ### 顶部导航栏(从左到右)
501
-
502
- | 导航项 | 说明 | 别名 |
503
- |---|---|---|
504
- | 飞猪千问 | 飞猪 AI 助手,可以对话提问 | 也叫"问一问" |
505
- | 推荐 | 飞猪 App 首页(默认停留页) | 首页 |
506
- | [城市名] | 基于定位显示的城市(如"杭州") | 目的地推荐 |
507
-
508
- ### 搜索区域
509
-
510
- - **搜索框**:内有滚动词(称为"底纹词"),点击搜索框进入搜索页,这个搜索叫"飞猪搜索"(简称"猪搜")
511
- - **搜索按钮**:搜索框右侧,点击可以用当前滚动到的"底纹词"直接搜索
512
- - **热词**:搜索框下方,推荐的搜索关键词
513
-
514
- ### 类目入口(热词下方)
515
-
516
- 用户要求"进入XX类目"时,优先从这里进入。包括:
517
- - 酒店、机票、火车票、汽车票、景点门票、旅游、用车、签证、邮轮、特价机票等
518
-
519
- ### 中间内容区
520
-
521
- - 活动 Banner(轮播图)
522
- - 推荐内容(酒店、旅游产品推荐卡片)
523
-
524
- ### 底部 Tab 导航栏
525
-
526
- - 首页 | 消息 | 行程 | 我的
527
-
528
- ### 首页弹窗处理
529
-
530
- 当 App 不在后台(冷启动)时,首页可能出现以下弹窗,需要先处理:
531
-
532
- 1. **全屏广告**:覆盖整个屏幕的推广广告
533
- - 处理方式:点击右上角"跳过"按钮,或等待几秒自动消失
534
- 2. **领券弹窗**:弹出的优惠券领取提示
535
- - 处理方式:点击弹窗上的 X 按钮关闭
536
-
537
- **操作顺序**:打开 App → 处理弹窗(如有) → 确认在首页 → 执行任务
538
-
539
- ## 默认页(从首页点击搜索框进入)
540
-
541
- 入口:首页 → 点击搜索框
542
-
543
- 页面布局(从上到下):
544
-
545
- 1. **搜索框**:
546
- - 内有灰色的"底纹词"(从首页进入时滚动停留的词,进入默认页后不再滚动)
547
- - 未输入内容时显示底纹词,输入内容后底纹词消失
548
- - 输入内容后点击搜索 → 进入搜索结果页(简称"结果页")
549
- - 输入过程中,搜索框下方会动态出现 **SUG(联想词)**,对用户输入内容进行补全
550
- 2. **金刚词**:搜索框下方的推荐词,点击可直接搜索
551
- 3. **搜索历史**:之前搜索过的词,点击也会跳转到结果页
552
- 4. **榜单**:搜索历史下方,包含一些榜单内容,可左右滑动,一般有 3-4 个
553
-
554
- ## SUG(联想词)
555
-
556
- - 用户在搜索框输入过程中,搜索框下方会动态显示联想补全词
557
- - 出现场景:
558
- 1. 默认页搜索框输入时
559
- 2. 搜索结果页点击搜索框输入时
560
- - SUG 内容可点击,不同类型的 SUG 会进入不同的页面
561
-
562
- ## 搜索结果页(结果页)
563
-
564
- 入口:默认页 → 输入内容点击搜索 / 点击金刚词 / 点击搜索历史
565
-
566
- 页面布局(从上到下):
567
-
568
- 1. **顶部搜索框**:
569
- - 点击可以不退出当前页面重新搜索
570
- - 左侧:返回按钮(回到上一页,很多页面都有)
571
- - 右侧:"我的收藏"(收藏夹,首页也有入口)
572
- 2. **品专**(可能出现):
573
- - 广告位,不一定每次都有,只有特定的搜索词才会有投放
574
- 3. **筛选**:品专下方的筛选条件
575
- 4. **商品卡片(商卡)**:
576
- - 左上角标注商卡类型(如"景点"、"酒店"等)
577
- - 景点也叫 POI
578
- - 左侧:图片;右侧:标题、标签、价格
579
- - 点击商卡 → 进入商品详情页(但景点商卡点击进入的是"景点货架",待补充)
580
-
581
- ## 飞猪千问(问一问)
582
-
583
- - **入口**:首页顶部导航栏最左侧的"飞猪千问"
584
- - 这是飞猪的 AI 助手对话页面,可以输入问题进行对话
585
- - **注意**:飞猪千问 ≠ 飞猪搜索(猪搜)
586
- - 飞猪千问:从顶部导航栏点击"飞猪千问"进入,是 AI 对话页面
587
- - 飞猪搜索:从搜索框进入,是商品搜索结果页
588
-
589
- ### 千问页面布局
590
-
591
- 1. **顶部导航栏**:
592
- - 左侧 icon:历史会话(查看之前的对话记录)
593
- - 右侧 icon:新建会话(开始一个新的对话)
594
- 2. **中间对话区域**:显示 AI 对话内容,包含推荐问题卡片
595
- 3. **底部输入框**:输入问题并发送消息
596
-
597
- ### 操作流程
598
-
599
- - 在底部输入框输入问题 → 点击发送 → 等待 AI 回复完成
600
- - 回复内容可能包含文字、卡片、推荐商品等
601
-
602
- ## 门票下单链路
603
-
604
- ### 门票列表页
605
-
606
- 在上方的搜索框里可以搜索对应的景点
607
-
608
- ### 门票详情页
609
-
610
- - 页面描述
611
- - 顶部是图片或者相关介绍视频,下方是预订、评价、推荐tab
612
- - 预订tab
613
- - 左侧是是可以可以快速锚定的索引,包括门票、联票或者更多套餐
614
- - 购买的话点击景点门票,此时会有不同的sku,成人票、儿童票等,选择其中一个开始预订。此时会直接调到下单页或者唤起一个浮层。在浮层里从上到下依次选择对应的选项,比如种类、日期等
615
- - 选择日期应该点日历图标来选择
616
-
617
- ### 门票下单页
618
-
619
- - 页面描述
620
- - 在该页面确保联系电话是否正确填写
621
- - 如果要填写出行人信息,请确保出行人信息右侧的勾选框已经勾选
622
- - **⚠️ 支付时唤起收银台即可,不要真正支付,否则会对用户的资产造成损失!!!**
623
-
624
- ## 酒店下单链路
625
-
626
- ### 飞猪首页 for 酒店
627
-
628
- - 页面描述
629
- - 头部搜索框和搜索按钮
630
- - 头部我的收藏按钮
631
- - 顶部金刚区域, 有各行业的入口, 一般为图片+文字,如酒店文案+酒店logo
632
- - 该页面操作限制规则:
633
- - 酒店应点击金刚区域的酒店按钮所在金刚(它在金刚区域第一个,在机票金刚的前一个), 点击即可进入酒店首页
634
-
635
- ### 酒店首页
636
-
637
- - 页面描述
638
- - 顶部4个tab: 国内(默认是被选中的)、国际、钟点房、民宿, 购买酒店流程中, 一般不需要切换点击
639
- - 国内tab下有五行,第一行左侧城市选择按钮(一般有城市名显示, 如: 北京/杭州/上海)、右侧关键字输入栏
640
- - 国内tab下有五行,第二行左侧酒店入住日期,右侧酒店离店日期
641
- - 国内tab下有五行,第三行左侧是房间数/成人数/儿童数选择按钮(点击出现浮层,可以修改房间数/成人数/儿童数),右侧是星级选择按钮(点击出现浮层,可以修改每间每晚价格/星级)
642
- - 国内tab下有五行,第四行是特殊关键字标签行,横向显示多个关键字标签,点击单个关键字标签,可以跳转带默认关键搜索词的酒店列表页
643
- - 国内tab下有五行,第五行是搜索酒店按钮,一般通过点击搜索酒店按钮,跳转酒店列表页。
644
- - 国内tab下的五行下方,是酒店猜你喜欢卡片列表。
645
- - 其他不常用按钮不做过多赘述
646
- - 页面操作限制规则
647
- - 只点击酒店详情页中的某一张房型卡片,不点击其他元素
648
-
649
- ### 酒店列表页
650
-
651
- - 页面描述
652
- - 顶部搜索栏左侧返回按钮,中间搜索栏(包含城市名称/酒店入离日期/房间数+人数/关键词栏(文字可能为空)),右侧地图icon按钮及三个点按钮
653
- - 搜索栏下方是筛选栏(包含推荐排序按钮、位置距离、价格/星级、筛选等按钮)
654
- - 筛选栏下方是卡片列表,由多张酒店卡片上下排列而成,点击卡片可以进入酒店详情页
655
- - 其他不常用按钮不做过多赘述
656
- - 页面操作限制规则
657
- - 只点击国内tab下第五行搜索酒店按钮
658
-
659
- ### 酒店详情页
660
-
661
- - 页面描述:
662
- - 顶部导航栏(导航栏一般为透明色)及按钮(按钮主要包括左侧返回按钮、右侧搜索Icon按钮、收藏星星Icon按钮、转发Icon按钮、三个点Icon的更多按钮)
663
- - 顶部导航栏下方是多媒体头图容器,支持横向滚动的视频、图片。
664
- - 头图容器下方是酒店标题组件(上下排布,上面是酒店标题文字,下方是酒店文字标签)
665
- - 酒店标题组件下方是一排三按钮(左中右三部分区块构成,左侧是酒店评分,中间是装修信息,右侧是地址信息)
666
- - 一排三按钮下方是会员及广告栏
667
- - 会员及广告栏下方是入离日期栏(包含左侧酒店入住离店日期、右侧房间数及人员数)
668
- - 入离日期栏下方是酒店房型卡片列表(一个由酒店房型卡片组成的支持上下滑动的卡片列表,每张卡片左侧是图片,右侧是上下多行文字描述,右下角带价格显示及预订按钮)
669
- - 其他不常用按钮不做过多赘述
670
- - 页面操作限制规则:
671
- - 当用户要下单某个房型时,应该先找到目标房型卡片(比如:大床房),可以先点击快捷筛选按钮,选择对应房型,再点击房型卡片中的 "订" 或者 "抢" 按钮操作进入下单页
672
-
673
- ### 酒店下单页
674
-
675
- - 页面描述
676
- - 头部有房型信息、入离日期等
677
- - 中间有入驻人信息和手机号
678
- - 底部有支付按钮和价格
679
- - **⚠️ 页面操作限制规则:判断到达该页面后,即可返回END结束,不要真实操作下单。**
680
-
681
- ### AI酒店组件校验列表页
682
-
683
- - 页面描述:
684
- - 顶部导航栏左侧返回按钮,中间文字标题"AI酒店组件校验列表"
685
- - 导航栏下方是一个列表页,由多个列表项的行组件组成,每个行组件左侧是icon,中间是上下两行文字,右侧是一个右箭头
686
- - 其他不常用按钮不做过多赘述
687
- - 页面操作限制规则
688
- - 可以点击单个列表项行组件进行跳转
689
- - 一次只可以点击单个行组件
690
-
691
- ## 机票下单链路
692
-
693
- ### 飞猪首页 for 机票
694
-
695
- - 页面描述
696
- - 头部搜索框和搜索按钮
697
- - 头部我的收藏按钮
698
- - 顶部金刚区域, 有各行业的入口, 一般为图片+文字,如机票文案+飞机logo
699
- - 该页面操作限制规则:
700
- - 机票应点击金刚区域的飞机按钮所在金刚(它在金刚区域第二个,酒店金刚后,火车票金刚前), 点击即可进入机票首页
701
-
702
- ### 机票首页
703
-
704
- - 页面描述:
705
- - 顶部4个tab: 机票/特价机票(默认)、火车票、汽车票、接送/租车, 购买机票流程中, 一般不需要切换点击
706
- - 顶部3个tab切换行程类型: 单程(默认), 多程, 往返, 如果没有特别说明, 一般是单程机票购买流程
707
- - **注意**:出发城市、到达城市位于页面左右两侧, 并且在搜索按钮前, OCR结果一般因为在左右两侧所有分为两个独立的元素, 在搜索按钮后, 可能存在出发城市-到达城市相关的快捷操作文案, 这不是入口, 在candidate_elements, 以及locate里都不要选择那些快捷操作文案
708
- - 出发城市: 一般有城市名(在页面左侧), 如: 北京
709
- - 到达城市: 一般有城市名(在页面右侧), 如: 杭州
710
- - 搜索机票: 按钮, 点击进入机票列表页
711
- - 其他不常用按钮不做过多赘述
712
-
713
- ### 机票首页-城市选择页
714
-
715
- - 页面描述: 城市选择页是机票首页的弹窗, 从点击出发地、目的地进入, 主要特征是一个遮罩在机票首页的半弹窗, 顶部有单目的地、多目的地的切换tab、中间有国内、国际城市的切换tab、主体内容是各个热门城市、地区的选项
716
- - 该页面操作限制规则:
717
- 1. 机票首页, 点击默认出发地、或目的地, 进入城市选择页
718
- 2. 返回Input, 参数是点击的搜索框以及输入的机场/城市内容
719
- 3. 选择对应的机场/城市
720
-
721
- ### 机票列表页
722
-
723
- - 页面描述:
724
- - 机票日期选择器: 页面顶部, 点击可切换日期, 会刷新列表
725
- - 机票列表, 点击进入不同机票详情
726
- - 页面操作限制规则
727
- - 机票日期选择器 (如无特殊要求, 请不要操作这部分): 如果最终目标中没有明确说明, 请不要在这里操作切换日期
728
- - 机票列表(请在此部分选择航班, 进入机票详情页): 一般展示航班的出发时间/机场、到达时间/机场, 以及机票价格, 如果最终目标提到了对应信息请选择对应的航班, 如果没有说明请任选一个进入机票详情页
729
-
730
- ### 机票详情页
731
-
732
- - 页面描述:
733
- - 头部信息: 展示机票航班信息, 只做展示都不可点击
734
- - 仓位切换tab: 默认经济舱, 可点击切换成公务/头等舱
735
- - 航班报价列表: 只有 "订" (完全一致, 不包含: 预订, 下订单等其他字)关键字的才可点击, 点击后进入填单页
736
- - 页面操作限制规则
737
- - 弹窗处理逻辑: 点击订后, 如果出现弹窗, 请点击继续购买相关的选项
738
-
739
- ### 机票下单页
740
-
741
- - 页面描述:
742
- - 头部信息: 展示机票航班信息, 只做展示都不可点击
743
- - 选择完乘机人后点击底部的按钮就行下单。按钮可能是"去支付"或者"预订"
744
- - 支付按钮
745
- - **⚠️ 该页面操作限制规则:判断到达该页面后,即可返回END结束,不要真实操作下单。**`;
746
- const debug = (0, logger_.getDebug)('android:app-knowledge');
747
- const MAX_SCREENSHOTS_PER_PAGE = 2;
748
- const packageConfigMap = {
749
- 'com.taobao.trip': {
750
- knowledge: fliggyKnowledge,
751
- screenshotDirName: 'fliggy',
752
- defaultPage: 'home',
753
- pageIds: [
754
- 'home',
755
- 'search-default',
756
- 'search-sug',
757
- 'search-result',
758
- 'fliggy-qwen',
759
- 'ticket-detail',
760
- 'hotel-list',
761
- 'hotel-detail',
762
- 'flight-list',
763
- 'flight-detail',
764
- 'ticket-order',
765
- 'hotel-order',
766
- 'flight-order'
767
- ]
469
+ const debug = (0, logger_.getDebug)('android:app-knowledge:oss');
470
+ function getOSSKnowledgeBaseUrl() {
471
+ const domain = process.env.MIDSCENE_OSS_DOMAIN;
472
+ if (domain) return `${domain.replace(/\/$/, '')}/app-knowledge`;
473
+ const bucket = process.env.MIDSCENE_OSS_BUCKET || 'xrayandroid';
474
+ const region = process.env.MIDSCENE_OSS_REGION || 'oss-cn-beijing';
475
+ return `https://${bucket}.${region}.aliyuncs.com/app-knowledge`;
476
+ }
477
+ async function fetchText(url) {
478
+ try {
479
+ const response = await fetch(url);
480
+ if (!response.ok) {
481
+ debug('HTTP %d for %s', response.status, url);
482
+ return null;
483
+ }
484
+ return await response.text();
485
+ } catch (error) {
486
+ debug('fetch failed for %s: %s', url, error.message);
487
+ return null;
768
488
  }
769
- };
770
- function getKnowledgeForPackage(packageName) {
771
- const config = packageConfigMap[packageName];
772
- if (config) {
773
- debug('found knowledge for package: %s', packageName);
774
- return config.knowledge;
489
+ }
490
+ async function fetchBuffer(url) {
491
+ try {
492
+ const response = await fetch(url);
493
+ if (!response.ok) {
494
+ debug('HTTP %d for %s', response.status, url);
495
+ return null;
496
+ }
497
+ const arrayBuffer = await response.arrayBuffer();
498
+ return Buffer.from(arrayBuffer);
499
+ } catch (error) {
500
+ debug('fetch failed for %s: %s', url, error.message);
501
+ return null;
775
502
  }
776
- debug('no knowledge found for package: %s', packageName);
777
- return null;
778
503
  }
779
- function getScreenshotsForPage(packageName, pageId) {
780
- const config = packageConfigMap[packageName];
781
- if (!config) {
782
- debug('no config for package: %s, skipping screenshots', packageName);
504
+ async function fetchAppManifest(appDirName) {
505
+ const baseUrl = getOSSKnowledgeBaseUrl();
506
+ const url = `${baseUrl}/${appDirName}/manifest.json`;
507
+ debug('fetching manifest from %s', url);
508
+ const text = await fetchText(url);
509
+ if (!text) return null;
510
+ try {
511
+ const manifest = JSON.parse(text);
512
+ debug('manifest loaded for %s: %d pageIds', appDirName, manifest.pageIds?.length ?? 0);
513
+ return manifest;
514
+ } catch (error) {
515
+ debug('failed to parse manifest for %s: %s', appDirName, error.message);
516
+ return null;
517
+ }
518
+ }
519
+ async function fetchTextKnowledge(appDirName) {
520
+ const baseUrl = getOSSKnowledgeBaseUrl();
521
+ const url = `${baseUrl}/${appDirName}/knowledge.txt`;
522
+ debug('fetching text knowledge from %s', url);
523
+ const text = await fetchText(url);
524
+ if (!text) return null;
525
+ debug('text knowledge loaded for %s (%d chars)', appDirName, text.length);
526
+ return text;
527
+ }
528
+ async function fetchScreenshotAsBase64(appDirName, pageId, fileName) {
529
+ const baseUrl = getOSSKnowledgeBaseUrl();
530
+ const url = `${baseUrl}/${appDirName}/screenshots/annotated/${pageId}/${fileName}`;
531
+ debug('fetching screenshot from %s', url);
532
+ const buffer = await fetchBuffer(url);
533
+ if (!buffer) return null;
534
+ const ext = fileName.split('.').pop()?.toLowerCase() || 'jpeg';
535
+ const mimeType = 'png' === ext ? 'image/png' : 'image/jpeg';
536
+ return {
537
+ name: `${pageId}-${fileName}`,
538
+ url: `data:${mimeType};base64,${buffer.toString('base64')}`
539
+ };
540
+ }
541
+ const app_knowledge_debug = (0, logger_.getDebug)('android:app-knowledge');
542
+ const packageOssDirMap = {
543
+ 'com.taobao.trip': 'fliggy'
544
+ };
545
+ function getOssDirForPackage(packageName) {
546
+ return packageOssDirMap[packageName] ?? null;
547
+ }
548
+ async function getKnowledgeForPackage(packageName) {
549
+ const ossDirName = getOssDirForPackage(packageName);
550
+ if (!ossDirName) {
551
+ app_knowledge_debug('no OSS dir mapping for package: %s', packageName);
552
+ return null;
553
+ }
554
+ const knowledge = await fetchTextKnowledge(ossDirName);
555
+ knowledge ? app_knowledge_debug('loaded OSS text knowledge for package: %s', packageName) : app_knowledge_debug('no OSS text knowledge available for package: %s', packageName);
556
+ return knowledge;
557
+ }
558
+ async function getScreenshotsForPage(packageName, pageId) {
559
+ const ossDirName = getOssDirForPackage(packageName);
560
+ if (!ossDirName) {
561
+ app_knowledge_debug('no OSS dir mapping for package: %s, skipping screenshots', packageName);
783
562
  return [];
784
563
  }
785
- const pageDir = external_node_path_.join(__dirname, 'screenshots', config.screenshotDirName, pageId);
786
- if (!external_node_fs_.existsSync(pageDir)) {
787
- debug('screenshot directory not found: %s', pageDir);
564
+ const manifest = await fetchAppManifest(ossDirName);
565
+ if (!manifest) {
566
+ app_knowledge_debug('no manifest available for %s, skipping screenshots', ossDirName);
788
567
  return [];
789
568
  }
790
- try {
791
- const files = external_node_fs_.readdirSync(pageDir);
792
- const imageFiles = files.filter((f)=>/\.(jpg|jpeg|png)$/i.test(f)).map((f)=>external_node_path_.join(pageDir, f));
793
- if (0 === imageFiles.length) {
794
- debug('no image files in directory: %s', pageDir);
795
- return [];
796
- }
797
- const result = imageFiles.slice(0, MAX_SCREENSHOTS_PER_PAGE);
798
- debug('found %d screenshot(s) for %s/%s (returning %d)', imageFiles.length, packageName, pageId, result.length);
799
- return result;
800
- } catch (err) {
801
- debug('error reading screenshot directory %s: %s', pageDir, err);
569
+ const fileNames = manifest.screenshots?.[pageId];
570
+ if (!fileNames?.length) {
571
+ app_knowledge_debug('no screenshot files listed for %s/%s', ossDirName, pageId);
802
572
  return [];
803
573
  }
574
+ const results = await Promise.all(fileNames.map((fileName)=>fetchScreenshotAsBase64(ossDirName, pageId, fileName)));
575
+ const screenshots = results.filter((r)=>null !== r);
576
+ app_knowledge_debug('fetched %d/%d screenshot(s) for %s/%s', screenshots.length, fileNames.length, packageName, pageId);
577
+ return screenshots;
804
578
  }
805
- function getDefaultPageForPackage(packageName) {
806
- const config = packageConfigMap[packageName];
807
- return config?.defaultPage ?? null;
579
+ async function getDefaultPageForPackage(packageName) {
580
+ const ossDirName = getOssDirForPackage(packageName);
581
+ if (!ossDirName) return null;
582
+ const manifest = await fetchAppManifest(ossDirName);
583
+ return manifest?.defaultPage ?? null;
808
584
  }
809
- function getPageIdsForPackage(packageName) {
810
- const config = packageConfigMap[packageName];
811
- return config?.pageIds ?? [];
585
+ async function getPageIdsForPackage(packageName) {
586
+ const ossDirName = getOssDirForPackage(packageName);
587
+ if (!ossDirName) return [];
588
+ const manifest = await fetchAppManifest(ossDirName);
589
+ return manifest?.pageIds ?? [];
812
590
  }
813
591
  const defaultAppNameMapping = {
814
592
  微信: 'com.tencent.mm',
@@ -909,7 +687,11 @@ description: >
909
687
  const external_node_assert_namespaceObject = require("node:assert");
910
688
  var external_node_assert_default = /*#__PURE__*/ __webpack_require__.n(external_node_assert_namespaceObject);
911
689
  const external_node_child_process_namespaceObject = require("node:child_process");
690
+ var external_node_fs_ = __webpack_require__("node:fs");
691
+ var external_node_fs_default = /*#__PURE__*/ __webpack_require__.n(external_node_fs_);
912
692
  var external_node_module_ = __webpack_require__("node:module");
693
+ var external_node_path_ = __webpack_require__("node:path");
694
+ var external_node_path_default = /*#__PURE__*/ __webpack_require__.n(external_node_path_);
913
695
  const core_namespaceObject = require("@xiuchang-midscene/core");
914
696
  const device_namespaceObject = require("@xiuchang-midscene/core/device");
915
697
  const core_utils_namespaceObject = require("@xiuchang-midscene/core/utils");
@@ -2282,20 +2064,21 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
2282
2064
  command
2283
2065
  });
2284
2066
  }
2285
- loadAppKnowledge(packageName) {
2067
+ async loadAppKnowledge(packageName) {
2286
2068
  if (this.lastKnowledgePackageName === packageName) return void debugAgent('knowledge already loaded for package: %s, skipping', packageName);
2287
- const knowledge = getKnowledgeForPackage(packageName);
2069
+ const knowledge = await getKnowledgeForPackage(packageName);
2288
2070
  if (knowledge) {
2289
2071
  this.setAIActContext(knowledge);
2290
2072
  debugAgent('loaded app knowledge for package: %s', packageName);
2291
2073
  } else debugAgent('no app knowledge available for package: %s', packageName);
2292
- const pageIds = getPageIdsForPackage(packageName);
2074
+ const pageIds = await getPageIdsForPackage(packageName);
2293
2075
  if (pageIds.length > 0) {
2294
2076
  this.availablePageIds = pageIds;
2077
+ const defaultPage = await getDefaultPageForPackage(packageName);
2295
2078
  this.referenceScreenshotProvider = async (pageId)=>{
2296
- const effectivePageId = pageId ?? getDefaultPageForPackage(packageName);
2079
+ const effectivePageId = pageId ?? defaultPage;
2297
2080
  if (!effectivePageId) return [];
2298
- return this.resolveReferenceScreenshots(effectivePageId, packageName);
2081
+ return getScreenshotsForPage(packageName, effectivePageId);
2299
2082
  };
2300
2083
  debugAgent('screenshot knowledge provider set for package: %s (%d pages)', packageName, pageIds.length);
2301
2084
  } else {
@@ -2304,25 +2087,11 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
2304
2087
  }
2305
2088
  this.lastKnowledgePackageName = packageName;
2306
2089
  }
2307
- async resolveReferenceScreenshots(pageId, packageName) {
2308
- const screenshotPaths = getScreenshotsForPage(packageName, pageId);
2309
- if (!screenshotPaths.length) return [];
2310
- return Promise.all(screenshotPaths.map(async (p, i)=>{
2311
- const base64 = await external_node_fs_.promises.readFile(p, {
2312
- encoding: 'base64'
2313
- });
2314
- const ext = external_node_path_.extname(p).slice(1) || 'jpeg';
2315
- return {
2316
- name: `${pageId}-${i + 1}`,
2317
- url: `data:image/${ext};base64,${base64}`
2318
- };
2319
- }));
2320
- }
2321
2090
  async detectAndLoadAppKnowledge() {
2322
2091
  try {
2323
2092
  const dumpsysOutput = await this.runAdbShell('dumpsys activity activities | grep -E "mResumedActivity|mTopActivityRecord"');
2324
2093
  const packageName = parseForegroundPackageName(dumpsysOutput);
2325
- if (packageName) this.loadAppKnowledge(packageName);
2094
+ if (packageName) await this.loadAppKnowledge(packageName);
2326
2095
  else debugAgent('could not detect foreground app package name');
2327
2096
  } catch (error) {
2328
2097
  debugAgent('failed to detect foreground app for knowledge injection:', error);
@@ -61,14 +61,7 @@ export declare class AndroidAgent extends Agent<AndroidDevice> {
61
61
  * Skips if the same package knowledge is already loaded.
62
62
  * @param packageName - The Android package name (e.g. "com.taobao.trip")
63
63
  */
64
- loadAppKnowledge(packageName: string): void;
65
- /**
66
- * Resolve annotated reference screenshots for a given page to base64 data URLs.
67
- * @param pageId - The page identifier
68
- * @param packageName - The Android package name
69
- * @returns Array of base64 image data objects, empty if no screenshots found
70
- */
71
- private resolveReferenceScreenshots;
64
+ loadAppKnowledge(packageName: string): Promise<void>;
72
65
  /**
73
66
  * Detect the foreground Android app and automatically load its business knowledge.
74
67
  * Uses ADB to get the currently resumed activity and extract the package name,
@@ -60,14 +60,7 @@ declare class AndroidAgent extends Agent<AndroidDevice> {
60
60
  * Skips if the same package knowledge is already loaded.
61
61
  * @param packageName - The Android package name (e.g. "com.taobao.trip")
62
62
  */
63
- loadAppKnowledge(packageName: string): void;
64
- /**
65
- * Resolve annotated reference screenshots for a given page to base64 data URLs.
66
- * @param pageId - The page identifier
67
- * @param packageName - The Android package name
68
- * @returns Array of base64 image data objects, empty if no screenshots found
69
- */
70
- private resolveReferenceScreenshots;
63
+ loadAppKnowledge(packageName: string): Promise<void>;
71
64
  /**
72
65
  * Detect the foreground Android app and automatically load its business knowledge.
73
66
  * Uses ADB to get the currently resumed activity and extract the package name,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xiuchang-midscene/android",
3
- "version": "2.0.3",
3
+ "version": "2.0.4",
4
4
  "description": "Android automation library for Midscene",
5
5
  "keywords": [
6
6
  "Android UI automation",
@@ -42,8 +42,8 @@
42
42
  "@yume-chan/stream-extra": "2.1.0",
43
43
  "appium-adb": "12.12.1",
44
44
  "sharp": "^0.34.3",
45
- "@xiuchang-midscene/shared": "^2.0.3",
46
- "@xiuchang-midscene/core": "^2.0.3"
45
+ "@xiuchang-midscene/shared": "^2.0.4",
46
+ "@xiuchang-midscene/core": "^2.0.4"
47
47
  },
48
48
  "optionalDependencies": {
49
49
  "@ffmpeg-installer/ffmpeg": "^1.1.0"