@vmosedge/workflow-agent-sdk 1.0.0 → 1.0.1

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.
@@ -1 +1 @@
1
- "use strict";var e=require("uuid"),t=require("../chunks/scriptGenerator-B-5_FRJ5.cjs"),s=require("fs"),n=require("os"),r=require("path"),o=require("better-sqlite3"),i=require("zod"),a=require("zod-to-json-schema"),c=require("@langchain/mcp-adapters");require("@langchain/core/prompts"),require("@langchain/core/output_parsers"),require("@langchain/core/runnables"),require("@langchain/anthropic"),require("@langchain/core/messages"),require("@langchain/core/tools"),require("@langchain/google-genai"),require("@langchain/openai"),require("crypto");function l(){return r.join(function(){const e=n.homedir();if(!e)throw new Error("Failed to resolve user home directory for session persistence");return r.join(e,".vmosedge")}(),"agent_sessions.db")}function u(e){return{sessionId:e.session_id,goal:e.goal,status:(t=e.status,"running"===t||"paused"===t||"stopped"===t?t:"stopped"),providerVendor:e.provider_vendor||"unknown",deviceId:e.device_id||"",totalIterations:e.total_iterations||0,lastError:e.last_error||void 0,lastErrorCode:e.last_error_code||void 0,createTime:e.create_time,updateTime:e.update_time};var t}class d{db;constructor(e){!function(e){const t=r.dirname(e);s.existsSync(t)||s.mkdirSync(t,{recursive:!0})}(e),this.db=new o(e),this.initPragmas(),this.initSchema()}initPragmas(){this.db.pragma("busy_timeout = 5000"),this.db.pragma("foreign_keys = ON"),this.db.pragma("synchronous = NORMAL");try{this.db.pragma("journal_mode = WAL")}catch{this.db.pragma("journal_mode = DELETE")}}initSchema(){this.db.exec("\n CREATE TABLE IF NOT EXISTS agent_meta (\n meta_key TEXT PRIMARY KEY,\n meta_value TEXT NOT NULL,\n update_time INTEGER NOT NULL\n );\n CREATE TABLE IF NOT EXISTS agent_sessions (\n session_id TEXT PRIMARY KEY,\n goal TEXT NOT NULL,\n status TEXT NOT NULL,\n provider_vendor TEXT NOT NULL DEFAULT '',\n device_id TEXT NOT NULL DEFAULT '',\n total_iterations INTEGER NOT NULL DEFAULT 0,\n last_error TEXT,\n last_error_code TEXT,\n state_json TEXT NOT NULL,\n create_time INTEGER NOT NULL,\n update_time INTEGER NOT NULL\n );\n "),this.ensureSessionColumns(),this.db.exec("\n CREATE INDEX IF NOT EXISTS idx_agent_sessions_update_time\n ON agent_sessions(update_time DESC);\n CREATE INDEX IF NOT EXISTS idx_agent_sessions_status_update_time\n ON agent_sessions(status, update_time DESC);\n CREATE INDEX IF NOT EXISTS idx_agent_sessions_device_update_time\n ON agent_sessions(device_id, update_time DESC);\n "),this.saveSchemaVersion("3")}ensureSessionColumns(){const e=this.db.prepare("PRAGMA table_info(agent_sessions)").all(),t=new Set(e.map(e=>e.name)),s=[{name:"provider_vendor",definition:"TEXT NOT NULL DEFAULT ''"},{name:"device_id",definition:"TEXT NOT NULL DEFAULT ''"},{name:"total_iterations",definition:"INTEGER NOT NULL DEFAULT 0"},{name:"last_error",definition:"TEXT"},{name:"last_error_code",definition:"TEXT"}];for(const e of s)t.has(e.name)||this.db.exec(`ALTER TABLE agent_sessions ADD COLUMN ${e.name} ${e.definition}`)}saveSchemaVersion(e){this.db.prepare("\n INSERT INTO agent_meta (meta_key, meta_value, update_time)\n VALUES (@meta_key, @meta_value, @update_time)\n ON CONFLICT(meta_key) DO UPDATE SET\n meta_value=excluded.meta_value,\n update_time=excluded.update_time\n ").run({meta_key:"schema_version",meta_value:e,update_time:Date.now()})}buildQueryFilter(e){const t=[],s={};e.status&&(t.push("status = @status"),s.status=e.status),e.deviceId&&(t.push("device_id = @device_id"),s.device_id=e.deviceId);const n=e.keyword?.trim();return n&&(t.push("goal LIKE @keyword"),s.keyword=`%${n}%`),{whereSql:t.length>0?`WHERE ${t.join(" AND ")}`:"",params:s}}saveSession(e,t){const s=Date.now(),n=e.running?"running":e.paused?"paused":"stopped",r=JSON.stringify(e);this.db.prepare("\n INSERT INTO agent_sessions (\n session_id, goal, status, provider_vendor, device_id,\n total_iterations,\n last_error, last_error_code, state_json, create_time, update_time\n )\n VALUES (\n @session_id, @goal, @status, @provider_vendor, @device_id,\n @total_iterations,\n @last_error, @last_error_code, @state_json, @create_time, @update_time\n )\n ON CONFLICT(session_id) DO UPDATE SET\n goal=excluded.goal,\n status=excluded.status,\n provider_vendor=excluded.provider_vendor,\n device_id=excluded.device_id,\n total_iterations=excluded.total_iterations,\n last_error=excluded.last_error,\n last_error_code=excluded.last_error_code,\n state_json=excluded.state_json,\n update_time=excluded.update_time\n ").run({session_id:e.sessionId,goal:e.goal,status:n,provider_vendor:e.provider.vendor,device_id:e.device.deviceId,total_iterations:e.iteration,last_error:t?.lastError||null,last_error_code:t?.lastErrorCode||null,state_json:r,create_time:s,update_time:s})}loadSession(e){const t=this.db.prepare("SELECT * FROM agent_sessions WHERE session_id = ? LIMIT 1").get(e);if(!t)return null;try{return JSON.parse(t.state_json)}catch{return null}}getSessionSummary(e){const t=this.db.prepare("\n SELECT\n session_id, goal, status, provider_vendor, device_id, total_iterations,\n last_error, last_error_code, create_time, update_time, state_json\n FROM agent_sessions\n WHERE session_id = ? LIMIT 1\n ").get(e);return t?u(t):null}listSessions(e={}){const{whereSql:t,params:s}=this.buildQueryFilter(e),n=function(e){return"number"!=typeof e||Number.isNaN(e)?50:Math.min(500,Math.max(1,Math.floor(e)))}(e.limit),r=function(e){return"number"!=typeof e||Number.isNaN(e)?0:Math.max(0,Math.floor(e))}(e.offset);return this.db.prepare(`\n SELECT\n session_id, goal, status, provider_vendor, device_id, total_iterations,\n last_error, last_error_code, create_time, update_time, state_json\n FROM agent_sessions\n ${t}\n ORDER BY update_time DESC\n LIMIT @limit OFFSET @offset\n `).all({...s,limit:n,offset:r}).map(u)}countSessions(e={}){const{whereSql:t,params:s}=this.buildQueryFilter(e),n=this.db.prepare(`\n SELECT COUNT(1) as total\n FROM agent_sessions\n ${t}\n `).get(s);return n?.total||0}deleteSession(e){return this.db.prepare("DELETE FROM agent_sessions WHERE session_id = ?").run(e).changes>0}close(){this.db.close()}}function m(e,t){const s=t.normalize("NFKC");return"text"===e||"contentDesc"===e?s.replace(/\s+/gu," ").trim():"bounds"===e?s.replace(/\s+/gu,""):s.trim()}function _(e,t,s){return m(e,t)===m(e,s)}function p(e,t){return(void 0===t.index||e.index===t.index)&&(!!(!t.resourceId||e.resourceId&&_("resourceId",t.resourceId,e.resourceId))&&(!!(!t.text||e.text&&_("text",t.text,e.text))&&(!!(!t.contentDesc||e.contentDesc&&_("contentDesc",t.contentDesc,e.contentDesc))&&(!!(!t.className||e.className&&_("className",t.className,e.className))&&!!(!t.bounds||e.bounds&&_("bounds",t.bounds,e.bounds))))))}function f(e,t){let s=0;for(const n of e)p(n,t)&&(s+=1);return s}function g(e,t,s){return void 0===t||void 0===s?t===s:_(e,t,s)}function h(e,t){return e===t||e.index===t.index&&g("resourceId",e.resourceId,t.resourceId)&&g("text",e.text,t.text)&&g("contentDesc",e.contentDesc,t.contentDesc)&&g("className",e.className,t.className)&&g("bounds",e.bounds,t.bounds)}function b(e,t,s){let n=0;for(const r of e)if(p(r,t)){if(h(r,s))return n;n+=1}}const y=e=>i.z.string().describe(e),x=e=>i.z.coerce.number().describe(e),v=e=>i.z.coerce.number().int().positive().describe(e),I=e=>{const t=i.z.coerce.number().int().positive().optional();return e?t.describe(e):t},T=e=>{const t=i.z.coerce.number().int().min(0).optional();return e?t.describe(e):t},w=e=>{const t=i.z.string().optional();return e?t.describe(e):t},N=(e,t)=>i.z.enum(e).optional().describe(t);function C(e){const t=a.zodToJsonSchema(e,{$refStrategy:"none"}),{$schema:s,definitions:n,...r}=t;return r}function E(e){return{name:e.name,description:e.description,category:e.category,schema:e.schema,parameters:C(e.schema)}}const A=i.z.object({resource_id:w("元素资源ID,如 com.example:id/btn"),text:w("元素文本,支持正则"),content_desc:w("元素内容描述,支持正则"),class_name:w("元素类名"),bounds:w("元素 bounds,格式 [x1,y1][x2,y2],仅用于兜底消歧,不建议直接进入脚本")}).strict(),S=N(["home","list","detail","dialog","search","form","unknown"],"可选:显式声明当前页面类型。仅在你有把握时填写;不确定则省略。"),O=N(["navigation","search_entry","action_button","input_field","content_item","generic_target"],"可选:显式声明目标元素角色。仅在你有把握时填写;不确定则省略。"),L=N(["critical","supporting","noise"],"可选:显式声明本轮动作对主流程的重要性。仅在你有把握时填写;不确定则省略。"),R=N(["main_flow","exception_handler_candidate","log_only"],"可选:显式声明该记录应进入主流程、异常处理还是仅日志。仅在你有把握时填写;不确定则省略。"),D=N(["next_action","next_verified_transition","manual_close"],"可选:声明该上下文持续到何时结束。默认 next_verified_transition。"),k=i.z.object({step_intent:y("本次操作在任务流程中的目的和预期效果。必须说明为什么执行此操作、期望达到什么状态,而非重复元素属性描述。"),merge_key:w("可选:稳定的步骤合并键。用于把同类动作合并为同一脚本步骤,例如 open_search_page、scroll_result_list。仅在你能明确抽象出可复用步骤语义时填写。"),expected_result:w("可选:本次动作完成后预期达到的状态描述。建议使用比 step_intent 更直接的结果表述,例如“进入搜索页”“列表向下推进一屏”。"),wait_ms_hint:I("可选:建议回放时在主动作后等待或验证的毫秒数。仅在你明确知道该步骤通常需要额外等待时填写。"),page_kind:S,target_role:O,criticality_hint:L,include_mode_hint:R,anchor:i.z.object({desc:y("页面锚点元素描述,确认当前所在页面"),resource_id:w(),text:w(),content_desc:w(),class_name:w(),bounds:w()}).describe("页面锚点元素,必填"),target:i.z.object({desc:y("操作目标元素描述"),resource_id:w(),text:w(),content_desc:w(),class_name:w(),bounds:w()}).describe("本次操作的目标元素,必填"),post_verify:i.z.object({desc:y("操作成功后验证元素描述"),resource_id:w(),text:w(),content_desc:w(),class_name:w(),bounds:w()}).optional().describe("Phase 1 内部提示字段。告知 LLM 本次操作的最终预期状态,用于决定是否需要执行 observe_screen(wait_ms) + verify_ui_state。此字段不参与脚本生成;脚本的验证条件来自 verify_ui_state 的 verify_element。")}).describe("操作前必填的 UI 上下文,anchor+target+step_intent 强制要求;page_kind/target_role/criticality_hint/include_mode_hint 在明确时建议显式填写,用于录制质量约束和回放。"),F=i.z.coerce.number().int().min(0).optional().describe("匹配结果动作索引(从 0 开始,仅 accessibility/node 使用)"),$=i.z.object({wait_timeout:v("等待元素出现超时(ms)").optional(),wait_interval:v("重试间隔(ms)").optional()}),U=E({name:"observe_screen",description:"获取当前屏幕的 UI 无障碍节点树。返回简化后的 UI 结构,包含每个可交互元素的 text、resource-id、content-desc、class、bounds 等属性。可选传 wait_ms:先等待指定毫秒再拉取,用于动作后「等待加载 + 获取新页面」一步完成。",schema:i.z.object({wait_ms:v("可选:先等待的毫秒数,再拉取 UI").optional()}),category:"observation"}),M=E({name:"get_installed_apps",description:"获取设备上已安装的应用列表,返回每个应用的包名(package_name)和应用名(app_name)。用于查找目标应用的包名以便启动。",schema:i.z.object({type:i.z.enum(["user","system","all"]).optional().describe("应用类型过滤:user(默认) / system / all")}),category:"observation"}),P=E({name:"get_top_activity",description:"获取当前前台应用的 Activity 信息,返回 package_name 和 class_name。用于判断当前在哪个应用的哪个页面。",schema:i.z.object({}),category:"observation"}),j=E({name:"get_screen_info",description:"获取屏幕显示信息,返回 width、height、rotation、orientation。用于确定屏幕分辨率以计算滑动/点击坐标。",schema:i.z.object({}),category:"observation"}),z=i.z.object({desc:y("元素简短描述"),resource_id:w("元素 resource-id,来自 observe_screen"),text:w("元素文本,来自 observe_screen"),content_desc:w("元素 content-desc"),class_name:w("元素类名,来自 observe_screen"),index:T("节点 index(来自 observe_screen 的 [index])")}),q=E({name:"verify_ui_state",description:"动作执行后,记录用于验证操作成功的标志性 UI 元素。必须先调用 observe_screen(可带 wait_ms)获取新界面,再调用本工具;verify_element 的 text/resource_id 必须来自当前界面真实节点,禁止编造。建议同时记录节点 index(observe_screen 的 [index])提高回放稳定性。当操作导致页面切换时,page_anchor 必须填写新页面的标志性元素。screen_desc 必填,用一句话描述当前页面;若你明确知道当前页面类型,可额外填写 page_kind;若该步骤回放通常需要额外等待,可填写 wait_ms_hint。",schema:i.z.object({step_desc:y("验证步骤描述"),page_kind:S,wait_ms_hint:I("可选:建议回放在验证前等待的毫秒数。仅在你明确知道该步骤通常需要额外等待时填写。"),verify_element:z.describe("用于验证的 UI 元素,必须来自当前 observe_screen 结果"),screen_desc:y('当前页面的简要描述,用一句话概括所在页面(如"抖音首页信息流"、"微信聊天列表"、"系统设置-通用页面")。此字段帮助脚本生成 AI 理解操作上下文,必填。'),page_anchor:z.optional().describe("动作后的页面锚点元素(如页面切换后的新页面标题/导航栏)。当操作导致页面切换时必填,用于脚本生成时确认导航成功。")}),category:"observation"}),G=i.z.object({desc:y("元素描述"),resource_id:w(),text:w(),content_desc:w(),class_name:w(),bounds:w(),index:T("节点 index(来自 observe_screen 的 [index])")}),V=E({name:"record_search_context",description:"在 swipe 或 press_key 前调用,记录本轮查找目标(target_desc + target_element)和当前页面锚点(anchor_element)。元素属性必须来自最近一次 observe_screen 的真实结果;step_intent、target_desc、anchor_element 必填。建议同时记录节点 index(observe_screen 的 [index])。",schema:i.z.object({step_intent:y("本次滑动/按键在任务流程中的目的和预期效果,说明为什么执行此操作、期望达到什么状态。"),merge_key:w("可选:稳定的步骤合并键。用于把同类滑动/按键动作合并为同一脚本步骤,例如 scroll_result_list、submit_search。"),expected_result:w("可选:本次动作完成后预期达到的状态描述,例如“结果列表继续向下推进”“进入详情页”。"),wait_ms_hint:I("可选:建议回放时在本轮动作后等待或验证的毫秒数。仅在你明确知道该步骤通常需要额外等待时填写。"),target_desc:y("本轮查找目标描述"),target_element:G.optional().describe("查找目标元素,可选"),anchor_element:G.describe("当前页面锚点元素,必填"),page_kind:S,target_role:O,criticality_hint:L,include_mode_hint:R,valid_until:D}),category:"observation"});var Y;const B=[E({name:"start_app",description:"启动指定应用。需要 package_name。工具会在启动前自动执行 permission/set(grant_all=true)。",schema:i.z.object({package_name:y("应用包名")}),category:"action"}),E({name:"stop_app",description:"停止(强制关闭)指定应用。",schema:i.z.object({package_name:y("应用包名")}),category:"action"}),E({name:"tap",description:"在屏幕指定坐标点击。属于兜底方案:仅当按 resource_id -> text -> content_desc -> 最小组合 仍无法唯一定位元素时才允许使用。",schema:i.z.object({x:x("X 坐标"),y:x("Y 坐标")}),category:"action"}),E({name:"tap_element",description:"通过选择器查找并点击 UI 元素。这是推荐点击方式。\n\n调用规则:\n1. pre_context.anchor(页面锚点)和 pre_context.target(目标元素)必填,不可省略\n2. selector 遵循脚本优先策略:优先稳定 resource_id,其次稳定 text,再次稳定 content_desc,最后最小组合;bounds 仅作兜底消歧\n3. 序号点击使用 action_index(从 0 开始),严禁 selector.index\n4. 若点击会导致界面变化,最终验证必须:observe_screen(wait_ms) -> verify_ui_state",schema:i.z.object({pre_context:k,selector:A.describe("元素选择器"),action_index:F,...$.shape}).strict(),category:"action"}),E({name:"long_press_element",description:"通过选择器查找并长按 UI 元素。\n\n调用规则:\n1. pre_context.anchor 和 pre_context.target 必填,不可省略\n2. selector 遵循脚本优先策略:优先稳定 resource_id,其次稳定 text,再次稳定 content_desc,最后最小组合;bounds 仅作兜底消歧\n3. 序号操作使用 action_index(从 0 开始),严禁 selector.index\n4. 长按通常触发界面变化,最终验证必须:observe_screen(wait_ms) -> verify_ui_state",schema:i.z.object({pre_context:k,selector:A.describe("元素选择器"),action_index:F,...$.shape}).strict(),category:"action"}),E({name:"set_text",description:"通过选择器定位输入框并设置文本内容。\n\n调用规则:\n1. pre_context.anchor 和 pre_context.target 必填,不可省略\n2. 多个输入框时使用 action_index(从 0 开始),严禁 selector.index\n3. 若需要提交输入内容,紧接调用 press_key(66) 或等价提交动作\n4. 若后续操作导致界面变化,最终验证必须:observe_screen(wait_ms) -> verify_ui_state",schema:i.z.object({pre_context:k,selector:A.describe("输入框选择器"),action_index:F,text:y("要输入的文本"),...$.shape}).strict(),category:"action"}),E({name:"input_text",description:"向当前聚焦输入框直接输入文本,不需要 selector。",schema:i.z.object({text:y("要输入的文本")}),category:"action"}),E({name:"swipe",description:"执行坐标滑动手势,常用于滚动列表加载更多内容。\n\n调用规则:\n1. swipe 前先调用 record_search_context 记录查找目标与锚点\n2. swipe 后调用 observe_screen(wait_ms=500~1200),再根据结果决定是否继续滑动或调用 verify_ui_state\n3. 坐标依赖屏幕分辨率,调用前应已通过 get_screen_info 确认分辨率",schema:i.z.object({start_x:x("起点 X"),start_y:x("起点 Y"),end_x:x("终点 X"),end_y:x("终点 Y"),duration:x("持续时间(ms)").optional()}),category:"action"}),E({name:"press_key",description:"按下物理/虚拟按键。常用键码:3=Home, 4=Back, 66=Enter, 82=菜单等。调用前可先 record_search_context;按键会改变界面时,后续需 observe_screen(wait_ms) -> verify_ui_state。",schema:i.z.object({key_code:(Y="Android KeyCode",i.z.coerce.number().int().describe(Y))}),category:"action"}),E({name:"wait",description:"暂停指定时间。用于等待页面加载或动画完成。时长需按前一动作自适应:轻交互 300~800ms,滑动后 500~1200ms,页面切换/返回 1200~2500ms,启动应用 2000~4000ms;禁止全程固定同一时长。",schema:i.z.object({duration:x("等待时间(ms)")}),category:"action"})],X=[...[U,q,V,M,P,j],...B];function W(e){return B.some(t=>t.name===e)}const K=new Set(["btn","button","text","txt","img","image","icon","view","layout","container","content","header","footer","title","subtitle","label","input","edit","search","menu","nav","tab","bar","list","item","card","dialog","popup","modal","scroll","pager","recycler","grid","frame","root","main","action","toolbar","status","progress","loading","empty","error","divider","separator","wrapper","panel","avatar","profile","name","email","phone","password","submit","cancel","confirm","close","back","next","home","setting","notification","message","chat","comment","like","share","follow","post","feed","story","video","audio","play","pause","camera","photo","gallery","download","upload","save","delete","add","create","update","refresh","filter","sort","check","switch","toggle","radio","select","option","dropdown","picker","date","row","column","cell","section","group","block","region","area","top","bottom","left","right","center","start","end","inner","outer","overlay","badge","count","number","description","info","hint","placeholder","detail","summary","expand","collapse","more","viewpager","appbar","bottombar","snackbar","fab","chip","spinner","seekbar","slider","webview","mapview","banner","splash","widget"]);function J(e){if(!e)return!1;const t=e.includes("/")?e.split("/").pop():e;return!!t&&(!e.startsWith("android:")&&(!!/obfuscated/i.test(t)||(!!/^\d+_/.test(t)||!function(e){const t=e.toLowerCase(),s=t.split("_");for(const e of s)if(e.length>=3&&K.has(e))return!0;for(const e of K)if(e.length>=4&&t.includes(e))return!0;return!1}(t)&&(t.length<=5||t.length<=10&&function(e){if(0===e.length)return 0;const t=new Map;for(const s of e)t.set(s,(t.get(s)||0)+1);let s=0;for(const n of t.values()){const t=n/e.length;s-=t*Math.log2(t)}return s}(t)>3))))}function H(e){return e.normalize("NFKC").replace(/\s+/gu," ").trim()}function Q(e,t,s,n){const r=[],o=function(e,t,s){if(!s)return 0;const n=new Set;for(const r of e){if(r.resourceId!==s)continue;const e="text"===t?r.text:r.contentDesc;e&&n.add(H(e))}return n.size}(s,"text"===n?"text":"contentDesc",t.resourceId),i=function(e){const t=H(e);return!!t&&(/\b\d+\b/u.test(t)||/\d+[::]\d+/u.test(t)||/\d+[./-]\d+/u.test(t)||/[$¥¥]\s*\d/u.test(t)||/\brow\b|\bcolumn\b|行|列/u.test(t)||/@\w+/u.test(t))}(e),a=function(e){const t=H(e);return t.length>24||/合集|日记|vlog|攻略|教程|推荐|详情|播放|第.+集/u.test(t)}(e),c=e.length<=12,l=1===f(s,"text"===n?{text:e}:{contentDesc:e});o>=2&&r.push("same resource_id carries multiple values on screen"),i&&r.push("contains dynamic segment"),a&&r.push("looks like content item text"),c||r.push("text is long or verbose"),l||r.push("not unique on current screen");const u=0===r.length||c&&l&&r.length<=1,d=u&&!i&&!a;return u&&t.clickable&&c&&r.push("short interactive label"),{stable:u,reusable:d,risk:d?"low":u?"medium":"high",reasons:r}}function Z(e,t){const s=[];if(e.resourceId){const t=function(e){const t=[];J(e)?t.push("resource_id looks obfuscated"):t.push("resource_id contains semantic naming");const s=!J(e);return{stable:s,reusable:s,risk:s?"low":"high",reasons:t}}(e.resourceId);s.push({field:"resource_id",value:e.resourceId,stable:t.stable,reusable:t.reusable,risk:t.risk,reasons:t.reasons})}if(e.text){const n=Q(e.text,e,t,"text");s.push({field:"text",value:e.text,stable:n.stable,reusable:n.reusable,risk:n.risk,reasons:n.reasons})}if(e.contentDesc){const n=Q(e.contentDesc,e,t,"content_desc");s.push({field:"content_desc",value:e.contentDesc,stable:n.stable,reusable:n.reusable,risk:n.risk,reasons:n.reasons})}if(e.className){const t={stable:!0,reusable:!1,risk:"medium",reasons:["class_name is only a disambiguation field, not a primary selector"]};s.push({field:"class_name",value:e.className,stable:t.stable,reusable:t.reusable,risk:t.risk,reasons:t.reasons})}if(e.bounds){const t={stable:!1,reusable:!1,risk:"high",reasons:[`bounds=${e.bounds} is screen-dependent and should stay debug-only`]};s.push({field:"bounds",value:e.bounds,stable:t.stable,reusable:t.reusable,risk:t.risk,reasons:t.reasons})}return s}function ee(e){if(e.length<2)return!1;const t=e.map(e=>{const t=function(e){if(!e)return null;const t=e.match(/\[(\d+),(\d+)\]\[(\d+),(\d+)\]/);return t?{x1:Number(t[1]),y1:Number(t[2]),x2:Number(t[3]),y2:Number(t[4])}:null}(e.bounds);return t?{x:(t.x1+t.x2)/2,y:(t.y1+t.y2)/2}:null}).filter(e=>!!e);if(t.length!==e.length)return!1;const s=new Set(t.map(e=>Math.round(e.x/10))).size,n=new Set(t.map(e=>Math.round(e.y/10))).size;return s>1||n>1}function te(e,t,s,n){const r=f(t,{resourceId:s.resource_id,text:s.text,contentDesc:s.content_desc,className:s.class_name,bounds:s.bounds});if(0===r)return;if(n.requireUnique&&1!==r)return;if(!n.requireUnique&&r>1&&void 0===n.action_index)return;e.some(e=>JSON.stringify(e.selector)===JSON.stringify(s)&&e.action_index===n.action_index)||e.push({selector:s,action_index:n.action_index,stability:n.stability,reusable:n.reusable,matched_count:r,reason:n.reason,scope:n.scope,policy_decision:n.policy_decision,policy_reason:n.policy_reason})}function se(e,t,s={}){if(!e||0===t.length)return null;const n=s.mode||"action",r=function(e){return"action"===e?{allowCollectionScope:!0,allowActionIndex:!0,allowContentCombos:!0,allowedScopes:["global","page","collection"],policyReason:"action selectors may use collection disambiguation"}:{allowCollectionScope:!1,allowActionIndex:!1,allowContentCombos:!1,allowedScopes:["global","page"],policyReason:`${e} selectors must stay single-field and non-positional`}}(n),o=Z(e,t),i=new Map;for(const e of o)i.set(e.field,e);const a=[],c=e.resourceId,l=e.text,u=e.contentDesc,d=e.className,m=e.bounds;if(function(e,t,s,n,r){s.resourceId&&!0===n.get("resource_id")?.reusable&&te(e,t,{resource_id:s.resourceId},{stability:"high",reusable:!0,reason:"unique_resource_id",scope:"global",requireUnique:!0,policy_decision:"approved",policy_reason:r.policyReason}),s.text&&!0===n.get("text")?.reusable&&te(e,t,{text:s.text},{stability:"high",reusable:!0,reason:"unique_reliable_text",scope:"global",requireUnique:!0,policy_decision:"approved",policy_reason:r.policyReason}),s.contentDesc&&!0===n.get("content_desc")?.reusable&&te(e,t,{content_desc:s.contentDesc},{stability:"high",reusable:!0,reason:"unique_reliable_content_desc",scope:"global",requireUnique:!0,policy_decision:"approved",policy_reason:r.policyReason})}(a,t,{resourceId:c,text:l,contentDesc:u},i,r),function(e,t,s,n,r){r.allowContentCombos&&(s.resourceId&&!0===n.get("resource_id")?.reusable&&s.text&&n.get("text")?.stable&&te(e,t,{resource_id:s.resourceId,text:s.text},{stability:"medium",reusable:!0===n.get("text")?.reusable,reason:"resource_id_text_combo",scope:"page",requireUnique:!0,policy_decision:"approved",policy_reason:r.policyReason}),s.resourceId&&!0===n.get("resource_id")?.reusable&&s.contentDesc&&n.get("content_desc")?.stable&&te(e,t,{resource_id:s.resourceId,content_desc:s.contentDesc},{stability:"medium",reusable:!0===n.get("content_desc")?.reusable,reason:"resource_id_content_desc_combo",scope:"page",requireUnique:!0,policy_decision:"approved",policy_reason:r.policyReason}),s.text&&s.className&&n.get("text")?.stable&&te(e,t,{text:s.text,class_name:s.className},{stability:"medium",reusable:!0===n.get("text")?.reusable,reason:"text_class_combo",scope:"page",requireUnique:!0,policy_decision:"approved",policy_reason:r.policyReason}),s.contentDesc&&s.className&&n.get("content_desc")?.stable&&te(e,t,{content_desc:s.contentDesc,class_name:s.className},{stability:"medium",reusable:!0===n.get("content_desc")?.reusable,reason:"content_desc_class_combo",scope:"page",requireUnique:!0,policy_decision:"approved",policy_reason:r.policyReason}))}(a,t,{resourceId:c,text:l,contentDesc:u,className:d},i,r),function(e,t,s,n,r,o,i){if(o.allowCollectionScope&&o.allowActionIndex){if(n.resourceId){const a={resourceId:n.resourceId},c=s.filter(e=>p(e,a));c.length>1&&ee(c)&&te(e,s,{resource_id:n.resourceId},{action_index:i??b(s,a,t),stability:!0===r.get("resource_id")?.reusable?"medium":"low",reusable:!0===r.get("resource_id")?.reusable,reason:!0===r.get("resource_id")?.reusable?"resource_id_positional":"obfuscated_id_positional",scope:"collection",policy_decision:"approved",policy_reason:o.policyReason})}if(n.text){const a={text:n.text},c=s.filter(e=>p(e,a));c.length>1&&ee(c)&&te(e,s,{text:n.text},{action_index:i??b(s,a,t),stability:"low",reusable:!0===r.get("text")?.reusable,reason:"text_positional",scope:"collection",policy_decision:"approved",policy_reason:o.policyReason})}if(n.contentDesc){const a={contentDesc:n.contentDesc},c=s.filter(e=>p(e,a));c.length>1&&ee(c)&&te(e,s,{content_desc:n.contentDesc},{action_index:i??b(s,a,t),stability:"low",reusable:!0===r.get("content_desc")?.reusable,reason:"content_desc_positional",scope:"collection",policy_decision:"approved",policy_reason:o.policyReason})}n.className&&n.bounds&&te(e,s,{class_name:n.className,bounds:n.bounds},{stability:"low",reusable:!1,reason:"class_bounds_fallback",scope:"page",requireUnique:!0,policy_decision:"approved",policy_reason:o.policyReason})}}(a,e,t,{resourceId:c,text:l,contentDesc:u,className:d,bounds:m},i,r,s.requestedActionIndex),0===a.length){if("action"!==n)return null;const e=!0===i.get("resource_id")?.reusable,o=!0===i.get("text")?.reusable,_=!0===i.get("content_desc")?.reusable;c?te(a,t,{resource_id:c},{stability:e?"medium":"low",reusable:e,reason:e?"resource_id_positional":"obfuscated_id_positional",scope:"collection",action_index:s.requestedActionIndex,policy_decision:"approved",policy_reason:r.policyReason}):l?te(a,t,{text:l},{stability:"low",reusable:o,reason:"text_positional",scope:"collection",action_index:s.requestedActionIndex,policy_decision:"approved",policy_reason:r.policyReason}):u?te(a,t,{content_desc:u},{stability:"low",reusable:_,reason:"content_desc_positional",scope:"collection",action_index:s.requestedActionIndex,policy_decision:"approved",policy_reason:r.policyReason}):d&&m&&te(a,t,{class_name:d,bounds:m},{stability:"low",reusable:!1,reason:"class_bounds_fallback",scope:"page",requireUnique:!0,policy_decision:"approved",policy_reason:r.policyReason})}const _=a[0];return _?{mode:n,field_assessments:o,selector_candidates:a,script_selector:{selector:_.selector,action_index:_.action_index,stability:_.stability,reusable:_.reusable,reason:_.reason,scope:_.scope,policy_decision:_.policy_decision,policy_reason:_.policy_reason}}:null}function ne(e,t){return e.lastObserveRawDump=t,e.lastObserveNodes=function(e){const t=[],s=e.split("\n");for(const e of s){const s=e.trim(),n=s.match(/^\[(\d+)\]\s+(\S+)/);n&&t.push({index:Number(n[1]),className:n[2],resourceId:s.match(/\bresource-id="([^"]*)"/)?.[1]||void 0,text:s.match(/\btext="([^"]*)"/)?.[1]||void 0,contentDesc:s.match(/\bcontent-desc="([^"]*)"/)?.[1]||void 0,packageName:s.match(/\bpackage="([^"]*)"/)?.[1]||void 0,bounds:s.match(/\bbounds=(\[[^\]]*\]\[[^\]]*\])/)?.[1]||void 0,clickable:/\bclickable=true\b/.test(s),longClickable:/\blong-clickable=true\b/.test(s),scrollable:/\bscrollable=true\b/.test(s),focusable:/\bfocusable=true\b/.test(s),enabled:!/\benabled=false\b/.test(s),checked:/\bchecked=true\b/.test(s),selected:/\bselected=true\b/.test(s)})}return t}(t),e.lastObserveNodes}function re(e,t,s={}){const n=se(e,t,s);return n?{...n,selected_node:(r=e,{index:r.index,resource_id:r.resourceId,text:r.text,content_desc:r.contentDesc,class_name:r.className,bounds:r.bounds})}:null;var r}const oe=new Set(["btn","button","text","txt","img","image","icon","view","layout","container","content","header","footer","title","subtitle","label","input","edit","search","menu","nav","tab","bar","list","item","card","dialog","popup","modal","scroll","pager","recycler","grid","frame","root","main","action","toolbar","status","progress","loading","empty","error","divider","separator","wrapper","panel","avatar","profile","name","email","phone","password","submit","cancel","confirm","close","back","next","home","setting","notification","message","chat","comment","like","share","follow","post","feed","story","video","audio","play","pause","camera","photo","gallery","download","upload","save","delete","add","create","update","refresh","filter","sort","check","switch","toggle","radio","select","option","dropdown","picker","date","row","column","cell","section","group","block","region","area","top","bottom","left","right","center","start","end","inner","outer","overlay","badge","count","number","description","info","hint","placeholder","detail","summary","expand","collapse","more","viewpager","appbar","bottombar","snackbar","fab","chip","spinner","seekbar","slider","webview","mapview","banner","splash","widget"]);function ie(e){if(!e)return!1;const t=e.includes("/")?e.split("/").pop():e;return!!t&&(!e.startsWith("android:")&&(!!/obfuscated/i.test(t)||(!!/^\d+_/.test(t)||!function(e){const t=e.toLowerCase(),s=t.split("_");for(const e of s)if(e.length>=3&&oe.has(e))return!0;for(const e of oe)if(e.length>=4&&t.includes(e))return!0;return!1}(t)&&(t.length<=5||t.length<=10&&function(e){if(0===e.length)return 0;const t=new Map;for(const s of e)t.set(s,(t.get(s)||0)+1);let s=0;for(const n of t.values()){const t=n/e.length;s-=t*Math.log2(t)}return s}(t)>3))))}function ae(e,t,s){let n=0;for(const r of e)if(he(r,t)){if(n===s)return r;n++}return null}async function ce(e,t,s,n,r){const o=function(e){return`http://${e.hostIp}:18182/android_api/v2/${e.deviceId}`}(e),i=`${o}/${s}`,a={method:t,headers:{"Content-Type":"application/json"},signal:r};void 0!==n&&"POST"===t&&(a.body=JSON.stringify(n));const c=await fetch(i,a);if(!c.ok){const e=await c.text();throw new Error(`API ${s} failed: ${c.status} - ${e}`)}return await c.json()}function le(e){return!e||"object"!=typeof e||Array.isArray(e)?null:e}function ue(e,t){return Object.prototype.hasOwnProperty.call(e,t)}function de(e){if(null==e||""===e)return{ok:!0};const t="string"==typeof e?Number(e):e;return"number"==typeof t&&Number.isFinite(t)?!Number.isInteger(t)||t<0?{ok:!1}:{ok:!0,value:t}:{ok:!1}}function me(e,t){if(!ue(e,"index"))return null;return de(e.index).ok?null:`${t}.index 必须是从 0 开始的非负整数。`}function _e(e){if("string"!=typeof e)return;const t=e.trim();return t.length>0?t:void 0}function pe(e){const t={};let s=!1;for(const[n,r]of Object.entries(e))null!=r&&""!==r&&(t[n]=r,s=!0);return s?t:void 0}function fe(e){return pe({context_id:`ctx_${Date.now()}_${Math.floor(1e5*Math.random())}`,page_kind:_e(e.page_kind),target_role:_e(e.target_role),criticality_hint:_e(e.criticality_hint),include_mode_hint:_e(e.include_mode_hint),valid_until:_e(e.valid_until)||"next_verified_transition"})||{context_id:`ctx_${Date.now()}_${Math.floor(1e5*Math.random())}`,valid_until:"next_verified_transition"}}function ge(e){if(!e||"object"!=typeof e||Array.isArray(e))return null;const t=e,s=de(t.index);if(!s.ok)return null;const n={index:s.value,resourceId:_e(t.resource_id),text:_e(t.text),contentDesc:_e(t.content_desc),className:_e(t.class_name),bounds:_e(t.bounds)};return n.resourceId||n.text||n.contentDesc||n.className||n.bounds?n:null}function he(e,t){return p(e,t)}function be(e){const t=[];return e.resourceId&&t.push({key:"resource_id",value:e.resourceId}),e.text&&t.push({key:"text",value:e.text}),e.contentDesc&&t.push({key:"content_desc",value:e.contentDesc}),e.className&&t.push({key:"class_name",value:e.className}),e.bounds&&t.push({key:"bounds",value:e.bounds}),t}function ye(e,t){if(e.index!==t.index)return!1;const s=be(e),n=be(t);return s.length===n.length&&s.every(({key:e,value:s})=>{switch(e){case"resource_id":return!!t.resourceId&&_("resourceId",s,t.resourceId);case"text":return!!t.text&&_("text",s,t.text);case"content_desc":return!!t.contentDesc&&_("contentDesc",s,t.contentDesc);case"class_name":return!!t.className&&_("className",s,t.className);case"bounds":return!!t.bounds&&_("bounds",s,t.bounds)}})}function xe(e,t){return f(e,t)}function ve(e,t){const s=ge(e.selector);if(!s?.resourceId)return null;const n=function(e){const t=new Map;for(const s of e)s.resourceId&&t.set(s.resourceId,(t.get(s.resourceId)||0)+1);const s=new Set;for(const[e,n]of t)(ie(e)||!e.startsWith("android:")&&n>=5)&&s.add(e);return s}(t);return n.has(s.resourceId)?`selector.resource_id="${s.resourceId}" 疑似混淆值,回放时可能不稳定。建议下次优先使用 text/content_desc/class_name 定位。`:null}function Ie(e,s,n){if(!t.SELECTOR_ACTION_TOOLS.has(e))return null;const r=ge(s.selector);if(!r)return"validation failed: selector is missing or invalid. Provide resource_id/text/content_desc/class_name/bounds.";const o=n?.lastObserveNodes;if(!o||0===o.length)return"validation failed: missing observe evidence. Call observe_screen before action tools.";const i=xe(o,r);if(0===i)return"validation failed: selector has no match on latest observed screen.";const a=de(s.action_index);if(!a.ok)return"validation failed: action_index must be a non-negative integer.";if(void 0!==a.value&&a.value>=i)return`validation failed: action_index=${a.value} is out of range for matched_count=${i}.`;if(i>1&&void 0===a.value)return`validation failed: selector is ambiguous (matched_count=${i}). Refine selector or provide action_index.`;const c=function(e){if(!e||"object"!=typeof e||Array.isArray(e))return null;const t=e.target;if(!t||"object"!=typeof t||Array.isArray(t))return null;const s=t,n=de(s.index);if(!n.ok)return null;const r={index:n.value,resourceId:_e(s.resource_id),text:_e(s.text),contentDesc:_e(s.content_desc),className:_e(s.class_name),bounds:_e(s.bounds)};return r.resourceId||r.text||r.contentDesc||r.className||r.bounds?r:null}(s.pre_context);return c&&!function(e,t){return void 0!==e.index&&void 0!==t.index&&e.index===t.index||!!(e.resourceId&&t.resourceId&&_("resourceId",e.resourceId,t.resourceId))||!!(e.text&&t.text&&_("text",e.text,t.text))||!!(e.contentDesc&&t.contentDesc&&_("contentDesc",e.contentDesc,t.contentDesc))||!!(e.className&&t.className&&_("className",e.className,t.className))||!!(e.bounds&&t.bounds&&_("bounds",e.bounds,t.bounds))}(r,c)?"validation failed: selector does not align with pre_context.target evidence.":null}function Te(e,t){const s=ge(e.selector),n=le(t.script_selector),r=ge(n?.selector);if(!s||!r)return null;const o=de(e.action_index),i=de(n?.action_index),a=be(s).map(e=>e.key),c=be(r).map(e=>e.key);if(ye(s,r)&&(o.value??void 0)===(i.value??void 0))return{requested_is_minimal:!0,diff_type:"same",requested_selector_fields:a,script_selector_fields:c,script_selector:n?.selector,script_action_index:i.value};const l=function(e,t){const s=[];for(const{key:s,value:n}of be(t))if(!(()=>{switch(s){case"resource_id":return!!e.resourceId&&_("resourceId",n,e.resourceId);case"text":return!!e.text&&_("text",n,e.text);case"content_desc":return!!e.contentDesc&&_("contentDesc",n,e.contentDesc);case"class_name":return!!e.className&&_("className",n,e.className);case"bounds":return!!e.bounds&&_("bounds",n,e.bounds)}})())return{matchesBase:!1,redundantFields:[]};for(const{key:n}of be(e))(()=>{switch(n){case"resource_id":return!!t.resourceId;case"text":return!!t.text;case"content_desc":return!!t.contentDesc;case"class_name":return!!t.className;case"bounds":return!!t.bounds}})()||s.push(n);return{matchesBase:!0,redundantFields:s}}(s,r),u=void 0!==o.value&&void 0===i.value;return l.matchesBase&&(l.redundantFields.length>0||u)?{requested_is_minimal:!1,diff_type:"redundant_constraint",requested_selector_fields:a,script_selector_fields:c,redundant_selector_fields:l.redundantFields,requested_action_index:o.value,script_action_index:i.value,script_selector:n?.selector}:{requested_is_minimal:!1,diff_type:"different_contract",requested_selector_fields:a,script_selector_fields:c,requested_action_index:o.value,script_action_index:i.value,script_selector:n?.selector}}const we={async observe_screen(e,t,s,n){const r="number"==typeof e.wait_ms&&e.wait_ms>0?e.wait_ms:0;if(r>0){const e=await ce(t,"POST","base/sleep",{duration:r},s);if(200!==e.code)return{success:!1,error:e.msg||"Wait before observe failed"}}const o=await ce(t,"POST","accessibility/dump_compact",{},s);return 200==o.code&&"string"==typeof o.data?(n&&ne(n,o.data),{success:!0,data:o.data}):{success:!1,error:o.msg||"Failed to get UI dump"}},async verify_ui_state(e,t,s,n){const r=_e(e.step_desc);if(!r)return{success:!1,error:"verify_ui_state 缺少 step_desc。"};const o=_e(e.screen_desc);if(!o)return{success:!1,error:"verify_ui_state 缺少 screen_desc。"};const i=function(e){return pe({page_kind:_e(e.page_kind)})}(e),a=e.verify_element;if(!a||"object"!=typeof a||Array.isArray(a))return{success:!1,error:"verify_ui_state 缺少 verify_element 对象。"};const c=me(a,"verify_element");if(c)return{success:!1,error:c};const l=ge(a);if(!l)return{success:!1,error:"verify_element 必须提供 resource_id、text、content_desc、class_name 或 bounds 至少一个(可选 index),且需来自当前 observe_screen 结果。"};const u=e.page_anchor;if(u&&"object"==typeof u&&!Array.isArray(u)){const e=me(u,"page_anchor");if(e)return{success:!1,error:e}}const d=n?.lastObserveNodes;if(!d||0===d.length)return{success:!1,error:"请先调用 observe_screen(可带 wait_ms)获取当前界面,再调用 verify_ui_state。"};if(xe(d,l)>0){const e={step_desc:r,screen_desc:o,verify_element:a,...i?{recorded_page:i}:{}},t=ae(d,l,0);if(t){const s=re(t,d,{mode:"verify"});s&&(e.verification_plan=s)}if(u&&"object"==typeof u&&!Array.isArray(u)){const t=ge(u);if(!t)return{success:!1,error:"page_anchor 必须提供 resource_id、text、content_desc、class_name 或 bounds 至少一个(可选 index)。"};if(0===xe(d,t))return{success:!1,error:"page_anchor 必须来自当前 observe_screen 的真实节点,当前界面未匹配。"};e.page_anchor=u;const s=ae(d,t,0);if(s){const t=re(s,d,{mode:"anchor"});t&&(e.page_anchor_plan=t)}}return{success:!0,data:e}}const m=[void 0!==l.index&&`index=${l.index}`,l.resourceId&&`resource_id="${l.resourceId}"`,l.text&&`text="${l.text}"`,l.contentDesc&&`content_desc="${l.contentDesc}"`,l.className&&`class_name="${l.className}"`].filter(Boolean).join(", "),_=d.filter(e=>e.text||e.contentDesc||e.resourceId&&!ie(e.resourceId)).slice(0,15).map(e=>{const t=[];return t.push(`index=${e.index}`),e.text&&t.push(`text="${e.text}"`),e.contentDesc&&t.push(`desc="${e.contentDesc}"`),e.resourceId&&!ie(e.resourceId)&&t.push(`id="${e.resourceId}"`),t.push(`[${e.className.split(".").pop()}]`),` ${t.join(" ")}`});return{success:!1,error:`验证元素在当前界面中不存在:${m} 未匹配。请从当前界面重新选择 verify 元素。${_.length>0?`\n当前界面可用的验证候选元素(直接从中选择,无需再调 observe_screen):\n${_.join("\n")}`:""}`}},async record_search_context(e,t,s,n){const r=_e(e.step_intent);if(!r)return{success:!1,error:"record_search_context 缺少 step_intent。"};const o=_e(e.target_desc);if(!o)return{success:!1,error:"record_search_context 缺少 target_desc。"};const i=e.anchor_element;if(!i||"object"!=typeof i||Array.isArray(i))return{success:!1,error:"record_search_context 缺少 anchor_element(当前页面锚点),必填。"};const a=i,c=me(a,"anchor_element");if(c)return{success:!1,error:c};if(!function(e){const t=e.resource_id,s=e.text,n=e.content_desc,r=e.class_name,o=e.bounds;return"string"==typeof t&&t.trim().length>0||"string"==typeof s&&s.trim().length>0||"string"==typeof n&&n.trim().length>0||"string"==typeof r&&r.trim().length>0||"string"==typeof o&&o.trim().length>0}(a))return{success:!1,error:"anchor_element 必须包含 resource_id、text、content_desc、class_name 或 bounds 至少一个(可选 index)。"};const l=n?.lastObserveNodes;if(!l||0===l.length)return{success:!1,error:"record_search_context 前必须先调用 observe_screen 获取当前界面。"};const u=ge(i);if(!u)return{success:!1,error:"anchor_element 不是合法 selector。"};if(0===xe(l,u))return{success:!1,error:"anchor_element 必须来自当前 observe_screen 的真实节点,当前界面未匹配。"};const d=e.target_element,m={...e,step_intent:r,target_desc:o,recorded_context:fe({...e})},_=ae(l,u,0);if(_){const e=re(_,l,{mode:"anchor"});e&&(m.anchor_plan=e)}if(d&&"object"==typeof d&&!Array.isArray(d)){const e=me(d,"target_element");if(e)return{success:!1,error:e};const t=ge(d);if(!t)return{success:!1,error:"target_element 必须提供 resource_id、text、content_desc、class_name 或 bounds 至少一个(可选 index)。"};if(0===xe(l,t))return{success:!1,error:"target_element 必须来自当前 observe_screen 的真实节点,当前界面未匹配。"};const s=ae(l,t,0);if(s){const e=re(s,l,{mode:"action"});e&&(m.target_plan=e)}}return{success:!0,data:m}},async get_installed_apps(e,t,s){const n=e.type||"user",r=await ce(t,"GET",`package/list?type=${n}`,void 0,s);if(200==r.code&&r.data?.packages){return{success:!0,data:r.data.packages.map(e=>`${e.app_name||"unknown"} = ${e.package_name||""}`).join("\n")}}return{success:!1,error:r.msg||"Failed to get app list"}},async get_top_activity(e,t,s){const n=await ce(t,"GET","activity/top_activity",void 0,s);return 200==n.code?{success:!0,data:n.data}:{success:!1,error:n.msg||"Failed to get top activity"}},async get_screen_info(e,t,s){const n=await ce(t,"GET","display/info",void 0,s);return 200==n.code?{success:!0,data:n.data}:{success:!1,error:n.msg||"Failed to get screen info"}},async start_app(e,t,s){const n=e.package_name;if(!n)return{success:!1,error:"Missing required param: package_name"};const r=await ce(t,"POST","permission/set",{package_name:n,grant_all:!0},s);if(200!==r.code)return{success:!1,error:`权限授权失败 (${n}): ${r.msg}。建议:通过 get_installed_apps 确认包名是否正确。`};const o=await ce(t,"POST","activity/start",{package_name:n},s);return 200==o.code?{success:!0,data:o.data}:{success:!1,error:o.msg||"Failed to start app"}},async stop_app(e,t,s){const n=e.package_name,r=await ce(t,"POST","activity/stop",{package_name:n},s);return 200==r.code?{success:!0,data:r.data}:{success:!1,error:r.msg||"Failed to stop app"}},async tap(e,t,s){const n=await ce(t,"POST","input/click",{x:e.x,y:e.y},s);return 200==n.code?{success:!0,data:n.data}:{success:!1,error:n.msg||"Tap failed"}},async tap_element(e,t,s){const n=de(e.action_index);if(!n.ok)return{success:!1,error:"Invalid action_index: expected non-negative integer"};const r={selector:e.selector,action:"click",wait_timeout:e.wait_timeout??5e3,wait_interval:e.wait_interval??1e3};void 0!==n.value&&(r.action_index=n.value);const o=await ce(t,"POST","accessibility/node",r,s);return 200==o.code?{success:!0,data:o.data}:{success:!1,error:o.msg?`元素操作失败: ${o.msg}。建议:重新 observe_screen 确认元素是否存在,检查 selector 唯一性,必要时补充 action_index。`:"元素未找到或点击失败,请重新 observe_screen 后检查 selector"}},async long_press_element(e,t,s){const n=de(e.action_index);if(!n.ok)return{success:!1,error:"Invalid action_index: expected non-negative integer"};const r={selector:e.selector,action:"long_click",wait_timeout:e.wait_timeout??5e3,wait_interval:e.wait_interval??1e3};void 0!==n.value&&(r.action_index=n.value);const o=await ce(t,"POST","accessibility/node",r,s);return 200==o.code?{success:!0,data:o.data}:{success:!1,error:o.msg||"Long press element failed"}},async set_text(e,t,s){const n=de(e.action_index);if(!n.ok)return{success:!1,error:"Invalid action_index: expected non-negative integer"};const r={selector:e.selector,action:"set_text",action_params:{text:e.text},wait_timeout:e.wait_timeout??5e3,wait_interval:e.wait_interval??1e3};void 0!==n.value&&(r.action_index=n.value);const o=await ce(t,"POST","accessibility/node",r,s);return 200==o.code?{success:!0,data:o.data}:{success:!1,error:o.msg?`文本输入失败: ${o.msg}。建议:确认输入框已聚焦,或尝试先 tap_element 聚焦后使用 input_text。`:"文本输入失败,请确认输入框已存在且可聚焦"}},async input_text(e,t,s){const n=await ce(t,"POST","input/text",{text:e.text},s);return 200==n.code?{success:!0,data:n.data}:{success:!1,error:n.msg||"Input text failed"}},async swipe(e,t,s){const n=await ce(t,"POST","input/scroll_bezier",{start_x:e.start_x,start_y:e.start_y,end_x:e.end_x,end_y:e.end_y,duration:e.duration??300},s);return 200==n.code?{success:!0,data:n.data}:{success:!1,error:n.msg||"Swipe failed"}},async press_key(e,t,s){const n=await ce(t,"POST","input/keyevent",{key_code:e.key_code},s);return 200==n.code?{success:!0,data:n.data}:{success:!1,error:n.msg||"Key event failed"}},async wait(e,t,s){const n=await ce(t,"POST","base/sleep",{duration:e.duration},s);return 200==n.code?{success:!0,data:!0}:{success:!1,error:n.msg||"Wait failed"}}};async function Ne(e,s,n,r){const o=we[e.name],i=(a=e.name,X.find(e=>e.name===a));var a;if(!o||!i)return{toolCallId:e.id,name:e.name,success:!1,error:`Unknown tool: ${e.name}`};const c=(l=e.arguments)&&"object"==typeof l&&!Array.isArray(l)?l:{};var l;const u=function(e,s){const n=ue(s,"index"),r=ue(s,"action_index"),o=le(s.selector),i=!!o&&ue(o,"index");return t.ACCESSIBILITY_NODE_TOOLS.has(e)?n?"Invalid index usage: use action_index (top-level) for accessibility/node tools":i?"Invalid selector.index: selector does not support index, use action_index instead":null:n||r?"Invalid index usage: index/action_index are only allowed for accessibility/node tools":null}(e.name,c);if(u)return{toolCallId:e.id,name:e.name,success:!1,error:u};const d=i.schema.safeParse(c);if(!d.success&&t.STRICT_SCHEMA_TOOLS.has(e.name)){const t=d.error.issues.map(e=>`${e.path.join(".")}: ${e.message}`).join("; ");return{toolCallId:e.id,name:e.name,success:!1,error:`参数校验失败,拒绝执行: ${t}。请检查 pre_context.anchor 和 pre_context.target 是否已填写,selector 字段是否合法。`}}const m=d.success?d.data:c,_=function(e,s){if(!t.PRE_CONTEXT_REQUIRED_TOOLS.has(e))return null;const n=s.pre_context;if(!n||"object"!=typeof n||Array.isArray(n))return`pre_context 缺失:${e} 调用前必须提供 pre_context(anchor + target)。请先 observe_screen 确认页面状态,再填写 pre_context 后重新调用。`;const r=n,o=r.anchor;if(!o||"object"!=typeof o||Array.isArray(o))return"pre_context.anchor 缺失:必须提供页面锚点元素用于确认当前页面。anchor 应选择页面中稳定存在的框架级元素(如导航栏、标题栏)。";if(!_e(o.desc))return"pre_context.anchor.desc 缺失:必须提供页面锚点描述,用于说明当前页面上下文。";const i=r.target;return!i||"object"!=typeof i||Array.isArray(i)?"pre_context.target 缺失:必须提供操作目标元素信息。target 应来自当前 observe_screen 的真实节点属性。":_e(i.desc)?_e(r.step_intent)?null:"pre_context.step_intent 缺失:必须说明本次操作在任务中的目的和预期效果,而非重复元素描述。此字段用于脚本生成的步骤命名和合并。":"pre_context.target.desc 缺失:必须提供目标元素描述,用于说明本次操作意图。"}(e.name,m);if(_)return{toolCallId:e.id,name:e.name,success:!1,error:_};const p=Ie(e.name,m,r);if(p)return{toolCallId:e.id,name:e.name,success:!1,error:p};try{const i=await o(m,s,n,r);if(i.success){const s=[];if(t.SELECTOR_ACTION_TOOLS.has(e.name)&&r?.lastObserveNodes?.length){const e=ge(m.selector),t=de(m.action_index);if(e){const n=ae(r.lastObserveNodes,e,t.value??0);if(n){const e=re(n,r.lastObserveNodes,{mode:"action",requestedActionIndex:t.value});if(e){const t=i.data&&"object"==typeof i.data&&!Array.isArray(i.data)?i.data:{},n=Te(m,e);i.data={...t,_selector_recording_profile:e,...n?{_selector_request_analysis:n}:{}};const r=n?function(e){if(!0===e.requested_is_minimal)return null;if("redundant_constraint"===("string"==typeof e.diff_type?e.diff_type:"different_contract")){const t=Array.isArray(e.redundant_selector_fields)?e.redundant_selector_fields.filter(e=>"string"==typeof e):[],s=[...t.length>0?[`冗余字段: ${t.join(", ")}`]:[],void 0!==e.requested_action_index&&void 0===e.script_action_index?"冗余 action_index":void 0].filter(Boolean);return"selector 最小化提醒: 当前请求 selector 可执行,但不是最小稳定唯一方案。录制阶段批准的 script_selector 已足够定位。"+(s.length>0?`请去掉 ${s.join(",")}。`:"")}return"selector 对齐提醒: 当前请求 selector 与录制阶段批准的 script_selector 不一致。虽然本次执行成功,但后续应优先对齐为 recording-approved script_selector。"}(n):null;r&&s.push(r)}}}}if(t.SELECTOR_ACTION_TOOLS.has(e.name)&&r?.lastObserveNodes){const e=ve(m,r.lastObserveNodes);e&&s.push(e)}if(t.SELECTOR_ACTION_TOOLS.has(e.name)){const e=function(e){const t=le(e.pre_context);if(!t)return null;const s=ge(t.anchor),n=ge(t.target);return s&&n&&ye(s,n)?"pre_context 质量提醒: anchor 与 target 使用了完全相同的定位证据。anchor 应优先描述当前页面或容器级稳定锚点,而不是与点击目标重合。":null}(m);e&&s.push(e)}if(r?.softPendingVerify&&W(e.name)){const e=r.softPendingVerify;s.push(`⚠️ 补录提醒: 上一步 #${e.index} ${e.toolName} 的 verify_ui_state 被跳过,录制信息不完整,将影响脚本生成质量。建议在当前操作完成后补充 observe_screen → verify_ui_state。`)}return t.POST_VERIFY_HINT_TOOLS.has(e.name)&&s.push("录制提醒: 此动作可能改变界面,请执行 observe_screen(wait_ms) → verify_ui_state 记录验证信息。"),"swipe"===e.name&&(!r?.lastSearchContextTimestamp||Date.now()-r.lastSearchContextTimestamp>3e4)&&s.push("⚠️ 录制提醒: swipe 前未调用 record_search_context,录制信息将不完整。建议先调用 record_search_context 记录查找目标与页面锚点。"),{toolCallId:e.id,name:e.name,success:!0,data:i.data,warning:s.length>0?s.join("\n"):void 0}}return{toolCallId:e.id,name:e.name,success:i.success,data:i.data,error:i.error}}catch(t){return{toolCallId:e.id,name:e.name,success:!1,error:t.message||String(t)}}}const Ce=["search","explore","discover","for you","home","friends","inbox","profile","close","log in","sign up","comment","搜索","首页","消息","我的","关闭","登录","评论"];function Ee(e){const t=e.match(/\bbounds=\[(\d+),(\d+)\]\[(\d+),(\d+)\]/);if(!t)return null;const s=Number(t[2]),n=Number(t[4]);return Number.isFinite(s)&&Number.isFinite(n)?{y1:s,y2:n}:null}function Ae(e){if(0===e.length)return null;const t=function(e){const t=e.match(/^Screen\s+\d+x(\d+)/);if(!t)return null;const s=Number(t[1]);return Number.isFinite(s)&&s>0?s:null}(e[0]||"");if(!t)return null;let s=0,n=0,r=0;for(let o=1;o<e.length;o++){const i=e[o];if(!i.includes("clickable=true"))continue;const a=Ee(i);if(!a)continue;const c=(a.y1+a.y2)/2/t;c<.33?s++:c<=.67?n++:r++}return 0===s+n+r?null:`[可点击分布] 顶部:${s} 中部:${n} 底部:${r} (按 bounds center_y)`}function Se(e){if(!e.success)return`Error: ${e.error||"operation failed"}`;const t=e.data;let s;if("verify_ui_state"===e.name&&t&&"object"==typeof t){const e=t,n=e.step_desc,r=e.verify_element;s=n?`verify_ui_state OK: ${n}`:"verify_ui_state OK",r&&(r.resource_id||r.text||r.content_desc)&&(s+=` (element: ${[r.resource_id,r.text,r.content_desc].filter(Boolean).join(" / ")})`)}else if("record_search_context"===e.name&&t&&"object"==typeof t){const e=t;s=e.target_desc?`record_search_context OK: ${e.target_desc}`:"record_search_context OK"}else if("observe_screen"===e.name&&"string"==typeof t){const e=t.split("\n"),n=(t.match(/clickable/g)||[]).length,r=function(e){if(e.length<=120)return{content:e.join("\n"),shownLines:e.length,truncated:!1};const t=new Map,s=s=>{s<0||s>=e.length||t.has(s)||t.set(s,e[s])};s(0);for(let t=1;t<Math.min(e.length,51);t++)s(t);for(let t=Math.max(1,e.length-40);t<e.length;t++)s(t);let n=0;for(let s=1;s<e.length&&n<30;s++){const r=e[s],o=r.toLowerCase(),i=Ce.some(e=>o.includes(e)),a=/\bbounds=\[/.test(r)&&(/\bclickable=true\b/.test(r)||/\bcontent-desc=/.test(r)||/\btext="/.test(r)||/\bresource-id=/.test(r));(i||a)&&(t.has(s)||(t.set(s,r),n++))}const r=Array.from(t.entries()).sort((e,t)=>e[0]-t[0]).slice(0,120).map(([,e])=>e);return{content:r.join("\n"),shownLines:r.length,truncated:!0}}(e),o=`[元素总行数: ${e.length}, 可点击: ${n}, 展示行: ${r.shownLines}]`,i=Ae(e),a="[位置规则] 仅依据 bounds=[x1,y1][x2,y2] + Screen WxH 判断顶部/底部;禁止按节点顺序或缩进推断位置。",c=r.truncated?"\n... (已按头部+尾部+关键词/可交互关键行压缩展示;定位不确定时请重新 observe_screen 并用 verify_ui_state 验证)":"";s=`${o}\n${a}${i?`\n${i}`:""}\n${r.content}${c}`}else s=function(e){if(!e||"object"!=typeof e||Array.isArray(e))return!1;const t=e;return"action_success"in t&&"nodes"in t&&Array.isArray(t.nodes)}(t)?function(e){const t=e,s="number"==typeof t.count?t.count:0,n="string"==typeof t.action?t.action:"find";return!0!==t.action_success?`${n} failed (matched ${s} nodes)`:s<=1?`${n} OK`:`${n} OK (注意: 匹配到 ${s} 个元素,操作了第 ${"number"==typeof t.action_index?t.action_index:0} 个)`}(t):"string"==typeof t?t:!0===t||void 0===t?"OK":JSON.stringify(t,null,2);return e.warning&&(s+=`\n⚠️ ${e.warning}`),s}class Oe{handlers={planning:new Set,thinking:new Set,toolcall:new Set,toolresult:new Set,diagnostic:new Set,paused:new Set,scriptgenerating:new Set,complete:new Set,error:new Set};on(e,t){return this.handlers[e].add(t),()=>this.off(e,t)}off(e,t){this.handlers[e].delete(t)}emit(e,t){for(const s of Array.from(this.handlers[e]))s(t)}clear(){Object.keys(this.handlers).forEach(e=>{this.handlers[e].clear()})}}class Le extends Error{code;details;constructor(e,t,s){super(t),this.name="AgentRuntimeError",this.code=e,this.details=s}}const Re=["INVALID_CONFIG","SESSION_NOT_FOUND","SESSION_NOT_PAUSED","AGENT_RUNNING","EMPTY_EXECUTION_LOG","MCP_CONNECT_FAILED","SCRIPT_GENERATION_FAILED","RUNTIME_LOOP_FAILED","UNKNOWN"];function De(e){return!!e&&"object"==typeof e&&!Array.isArray(e)}function ke(e,t="UNKNOWN"){if(e instanceof Le)return e;const s=function(e){return e instanceof Error&&e.message||De(e)&&"string"==typeof e.message?e.message:String(e)}(e);return De(e)?new Le("string"==typeof(n=e.code)&&Re.includes(n)?e.code:t,s,De(e.details)?e.details:void 0):new Le(t,s);var n}const Fe=i.z.object({goal:i.z.string().min(1),subtasks:i.z.array(i.z.object({id:i.z.number().int().positive(),description:i.z.string().min(1),successCriteria:i.z.string().min(1),estimatedActions:i.z.array(i.z.string().min(1)).default([])})).default([]),risks:i.z.array(i.z.string()).default([]),assumptions:i.z.array(i.z.string()).default([]),estimatedSteps:i.z.number().int().positive().default(1)}),$e=i.z.object({status:i.z.enum(["completed","paused"]).default("completed"),goal:i.z.string().min(1).optional(),subtasks:Fe.shape.subtasks.default([]),risks:Fe.shape.risks.default([]),assumptions:Fe.shape.assumptions.default([]),estimatedSteps:i.z.number().int().positive().optional(),pauseMessage:i.z.string().min(1).optional(),pauseReason:i.z.string().min(1).optional()}).superRefine((e,t)=>{"paused"!==e.status?e.goal||t.addIssue({code:i.z.ZodIssueCode.custom,message:"goal is required when status=completed",path:["goal"]}):e.pauseMessage||t.addIssue({code:i.z.ZodIssueCode.custom,message:"pauseMessage is required when status=paused",path:["pauseMessage"]})});function Ue(e){const t=e.trim();if(t.startsWith("{")&&t.endsWith("}"))return t;const s=t.replace(/^```json\s*/i,"").replace(/^```\s*/i,"").replace(/\s*```$/,"").trim();if(s.startsWith("{")&&s.endsWith("}"))return s;throw new Error("plan output is not a valid standalone JSON object")}function Me(e,t){return"zh"===t?{goal:e,subtasks:[{id:1,description:"识别目标应用或目标页面入口",successCriteria:"当前界面可见可执行入口元素",estimatedActions:["observe_screen","tap_element"]},{id:2,description:"执行目标核心动作",successCriteria:"动作后出现预期的关键页面变化",estimatedActions:["tap_element","set_text","press_key"]},{id:3,description:"记录动作结果并验证状态",successCriteria:"verify_ui_state 成功且状态可复用",estimatedActions:["observe_screen","verify_ui_state"]}],risks:["页面元素可能动态变化","页面加载延迟导致观察结果滞后"],assumptions:["设备已连通且可调用 Android 控制 API"],estimatedSteps:8}:{goal:e,subtasks:[{id:1,description:"Identify the target app or the entry point screen",successCriteria:"A visible and actionable entry element is observed",estimatedActions:["observe_screen","tap_element"]},{id:2,description:"Execute the core target action",successCriteria:"Expected key screen transition appears after action",estimatedActions:["tap_element","set_text","press_key"]},{id:3,description:"Record outcome and verify resulting UI state",successCriteria:"verify_ui_state succeeds with reusable state evidence",estimatedActions:["observe_screen","verify_ui_state"]}],risks:["Dynamic UI elements may change during execution","Page loading delays can make observations stale"],assumptions:["Device is reachable and Android control APIs are available"],estimatedSteps:8}}function Pe(e,t,s){return e<=1?"":"zh"===s?`\n\n[上一次失败]\n${t[t.length-1]||"unknown"}\n请严格输出合法 JSON。`:`\n\n[PreviousFailure]\n${t[t.length-1]||"unknown"}\nOutput strict valid JSON only.`}function je(e,t,s,n){return"zh"===n?`## 目标\n\n${e}\n\n## 当前设备屏幕\n\n${t}${s}`:`## Goal\n\n${e}\n\n## Current Device Screen\n\n${t}${s}`}async function ze(e,s,n,r,o={}){return async function(e,s,n,r,o={}){const i=t.detectPromptLocale(e),a={lastObserveRawDump:null,lastObserveNodes:[]},c=o.planningMessages||[],l=!1!==o.allowPause,u=await Ne({id:"plan-observe-screen",name:"observe_screen",arguments:{},source:"local"},n,o.signal,a),d=u.success&&"string"==typeof u.data?u.data:Se(u),m=a.lastObserveRawDump||void 0,_=t.createProvider(s),p=[],f=Math.max(1,r.planner.maxAttempts);for(let s=1;s<=f;s++){const n=Pe(s,p,i),r=[{role:"system",content:t.buildTaskPlanPrompt(e,l)},{role:"user",content:je(e,d,n,i)},...c];try{let t;if(_.capabilities.structuredJson)t=await _.chatStructuredJson(r,$e,o.signal);else{const e=await _.chatWithTools(r,[],o.signal);t=JSON.parse(Ue(e.content))}const n=$e.parse(t);if("paused"===n.status){if(!l)throw new Error("planner requested clarification but pause is disabled for this entrypoint");return{status:"paused",text:n.pauseMessage||"planner requested clarification",reason:n.pauseReason,screenDump:d,screenRawDump:m,attempts:s,errors:p}}const i=Math.max(n.estimatedSteps||1,2*n.subtasks.length,1);return{status:"completed",plan:{goal:n.goal||e,subtasks:n.subtasks,risks:n.risks,assumptions:n.assumptions,estimatedSteps:i},screenDump:d,screenRawDump:m,attempts:s,errors:p}}catch(e){p.push(e.message||String(e))}}return{status:"completed",plan:Me(e,i),screenDump:d,screenRawDump:m,attempts:f,errors:p}}(e,s,n,r,o)}function qe(e,t){return t<=0?null:e/t}function Ge(e,t){const s={...t,timestamp:Date.now()};return e.diagnostics.push(s),s}function Ve(e){const t=[...e.diagnostics].reverse().find(e=>"runtime"===e.phase||"script_generation"===e.phase)?.code,s=function(e){let t=0,s=0,n=0;for(const r of e)r.result.success&&(t+=1),"action"===r.category&&(s+=1,r.result.success&&(n+=1));return{totalToolCalls:e.length,successfulToolCalls:t,runtimeSuccessRate:qe(t,e.length),totalActionCalls:s,successfulActionCalls:n,automationSuccessRate:qe(n,s)}}(e.executionLog),n={};let r=0;for(const t of e.executionLog){if(t.result.success)continue;r+=1;const e=t.result.errorCode||"UNKNOWN";n[e]=(n[e]||0)+1}return{planningRetries:e.runtimeContext.planningRetries,toolRetries:e.runtimeContext.toolRetries,scriptGenerationRetries:e.runtimeContext.scriptGenerationRetries,convergenceTriggers:e.runtimeContext.convergenceTriggers,totalToolCalls:s.totalToolCalls,successfulToolCalls:s.successfulToolCalls,runtimeSuccessRate:s.runtimeSuccessRate,totalActionCalls:s.totalActionCalls,successfulActionCalls:s.successfulActionCalls,automationSuccessRate:s.automationSuccessRate,totalFailures:r,failureByCode:n,finalFailureCode:t}}function Ye(e,s,n,r=[]){const o=[{role:"system",content:t.buildAgentSystemPrompt(e,s)},{role:"user",content:e}];for(const e of r)"assistant"!==e.role&&"user"!==e.role||o.push({role:e.role,content:e.content,timestamp:e.timestamp});return n&&(o.push({role:"assistant",content:"",toolCalls:[{id:"plan-initial-observe",name:"observe_screen",arguments:{},source:"local"}]}),o.push({role:"tool",content:n,toolCallId:"plan-initial-observe"})),o}function Be(e){return e.currentPhase||(e.currentPhase="runtime"),e.planningContext?Array.isArray(e.planningContext.messages)||(e.planningContext.messages=[]):e.planningContext={messages:[]},e}function Xe(e,t,s){Be(e),e.plan=t,e.currentPhase="runtime",e.messages=Ye(e.goal,t,s,e.planningContext?.messages||[])}function We(e,t,s=[]){s.length>0?e.messages.push({role:"assistant",content:t,toolCalls:s,timestamp:Date.now()}):e.messages.push({role:"assistant",content:t,timestamp:Date.now()})}function Ke(e,t,s){e.messages.push({role:"tool",content:s,toolCallId:t,timestamp:Date.now()})}const Je=new Set(["verify_ui_state","record_search_context","get_screen_info"]);function He(e){return!e||"object"!=typeof e||Array.isArray(e)?null:e}function Qe(e){if("number"==typeof e&&Number.isInteger(e)&&e>=0)return e;if("string"==typeof e&&e.trim().length>0){const t=Number(e);if(Number.isInteger(t)&&t>=0)return t}}function Ze(e,t){for(const s of t){const t=e[s];if("string"==typeof t){const e=t.trim();if(e.length>0)return e}}}function et(e){const t={};let s=!1;for(const[n,r]of Object.entries(e))null!=r&&""!==r&&(t[n]=r,s=!0);return s?t:void 0}function tt(e){const t=He(e);if(!t)return;return et({index:Qe(t.index),resource_id:Ze(t,["resource_id","resource-id","resourceId","id"]),text:Ze(t,["text"]),content_desc:Ze(t,["content_desc","content-desc","contentDesc"]),class_name:Ze(t,["class_name","class","className"]),bounds:Ze(t,["bounds"])})}function st(e,s){if(!t.SELECTOR_ACTION_TOOLS.has(e.name)||!s.success)return null;const n=He(e.arguments),r=He(s.data);if(!n&&!r)return null;const o=et(He(n?.selector)||{}),i=Qe(n?.action_index),a=Array.isArray(r?.nodes)?r.nodes:[],c=Qe(r?.action_index),l=i??c??0,u=tt(a[l]??a[0]),d=a.slice(0,5).map(e=>tt(e)).filter(e=>!!e),m="number"==typeof r?.count&&Number.isFinite(r.count)?r.count:a.length,_={};o&&(_.requested_selector=o),void 0===i&&void 0===c||(_.action_index=l),m>=0&&(_.matched_count=m),u&&(_.selected_node=u),d.length>0&&(_.candidate_nodes=d);const p=function(e){const t=He(e);if(!t)return;return et({mode:"string"==typeof t.mode?t.mode:void 0,script_selector:He(t.script_selector)||void 0,field_assessments:Array.isArray(t.field_assessments)?t.field_assessments.map(e=>He(e)).filter(e=>!!e).slice(0,8):void 0,selector_candidates:Array.isArray(t.selector_candidates)?t.selector_candidates.map(e=>He(e)).filter(e=>!!e).slice(0,8):void 0,selected_node:tt(t.selected_node)})}(r?._selector_recording_profile);if(p){_.selector_profile=p;const e=He(p.script_selector);if(e){const t=He(e.selector);t&&(_.recommended_selector=t);const s=Qe(e.action_index);void 0!==s&&(_.recommended_action_index=s),"string"==typeof e.stability&&(_.selector_stability=e.stability),"string"==typeof e.reason&&(_.selector_reason=e.reason),"boolean"==typeof e.reusable&&(_.selector_reusable=e.reusable),"string"==typeof e.scope&&(_.selector_scope=e.scope)}}const f=He(r?._selector_request_analysis);return f&&(_.selector_request_analysis=f),Object.keys(_).length>0?_:null}function nt(e,t){const s=e.name;if(Je.has(s))return t;if("observe_screen"===s&&t.success&&"string"==typeof t.data){const e=t.data.split("\n").length;return{...t,data:`(${e} lines)`}}const n=st(e,t);if(n){const e={toolCallId:t.toolCallId,name:t.name,success:t.success,data:{runtime_selector_evidence:n},source:t.source,server:t.server};return t.error&&(e.error=t.error),t.warning&&(e.warning=t.warning),e}const r={toolCallId:t.toolCallId,name:t.name,success:t.success,source:t.source,server:t.server};return t.error&&(r.error=t.error),t.warning&&(r.warning=t.warning),r}function rt(e,t,s,n){e.executionLog.push({index:e.executionLog.length+1,toolName:t.name,arguments:t.arguments,result:nt(t,s),category:n,timestamp:Date.now()})}const ot=[/resource-id=/,/resource_id=/,/\btext="/,/content-desc=/,/content_desc=/,/clickable=true/,/bounds=\[/];function it(e){return e.startsWith("Screen ")||e.startsWith("[Package]")||e.startsWith("[元素总行数:")}function at(e){const t=e.split("\n");if(t.length<=7)return e;const s=t[0]||"",n=new Set,r=[];let o=0;for(let e=1;e<t.length;e++){const s=t[e];s.includes("clickable=true")&&o++,ot.some(e=>e.test(s))&&(n.has(s)||(n.add(s),r.push(s)))}return[s,...r.length>0?r.slice(0,6):t.slice(1,7),"[位置规则] 仅依据 bounds + Screen WxH 判断顶部/底部,禁止按节点顺序推断。",`[... 已压缩,原始 ${t.length} 行,关键候选 ${r.length} 行,可点击 ${o} 个]`].join("\n")}function ct(e){if(e.length<=14)return e;const t=e.length-12,s=[],n=[],r=[];for(let t=0;t<e.length;t++){const s=e[t];"tool"===s.role&&it(s.content)&&n.push(t),"tool"===s.role&&(s.content.includes('"step_desc"')||s.content.includes('"target_desc"'))&&r.push(t)}const o=new Set(n.slice(-1)),i=new Set(r.slice(-3));for(let n=0;n<e.length;n++){const r=e[n];if(n<=1)s.push(r);else if(n>=t)s.push(r);else if("user"!==r.role)if("assistant"!==r.role)if(r.content.length<=200)s.push(r);else if(!r.content.includes('"step_desc"')&&!r.content.includes('"target_desc"')||i.has(n))if(it(r.content)){if(o.has(n)){s.push(r);continue}s.push({...r,content:at(r.content)})}else s.push({...r,content:r.content.substring(0,200)+"\n[... 已截断]"});else try{const e=JSON.parse(r.content);s.push({...r,content:JSON.stringify({step_desc:e.step_desc,target_desc:e.target_desc})})}catch{s.push({...r,content:r.content.substring(0,200)+"\n[... 已截断]"})}else r.content.length<=120?s.push(r):s.push({...r,content:r.content.substring(0,120)+"\n[... 已截断]"});else s.push(r)}return s}function lt(e){return"stdio"===e.transport?{transport:"stdio",command:e.command,args:e.args||[],env:e.env||{}}:{transport:"streamable_http"===e.transport?"http":e.transport,url:e.url,headers:e.headers||{}}}class ut{servers;client=null;constructor(e){this.servers=e}async connect(){if(!this.servers||0===Object.keys(this.servers).length)return[];if(!this.client){const e={};for(const[t,s]of Object.entries(this.servers))e[t]=lt(s);this.client=new c.MultiServerMCPClient({mcpServers:e})}const e=[];for(const t of Object.keys(this.servers)){const s=await this.client.getTools(t);for(const n of s)n.name&&e.push({name:n.name,tool:n,server:t})}return e}async close(){this.client&&(await this.client.close(),this.client=null)}}const dt=new Set(X.map(e=>e.name));class mt{mcpClient=null;mcpTools=new Map;modelTools=[...X];getModelTools(){return this.modelTools}async prepare(e={}){if(await this.closeMcpClient(),this.mcpTools.clear(),this.modelTools=[...X],0===Object.keys(e).length)return;const t=new ut(e);try{const e=await t.connect();!function(e,t){const s=[],n=new Set(e),r=new Set;for(const e of t){const t=e.name;t&&(n.has(t)||r.has(t)?s.push(t):r.add(t))}if(s.length>0){const e=Array.from(new Set(s)).join(", ");throw new Error(`MCP tool name conflict detected: ${e}`)}}(X.map(e=>e.name),e);for(const t of e)this.mcpTools.set(t.name,{tool:t.tool,server:t.server});this.modelTools=[...X,...e.map(e=>e.tool)],this.mcpClient=t}catch(e){throw await t.close().catch(()=>{}),e}}annotateToolCalls(e){return e.map(e=>{const t=this.resolveTool(e.name);return{...e,source:t.source,server:t.server}})}getCategory(e,t){return"local"===t.source&&W(e.name)?"action":"observation"}async execute(e,t,s,n){const r=this.resolveTool(e.name);if("local"===r.source){return{...await Ne({id:e.id,name:e.name,arguments:e.arguments},t,s,n),source:"local"}}if(!r.tool)return{toolCallId:e.id,name:e.name,success:!1,error:`MCP tool not found: ${e.name}`,source:"mcp",server:r.server};try{const t=await r.tool.invoke(e.arguments);return{toolCallId:e.id,name:e.name,success:!0,data:t,source:"mcp",server:r.server}}catch(t){return{toolCallId:e.id,name:e.name,success:!1,error:t.message||String(t),source:"mcp",server:r.server}}}async destroy(){await this.closeMcpClient(),this.mcpTools.clear(),this.modelTools=[...X]}resolveTool(e){if(dt.has(e))return{source:"local"};const t=this.mcpTools.get(e);return t?{source:"mcp",server:t.server,tool:t.tool}:{source:"local"}}async closeMcpClient(){this.mcpClient&&(await this.mcpClient.close(),this.mcpClient=null)}}const _t="PLANNER_RETRY",pt="PLANNER_FALLBACK",ft="TOOL_RETRY",gt="VERIFY_GATE_BLOCKED",ht="MAX_ITERATIONS_REACHED",bt="CONSECUTIVE_FAILURE_LIMIT_REACHED",yt="SAME_TOOL_REPEAT_LIMIT_REACHED",xt="SCRIPT_RETRY",vt=new Set(["TIMEOUT","NETWORK","HTTP_5XX"]);function It(e){if(e.errorCode)return{code:e.errorCode,retryable:e.retryable??vt.has(e.errorCode)};const t=(e.error||"").toLowerCase();if(!t)return{code:"UNKNOWN",retryable:!1};if(t.includes("aborted"))return{code:"ABORTED",retryable:!1};if(t.includes("timeout")||t.includes("timed out"))return{code:"TIMEOUT",retryable:!0};if(t.includes("econnreset")||t.includes("enotfound")||t.includes("econnrefused")||t.includes("network")||t.includes("fetch failed"))return{code:"NETWORK",retryable:!0};const s=function(e){const t=e.match(/\b(?:status|failed:)\s*(\d{3})\b/i);if(!t)return null;const s=Number(t[1]);return Number.isFinite(s)?s:null}(t);if(null!==s){if(s>=500)return{code:"HTTP_5XX",retryable:!0};if(s>=400)return{code:"HTTP_4XX",retryable:!1}}return t.includes("unknown tool")?{code:"UNKNOWN_TOOL",retryable:!1}:t.includes("参数校验失败")||t.includes("validation")||t.includes("invalid")?{code:"VALIDATION",retryable:!1}:t.includes("拦截:上一步动作尚未验证")||t.includes("blocked by verify gate")?{code:"RUNTIME_GUARD",retryable:!1}:t.includes("plan")&&t.includes("invalid")?{code:"PLAN_VALIDATION_FAILED",retryable:!1}:t.includes("workflow")&&t.includes("invalid")?{code:"SCRIPT_VALIDATION_FAILED",retryable:!1}:{code:"UNKNOWN",retryable:!1}}function Tt(e,t){const s=new AbortController,n=()=>{s.abort(t?.reason)};t&&(t.aborted&&s.abort(t.reason),t.addEventListener("abort",n));const r=setTimeout(()=>{s.abort(new Error(`Tool timeout after ${e}ms`))},e);return{signal:s.signal,dispose:()=>{clearTimeout(r),t&&t.removeEventListener("abort",n)}}}function wt(e,t){return{toolCallId:e.id,name:e.name,success:!1,error:`Tool timeout after ${t}ms`,errorCode:"TIMEOUT",retryable:!0}}function Nt(e,t){return t instanceof Error&&/timeout/i.test(t.message)?wt(e,0):t instanceof Error&&"AbortError"===t.name?{toolCallId:e.id,name:e.name,success:!1,error:t.message||"Tool aborted",errorCode:"ABORTED",retryable:!1}:{toolCallId:e.id,name:e.name,success:!1,error:t?.message||String(t),errorCode:"UNKNOWN",retryable:!1}}async function Ct(e){const t=Math.max(1,e.maxAttempts);for(let s=1;s<=t;s++){const n=Date.now(),r=Tt(e.timeoutMs,e.signal);let o;try{o=await e.execute(e.toolCall,e.device,r.signal)}catch(t){const s=r.signal.reason instanceof Error?r.signal.reason.message:String(r.signal.reason||"");o=r.signal.aborted&&/timeout/i.test(s)?wt(e.toolCall,e.timeoutMs):Nt(e.toolCall,t)}finally{r.dispose()}if(o.latencyMs||(o.latencyMs=Date.now()-n),o.attempt=s,o.success)return o;const i=It(o);if(o.errorCode=o.errorCode??i.code,o.retryable=o.retryable??i.retryable,"ABORTED"===o.errorCode)return o;if(!o.retryable||s>=t)return o;e.onRetry?.(s,o)}return{toolCallId:e.toolCall.id,name:e.toolCall.name,success:!1,error:"Unreachable: retry loop terminated unexpectedly",errorCode:"UNKNOWN",retryable:!1}}function Et(e,t){if(!Number.isFinite(e))return t;const s=Math.floor(e);return s<=0?t:s}function At(e,t){return"action"===t?e.retries.action:e.retries.observation}function St(e,s){const n=t.TOOL_TIMEOUT_OVERRIDES_MS[s];return n?e.timeouts[n]:e.timeouts.defaultMs}function Ot(e){if(null==e)return String(e);if("object"!=typeof e)return JSON.stringify(e);if(Array.isArray(e))return`[${e.map(e=>Ot(e)).join(",")}]`;return`{${Object.entries(e).sort(([e],[t])=>e.localeCompare(t)).map(([e,t])=>`${JSON.stringify(e)}:${Ot(t)}`).join(",")}}`}function Lt(e){return`${e.name}:${Ot(e.arguments||{})}`}function Rt(e){return"local"===e.source&&W(e.name)?"action":"observation"}class Dt{options;emitter=new Oe;toolRegistry=new mt;session=null;provider=null;abortController=null;sessionStore=null;localToolContext={lastObserveRawDump:null,lastObserveNodes:[]};constructor(e={}){this.options=e;if("sqlite"!==(e.persistence?.mode||"sqlite"))return;const t=l();try{this.sessionStore=new d(t)}catch(e){const s=ke(e,"INVALID_CONFIG");throw new Le("INVALID_CONFIG",`session store initialization failed: ${s.message}`,{sqlitePath:t,cause:s.message})}}on(e,t){return this.emitter.on(e,t)}off(e,t){this.emitter.off(e,t)}async start(s){this.session?.running&&this.stop(this.session.sessionId);try{this.provider=t.createProvider(s.provider)}catch(e){const t=ke(e,"INVALID_CONFIG");throw new Le("INVALID_CONFIG",`invalid model provider configuration: ${t.message}`,{provider:{vendor:s.provider.vendor,baseUrl:s.provider.baseUrl,model:s.provider.model},cause:t.message})}try{await this.toolRegistry.prepare(this.options.mcpServers||{})}catch(e){const t=ke(e,"MCP_CONNECT_FAILED");throw new Le("MCP_CONNECT_FAILED",`failed to connect MCP tools: ${t.message}`,{servers:Object.keys(this.options.mcpServers||{}),cause:t.message,...t.details})}const n=function(e){if(!e)return t.DEFAULT_RELIABILITY_CONFIG;const s=t.DEFAULT_RELIABILITY_CONFIG.scriptGeneration.waitRandomization||{},n=Et(e.scriptGeneration?.waitRandomization?.minMs??s.minMs??400,s.minMs??400),r=Math.max(n,Et(e.scriptGeneration?.waitRandomization?.maxMs??s.maxMs??3e4,s.maxMs??3e4));return{retries:{observation:Et(e.retries?.observation??t.DEFAULT_RELIABILITY_CONFIG.retries.observation,t.DEFAULT_RELIABILITY_CONFIG.retries.observation),action:Et(e.retries?.action??t.DEFAULT_RELIABILITY_CONFIG.retries.action,t.DEFAULT_RELIABILITY_CONFIG.retries.action)},timeouts:{defaultMs:Et(e.timeouts?.defaultMs??t.DEFAULT_RELIABILITY_CONFIG.timeouts.defaultMs,t.DEFAULT_RELIABILITY_CONFIG.timeouts.defaultMs),observeScreenMs:Et(e.timeouts?.observeScreenMs??t.DEFAULT_RELIABILITY_CONFIG.timeouts.observeScreenMs,t.DEFAULT_RELIABILITY_CONFIG.timeouts.observeScreenMs),startAppMs:Et(e.timeouts?.startAppMs??t.DEFAULT_RELIABILITY_CONFIG.timeouts.startAppMs,t.DEFAULT_RELIABILITY_CONFIG.timeouts.startAppMs)},limits:{maxIterations:Et(e.limits?.maxIterations??t.DEFAULT_RELIABILITY_CONFIG.limits.maxIterations,t.DEFAULT_RELIABILITY_CONFIG.limits.maxIterations),maxConsecutiveFailures:Et(e.limits?.maxConsecutiveFailures??t.DEFAULT_RELIABILITY_CONFIG.limits.maxConsecutiveFailures,t.DEFAULT_RELIABILITY_CONFIG.limits.maxConsecutiveFailures),maxSameToolFingerprint:Et(e.limits?.maxSameToolFingerprint??t.DEFAULT_RELIABILITY_CONFIG.limits.maxSameToolFingerprint,t.DEFAULT_RELIABILITY_CONFIG.limits.maxSameToolFingerprint)},planner:{maxAttempts:Et(e.planner?.maxAttempts??t.DEFAULT_RELIABILITY_CONFIG.planner.maxAttempts,t.DEFAULT_RELIABILITY_CONFIG.planner.maxAttempts)},scriptGeneration:{maxAttempts:Et(e.scriptGeneration?.maxAttempts??t.DEFAULT_RELIABILITY_CONFIG.scriptGeneration.maxAttempts,t.DEFAULT_RELIABILITY_CONFIG.scriptGeneration.maxAttempts),waitRandomization:{enabled:e.scriptGeneration?.waitRandomization?.enabled??s.enabled??!0,minMs:n,maxMs:r,jitterRatio:(o=e.scriptGeneration?.waitRandomization?.jitterRatio??s.jitterRatio,i=s.jitterRatio??.2,"number"==typeof o&&Number.isFinite(o)?o<0?0:o>1?1:o:i),roundingMs:Et(e.scriptGeneration?.waitRandomization?.roundingMs??s.roundingMs??100,s.roundingMs??100)}}};var o,i}(s.reliability),r=s.sessionId||e.v4();this.localToolContext={lastObserveRawDump:null,lastObserveNodes:[]},this.session=Be(function(e,t,s,n,r){return{sessionId:t,goal:e.goal,provider:e.provider,device:e.device,reliability:s,iteration:0,running:!1,paused:!1,messages:Ye(e.goal,n,r),executionLog:[],diagnostics:[],runtimeContext:{lastObserveRawDump:null,lastObserveNodes:[],pendingVerifyAction:null,softPendingVerify:null,lastSearchContextTimestamp:null,consecutiveFailures:0,sameToolFingerprintCount:0,lastToolFingerprint:null,planningRetries:0,toolRetries:0,scriptGenerationRetries:0,convergenceTriggers:0},plan:n,currentPhase:"runtime",planningContext:{messages:[]}}}(s,r,n)),this.persistSession();if(!1!==s.enablePlanning){this.session.currentPhase="planning",this.persistSession();if("paused"===await this.runPlanningStage(this.session))return{sessionId:r}}else this.session.currentPhase="runtime";return this.persistSession(),await this.runLoop(),{sessionId:r}}async resume(e){if(!this.session||this.session.sessionId!==e.sessionId)throw new Le("SESSION_NOT_FOUND","session not found or expired");if(Be(this.session),!this.session.paused)throw new Le("SESSION_NOT_PAUSED","agent is not paused");if("planning"===this.session.currentPhase){this.session.planningContext?.messages.push({role:"user",content:e.message,timestamp:Date.now()}),this.session.paused=!1,this.persistSession();if("paused"===await this.runPlanningStage(this.session))return;return void await this.runLoop()}var t,s;t=this.session,s=e.message,t.messages.push({role:"user",content:s,timestamp:Date.now()}),this.persistSession(),await this.runLoop()}stop(e){this.session&&this.session.sessionId===e&&(this.session.running=!1,this.session.paused=!1,this.abortController&&(this.abortController.abort(),this.abortController=null),this.persistSession())}async generateScript(e){const s=this.loadSessionIfNeeded(e);if(Be(s),s.running)throw new Le("AGENT_RUNNING","agent is still running");if(0===s.executionLog.length)throw new Le("EMPTY_EXECUTION_LOG","no execution log available for script generation; run actions first");if(s.runtimeContext.pendingVerifyAction){const t=s.runtimeContext.pendingVerifyAction,n=[...s.diagnostics].reverse().find(e=>e.code===gt);throw new Le("SCRIPT_GENERATION_FAILED",`script generation blocked: action #${t.index} ${t.toolName} has not been verified. Resume with observe_screen(wait_ms) -> verify_ui_state before generating script.`,{sessionId:e,pendingVerify:t,...n?{lastDiagnostic:n}:{}})}let n;s.currentPhase="script_generation",this.emitter.emit("scriptgenerating",{sessionId:s.sessionId});try{const r=await t.generateScriptWithReliability(s.goal,s.executionLog,s.provider,s.reliability);if(n=r.workflow,r.attempts>1){s.runtimeContext.scriptGenerationRetries+=r.attempts-1;const t=Ge(s,{sessionId:e,phase:"script_generation",code:xt,message:`script generation retried ${r.attempts} attempts`,details:{errors:r.errors}});this.emitter.emit("diagnostic",t)}}catch(t){const n=ke(t,"SCRIPT_GENERATION_FAILED");throw new Le("SCRIPT_GENERATION_FAILED",`script generation failed: ${n.message}`,{sessionId:e,executionLogSize:s.executionLog.length,cause:n.message,...n.details})}s.running=!1,s.paused=!0,this.persistSession();const r={sessionId:s.sessionId,workflow:n,executionLog:[...s.executionLog],totalIterations:s.iteration,diagnostics:Ve(s)};return this.emitter.emit("complete",r),r}listSessions(e={}){return this.sessionStore?this.sessionStore.listSessions(e):[]}countSessions(e={}){return this.sessionStore?this.sessionStore.countSessions(e):0}getSessionSummary(e){return this.sessionStore?this.sessionStore.getSessionSummary(e):null}deleteSession(e){return!!this.sessionStore&&(this.session?.sessionId===e&&(this.stop(e),this.session=null,this.localToolContext={lastObserveRawDump:null,lastObserveNodes:[]}),this.sessionStore.deleteSession(e))}async destroy(){this.session&&this.stop(this.session.sessionId),await this.toolRegistry.destroy(),this.localToolContext={lastObserveRawDump:null,lastObserveNodes:[]},this.sessionStore&&(this.sessionStore.close(),this.sessionStore=null),this.emitter.clear()}async runPlanningStage(e){this.abortController=new AbortController;const t=this.abortController.signal;this.emitter.emit("planning",{sessionId:e.sessionId,status:"started"});let s=0,n=[];try{const r=await ze(e.goal,e.provider,e.device,e.reliability,{signal:t,planningMessages:e.planningContext?.messages||[]});return s=r.attempts,n=r.errors,"paused"===r.status?(e.currentPhase="planning",e.planningContext?.messages.push({role:"assistant",content:r.text,timestamp:Date.now()}),this.applyPlanningDiagnostics(e,s,n),this.emitter.emit("planning",{sessionId:e.sessionId,status:"paused",text:r.text}),this.markSessionPaused(e,r.text,"planning"),"paused"):(Xe(e,r.plan,r.screenDump),r.screenRawDump&&ne(this.localToolContext,r.screenRawDump),this.applyPlanningDiagnostics(e,s,n),this.emitter.emit("planning",{sessionId:e.sessionId,status:"completed",plan:r.plan}),"completed")}catch(t){if("AbortError"===t.name)throw t;const r=ke(t,"RUNTIME_LOOP_FAILED");return n.push(r.message),this.emitter.emit("error",{sessionId:e.sessionId,error:`task planning failed and was skipped: ${r.message}`,code:"RUNTIME_LOOP_FAILED",details:{phase:"planning",cause:r.message}}),Xe(e),this.applyPlanningDiagnostics(e,s,n),"completed"}finally{this.abortController=null}}applyPlanningDiagnostics(e,t,s){if(t>1&&(e.runtimeContext.planningRetries+=t-1),0===s.length)return;const n=s[s.length-1],r=Ge(e,{sessionId:e.sessionId,phase:"planning",code:t>1?_t:pt,message:`planning stage used retries or fallback: ${n}`,details:{attempts:t||1,errors:s}});this.emitter.emit("diagnostic",r)}async runLoop(){const e=this.session;if(!e)return;if(!this.provider)throw new Le("INVALID_CONFIG","model provider has not been initialized");Be(e),e.running=!0,e.paused=!1,e.currentPhase="runtime",this.abortController=new AbortController;const t=this.abortController.signal;try{for(;e.running;){const s=e.reliability.limits.maxIterations;if(s>0&&e.iteration>=s){e.runtimeContext.convergenceTriggers+=1;const t=Ge(e,{sessionId:e.sessionId,phase:"policy",code:ht,message:`iteration limit reached: ${s}`,iteration:e.iteration});return this.emitter.emit("diagnostic",t),void this.markSessionPaused(e,"execution paused after hitting iteration limit; review goal or adjust strategy","policy")}e.iteration++;const n=ct(e.messages),r=await this.provider.chatWithTools(n,this.toolRegistry.getModelTools(),t),o=r.content||"";o&&this.emitter.emit("thinking",{sessionId:e.sessionId,text:o,iteration:e.iteration});const i=this.toolRegistry.annotateToolCalls(r.toolCalls||[]);if(0===i.length)return o.trim().length>0&&We(e,o),void this.markSessionPaused(e,o,"runtime");We(e,o,i);for(const s of i){if(!e.running)break;this.emitter.emit("toolcall",{sessionId:e.sessionId,toolCall:s,iteration:e.iteration});const n=this.getVerifyGateError(e,s);if(n){const t={toolCallId:s.id,name:s.name,success:!1,error:n,errorCode:"RUNTIME_GUARD",retryable:!1,source:s.source,server:s.server},r=Ge(e,{sessionId:e.sessionId,phase:"runtime",code:gt,message:n,iteration:e.iteration,details:{pendingVerify:e.runtimeContext.pendingVerifyAction,blockedTool:s.name}});this.emitter.emit("diagnostic",r),this.emitter.emit("toolresult",{sessionId:e.sessionId,result:t,iteration:e.iteration}),Ke(e,s.id,Se(t)),this.persistSession();continue}const r=Rt(s),o=At(e.reliability,r),i=St(e.reliability,s.name),a=await Ct({toolCall:s,device:e.device,maxAttempts:o,timeoutMs:i,signal:t,execute:(e,t,s)=>this.toolRegistry.execute(e,t,s,this.localToolContext),onRetry:(t,n)=>{e.runtimeContext.toolRetries+=1;const r=Ge(e,{sessionId:e.sessionId,phase:"runtime",code:ft,message:`tool retry: ${s.name}, attempt ${t+1}`,iteration:e.iteration,details:{attempt:t,maxAttempts:o,errorCode:n.errorCode,error:n.error}});this.emitter.emit("diagnostic",r)}});a.source||(a.source=s.source,a.server=s.server),this.emitter.emit("toolresult",{sessionId:e.sessionId,result:a,iteration:e.iteration});if(!a.success&&"VALIDATION"===a.errorCode||rt(e,s,a,this.toolRegistry.getCategory(s,a)),Ke(e,s.id,Se(a)),this.updateRuntimeStateAfterTool(e,s,a,r),this.localToolContext.softPendingVerify=e.runtimeContext.softPendingVerify,this.localToolContext.lastSearchContextTimestamp=e.runtimeContext.lastSearchContextTimestamp,this.persistSession(),!e.running)break}}}catch(t){if("AbortError"===t.name)return e.running=!1,e.paused=!1,void this.persistSession();e.running=!1,e.paused=!0;const s=ke(t,"RUNTIME_LOOP_FAILED");this.emitter.emit("error",{sessionId:e.sessionId,error:s.message,code:s.code,details:s.details}),this.persistSession({lastError:s.message,lastErrorCode:s.code})}finally{this.abortController=null}}updateRuntimeStateAfterTool(e,s,n,r){const o=Lt(s);if(e.runtimeContext.lastToolFingerprint===o?e.runtimeContext.sameToolFingerprintCount+=1:(e.runtimeContext.lastToolFingerprint=o,e.runtimeContext.sameToolFingerprintCount=1),n.success)e.runtimeContext.consecutiveFailures=0;else{"VALIDATION"===n.errorCode||"RUNTIME_GUARD"===n.errorCode||(e.runtimeContext.consecutiveFailures+=1)}if(t.VERIFY_COMPLETION_TOOLS.has(s.name)&&n.success)e.runtimeContext.pendingVerifyAction=null,e.runtimeContext.softPendingVerify=null;else if("action"===r&&n.success&&t.VERIFY_REQUIRED_ACTIONS.has(s.name)){const t=e.executionLog.length;e.runtimeContext.pendingVerifyAction={index:t,toolName:s.name}}if(t.SOFT_VERIFY_ACTIONS.has(s.name)&&n.success&&(e.runtimeContext.softPendingVerify={index:e.executionLog.length,toolName:s.name}),"record_search_context"===s.name&&n.success&&(e.runtimeContext.lastSearchContextTimestamp=Date.now()),e.runtimeContext.consecutiveFailures>=e.reliability.limits.maxConsecutiveFailures){e.runtimeContext.convergenceTriggers+=1;const t=Ge(e,{sessionId:e.sessionId,phase:"policy",code:bt,message:`consecutive failure limit reached: ${e.reliability.limits.maxConsecutiveFailures}`,iteration:e.iteration,details:{consecutiveFailures:e.runtimeContext.consecutiveFailures}});return this.emitter.emit("diagnostic",t),void this.markSessionPaused(e,"execution paused due to excessive consecutive failures","policy")}if(e.runtimeContext.sameToolFingerprintCount>=e.reliability.limits.maxSameToolFingerprint){e.runtimeContext.convergenceTriggers+=1;const t=Ge(e,{sessionId:e.sessionId,phase:"policy",code:yt,message:`same tool fingerprint repeat limit reached: ${e.reliability.limits.maxSameToolFingerprint}`,iteration:e.iteration,details:{fingerprint:o,repeatCount:e.runtimeContext.sameToolFingerprintCount}});this.emitter.emit("diagnostic",t),this.markSessionPaused(e,"execution paused due to repeated non-converging tool calls","policy")}}getVerifyGateError(e,s){return function(e,s){return"local"!==s.source?null:e&&W(s.name)?t.VERIFY_GATE_ALLOWED_TOOLS.has(s.name)?null:`BLOCKED: #${e.index} ${e.toolName} 尚未验证。你必须先调用 observe_screen(wait_ms) 再调用 verify_ui_state,然后才能执行下一个动作。`:null}(e.runtimeContext.pendingVerifyAction,s)}markSessionPaused(e,t,s){e.running=!1,e.paused=!0,this.emitter.emit("paused",{sessionId:e.sessionId,text:t,iteration:e.iteration,phase:s}),this.persistSession()}persistSession(e){this.session&&this.sessionStore&&this.sessionStore.saveSession(this.session,e)}loadSessionIfNeeded(e){if(this.session&&this.session.sessionId===e)return Be(this.session);if(!this.sessionStore)throw new Le("SESSION_NOT_FOUND","session not found or expired");const t=this.sessionStore.loadSession(e);if(!t)throw new Le("SESSION_NOT_FOUND","session not found or expired");return this.session=Be(t),this.localToolContext={lastObserveRawDump:null,lastObserveNodes:[]},this.session}}exports.DEFAULT_PROVIDER_CONFIG=t.DEFAULT_PROVIDER_CONFIG,exports.DEFAULT_RELIABILITY_CONFIG=t.DEFAULT_RELIABILITY_CONFIG,exports.AgentRuntimeError=Le,exports.AutomationAgentRuntime=Dt,exports.createAutomationAgentRuntime=function(e={}){return new Dt(e)},exports.optimizeIntent=async function(e,s){const n=t.createProvider(s),r=[{role:"system",content:t.buildOptimizeIntentPrompt(e)},{role:"user",content:e}];return(await n.chatWithTools(r,[])).content.trim()},exports.toRuntimeError=ke;
1
+ "use strict";var e=require("uuid"),t=require("../chunks/scriptGenerator-B-5_FRJ5.cjs"),s=require("fs"),n=require("os"),r=require("path"),o=require("better-sqlite3"),i=require("zod"),a=require("zod-to-json-schema");require("@langchain/core/prompts"),require("@langchain/core/output_parsers"),require("@langchain/core/runnables"),require("@langchain/anthropic"),require("@langchain/core/messages"),require("@langchain/core/tools"),require("@langchain/google-genai"),require("@langchain/openai"),require("crypto");function c(){return r.join(function(){const e=n.homedir();if(!e)throw new Error("Failed to resolve user home directory for session persistence");return r.join(e,".vmosedge")}(),"agent_sessions.db")}function l(e){return{sessionId:e.session_id,goal:e.goal,status:(t=e.status,"running"===t||"paused"===t||"stopped"===t?t:"stopped"),providerVendor:e.provider_vendor||"unknown",deviceId:e.device_id||"",totalIterations:e.total_iterations||0,lastError:e.last_error||void 0,lastErrorCode:e.last_error_code||void 0,createTime:e.create_time,updateTime:e.update_time};var t}class u{db;constructor(e){!function(e){const t=r.dirname(e);s.existsSync(t)||s.mkdirSync(t,{recursive:!0})}(e),this.db=new o(e),this.initPragmas(),this.initSchema()}initPragmas(){this.db.pragma("busy_timeout = 5000"),this.db.pragma("foreign_keys = ON"),this.db.pragma("synchronous = NORMAL");try{this.db.pragma("journal_mode = WAL")}catch{this.db.pragma("journal_mode = DELETE")}}initSchema(){this.db.exec("\n CREATE TABLE IF NOT EXISTS agent_meta (\n meta_key TEXT PRIMARY KEY,\n meta_value TEXT NOT NULL,\n update_time INTEGER NOT NULL\n );\n CREATE TABLE IF NOT EXISTS agent_sessions (\n session_id TEXT PRIMARY KEY,\n goal TEXT NOT NULL,\n status TEXT NOT NULL,\n provider_vendor TEXT NOT NULL DEFAULT '',\n device_id TEXT NOT NULL DEFAULT '',\n total_iterations INTEGER NOT NULL DEFAULT 0,\n last_error TEXT,\n last_error_code TEXT,\n state_json TEXT NOT NULL,\n create_time INTEGER NOT NULL,\n update_time INTEGER NOT NULL\n );\n "),this.ensureSessionColumns(),this.db.exec("\n CREATE INDEX IF NOT EXISTS idx_agent_sessions_update_time\n ON agent_sessions(update_time DESC);\n CREATE INDEX IF NOT EXISTS idx_agent_sessions_status_update_time\n ON agent_sessions(status, update_time DESC);\n CREATE INDEX IF NOT EXISTS idx_agent_sessions_device_update_time\n ON agent_sessions(device_id, update_time DESC);\n "),this.saveSchemaVersion("3")}ensureSessionColumns(){const e=this.db.prepare("PRAGMA table_info(agent_sessions)").all(),t=new Set(e.map(e=>e.name)),s=[{name:"provider_vendor",definition:"TEXT NOT NULL DEFAULT ''"},{name:"device_id",definition:"TEXT NOT NULL DEFAULT ''"},{name:"total_iterations",definition:"INTEGER NOT NULL DEFAULT 0"},{name:"last_error",definition:"TEXT"},{name:"last_error_code",definition:"TEXT"}];for(const e of s)t.has(e.name)||this.db.exec(`ALTER TABLE agent_sessions ADD COLUMN ${e.name} ${e.definition}`)}saveSchemaVersion(e){this.db.prepare("\n INSERT INTO agent_meta (meta_key, meta_value, update_time)\n VALUES (@meta_key, @meta_value, @update_time)\n ON CONFLICT(meta_key) DO UPDATE SET\n meta_value=excluded.meta_value,\n update_time=excluded.update_time\n ").run({meta_key:"schema_version",meta_value:e,update_time:Date.now()})}buildQueryFilter(e){const t=[],s={};e.status&&(t.push("status = @status"),s.status=e.status),e.deviceId&&(t.push("device_id = @device_id"),s.device_id=e.deviceId);const n=e.keyword?.trim();return n&&(t.push("goal LIKE @keyword"),s.keyword=`%${n}%`),{whereSql:t.length>0?`WHERE ${t.join(" AND ")}`:"",params:s}}saveSession(e,t){const s=Date.now(),n=e.running?"running":e.paused?"paused":"stopped",r=JSON.stringify(e);this.db.prepare("\n INSERT INTO agent_sessions (\n session_id, goal, status, provider_vendor, device_id,\n total_iterations,\n last_error, last_error_code, state_json, create_time, update_time\n )\n VALUES (\n @session_id, @goal, @status, @provider_vendor, @device_id,\n @total_iterations,\n @last_error, @last_error_code, @state_json, @create_time, @update_time\n )\n ON CONFLICT(session_id) DO UPDATE SET\n goal=excluded.goal,\n status=excluded.status,\n provider_vendor=excluded.provider_vendor,\n device_id=excluded.device_id,\n total_iterations=excluded.total_iterations,\n last_error=excluded.last_error,\n last_error_code=excluded.last_error_code,\n state_json=excluded.state_json,\n update_time=excluded.update_time\n ").run({session_id:e.sessionId,goal:e.goal,status:n,provider_vendor:e.provider.vendor,device_id:e.device.deviceId,total_iterations:e.iteration,last_error:t?.lastError||null,last_error_code:t?.lastErrorCode||null,state_json:r,create_time:s,update_time:s})}loadSession(e){const t=this.db.prepare("SELECT * FROM agent_sessions WHERE session_id = ? LIMIT 1").get(e);if(!t)return null;try{return JSON.parse(t.state_json)}catch{return null}}getSessionSummary(e){const t=this.db.prepare("\n SELECT\n session_id, goal, status, provider_vendor, device_id, total_iterations,\n last_error, last_error_code, create_time, update_time, state_json\n FROM agent_sessions\n WHERE session_id = ? LIMIT 1\n ").get(e);return t?l(t):null}listSessions(e={}){const{whereSql:t,params:s}=this.buildQueryFilter(e),n=function(e){return"number"!=typeof e||Number.isNaN(e)?50:Math.min(500,Math.max(1,Math.floor(e)))}(e.limit),r=function(e){return"number"!=typeof e||Number.isNaN(e)?0:Math.max(0,Math.floor(e))}(e.offset);return this.db.prepare(`\n SELECT\n session_id, goal, status, provider_vendor, device_id, total_iterations,\n last_error, last_error_code, create_time, update_time, state_json\n FROM agent_sessions\n ${t}\n ORDER BY update_time DESC\n LIMIT @limit OFFSET @offset\n `).all({...s,limit:n,offset:r}).map(l)}countSessions(e={}){const{whereSql:t,params:s}=this.buildQueryFilter(e),n=this.db.prepare(`\n SELECT COUNT(1) as total\n FROM agent_sessions\n ${t}\n `).get(s);return n?.total||0}deleteSession(e){return this.db.prepare("DELETE FROM agent_sessions WHERE session_id = ?").run(e).changes>0}close(){this.db.close()}}function d(e,t){const s=t.normalize("NFKC");return"text"===e||"contentDesc"===e?s.replace(/\s+/gu," ").trim():"bounds"===e?s.replace(/\s+/gu,""):s.trim()}function m(e,t,s){return d(e,t)===d(e,s)}function p(e,t){return(void 0===t.index||e.index===t.index)&&(!!(!t.resourceId||e.resourceId&&m("resourceId",t.resourceId,e.resourceId))&&(!!(!t.text||e.text&&m("text",t.text,e.text))&&(!!(!t.contentDesc||e.contentDesc&&m("contentDesc",t.contentDesc,e.contentDesc))&&(!!(!t.className||e.className&&m("className",t.className,e.className))&&!!(!t.bounds||e.bounds&&m("bounds",t.bounds,e.bounds))))))}function _(e,t){let s=0;for(const n of e)p(n,t)&&(s+=1);return s}function g(e,t,s){return void 0===t||void 0===s?t===s:m(e,t,s)}function f(e,t){return e===t||e.index===t.index&&g("resourceId",e.resourceId,t.resourceId)&&g("text",e.text,t.text)&&g("contentDesc",e.contentDesc,t.contentDesc)&&g("className",e.className,t.className)&&g("bounds",e.bounds,t.bounds)}function h(e,t,s){let n=0;for(const r of e)if(p(r,t)){if(f(r,s))return n;n+=1}}const b=e=>i.z.string().describe(e),y=e=>i.z.coerce.number().describe(e),x=e=>i.z.coerce.number().int().positive().describe(e),v=e=>{const t=i.z.coerce.number().int().positive().optional();return e?t.describe(e):t},I=e=>{const t=i.z.coerce.number().int().min(0).optional();return e?t.describe(e):t},T=e=>{const t=i.z.string().optional();return e?t.describe(e):t},w=(e,t)=>i.z.enum(e).optional().describe(t);function N(e){const t=a.zodToJsonSchema(e,{$refStrategy:"none"}),{$schema:s,definitions:n,...r}=t;return r}function C(e){return{name:e.name,description:e.description,category:e.category,schema:e.schema,parameters:N(e.schema)}}const E=i.z.object({resource_id:T("元素资源ID,如 com.example:id/btn"),text:T("元素文本,支持正则"),content_desc:T("元素内容描述,支持正则"),class_name:T("元素类名"),bounds:T("元素 bounds,格式 [x1,y1][x2,y2],仅用于兜底消歧,不建议直接进入脚本")}).strict(),S=w(["home","list","detail","dialog","search","form","unknown"],"可选:显式声明当前页面类型。仅在你有把握时填写;不确定则省略。"),A=w(["navigation","search_entry","action_button","input_field","content_item","generic_target"],"可选:显式声明目标元素角色。仅在你有把握时填写;不确定则省略。"),O=w(["critical","supporting","noise"],"可选:显式声明本轮动作对主流程的重要性。仅在你有把握时填写;不确定则省略。"),L=w(["main_flow","exception_handler_candidate","log_only"],"可选:显式声明该记录应进入主流程、异常处理还是仅日志。仅在你有把握时填写;不确定则省略。"),R=w(["next_action","next_verified_transition","manual_close"],"可选:声明该上下文持续到何时结束。默认 next_verified_transition。"),D=i.z.object({step_intent:b("本次操作在任务流程中的目的和预期效果。必须说明为什么执行此操作、期望达到什么状态,而非重复元素属性描述。"),merge_key:T("可选:稳定的步骤合并键。用于把同类动作合并为同一脚本步骤,例如 open_search_page、scroll_result_list。仅在你能明确抽象出可复用步骤语义时填写。"),expected_result:T("可选:本次动作完成后预期达到的状态描述。建议使用比 step_intent 更直接的结果表述,例如“进入搜索页”“列表向下推进一屏”。"),wait_ms_hint:v("可选:建议回放时在主动作后等待或验证的毫秒数。仅在你明确知道该步骤通常需要额外等待时填写。"),page_kind:S,target_role:A,criticality_hint:O,include_mode_hint:L,anchor:i.z.object({desc:b("页面锚点元素描述,确认当前所在页面"),resource_id:T(),text:T(),content_desc:T(),class_name:T(),bounds:T()}).describe("页面锚点元素,必填"),target:i.z.object({desc:b("操作目标元素描述"),resource_id:T(),text:T(),content_desc:T(),class_name:T(),bounds:T()}).describe("本次操作的目标元素,必填"),post_verify:i.z.object({desc:b("操作成功后验证元素描述"),resource_id:T(),text:T(),content_desc:T(),class_name:T(),bounds:T()}).optional().describe("Phase 1 内部提示字段。告知 LLM 本次操作的最终预期状态,用于决定是否需要执行 observe_screen(wait_ms) + verify_ui_state。此字段不参与脚本生成;脚本的验证条件来自 verify_ui_state 的 verify_element。")}).describe("操作前必填的 UI 上下文,anchor+target+step_intent 强制要求;page_kind/target_role/criticality_hint/include_mode_hint 在明确时建议显式填写,用于录制质量约束和回放。"),k=i.z.coerce.number().int().min(0).optional().describe("匹配结果动作索引(从 0 开始,仅 accessibility/node 使用)"),F=i.z.object({wait_timeout:x("等待元素出现超时(ms)").optional(),wait_interval:x("重试间隔(ms)").optional()}),$=C({name:"observe_screen",description:"获取当前屏幕的 UI 无障碍节点树。返回简化后的 UI 结构,包含每个可交互元素的 text、resource-id、content-desc、class、bounds 等属性。可选传 wait_ms:先等待指定毫秒再拉取,用于动作后「等待加载 + 获取新页面」一步完成。",schema:i.z.object({wait_ms:x("可选:先等待的毫秒数,再拉取 UI").optional()}),category:"observation"}),U=C({name:"get_installed_apps",description:"获取设备上已安装的应用列表,返回每个应用的包名(package_name)和应用名(app_name)。用于查找目标应用的包名以便启动。",schema:i.z.object({type:i.z.enum(["user","system","all"]).optional().describe("应用类型过滤:user(默认) / system / all")}),category:"observation"}),M=C({name:"get_top_activity",description:"获取当前前台应用的 Activity 信息,返回 package_name 和 class_name。用于判断当前在哪个应用的哪个页面。",schema:i.z.object({}),category:"observation"}),P=C({name:"get_screen_info",description:"获取屏幕显示信息,返回 width、height、rotation、orientation。用于确定屏幕分辨率以计算滑动/点击坐标。",schema:i.z.object({}),category:"observation"}),j=i.z.object({desc:b("元素简短描述"),resource_id:T("元素 resource-id,来自 observe_screen"),text:T("元素文本,来自 observe_screen"),content_desc:T("元素 content-desc"),class_name:T("元素类名,来自 observe_screen"),index:I("节点 index(来自 observe_screen 的 [index])")}),z=C({name:"verify_ui_state",description:"动作执行后,记录用于验证操作成功的标志性 UI 元素。必须先调用 observe_screen(可带 wait_ms)获取新界面,再调用本工具;verify_element 的 text/resource_id 必须来自当前界面真实节点,禁止编造。建议同时记录节点 index(observe_screen 的 [index])提高回放稳定性。当操作导致页面切换时,page_anchor 必须填写新页面的标志性元素。screen_desc 必填,用一句话描述当前页面;若你明确知道当前页面类型,可额外填写 page_kind;若该步骤回放通常需要额外等待,可填写 wait_ms_hint。",schema:i.z.object({step_desc:b("验证步骤描述"),page_kind:S,wait_ms_hint:v("可选:建议回放在验证前等待的毫秒数。仅在你明确知道该步骤通常需要额外等待时填写。"),verify_element:j.describe("用于验证的 UI 元素,必须来自当前 observe_screen 结果"),screen_desc:b('当前页面的简要描述,用一句话概括所在页面(如"抖音首页信息流"、"微信聊天列表"、"系统设置-通用页面")。此字段帮助脚本生成 AI 理解操作上下文,必填。'),page_anchor:j.optional().describe("动作后的页面锚点元素(如页面切换后的新页面标题/导航栏)。当操作导致页面切换时必填,用于脚本生成时确认导航成功。")}),category:"observation"}),q=i.z.object({desc:b("元素描述"),resource_id:T(),text:T(),content_desc:T(),class_name:T(),bounds:T(),index:I("节点 index(来自 observe_screen 的 [index])")}),G=C({name:"record_search_context",description:"在 swipe 或 press_key 前调用,记录本轮查找目标(target_desc + target_element)和当前页面锚点(anchor_element)。元素属性必须来自最近一次 observe_screen 的真实结果;step_intent、target_desc、anchor_element 必填。建议同时记录节点 index(observe_screen 的 [index])。",schema:i.z.object({step_intent:b("本次滑动/按键在任务流程中的目的和预期效果,说明为什么执行此操作、期望达到什么状态。"),merge_key:T("可选:稳定的步骤合并键。用于把同类滑动/按键动作合并为同一脚本步骤,例如 scroll_result_list、submit_search。"),expected_result:T("可选:本次动作完成后预期达到的状态描述,例如“结果列表继续向下推进”“进入详情页”。"),wait_ms_hint:v("可选:建议回放时在本轮动作后等待或验证的毫秒数。仅在你明确知道该步骤通常需要额外等待时填写。"),target_desc:b("本轮查找目标描述"),target_element:q.optional().describe("查找目标元素,可选"),anchor_element:q.describe("当前页面锚点元素,必填"),page_kind:S,target_role:A,criticality_hint:O,include_mode_hint:L,valid_until:R}),category:"observation"});var V;const Y=[C({name:"start_app",description:"启动指定应用。需要 package_name。工具会在启动前自动执行 permission/set(grant_all=true)。",schema:i.z.object({package_name:b("应用包名")}),category:"action"}),C({name:"stop_app",description:"停止(强制关闭)指定应用。",schema:i.z.object({package_name:b("应用包名")}),category:"action"}),C({name:"tap",description:"在屏幕指定坐标点击。属于兜底方案:仅当按 resource_id -> text -> content_desc -> 最小组合 仍无法唯一定位元素时才允许使用。",schema:i.z.object({x:y("X 坐标"),y:y("Y 坐标")}),category:"action"}),C({name:"tap_element",description:"通过选择器查找并点击 UI 元素。这是推荐点击方式。\n\n调用规则:\n1. pre_context.anchor(页面锚点)和 pre_context.target(目标元素)必填,不可省略\n2. selector 遵循脚本优先策略:优先稳定 resource_id,其次稳定 text,再次稳定 content_desc,最后最小组合;bounds 仅作兜底消歧\n3. 序号点击使用 action_index(从 0 开始),严禁 selector.index\n4. 若点击会导致界面变化,最终验证必须:observe_screen(wait_ms) -> verify_ui_state",schema:i.z.object({pre_context:D,selector:E.describe("元素选择器"),action_index:k,...F.shape}).strict(),category:"action"}),C({name:"long_press_element",description:"通过选择器查找并长按 UI 元素。\n\n调用规则:\n1. pre_context.anchor 和 pre_context.target 必填,不可省略\n2. selector 遵循脚本优先策略:优先稳定 resource_id,其次稳定 text,再次稳定 content_desc,最后最小组合;bounds 仅作兜底消歧\n3. 序号操作使用 action_index(从 0 开始),严禁 selector.index\n4. 长按通常触发界面变化,最终验证必须:observe_screen(wait_ms) -> verify_ui_state",schema:i.z.object({pre_context:D,selector:E.describe("元素选择器"),action_index:k,...F.shape}).strict(),category:"action"}),C({name:"set_text",description:"通过选择器定位输入框并设置文本内容。\n\n调用规则:\n1. pre_context.anchor 和 pre_context.target 必填,不可省略\n2. 多个输入框时使用 action_index(从 0 开始),严禁 selector.index\n3. 若需要提交输入内容,紧接调用 press_key(66) 或等价提交动作\n4. 若后续操作导致界面变化,最终验证必须:observe_screen(wait_ms) -> verify_ui_state",schema:i.z.object({pre_context:D,selector:E.describe("输入框选择器"),action_index:k,text:b("要输入的文本"),...F.shape}).strict(),category:"action"}),C({name:"input_text",description:"向当前聚焦输入框直接输入文本,不需要 selector。",schema:i.z.object({text:b("要输入的文本")}),category:"action"}),C({name:"swipe",description:"执行坐标滑动手势,常用于滚动列表加载更多内容。\n\n调用规则:\n1. swipe 前先调用 record_search_context 记录查找目标与锚点\n2. swipe 后调用 observe_screen(wait_ms=500~1200),再根据结果决定是否继续滑动或调用 verify_ui_state\n3. 坐标依赖屏幕分辨率,调用前应已通过 get_screen_info 确认分辨率",schema:i.z.object({start_x:y("起点 X"),start_y:y("起点 Y"),end_x:y("终点 X"),end_y:y("终点 Y"),duration:y("持续时间(ms)").optional()}),category:"action"}),C({name:"press_key",description:"按下物理/虚拟按键。常用键码:3=Home, 4=Back, 66=Enter, 82=菜单等。调用前可先 record_search_context;按键会改变界面时,后续需 observe_screen(wait_ms) -> verify_ui_state。",schema:i.z.object({key_code:(V="Android KeyCode",i.z.coerce.number().int().describe(V))}),category:"action"}),C({name:"wait",description:"暂停指定时间。用于等待页面加载或动画完成。时长需按前一动作自适应:轻交互 300~800ms,滑动后 500~1200ms,页面切换/返回 1200~2500ms,启动应用 2000~4000ms;禁止全程固定同一时长。",schema:i.z.object({duration:y("等待时间(ms)")}),category:"action"})],B=[...[$,z,G,U,M,P],...Y];function X(e){return Y.some(t=>t.name===e)}const W=new Set(["btn","button","text","txt","img","image","icon","view","layout","container","content","header","footer","title","subtitle","label","input","edit","search","menu","nav","tab","bar","list","item","card","dialog","popup","modal","scroll","pager","recycler","grid","frame","root","main","action","toolbar","status","progress","loading","empty","error","divider","separator","wrapper","panel","avatar","profile","name","email","phone","password","submit","cancel","confirm","close","back","next","home","setting","notification","message","chat","comment","like","share","follow","post","feed","story","video","audio","play","pause","camera","photo","gallery","download","upload","save","delete","add","create","update","refresh","filter","sort","check","switch","toggle","radio","select","option","dropdown","picker","date","row","column","cell","section","group","block","region","area","top","bottom","left","right","center","start","end","inner","outer","overlay","badge","count","number","description","info","hint","placeholder","detail","summary","expand","collapse","more","viewpager","appbar","bottombar","snackbar","fab","chip","spinner","seekbar","slider","webview","mapview","banner","splash","widget"]);function K(e){if(!e)return!1;const t=e.includes("/")?e.split("/").pop():e;return!!t&&(!e.startsWith("android:")&&(!!/obfuscated/i.test(t)||(!!/^\d+_/.test(t)||!function(e){const t=e.toLowerCase(),s=t.split("_");for(const e of s)if(e.length>=3&&W.has(e))return!0;for(const e of W)if(e.length>=4&&t.includes(e))return!0;return!1}(t)&&(t.length<=5||t.length<=10&&function(e){if(0===e.length)return 0;const t=new Map;for(const s of e)t.set(s,(t.get(s)||0)+1);let s=0;for(const n of t.values()){const t=n/e.length;s-=t*Math.log2(t)}return s}(t)>3))))}function J(e){return e.normalize("NFKC").replace(/\s+/gu," ").trim()}function H(e,t,s,n){const r=[],o=function(e,t,s){if(!s)return 0;const n=new Set;for(const r of e){if(r.resourceId!==s)continue;const e="text"===t?r.text:r.contentDesc;e&&n.add(J(e))}return n.size}(s,"text"===n?"text":"contentDesc",t.resourceId),i=function(e){const t=J(e);return!!t&&(/\b\d+\b/u.test(t)||/\d+[::]\d+/u.test(t)||/\d+[./-]\d+/u.test(t)||/[$¥¥]\s*\d/u.test(t)||/\brow\b|\bcolumn\b|行|列/u.test(t)||/@\w+/u.test(t))}(e),a=function(e){const t=J(e);return t.length>24||/合集|日记|vlog|攻略|教程|推荐|详情|播放|第.+集/u.test(t)}(e),c=e.length<=12,l=1===_(s,"text"===n?{text:e}:{contentDesc:e});o>=2&&r.push("same resource_id carries multiple values on screen"),i&&r.push("contains dynamic segment"),a&&r.push("looks like content item text"),c||r.push("text is long or verbose"),l||r.push("not unique on current screen");const u=0===r.length||c&&l&&r.length<=1,d=u&&!i&&!a;return u&&t.clickable&&c&&r.push("short interactive label"),{stable:u,reusable:d,risk:d?"low":u?"medium":"high",reasons:r}}function Q(e,t){const s=[];if(e.resourceId){const t=function(e){const t=[];K(e)?t.push("resource_id looks obfuscated"):t.push("resource_id contains semantic naming");const s=!K(e);return{stable:s,reusable:s,risk:s?"low":"high",reasons:t}}(e.resourceId);s.push({field:"resource_id",value:e.resourceId,stable:t.stable,reusable:t.reusable,risk:t.risk,reasons:t.reasons})}if(e.text){const n=H(e.text,e,t,"text");s.push({field:"text",value:e.text,stable:n.stable,reusable:n.reusable,risk:n.risk,reasons:n.reasons})}if(e.contentDesc){const n=H(e.contentDesc,e,t,"content_desc");s.push({field:"content_desc",value:e.contentDesc,stable:n.stable,reusable:n.reusable,risk:n.risk,reasons:n.reasons})}if(e.className){const t={stable:!0,reusable:!1,risk:"medium",reasons:["class_name is only a disambiguation field, not a primary selector"]};s.push({field:"class_name",value:e.className,stable:t.stable,reusable:t.reusable,risk:t.risk,reasons:t.reasons})}if(e.bounds){const t={stable:!1,reusable:!1,risk:"high",reasons:[`bounds=${e.bounds} is screen-dependent and should stay debug-only`]};s.push({field:"bounds",value:e.bounds,stable:t.stable,reusable:t.reusable,risk:t.risk,reasons:t.reasons})}return s}function Z(e){if(e.length<2)return!1;const t=e.map(e=>{const t=function(e){if(!e)return null;const t=e.match(/\[(\d+),(\d+)\]\[(\d+),(\d+)\]/);return t?{x1:Number(t[1]),y1:Number(t[2]),x2:Number(t[3]),y2:Number(t[4])}:null}(e.bounds);return t?{x:(t.x1+t.x2)/2,y:(t.y1+t.y2)/2}:null}).filter(e=>!!e);if(t.length!==e.length)return!1;const s=new Set(t.map(e=>Math.round(e.x/10))).size,n=new Set(t.map(e=>Math.round(e.y/10))).size;return s>1||n>1}function ee(e,t,s,n){const r=_(t,{resourceId:s.resource_id,text:s.text,contentDesc:s.content_desc,className:s.class_name,bounds:s.bounds});if(0===r)return;if(n.requireUnique&&1!==r)return;if(!n.requireUnique&&r>1&&void 0===n.action_index)return;e.some(e=>JSON.stringify(e.selector)===JSON.stringify(s)&&e.action_index===n.action_index)||e.push({selector:s,action_index:n.action_index,stability:n.stability,reusable:n.reusable,matched_count:r,reason:n.reason,scope:n.scope,policy_decision:n.policy_decision,policy_reason:n.policy_reason})}function te(e,t,s={}){if(!e||0===t.length)return null;const n=s.mode||"action",r=function(e){return"action"===e?{allowCollectionScope:!0,allowActionIndex:!0,allowContentCombos:!0,allowedScopes:["global","page","collection"],policyReason:"action selectors may use collection disambiguation"}:{allowCollectionScope:!1,allowActionIndex:!1,allowContentCombos:!1,allowedScopes:["global","page"],policyReason:`${e} selectors must stay single-field and non-positional`}}(n),o=Q(e,t),i=new Map;for(const e of o)i.set(e.field,e);const a=[],c=e.resourceId,l=e.text,u=e.contentDesc,d=e.className,m=e.bounds;if(function(e,t,s,n,r){s.resourceId&&!0===n.get("resource_id")?.reusable&&ee(e,t,{resource_id:s.resourceId},{stability:"high",reusable:!0,reason:"unique_resource_id",scope:"global",requireUnique:!0,policy_decision:"approved",policy_reason:r.policyReason}),s.text&&!0===n.get("text")?.reusable&&ee(e,t,{text:s.text},{stability:"high",reusable:!0,reason:"unique_reliable_text",scope:"global",requireUnique:!0,policy_decision:"approved",policy_reason:r.policyReason}),s.contentDesc&&!0===n.get("content_desc")?.reusable&&ee(e,t,{content_desc:s.contentDesc},{stability:"high",reusable:!0,reason:"unique_reliable_content_desc",scope:"global",requireUnique:!0,policy_decision:"approved",policy_reason:r.policyReason})}(a,t,{resourceId:c,text:l,contentDesc:u},i,r),function(e,t,s,n,r){r.allowContentCombos&&(s.resourceId&&!0===n.get("resource_id")?.reusable&&s.text&&n.get("text")?.stable&&ee(e,t,{resource_id:s.resourceId,text:s.text},{stability:"medium",reusable:!0===n.get("text")?.reusable,reason:"resource_id_text_combo",scope:"page",requireUnique:!0,policy_decision:"approved",policy_reason:r.policyReason}),s.resourceId&&!0===n.get("resource_id")?.reusable&&s.contentDesc&&n.get("content_desc")?.stable&&ee(e,t,{resource_id:s.resourceId,content_desc:s.contentDesc},{stability:"medium",reusable:!0===n.get("content_desc")?.reusable,reason:"resource_id_content_desc_combo",scope:"page",requireUnique:!0,policy_decision:"approved",policy_reason:r.policyReason}),s.text&&s.className&&n.get("text")?.stable&&ee(e,t,{text:s.text,class_name:s.className},{stability:"medium",reusable:!0===n.get("text")?.reusable,reason:"text_class_combo",scope:"page",requireUnique:!0,policy_decision:"approved",policy_reason:r.policyReason}),s.contentDesc&&s.className&&n.get("content_desc")?.stable&&ee(e,t,{content_desc:s.contentDesc,class_name:s.className},{stability:"medium",reusable:!0===n.get("content_desc")?.reusable,reason:"content_desc_class_combo",scope:"page",requireUnique:!0,policy_decision:"approved",policy_reason:r.policyReason}))}(a,t,{resourceId:c,text:l,contentDesc:u,className:d},i,r),function(e,t,s,n,r,o,i){if(o.allowCollectionScope&&o.allowActionIndex){if(n.resourceId){const a={resourceId:n.resourceId},c=s.filter(e=>p(e,a));c.length>1&&Z(c)&&ee(e,s,{resource_id:n.resourceId},{action_index:i??h(s,a,t),stability:!0===r.get("resource_id")?.reusable?"medium":"low",reusable:!0===r.get("resource_id")?.reusable,reason:!0===r.get("resource_id")?.reusable?"resource_id_positional":"obfuscated_id_positional",scope:"collection",policy_decision:"approved",policy_reason:o.policyReason})}if(n.text){const a={text:n.text},c=s.filter(e=>p(e,a));c.length>1&&Z(c)&&ee(e,s,{text:n.text},{action_index:i??h(s,a,t),stability:"low",reusable:!0===r.get("text")?.reusable,reason:"text_positional",scope:"collection",policy_decision:"approved",policy_reason:o.policyReason})}if(n.contentDesc){const a={contentDesc:n.contentDesc},c=s.filter(e=>p(e,a));c.length>1&&Z(c)&&ee(e,s,{content_desc:n.contentDesc},{action_index:i??h(s,a,t),stability:"low",reusable:!0===r.get("content_desc")?.reusable,reason:"content_desc_positional",scope:"collection",policy_decision:"approved",policy_reason:o.policyReason})}n.className&&n.bounds&&ee(e,s,{class_name:n.className,bounds:n.bounds},{stability:"low",reusable:!1,reason:"class_bounds_fallback",scope:"page",requireUnique:!0,policy_decision:"approved",policy_reason:o.policyReason})}}(a,e,t,{resourceId:c,text:l,contentDesc:u,className:d,bounds:m},i,r,s.requestedActionIndex),0===a.length){if("action"!==n)return null;const e=!0===i.get("resource_id")?.reusable,o=!0===i.get("text")?.reusable,p=!0===i.get("content_desc")?.reusable;c?ee(a,t,{resource_id:c},{stability:e?"medium":"low",reusable:e,reason:e?"resource_id_positional":"obfuscated_id_positional",scope:"collection",action_index:s.requestedActionIndex,policy_decision:"approved",policy_reason:r.policyReason}):l?ee(a,t,{text:l},{stability:"low",reusable:o,reason:"text_positional",scope:"collection",action_index:s.requestedActionIndex,policy_decision:"approved",policy_reason:r.policyReason}):u?ee(a,t,{content_desc:u},{stability:"low",reusable:p,reason:"content_desc_positional",scope:"collection",action_index:s.requestedActionIndex,policy_decision:"approved",policy_reason:r.policyReason}):d&&m&&ee(a,t,{class_name:d,bounds:m},{stability:"low",reusable:!1,reason:"class_bounds_fallback",scope:"page",requireUnique:!0,policy_decision:"approved",policy_reason:r.policyReason})}const _=a[0];return _?{mode:n,field_assessments:o,selector_candidates:a,script_selector:{selector:_.selector,action_index:_.action_index,stability:_.stability,reusable:_.reusable,reason:_.reason,scope:_.scope,policy_decision:_.policy_decision,policy_reason:_.policy_reason}}:null}function se(e,t){return e.lastObserveRawDump=t,e.lastObserveNodes=function(e){const t=[],s=e.split("\n");for(const e of s){const s=e.trim(),n=s.match(/^\[(\d+)\]\s+(\S+)/);n&&t.push({index:Number(n[1]),className:n[2],resourceId:s.match(/\bresource-id="([^"]*)"/)?.[1]||void 0,text:s.match(/\btext="([^"]*)"/)?.[1]||void 0,contentDesc:s.match(/\bcontent-desc="([^"]*)"/)?.[1]||void 0,packageName:s.match(/\bpackage="([^"]*)"/)?.[1]||void 0,bounds:s.match(/\bbounds=(\[[^\]]*\]\[[^\]]*\])/)?.[1]||void 0,clickable:/\bclickable=true\b/.test(s),longClickable:/\blong-clickable=true\b/.test(s),scrollable:/\bscrollable=true\b/.test(s),focusable:/\bfocusable=true\b/.test(s),enabled:!/\benabled=false\b/.test(s),checked:/\bchecked=true\b/.test(s),selected:/\bselected=true\b/.test(s)})}return t}(t),e.lastObserveNodes}function ne(e,t,s={}){const n=te(e,t,s);return n?{...n,selected_node:(r=e,{index:r.index,resource_id:r.resourceId,text:r.text,content_desc:r.contentDesc,class_name:r.className,bounds:r.bounds})}:null;var r}const re=new Set(["btn","button","text","txt","img","image","icon","view","layout","container","content","header","footer","title","subtitle","label","input","edit","search","menu","nav","tab","bar","list","item","card","dialog","popup","modal","scroll","pager","recycler","grid","frame","root","main","action","toolbar","status","progress","loading","empty","error","divider","separator","wrapper","panel","avatar","profile","name","email","phone","password","submit","cancel","confirm","close","back","next","home","setting","notification","message","chat","comment","like","share","follow","post","feed","story","video","audio","play","pause","camera","photo","gallery","download","upload","save","delete","add","create","update","refresh","filter","sort","check","switch","toggle","radio","select","option","dropdown","picker","date","row","column","cell","section","group","block","region","area","top","bottom","left","right","center","start","end","inner","outer","overlay","badge","count","number","description","info","hint","placeholder","detail","summary","expand","collapse","more","viewpager","appbar","bottombar","snackbar","fab","chip","spinner","seekbar","slider","webview","mapview","banner","splash","widget"]);function oe(e){if(!e)return!1;const t=e.includes("/")?e.split("/").pop():e;return!!t&&(!e.startsWith("android:")&&(!!/obfuscated/i.test(t)||(!!/^\d+_/.test(t)||!function(e){const t=e.toLowerCase(),s=t.split("_");for(const e of s)if(e.length>=3&&re.has(e))return!0;for(const e of re)if(e.length>=4&&t.includes(e))return!0;return!1}(t)&&(t.length<=5||t.length<=10&&function(e){if(0===e.length)return 0;const t=new Map;for(const s of e)t.set(s,(t.get(s)||0)+1);let s=0;for(const n of t.values()){const t=n/e.length;s-=t*Math.log2(t)}return s}(t)>3))))}function ie(e,t,s){let n=0;for(const r of e)if(fe(r,t)){if(n===s)return r;n++}return null}async function ae(e,t,s,n,r){const o=function(e){return`http://${e.hostIp}:18182/android_api/v2/${e.deviceId}`}(e),i=`${o}/${s}`,a={method:t,headers:{"Content-Type":"application/json"},signal:r};void 0!==n&&"POST"===t&&(a.body=JSON.stringify(n));const c=await fetch(i,a);if(!c.ok){const e=await c.text();throw new Error(`API ${s} failed: ${c.status} - ${e}`)}return await c.json()}function ce(e){return!e||"object"!=typeof e||Array.isArray(e)?null:e}function le(e,t){return Object.prototype.hasOwnProperty.call(e,t)}function ue(e){if(null==e||""===e)return{ok:!0};const t="string"==typeof e?Number(e):e;return"number"==typeof t&&Number.isFinite(t)?!Number.isInteger(t)||t<0?{ok:!1}:{ok:!0,value:t}:{ok:!1}}function de(e,t){if(!le(e,"index"))return null;return ue(e.index).ok?null:`${t}.index 必须是从 0 开始的非负整数。`}function me(e){if("string"!=typeof e)return;const t=e.trim();return t.length>0?t:void 0}function pe(e){const t={};let s=!1;for(const[n,r]of Object.entries(e))null!=r&&""!==r&&(t[n]=r,s=!0);return s?t:void 0}function _e(e){return pe({context_id:`ctx_${Date.now()}_${Math.floor(1e5*Math.random())}`,page_kind:me(e.page_kind),target_role:me(e.target_role),criticality_hint:me(e.criticality_hint),include_mode_hint:me(e.include_mode_hint),valid_until:me(e.valid_until)||"next_verified_transition"})||{context_id:`ctx_${Date.now()}_${Math.floor(1e5*Math.random())}`,valid_until:"next_verified_transition"}}function ge(e){if(!e||"object"!=typeof e||Array.isArray(e))return null;const t=e,s=ue(t.index);if(!s.ok)return null;const n={index:s.value,resourceId:me(t.resource_id),text:me(t.text),contentDesc:me(t.content_desc),className:me(t.class_name),bounds:me(t.bounds)};return n.resourceId||n.text||n.contentDesc||n.className||n.bounds?n:null}function fe(e,t){return p(e,t)}function he(e){const t=[];return e.resourceId&&t.push({key:"resource_id",value:e.resourceId}),e.text&&t.push({key:"text",value:e.text}),e.contentDesc&&t.push({key:"content_desc",value:e.contentDesc}),e.className&&t.push({key:"class_name",value:e.className}),e.bounds&&t.push({key:"bounds",value:e.bounds}),t}function be(e,t){if(e.index!==t.index)return!1;const s=he(e),n=he(t);return s.length===n.length&&s.every(({key:e,value:s})=>{switch(e){case"resource_id":return!!t.resourceId&&m("resourceId",s,t.resourceId);case"text":return!!t.text&&m("text",s,t.text);case"content_desc":return!!t.contentDesc&&m("contentDesc",s,t.contentDesc);case"class_name":return!!t.className&&m("className",s,t.className);case"bounds":return!!t.bounds&&m("bounds",s,t.bounds)}})}function ye(e,t){return _(e,t)}function xe(e,t){const s=ge(e.selector);if(!s?.resourceId)return null;const n=function(e){const t=new Map;for(const s of e)s.resourceId&&t.set(s.resourceId,(t.get(s.resourceId)||0)+1);const s=new Set;for(const[e,n]of t)(oe(e)||!e.startsWith("android:")&&n>=5)&&s.add(e);return s}(t);return n.has(s.resourceId)?`selector.resource_id="${s.resourceId}" 疑似混淆值,回放时可能不稳定。建议下次优先使用 text/content_desc/class_name 定位。`:null}function ve(e,s,n){if(!t.SELECTOR_ACTION_TOOLS.has(e))return null;const r=ge(s.selector);if(!r)return"validation failed: selector is missing or invalid. Provide resource_id/text/content_desc/class_name/bounds.";const o=n?.lastObserveNodes;if(!o||0===o.length)return"validation failed: missing observe evidence. Call observe_screen before action tools.";const i=ye(o,r);if(0===i)return"validation failed: selector has no match on latest observed screen.";const a=ue(s.action_index);if(!a.ok)return"validation failed: action_index must be a non-negative integer.";if(void 0!==a.value&&a.value>=i)return`validation failed: action_index=${a.value} is out of range for matched_count=${i}.`;if(i>1&&void 0===a.value)return`validation failed: selector is ambiguous (matched_count=${i}). Refine selector or provide action_index.`;const c=function(e){if(!e||"object"!=typeof e||Array.isArray(e))return null;const t=e.target;if(!t||"object"!=typeof t||Array.isArray(t))return null;const s=t,n=ue(s.index);if(!n.ok)return null;const r={index:n.value,resourceId:me(s.resource_id),text:me(s.text),contentDesc:me(s.content_desc),className:me(s.class_name),bounds:me(s.bounds)};return r.resourceId||r.text||r.contentDesc||r.className||r.bounds?r:null}(s.pre_context);return c&&!function(e,t){return void 0!==e.index&&void 0!==t.index&&e.index===t.index||!!(e.resourceId&&t.resourceId&&m("resourceId",e.resourceId,t.resourceId))||!!(e.text&&t.text&&m("text",e.text,t.text))||!!(e.contentDesc&&t.contentDesc&&m("contentDesc",e.contentDesc,t.contentDesc))||!!(e.className&&t.className&&m("className",e.className,t.className))||!!(e.bounds&&t.bounds&&m("bounds",e.bounds,t.bounds))}(r,c)?"validation failed: selector does not align with pre_context.target evidence.":null}function Ie(e,t){const s=ge(e.selector),n=ce(t.script_selector),r=ge(n?.selector);if(!s||!r)return null;const o=ue(e.action_index),i=ue(n?.action_index),a=he(s).map(e=>e.key),c=he(r).map(e=>e.key);if(be(s,r)&&(o.value??void 0)===(i.value??void 0))return{requested_is_minimal:!0,diff_type:"same",requested_selector_fields:a,script_selector_fields:c,script_selector:n?.selector,script_action_index:i.value};const l=function(e,t){const s=[];for(const{key:s,value:n}of he(t))if(!(()=>{switch(s){case"resource_id":return!!e.resourceId&&m("resourceId",n,e.resourceId);case"text":return!!e.text&&m("text",n,e.text);case"content_desc":return!!e.contentDesc&&m("contentDesc",n,e.contentDesc);case"class_name":return!!e.className&&m("className",n,e.className);case"bounds":return!!e.bounds&&m("bounds",n,e.bounds)}})())return{matchesBase:!1,redundantFields:[]};for(const{key:n}of he(e))(()=>{switch(n){case"resource_id":return!!t.resourceId;case"text":return!!t.text;case"content_desc":return!!t.contentDesc;case"class_name":return!!t.className;case"bounds":return!!t.bounds}})()||s.push(n);return{matchesBase:!0,redundantFields:s}}(s,r),u=void 0!==o.value&&void 0===i.value;return l.matchesBase&&(l.redundantFields.length>0||u)?{requested_is_minimal:!1,diff_type:"redundant_constraint",requested_selector_fields:a,script_selector_fields:c,redundant_selector_fields:l.redundantFields,requested_action_index:o.value,script_action_index:i.value,script_selector:n?.selector}:{requested_is_minimal:!1,diff_type:"different_contract",requested_selector_fields:a,script_selector_fields:c,requested_action_index:o.value,script_action_index:i.value,script_selector:n?.selector}}const Te={async observe_screen(e,t,s,n){const r="number"==typeof e.wait_ms&&e.wait_ms>0?e.wait_ms:0;if(r>0){const e=await ae(t,"POST","base/sleep",{duration:r},s);if(200!==e.code)return{success:!1,error:e.msg||"Wait before observe failed"}}const o=await ae(t,"POST","accessibility/dump_compact",{},s);return 200==o.code&&"string"==typeof o.data?(n&&se(n,o.data),{success:!0,data:o.data}):{success:!1,error:o.msg||"Failed to get UI dump"}},async verify_ui_state(e,t,s,n){const r=me(e.step_desc);if(!r)return{success:!1,error:"verify_ui_state 缺少 step_desc。"};const o=me(e.screen_desc);if(!o)return{success:!1,error:"verify_ui_state 缺少 screen_desc。"};const i=function(e){return pe({page_kind:me(e.page_kind)})}(e),a=e.verify_element;if(!a||"object"!=typeof a||Array.isArray(a))return{success:!1,error:"verify_ui_state 缺少 verify_element 对象。"};const c=de(a,"verify_element");if(c)return{success:!1,error:c};const l=ge(a);if(!l)return{success:!1,error:"verify_element 必须提供 resource_id、text、content_desc、class_name 或 bounds 至少一个(可选 index),且需来自当前 observe_screen 结果。"};const u=e.page_anchor;if(u&&"object"==typeof u&&!Array.isArray(u)){const e=de(u,"page_anchor");if(e)return{success:!1,error:e}}const d=n?.lastObserveNodes;if(!d||0===d.length)return{success:!1,error:"请先调用 observe_screen(可带 wait_ms)获取当前界面,再调用 verify_ui_state。"};if(ye(d,l)>0){const e={step_desc:r,screen_desc:o,verify_element:a,...i?{recorded_page:i}:{}},t=ie(d,l,0);if(t){const s=ne(t,d,{mode:"verify"});s&&(e.verification_plan=s)}if(u&&"object"==typeof u&&!Array.isArray(u)){const t=ge(u);if(!t)return{success:!1,error:"page_anchor 必须提供 resource_id、text、content_desc、class_name 或 bounds 至少一个(可选 index)。"};if(0===ye(d,t))return{success:!1,error:"page_anchor 必须来自当前 observe_screen 的真实节点,当前界面未匹配。"};e.page_anchor=u;const s=ie(d,t,0);if(s){const t=ne(s,d,{mode:"anchor"});t&&(e.page_anchor_plan=t)}}return{success:!0,data:e}}const m=[void 0!==l.index&&`index=${l.index}`,l.resourceId&&`resource_id="${l.resourceId}"`,l.text&&`text="${l.text}"`,l.contentDesc&&`content_desc="${l.contentDesc}"`,l.className&&`class_name="${l.className}"`].filter(Boolean).join(", "),p=d.filter(e=>e.text||e.contentDesc||e.resourceId&&!oe(e.resourceId)).slice(0,15).map(e=>{const t=[];return t.push(`index=${e.index}`),e.text&&t.push(`text="${e.text}"`),e.contentDesc&&t.push(`desc="${e.contentDesc}"`),e.resourceId&&!oe(e.resourceId)&&t.push(`id="${e.resourceId}"`),t.push(`[${e.className.split(".").pop()}]`),` ${t.join(" ")}`});return{success:!1,error:`验证元素在当前界面中不存在:${m} 未匹配。请从当前界面重新选择 verify 元素。${p.length>0?`\n当前界面可用的验证候选元素(直接从中选择,无需再调 observe_screen):\n${p.join("\n")}`:""}`}},async record_search_context(e,t,s,n){const r=me(e.step_intent);if(!r)return{success:!1,error:"record_search_context 缺少 step_intent。"};const o=me(e.target_desc);if(!o)return{success:!1,error:"record_search_context 缺少 target_desc。"};const i=e.anchor_element;if(!i||"object"!=typeof i||Array.isArray(i))return{success:!1,error:"record_search_context 缺少 anchor_element(当前页面锚点),必填。"};const a=i,c=de(a,"anchor_element");if(c)return{success:!1,error:c};if(!function(e){const t=e.resource_id,s=e.text,n=e.content_desc,r=e.class_name,o=e.bounds;return"string"==typeof t&&t.trim().length>0||"string"==typeof s&&s.trim().length>0||"string"==typeof n&&n.trim().length>0||"string"==typeof r&&r.trim().length>0||"string"==typeof o&&o.trim().length>0}(a))return{success:!1,error:"anchor_element 必须包含 resource_id、text、content_desc、class_name 或 bounds 至少一个(可选 index)。"};const l=n?.lastObserveNodes;if(!l||0===l.length)return{success:!1,error:"record_search_context 前必须先调用 observe_screen 获取当前界面。"};const u=ge(i);if(!u)return{success:!1,error:"anchor_element 不是合法 selector。"};if(0===ye(l,u))return{success:!1,error:"anchor_element 必须来自当前 observe_screen 的真实节点,当前界面未匹配。"};const d=e.target_element,m={...e,step_intent:r,target_desc:o,recorded_context:_e({...e})},p=ie(l,u,0);if(p){const e=ne(p,l,{mode:"anchor"});e&&(m.anchor_plan=e)}if(d&&"object"==typeof d&&!Array.isArray(d)){const e=de(d,"target_element");if(e)return{success:!1,error:e};const t=ge(d);if(!t)return{success:!1,error:"target_element 必须提供 resource_id、text、content_desc、class_name 或 bounds 至少一个(可选 index)。"};if(0===ye(l,t))return{success:!1,error:"target_element 必须来自当前 observe_screen 的真实节点,当前界面未匹配。"};const s=ie(l,t,0);if(s){const e=ne(s,l,{mode:"action"});e&&(m.target_plan=e)}}return{success:!0,data:m}},async get_installed_apps(e,t,s){const n=e.type||"user",r=await ae(t,"GET",`package/list?type=${n}`,void 0,s);if(200==r.code&&r.data?.packages){return{success:!0,data:r.data.packages.map(e=>`${e.app_name||"unknown"} = ${e.package_name||""}`).join("\n")}}return{success:!1,error:r.msg||"Failed to get app list"}},async get_top_activity(e,t,s){const n=await ae(t,"GET","activity/top_activity",void 0,s);return 200==n.code?{success:!0,data:n.data}:{success:!1,error:n.msg||"Failed to get top activity"}},async get_screen_info(e,t,s){const n=await ae(t,"GET","display/info",void 0,s);return 200==n.code?{success:!0,data:n.data}:{success:!1,error:n.msg||"Failed to get screen info"}},async start_app(e,t,s){const n=e.package_name;if(!n)return{success:!1,error:"Missing required param: package_name"};const r=await ae(t,"POST","permission/set",{package_name:n,grant_all:!0},s);if(200!==r.code)return{success:!1,error:`权限授权失败 (${n}): ${r.msg}。建议:通过 get_installed_apps 确认包名是否正确。`};const o=await ae(t,"POST","activity/start",{package_name:n},s);return 200==o.code?{success:!0,data:o.data}:{success:!1,error:o.msg||"Failed to start app"}},async stop_app(e,t,s){const n=e.package_name,r=await ae(t,"POST","activity/stop",{package_name:n},s);return 200==r.code?{success:!0,data:r.data}:{success:!1,error:r.msg||"Failed to stop app"}},async tap(e,t,s){const n=await ae(t,"POST","input/click",{x:e.x,y:e.y},s);return 200==n.code?{success:!0,data:n.data}:{success:!1,error:n.msg||"Tap failed"}},async tap_element(e,t,s){const n=ue(e.action_index);if(!n.ok)return{success:!1,error:"Invalid action_index: expected non-negative integer"};const r={selector:e.selector,action:"click",wait_timeout:e.wait_timeout??5e3,wait_interval:e.wait_interval??1e3};void 0!==n.value&&(r.action_index=n.value);const o=await ae(t,"POST","accessibility/node",r,s);return 200==o.code?{success:!0,data:o.data}:{success:!1,error:o.msg?`元素操作失败: ${o.msg}。建议:重新 observe_screen 确认元素是否存在,检查 selector 唯一性,必要时补充 action_index。`:"元素未找到或点击失败,请重新 observe_screen 后检查 selector"}},async long_press_element(e,t,s){const n=ue(e.action_index);if(!n.ok)return{success:!1,error:"Invalid action_index: expected non-negative integer"};const r={selector:e.selector,action:"long_click",wait_timeout:e.wait_timeout??5e3,wait_interval:e.wait_interval??1e3};void 0!==n.value&&(r.action_index=n.value);const o=await ae(t,"POST","accessibility/node",r,s);return 200==o.code?{success:!0,data:o.data}:{success:!1,error:o.msg||"Long press element failed"}},async set_text(e,t,s){const n=ue(e.action_index);if(!n.ok)return{success:!1,error:"Invalid action_index: expected non-negative integer"};const r={selector:e.selector,action:"set_text",action_params:{text:e.text},wait_timeout:e.wait_timeout??5e3,wait_interval:e.wait_interval??1e3};void 0!==n.value&&(r.action_index=n.value);const o=await ae(t,"POST","accessibility/node",r,s);return 200==o.code?{success:!0,data:o.data}:{success:!1,error:o.msg?`文本输入失败: ${o.msg}。建议:确认输入框已聚焦,或尝试先 tap_element 聚焦后使用 input_text。`:"文本输入失败,请确认输入框已存在且可聚焦"}},async input_text(e,t,s){const n=await ae(t,"POST","input/text",{text:e.text},s);return 200==n.code?{success:!0,data:n.data}:{success:!1,error:n.msg||"Input text failed"}},async swipe(e,t,s){const n=await ae(t,"POST","input/scroll_bezier",{start_x:e.start_x,start_y:e.start_y,end_x:e.end_x,end_y:e.end_y,duration:e.duration??300},s);return 200==n.code?{success:!0,data:n.data}:{success:!1,error:n.msg||"Swipe failed"}},async press_key(e,t,s){const n=await ae(t,"POST","input/keyevent",{key_code:e.key_code},s);return 200==n.code?{success:!0,data:n.data}:{success:!1,error:n.msg||"Key event failed"}},async wait(e,t,s){const n=await ae(t,"POST","base/sleep",{duration:e.duration},s);return 200==n.code?{success:!0,data:!0}:{success:!1,error:n.msg||"Wait failed"}}};async function we(e,s,n,r){const o=Te[e.name],i=(a=e.name,B.find(e=>e.name===a));var a;if(!o||!i)return{toolCallId:e.id,name:e.name,success:!1,error:`Unknown tool: ${e.name}`};const c=(l=e.arguments)&&"object"==typeof l&&!Array.isArray(l)?l:{};var l;const u=function(e,s){const n=le(s,"index"),r=le(s,"action_index"),o=ce(s.selector),i=!!o&&le(o,"index");return t.ACCESSIBILITY_NODE_TOOLS.has(e)?n?"Invalid index usage: use action_index (top-level) for accessibility/node tools":i?"Invalid selector.index: selector does not support index, use action_index instead":null:n||r?"Invalid index usage: index/action_index are only allowed for accessibility/node tools":null}(e.name,c);if(u)return{toolCallId:e.id,name:e.name,success:!1,error:u};const d=i.schema.safeParse(c);if(!d.success&&t.STRICT_SCHEMA_TOOLS.has(e.name)){const t=d.error.issues.map(e=>`${e.path.join(".")}: ${e.message}`).join("; ");return{toolCallId:e.id,name:e.name,success:!1,error:`参数校验失败,拒绝执行: ${t}。请检查 pre_context.anchor 和 pre_context.target 是否已填写,selector 字段是否合法。`}}const m=d.success?d.data:c,p=function(e,s){if(!t.PRE_CONTEXT_REQUIRED_TOOLS.has(e))return null;const n=s.pre_context;if(!n||"object"!=typeof n||Array.isArray(n))return`pre_context 缺失:${e} 调用前必须提供 pre_context(anchor + target)。请先 observe_screen 确认页面状态,再填写 pre_context 后重新调用。`;const r=n,o=r.anchor;if(!o||"object"!=typeof o||Array.isArray(o))return"pre_context.anchor 缺失:必须提供页面锚点元素用于确认当前页面。anchor 应选择页面中稳定存在的框架级元素(如导航栏、标题栏)。";if(!me(o.desc))return"pre_context.anchor.desc 缺失:必须提供页面锚点描述,用于说明当前页面上下文。";const i=r.target;return!i||"object"!=typeof i||Array.isArray(i)?"pre_context.target 缺失:必须提供操作目标元素信息。target 应来自当前 observe_screen 的真实节点属性。":me(i.desc)?me(r.step_intent)?null:"pre_context.step_intent 缺失:必须说明本次操作在任务中的目的和预期效果,而非重复元素描述。此字段用于脚本生成的步骤命名和合并。":"pre_context.target.desc 缺失:必须提供目标元素描述,用于说明本次操作意图。"}(e.name,m);if(p)return{toolCallId:e.id,name:e.name,success:!1,error:p};const _=ve(e.name,m,r);if(_)return{toolCallId:e.id,name:e.name,success:!1,error:_};try{const i=await o(m,s,n,r);if(i.success){const s=[];if(t.SELECTOR_ACTION_TOOLS.has(e.name)&&r?.lastObserveNodes?.length){const e=ge(m.selector),t=ue(m.action_index);if(e){const n=ie(r.lastObserveNodes,e,t.value??0);if(n){const e=ne(n,r.lastObserveNodes,{mode:"action",requestedActionIndex:t.value});if(e){const t=i.data&&"object"==typeof i.data&&!Array.isArray(i.data)?i.data:{},n=Ie(m,e);i.data={...t,_selector_recording_profile:e,...n?{_selector_request_analysis:n}:{}};const r=n?function(e){if(!0===e.requested_is_minimal)return null;if("redundant_constraint"===("string"==typeof e.diff_type?e.diff_type:"different_contract")){const t=Array.isArray(e.redundant_selector_fields)?e.redundant_selector_fields.filter(e=>"string"==typeof e):[],s=[...t.length>0?[`冗余字段: ${t.join(", ")}`]:[],void 0!==e.requested_action_index&&void 0===e.script_action_index?"冗余 action_index":void 0].filter(Boolean);return"selector 最小化提醒: 当前请求 selector 可执行,但不是最小稳定唯一方案。录制阶段批准的 script_selector 已足够定位。"+(s.length>0?`请去掉 ${s.join(",")}。`:"")}return"selector 对齐提醒: 当前请求 selector 与录制阶段批准的 script_selector 不一致。虽然本次执行成功,但后续应优先对齐为 recording-approved script_selector。"}(n):null;r&&s.push(r)}}}}if(t.SELECTOR_ACTION_TOOLS.has(e.name)&&r?.lastObserveNodes){const e=xe(m,r.lastObserveNodes);e&&s.push(e)}if(t.SELECTOR_ACTION_TOOLS.has(e.name)){const e=function(e){const t=ce(e.pre_context);if(!t)return null;const s=ge(t.anchor),n=ge(t.target);return s&&n&&be(s,n)?"pre_context 质量提醒: anchor 与 target 使用了完全相同的定位证据。anchor 应优先描述当前页面或容器级稳定锚点,而不是与点击目标重合。":null}(m);e&&s.push(e)}if(r?.softPendingVerify&&X(e.name)){const e=r.softPendingVerify;s.push(`⚠️ 补录提醒: 上一步 #${e.index} ${e.toolName} 的 verify_ui_state 被跳过,录制信息不完整,将影响脚本生成质量。建议在当前操作完成后补充 observe_screen → verify_ui_state。`)}return t.POST_VERIFY_HINT_TOOLS.has(e.name)&&s.push("录制提醒: 此动作可能改变界面,请执行 observe_screen(wait_ms) → verify_ui_state 记录验证信息。"),"swipe"===e.name&&(!r?.lastSearchContextTimestamp||Date.now()-r.lastSearchContextTimestamp>3e4)&&s.push("⚠️ 录制提醒: swipe 前未调用 record_search_context,录制信息将不完整。建议先调用 record_search_context 记录查找目标与页面锚点。"),{toolCallId:e.id,name:e.name,success:!0,data:i.data,warning:s.length>0?s.join("\n"):void 0}}return{toolCallId:e.id,name:e.name,success:i.success,data:i.data,error:i.error}}catch(t){return{toolCallId:e.id,name:e.name,success:!1,error:t.message||String(t)}}}const Ne=["search","explore","discover","for you","home","friends","inbox","profile","close","log in","sign up","comment","搜索","首页","消息","我的","关闭","登录","评论"];function Ce(e){const t=e.match(/\bbounds=\[(\d+),(\d+)\]\[(\d+),(\d+)\]/);if(!t)return null;const s=Number(t[2]),n=Number(t[4]);return Number.isFinite(s)&&Number.isFinite(n)?{y1:s,y2:n}:null}function Ee(e){if(0===e.length)return null;const t=function(e){const t=e.match(/^Screen\s+\d+x(\d+)/);if(!t)return null;const s=Number(t[1]);return Number.isFinite(s)&&s>0?s:null}(e[0]||"");if(!t)return null;let s=0,n=0,r=0;for(let o=1;o<e.length;o++){const i=e[o];if(!i.includes("clickable=true"))continue;const a=Ce(i);if(!a)continue;const c=(a.y1+a.y2)/2/t;c<.33?s++:c<=.67?n++:r++}return 0===s+n+r?null:`[可点击分布] 顶部:${s} 中部:${n} 底部:${r} (按 bounds center_y)`}function Se(e){if(!e.success)return`Error: ${e.error||"operation failed"}`;const t=e.data;let s;if("verify_ui_state"===e.name&&t&&"object"==typeof t){const e=t,n=e.step_desc,r=e.verify_element;s=n?`verify_ui_state OK: ${n}`:"verify_ui_state OK",r&&(r.resource_id||r.text||r.content_desc)&&(s+=` (element: ${[r.resource_id,r.text,r.content_desc].filter(Boolean).join(" / ")})`)}else if("record_search_context"===e.name&&t&&"object"==typeof t){const e=t;s=e.target_desc?`record_search_context OK: ${e.target_desc}`:"record_search_context OK"}else if("observe_screen"===e.name&&"string"==typeof t){const e=t.split("\n"),n=(t.match(/clickable/g)||[]).length,r=function(e){if(e.length<=120)return{content:e.join("\n"),shownLines:e.length,truncated:!1};const t=new Map,s=s=>{s<0||s>=e.length||t.has(s)||t.set(s,e[s])};s(0);for(let t=1;t<Math.min(e.length,51);t++)s(t);for(let t=Math.max(1,e.length-40);t<e.length;t++)s(t);let n=0;for(let s=1;s<e.length&&n<30;s++){const r=e[s],o=r.toLowerCase(),i=Ne.some(e=>o.includes(e)),a=/\bbounds=\[/.test(r)&&(/\bclickable=true\b/.test(r)||/\bcontent-desc=/.test(r)||/\btext="/.test(r)||/\bresource-id=/.test(r));(i||a)&&(t.has(s)||(t.set(s,r),n++))}const r=Array.from(t.entries()).sort((e,t)=>e[0]-t[0]).slice(0,120).map(([,e])=>e);return{content:r.join("\n"),shownLines:r.length,truncated:!0}}(e),o=`[元素总行数: ${e.length}, 可点击: ${n}, 展示行: ${r.shownLines}]`,i=Ee(e),a="[位置规则] 仅依据 bounds=[x1,y1][x2,y2] + Screen WxH 判断顶部/底部;禁止按节点顺序或缩进推断位置。",c=r.truncated?"\n... (已按头部+尾部+关键词/可交互关键行压缩展示;定位不确定时请重新 observe_screen 并用 verify_ui_state 验证)":"";s=`${o}\n${a}${i?`\n${i}`:""}\n${r.content}${c}`}else s=function(e){if(!e||"object"!=typeof e||Array.isArray(e))return!1;const t=e;return"action_success"in t&&"nodes"in t&&Array.isArray(t.nodes)}(t)?function(e){const t=e,s="number"==typeof t.count?t.count:0,n="string"==typeof t.action?t.action:"find";return!0!==t.action_success?`${n} failed (matched ${s} nodes)`:s<=1?`${n} OK`:`${n} OK (注意: 匹配到 ${s} 个元素,操作了第 ${"number"==typeof t.action_index?t.action_index:0} 个)`}(t):"string"==typeof t?t:!0===t||void 0===t?"OK":JSON.stringify(t,null,2);return e.warning&&(s+=`\n⚠️ ${e.warning}`),s}class Ae{handlers={planning:new Set,thinking:new Set,toolcall:new Set,toolresult:new Set,diagnostic:new Set,paused:new Set,scriptgenerating:new Set,complete:new Set,error:new Set};on(e,t){return this.handlers[e].add(t),()=>this.off(e,t)}off(e,t){this.handlers[e].delete(t)}emit(e,t){for(const s of Array.from(this.handlers[e]))s(t)}clear(){Object.keys(this.handlers).forEach(e=>{this.handlers[e].clear()})}}class Oe extends Error{code;details;constructor(e,t,s){super(t),this.name="AgentRuntimeError",this.code=e,this.details=s}}const Le=["INVALID_CONFIG","SESSION_NOT_FOUND","SESSION_NOT_PAUSED","AGENT_RUNNING","EMPTY_EXECUTION_LOG","MCP_CONNECT_FAILED","SCRIPT_GENERATION_FAILED","RUNTIME_LOOP_FAILED","UNKNOWN"];function Re(e){return!!e&&"object"==typeof e&&!Array.isArray(e)}function De(e,t="UNKNOWN"){if(e instanceof Oe)return e;const s=function(e){return e instanceof Error&&e.message||Re(e)&&"string"==typeof e.message?e.message:String(e)}(e);return Re(e)?new Oe("string"==typeof(n=e.code)&&Le.includes(n)?e.code:t,s,Re(e.details)?e.details:void 0):new Oe(t,s);var n}const ke=i.z.object({goal:i.z.string().min(1),subtasks:i.z.array(i.z.object({id:i.z.number().int().positive(),description:i.z.string().min(1),successCriteria:i.z.string().min(1),estimatedActions:i.z.array(i.z.string().min(1)).default([])})).default([]),risks:i.z.array(i.z.string()).default([]),assumptions:i.z.array(i.z.string()).default([]),estimatedSteps:i.z.number().int().positive().default(1)}),Fe=i.z.object({status:i.z.enum(["completed","paused"]).default("completed"),goal:i.z.string().min(1).optional(),subtasks:ke.shape.subtasks.default([]),risks:ke.shape.risks.default([]),assumptions:ke.shape.assumptions.default([]),estimatedSteps:i.z.number().int().positive().optional(),pauseMessage:i.z.string().min(1).optional(),pauseReason:i.z.string().min(1).optional()}).superRefine((e,t)=>{"paused"!==e.status?e.goal||t.addIssue({code:i.z.ZodIssueCode.custom,message:"goal is required when status=completed",path:["goal"]}):e.pauseMessage||t.addIssue({code:i.z.ZodIssueCode.custom,message:"pauseMessage is required when status=paused",path:["pauseMessage"]})});function $e(e){const t=e.trim();if(t.startsWith("{")&&t.endsWith("}"))return t;const s=t.replace(/^```json\s*/i,"").replace(/^```\s*/i,"").replace(/\s*```$/,"").trim();if(s.startsWith("{")&&s.endsWith("}"))return s;throw new Error("plan output is not a valid standalone JSON object")}function Ue(e,t){return"zh"===t?{goal:e,subtasks:[{id:1,description:"识别目标应用或目标页面入口",successCriteria:"当前界面可见可执行入口元素",estimatedActions:["observe_screen","tap_element"]},{id:2,description:"执行目标核心动作",successCriteria:"动作后出现预期的关键页面变化",estimatedActions:["tap_element","set_text","press_key"]},{id:3,description:"记录动作结果并验证状态",successCriteria:"verify_ui_state 成功且状态可复用",estimatedActions:["observe_screen","verify_ui_state"]}],risks:["页面元素可能动态变化","页面加载延迟导致观察结果滞后"],assumptions:["设备已连通且可调用 Android 控制 API"],estimatedSteps:8}:{goal:e,subtasks:[{id:1,description:"Identify the target app or the entry point screen",successCriteria:"A visible and actionable entry element is observed",estimatedActions:["observe_screen","tap_element"]},{id:2,description:"Execute the core target action",successCriteria:"Expected key screen transition appears after action",estimatedActions:["tap_element","set_text","press_key"]},{id:3,description:"Record outcome and verify resulting UI state",successCriteria:"verify_ui_state succeeds with reusable state evidence",estimatedActions:["observe_screen","verify_ui_state"]}],risks:["Dynamic UI elements may change during execution","Page loading delays can make observations stale"],assumptions:["Device is reachable and Android control APIs are available"],estimatedSteps:8}}function Me(e,t,s){return e<=1?"":"zh"===s?`\n\n[上一次失败]\n${t[t.length-1]||"unknown"}\n请严格输出合法 JSON。`:`\n\n[PreviousFailure]\n${t[t.length-1]||"unknown"}\nOutput strict valid JSON only.`}function Pe(e,t,s,n){return"zh"===n?`## 目标\n\n${e}\n\n## 当前设备屏幕\n\n${t}${s}`:`## Goal\n\n${e}\n\n## Current Device Screen\n\n${t}${s}`}async function je(e,s,n,r,o={}){return async function(e,s,n,r,o={}){const i=t.detectPromptLocale(e),a={lastObserveRawDump:null,lastObserveNodes:[]},c=o.planningMessages||[],l=!1!==o.allowPause,u=await we({id:"plan-observe-screen",name:"observe_screen",arguments:{},source:"local"},n,o.signal,a),d=u.success&&"string"==typeof u.data?u.data:Se(u),m=a.lastObserveRawDump||void 0,p=t.createProvider(s),_=[],g=Math.max(1,r.planner.maxAttempts);for(let s=1;s<=g;s++){const n=Me(s,_,i),r=[{role:"system",content:t.buildTaskPlanPrompt(e,l)},{role:"user",content:Pe(e,d,n,i)},...c];try{let t;if(p.capabilities.structuredJson)t=await p.chatStructuredJson(r,Fe,o.signal);else{const e=await p.chatWithTools(r,[],o.signal);t=JSON.parse($e(e.content))}const n=Fe.parse(t);if("paused"===n.status){if(!l)throw new Error("planner requested clarification but pause is disabled for this entrypoint");return{status:"paused",text:n.pauseMessage||"planner requested clarification",reason:n.pauseReason,screenDump:d,screenRawDump:m,attempts:s,errors:_}}const i=Math.max(n.estimatedSteps||1,2*n.subtasks.length,1);return{status:"completed",plan:{goal:n.goal||e,subtasks:n.subtasks,risks:n.risks,assumptions:n.assumptions,estimatedSteps:i},screenDump:d,screenRawDump:m,attempts:s,errors:_}}catch(e){_.push(e.message||String(e))}}return{status:"completed",plan:Ue(e,i),screenDump:d,screenRawDump:m,attempts:g,errors:_}}(e,s,n,r,o)}function ze(e,t){return t<=0?null:e/t}function qe(e,t){const s={...t,timestamp:Date.now()};return e.diagnostics.push(s),s}function Ge(e){const t=[...e.diagnostics].reverse().find(e=>"runtime"===e.phase||"script_generation"===e.phase)?.code,s=function(e){let t=0,s=0,n=0;for(const r of e)r.result.success&&(t+=1),"action"===r.category&&(s+=1,r.result.success&&(n+=1));return{totalToolCalls:e.length,successfulToolCalls:t,runtimeSuccessRate:ze(t,e.length),totalActionCalls:s,successfulActionCalls:n,automationSuccessRate:ze(n,s)}}(e.executionLog),n={};let r=0;for(const t of e.executionLog){if(t.result.success)continue;r+=1;const e=t.result.errorCode||"UNKNOWN";n[e]=(n[e]||0)+1}return{planningRetries:e.runtimeContext.planningRetries,toolRetries:e.runtimeContext.toolRetries,scriptGenerationRetries:e.runtimeContext.scriptGenerationRetries,convergenceTriggers:e.runtimeContext.convergenceTriggers,totalToolCalls:s.totalToolCalls,successfulToolCalls:s.successfulToolCalls,runtimeSuccessRate:s.runtimeSuccessRate,totalActionCalls:s.totalActionCalls,successfulActionCalls:s.successfulActionCalls,automationSuccessRate:s.automationSuccessRate,totalFailures:r,failureByCode:n,finalFailureCode:t}}function Ve(e,s,n,r=[]){const o=[{role:"system",content:t.buildAgentSystemPrompt(e,s)},{role:"user",content:e}];for(const e of r)"assistant"!==e.role&&"user"!==e.role||o.push({role:e.role,content:e.content,timestamp:e.timestamp});return n&&(o.push({role:"assistant",content:"",toolCalls:[{id:"plan-initial-observe",name:"observe_screen",arguments:{},source:"local"}]}),o.push({role:"tool",content:n,toolCallId:"plan-initial-observe"})),o}function Ye(e){return e.currentPhase||(e.currentPhase="runtime"),e.planningContext?Array.isArray(e.planningContext.messages)||(e.planningContext.messages=[]):e.planningContext={messages:[]},e}function Be(e,t,s){Ye(e),e.plan=t,e.currentPhase="runtime",e.messages=Ve(e.goal,t,s,e.planningContext?.messages||[])}function Xe(e,t,s=[]){s.length>0?e.messages.push({role:"assistant",content:t,toolCalls:s,timestamp:Date.now()}):e.messages.push({role:"assistant",content:t,timestamp:Date.now()})}function We(e,t,s){e.messages.push({role:"tool",content:s,toolCallId:t,timestamp:Date.now()})}const Ke=new Set(["verify_ui_state","record_search_context","get_screen_info"]);function Je(e){return!e||"object"!=typeof e||Array.isArray(e)?null:e}function He(e){if("number"==typeof e&&Number.isInteger(e)&&e>=0)return e;if("string"==typeof e&&e.trim().length>0){const t=Number(e);if(Number.isInteger(t)&&t>=0)return t}}function Qe(e,t){for(const s of t){const t=e[s];if("string"==typeof t){const e=t.trim();if(e.length>0)return e}}}function Ze(e){const t={};let s=!1;for(const[n,r]of Object.entries(e))null!=r&&""!==r&&(t[n]=r,s=!0);return s?t:void 0}function et(e){const t=Je(e);if(!t)return;return Ze({index:He(t.index),resource_id:Qe(t,["resource_id","resource-id","resourceId","id"]),text:Qe(t,["text"]),content_desc:Qe(t,["content_desc","content-desc","contentDesc"]),class_name:Qe(t,["class_name","class","className"]),bounds:Qe(t,["bounds"])})}function tt(e,s){if(!t.SELECTOR_ACTION_TOOLS.has(e.name)||!s.success)return null;const n=Je(e.arguments),r=Je(s.data);if(!n&&!r)return null;const o=Ze(Je(n?.selector)||{}),i=He(n?.action_index),a=Array.isArray(r?.nodes)?r.nodes:[],c=He(r?.action_index),l=i??c??0,u=et(a[l]??a[0]),d=a.slice(0,5).map(e=>et(e)).filter(e=>!!e),m="number"==typeof r?.count&&Number.isFinite(r.count)?r.count:a.length,p={};o&&(p.requested_selector=o),void 0===i&&void 0===c||(p.action_index=l),m>=0&&(p.matched_count=m),u&&(p.selected_node=u),d.length>0&&(p.candidate_nodes=d);const _=function(e){const t=Je(e);if(!t)return;return Ze({mode:"string"==typeof t.mode?t.mode:void 0,script_selector:Je(t.script_selector)||void 0,field_assessments:Array.isArray(t.field_assessments)?t.field_assessments.map(e=>Je(e)).filter(e=>!!e).slice(0,8):void 0,selector_candidates:Array.isArray(t.selector_candidates)?t.selector_candidates.map(e=>Je(e)).filter(e=>!!e).slice(0,8):void 0,selected_node:et(t.selected_node)})}(r?._selector_recording_profile);if(_){p.selector_profile=_;const e=Je(_.script_selector);if(e){const t=Je(e.selector);t&&(p.recommended_selector=t);const s=He(e.action_index);void 0!==s&&(p.recommended_action_index=s),"string"==typeof e.stability&&(p.selector_stability=e.stability),"string"==typeof e.reason&&(p.selector_reason=e.reason),"boolean"==typeof e.reusable&&(p.selector_reusable=e.reusable),"string"==typeof e.scope&&(p.selector_scope=e.scope)}}const g=Je(r?._selector_request_analysis);return g&&(p.selector_request_analysis=g),Object.keys(p).length>0?p:null}function st(e,t){const s=e.name;if(Ke.has(s))return t;if("observe_screen"===s&&t.success&&"string"==typeof t.data){const e=t.data.split("\n").length;return{...t,data:`(${e} lines)`}}const n=tt(e,t);if(n){const e={toolCallId:t.toolCallId,name:t.name,success:t.success,data:{runtime_selector_evidence:n},source:t.source,server:t.server};return t.error&&(e.error=t.error),t.warning&&(e.warning=t.warning),e}const r={toolCallId:t.toolCallId,name:t.name,success:t.success,source:t.source,server:t.server};return t.error&&(r.error=t.error),t.warning&&(r.warning=t.warning),r}function nt(e,t,s,n){e.executionLog.push({index:e.executionLog.length+1,toolName:t.name,arguments:t.arguments,result:st(t,s),category:n,timestamp:Date.now()})}const rt=[/resource-id=/,/resource_id=/,/\btext="/,/content-desc=/,/content_desc=/,/clickable=true/,/bounds=\[/];function ot(e){return e.startsWith("Screen ")||e.startsWith("[Package]")||e.startsWith("[元素总行数:")}function it(e){const t=e.split("\n");if(t.length<=7)return e;const s=t[0]||"",n=new Set,r=[];let o=0;for(let e=1;e<t.length;e++){const s=t[e];s.includes("clickable=true")&&o++,rt.some(e=>e.test(s))&&(n.has(s)||(n.add(s),r.push(s)))}return[s,...r.length>0?r.slice(0,6):t.slice(1,7),"[位置规则] 仅依据 bounds + Screen WxH 判断顶部/底部,禁止按节点顺序推断。",`[... 已压缩,原始 ${t.length} 行,关键候选 ${r.length} 行,可点击 ${o} 个]`].join("\n")}function at(e){if(e.length<=14)return e;const t=e.length-12,s=[],n=[],r=[];for(let t=0;t<e.length;t++){const s=e[t];"tool"===s.role&&ot(s.content)&&n.push(t),"tool"===s.role&&(s.content.includes('"step_desc"')||s.content.includes('"target_desc"'))&&r.push(t)}const o=new Set(n.slice(-1)),i=new Set(r.slice(-3));for(let n=0;n<e.length;n++){const r=e[n];if(n<=1)s.push(r);else if(n>=t)s.push(r);else if("user"!==r.role)if("assistant"!==r.role)if(r.content.length<=200)s.push(r);else if(!r.content.includes('"step_desc"')&&!r.content.includes('"target_desc"')||i.has(n))if(ot(r.content)){if(o.has(n)){s.push(r);continue}s.push({...r,content:it(r.content)})}else s.push({...r,content:r.content.substring(0,200)+"\n[... 已截断]"});else try{const e=JSON.parse(r.content);s.push({...r,content:JSON.stringify({step_desc:e.step_desc,target_desc:e.target_desc})})}catch{s.push({...r,content:r.content.substring(0,200)+"\n[... 已截断]"})}else r.content.length<=120?s.push(r):s.push({...r,content:r.content.substring(0,120)+"\n[... 已截断]"});else s.push(r)}return s}function ct(e){return"stdio"===e.transport?{transport:"stdio",command:e.command,args:e.args||[],env:e.env||{}}:{transport:"streamable_http"===e.transport?"http":e.transport,url:e.url,headers:e.headers||{}}}class lt{servers;client=null;constructor(e){this.servers=e}async connect(){if(!this.servers||0===Object.keys(this.servers).length)return[];if(!this.client){const{MultiServerMCPClient:e}=await async function(){try{return await import("@langchain/mcp-adapters")}catch(e){const t=e instanceof Error?e.message:String(e);throw new Error(`failed to load MCP runtime dependency @langchain/mcp-adapters: ${t}; ensure @langchain/langgraph is installed`)}}(),t={};for(const[e,s]of Object.entries(this.servers))t[e]=ct(s);this.client=new e({mcpServers:t})}const e=[];for(const t of Object.keys(this.servers)){const s=await this.client.getTools(t);for(const n of s)n.name&&e.push({name:n.name,tool:n,server:t})}return e}async close(){this.client&&(await this.client.close(),this.client=null)}}const ut=new Set(B.map(e=>e.name));class dt{mcpClient=null;mcpTools=new Map;modelTools=[...B];getModelTools(){return this.modelTools}async prepare(e={}){if(await this.closeMcpClient(),this.mcpTools.clear(),this.modelTools=[...B],0===Object.keys(e).length)return;const t=new lt(e);try{const e=await t.connect();!function(e,t){const s=[],n=new Set(e),r=new Set;for(const e of t){const t=e.name;t&&(n.has(t)||r.has(t)?s.push(t):r.add(t))}if(s.length>0){const e=Array.from(new Set(s)).join(", ");throw new Error(`MCP tool name conflict detected: ${e}`)}}(B.map(e=>e.name),e);for(const t of e)this.mcpTools.set(t.name,{tool:t.tool,server:t.server});this.modelTools=[...B,...e.map(e=>e.tool)],this.mcpClient=t}catch(e){throw await t.close().catch(()=>{}),e}}annotateToolCalls(e){return e.map(e=>{const t=this.resolveTool(e.name);return{...e,source:t.source,server:t.server}})}getCategory(e,t){return"local"===t.source&&X(e.name)?"action":"observation"}async execute(e,t,s,n){const r=this.resolveTool(e.name);if("local"===r.source){return{...await we({id:e.id,name:e.name,arguments:e.arguments},t,s,n),source:"local"}}if(!r.tool)return{toolCallId:e.id,name:e.name,success:!1,error:`MCP tool not found: ${e.name}`,source:"mcp",server:r.server};try{const t=await r.tool.invoke(e.arguments);return{toolCallId:e.id,name:e.name,success:!0,data:t,source:"mcp",server:r.server}}catch(t){return{toolCallId:e.id,name:e.name,success:!1,error:t.message||String(t),source:"mcp",server:r.server}}}async destroy(){await this.closeMcpClient(),this.mcpTools.clear(),this.modelTools=[...B]}resolveTool(e){if(ut.has(e))return{source:"local"};const t=this.mcpTools.get(e);return t?{source:"mcp",server:t.server,tool:t.tool}:{source:"local"}}async closeMcpClient(){this.mcpClient&&(await this.mcpClient.close(),this.mcpClient=null)}}const mt="PLANNER_RETRY",pt="PLANNER_FALLBACK",_t="TOOL_RETRY",gt="VERIFY_GATE_BLOCKED",ft="MAX_ITERATIONS_REACHED",ht="CONSECUTIVE_FAILURE_LIMIT_REACHED",bt="SAME_TOOL_REPEAT_LIMIT_REACHED",yt="SCRIPT_RETRY",xt=new Set(["TIMEOUT","NETWORK","HTTP_5XX"]);function vt(e){if(e.errorCode)return{code:e.errorCode,retryable:e.retryable??xt.has(e.errorCode)};const t=(e.error||"").toLowerCase();if(!t)return{code:"UNKNOWN",retryable:!1};if(t.includes("aborted"))return{code:"ABORTED",retryable:!1};if(t.includes("timeout")||t.includes("timed out"))return{code:"TIMEOUT",retryable:!0};if(t.includes("econnreset")||t.includes("enotfound")||t.includes("econnrefused")||t.includes("network")||t.includes("fetch failed"))return{code:"NETWORK",retryable:!0};const s=function(e){const t=e.match(/\b(?:status|failed:)\s*(\d{3})\b/i);if(!t)return null;const s=Number(t[1]);return Number.isFinite(s)?s:null}(t);if(null!==s){if(s>=500)return{code:"HTTP_5XX",retryable:!0};if(s>=400)return{code:"HTTP_4XX",retryable:!1}}return t.includes("unknown tool")?{code:"UNKNOWN_TOOL",retryable:!1}:t.includes("参数校验失败")||t.includes("validation")||t.includes("invalid")?{code:"VALIDATION",retryable:!1}:t.includes("拦截:上一步动作尚未验证")||t.includes("blocked by verify gate")?{code:"RUNTIME_GUARD",retryable:!1}:t.includes("plan")&&t.includes("invalid")?{code:"PLAN_VALIDATION_FAILED",retryable:!1}:t.includes("workflow")&&t.includes("invalid")?{code:"SCRIPT_VALIDATION_FAILED",retryable:!1}:{code:"UNKNOWN",retryable:!1}}function It(e,t){const s=new AbortController,n=()=>{s.abort(t?.reason)};t&&(t.aborted&&s.abort(t.reason),t.addEventListener("abort",n));const r=setTimeout(()=>{s.abort(new Error(`Tool timeout after ${e}ms`))},e);return{signal:s.signal,dispose:()=>{clearTimeout(r),t&&t.removeEventListener("abort",n)}}}function Tt(e,t){return{toolCallId:e.id,name:e.name,success:!1,error:`Tool timeout after ${t}ms`,errorCode:"TIMEOUT",retryable:!0}}function wt(e,t){return t instanceof Error&&/timeout/i.test(t.message)?Tt(e,0):t instanceof Error&&"AbortError"===t.name?{toolCallId:e.id,name:e.name,success:!1,error:t.message||"Tool aborted",errorCode:"ABORTED",retryable:!1}:{toolCallId:e.id,name:e.name,success:!1,error:t?.message||String(t),errorCode:"UNKNOWN",retryable:!1}}async function Nt(e){const t=Math.max(1,e.maxAttempts);for(let s=1;s<=t;s++){const n=Date.now(),r=It(e.timeoutMs,e.signal);let o;try{o=await e.execute(e.toolCall,e.device,r.signal)}catch(t){const s=r.signal.reason instanceof Error?r.signal.reason.message:String(r.signal.reason||"");o=r.signal.aborted&&/timeout/i.test(s)?Tt(e.toolCall,e.timeoutMs):wt(e.toolCall,t)}finally{r.dispose()}if(o.latencyMs||(o.latencyMs=Date.now()-n),o.attempt=s,o.success)return o;const i=vt(o);if(o.errorCode=o.errorCode??i.code,o.retryable=o.retryable??i.retryable,"ABORTED"===o.errorCode)return o;if(!o.retryable||s>=t)return o;e.onRetry?.(s,o)}return{toolCallId:e.toolCall.id,name:e.toolCall.name,success:!1,error:"Unreachable: retry loop terminated unexpectedly",errorCode:"UNKNOWN",retryable:!1}}function Ct(e,t){if(!Number.isFinite(e))return t;const s=Math.floor(e);return s<=0?t:s}function Et(e,t){return"action"===t?e.retries.action:e.retries.observation}function St(e,s){const n=t.TOOL_TIMEOUT_OVERRIDES_MS[s];return n?e.timeouts[n]:e.timeouts.defaultMs}function At(e){if(null==e)return String(e);if("object"!=typeof e)return JSON.stringify(e);if(Array.isArray(e))return`[${e.map(e=>At(e)).join(",")}]`;return`{${Object.entries(e).sort(([e],[t])=>e.localeCompare(t)).map(([e,t])=>`${JSON.stringify(e)}:${At(t)}`).join(",")}}`}function Ot(e){return`${e.name}:${At(e.arguments||{})}`}function Lt(e){return"local"===e.source&&X(e.name)?"action":"observation"}class Rt{options;emitter=new Ae;toolRegistry=new dt;session=null;provider=null;abortController=null;sessionStore=null;localToolContext={lastObserveRawDump:null,lastObserveNodes:[]};constructor(e={}){this.options=e;if("sqlite"!==(e.persistence?.mode||"sqlite"))return;const t=c();try{this.sessionStore=new u(t)}catch(e){const s=De(e,"INVALID_CONFIG");throw new Oe("INVALID_CONFIG",`session store initialization failed: ${s.message}`,{sqlitePath:t,cause:s.message})}}on(e,t){return this.emitter.on(e,t)}off(e,t){this.emitter.off(e,t)}async start(s){this.session?.running&&this.stop(this.session.sessionId);try{this.provider=t.createProvider(s.provider)}catch(e){const t=De(e,"INVALID_CONFIG");throw new Oe("INVALID_CONFIG",`invalid model provider configuration: ${t.message}`,{provider:{vendor:s.provider.vendor,baseUrl:s.provider.baseUrl,model:s.provider.model},cause:t.message})}try{await this.toolRegistry.prepare(this.options.mcpServers||{})}catch(e){const t=De(e,"MCP_CONNECT_FAILED");throw new Oe("MCP_CONNECT_FAILED",`failed to connect MCP tools: ${t.message}`,{servers:Object.keys(this.options.mcpServers||{}),cause:t.message,...t.details})}const n=function(e){if(!e)return t.DEFAULT_RELIABILITY_CONFIG;const s=t.DEFAULT_RELIABILITY_CONFIG.scriptGeneration.waitRandomization||{},n=Ct(e.scriptGeneration?.waitRandomization?.minMs??s.minMs??400,s.minMs??400),r=Math.max(n,Ct(e.scriptGeneration?.waitRandomization?.maxMs??s.maxMs??3e4,s.maxMs??3e4));return{retries:{observation:Ct(e.retries?.observation??t.DEFAULT_RELIABILITY_CONFIG.retries.observation,t.DEFAULT_RELIABILITY_CONFIG.retries.observation),action:Ct(e.retries?.action??t.DEFAULT_RELIABILITY_CONFIG.retries.action,t.DEFAULT_RELIABILITY_CONFIG.retries.action)},timeouts:{defaultMs:Ct(e.timeouts?.defaultMs??t.DEFAULT_RELIABILITY_CONFIG.timeouts.defaultMs,t.DEFAULT_RELIABILITY_CONFIG.timeouts.defaultMs),observeScreenMs:Ct(e.timeouts?.observeScreenMs??t.DEFAULT_RELIABILITY_CONFIG.timeouts.observeScreenMs,t.DEFAULT_RELIABILITY_CONFIG.timeouts.observeScreenMs),startAppMs:Ct(e.timeouts?.startAppMs??t.DEFAULT_RELIABILITY_CONFIG.timeouts.startAppMs,t.DEFAULT_RELIABILITY_CONFIG.timeouts.startAppMs)},limits:{maxIterations:Ct(e.limits?.maxIterations??t.DEFAULT_RELIABILITY_CONFIG.limits.maxIterations,t.DEFAULT_RELIABILITY_CONFIG.limits.maxIterations),maxConsecutiveFailures:Ct(e.limits?.maxConsecutiveFailures??t.DEFAULT_RELIABILITY_CONFIG.limits.maxConsecutiveFailures,t.DEFAULT_RELIABILITY_CONFIG.limits.maxConsecutiveFailures),maxSameToolFingerprint:Ct(e.limits?.maxSameToolFingerprint??t.DEFAULT_RELIABILITY_CONFIG.limits.maxSameToolFingerprint,t.DEFAULT_RELIABILITY_CONFIG.limits.maxSameToolFingerprint)},planner:{maxAttempts:Ct(e.planner?.maxAttempts??t.DEFAULT_RELIABILITY_CONFIG.planner.maxAttempts,t.DEFAULT_RELIABILITY_CONFIG.planner.maxAttempts)},scriptGeneration:{maxAttempts:Ct(e.scriptGeneration?.maxAttempts??t.DEFAULT_RELIABILITY_CONFIG.scriptGeneration.maxAttempts,t.DEFAULT_RELIABILITY_CONFIG.scriptGeneration.maxAttempts),waitRandomization:{enabled:e.scriptGeneration?.waitRandomization?.enabled??s.enabled??!0,minMs:n,maxMs:r,jitterRatio:(o=e.scriptGeneration?.waitRandomization?.jitterRatio??s.jitterRatio,i=s.jitterRatio??.2,"number"==typeof o&&Number.isFinite(o)?o<0?0:o>1?1:o:i),roundingMs:Ct(e.scriptGeneration?.waitRandomization?.roundingMs??s.roundingMs??100,s.roundingMs??100)}}};var o,i}(s.reliability),r=s.sessionId||e.v4();this.localToolContext={lastObserveRawDump:null,lastObserveNodes:[]},this.session=Ye(function(e,t,s,n,r){return{sessionId:t,goal:e.goal,provider:e.provider,device:e.device,reliability:s,iteration:0,running:!1,paused:!1,messages:Ve(e.goal,n,r),executionLog:[],diagnostics:[],runtimeContext:{lastObserveRawDump:null,lastObserveNodes:[],pendingVerifyAction:null,softPendingVerify:null,lastSearchContextTimestamp:null,consecutiveFailures:0,sameToolFingerprintCount:0,lastToolFingerprint:null,planningRetries:0,toolRetries:0,scriptGenerationRetries:0,convergenceTriggers:0},plan:n,currentPhase:"runtime",planningContext:{messages:[]}}}(s,r,n)),this.persistSession();if(!1!==s.enablePlanning){this.session.currentPhase="planning",this.persistSession();if("paused"===await this.runPlanningStage(this.session))return{sessionId:r}}else this.session.currentPhase="runtime";return this.persistSession(),await this.runLoop(),{sessionId:r}}async resume(e){if(!this.session||this.session.sessionId!==e.sessionId)throw new Oe("SESSION_NOT_FOUND","session not found or expired");if(Ye(this.session),!this.session.paused)throw new Oe("SESSION_NOT_PAUSED","agent is not paused");if("planning"===this.session.currentPhase){this.session.planningContext?.messages.push({role:"user",content:e.message,timestamp:Date.now()}),this.session.paused=!1,this.persistSession();if("paused"===await this.runPlanningStage(this.session))return;return void await this.runLoop()}var t,s;t=this.session,s=e.message,t.messages.push({role:"user",content:s,timestamp:Date.now()}),this.persistSession(),await this.runLoop()}stop(e){this.session&&this.session.sessionId===e&&(this.session.running=!1,this.session.paused=!1,this.abortController&&(this.abortController.abort(),this.abortController=null),this.persistSession())}async generateScript(e){const s=this.loadSessionIfNeeded(e);if(Ye(s),s.running)throw new Oe("AGENT_RUNNING","agent is still running");if(0===s.executionLog.length)throw new Oe("EMPTY_EXECUTION_LOG","no execution log available for script generation; run actions first");if(s.runtimeContext.pendingVerifyAction){const t=s.runtimeContext.pendingVerifyAction,n=[...s.diagnostics].reverse().find(e=>e.code===gt);throw new Oe("SCRIPT_GENERATION_FAILED",`script generation blocked: action #${t.index} ${t.toolName} has not been verified. Resume with observe_screen(wait_ms) -> verify_ui_state before generating script.`,{sessionId:e,pendingVerify:t,...n?{lastDiagnostic:n}:{}})}let n;s.currentPhase="script_generation",this.emitter.emit("scriptgenerating",{sessionId:s.sessionId});try{const r=await t.generateScriptWithReliability(s.goal,s.executionLog,s.provider,s.reliability);if(n=r.workflow,r.attempts>1){s.runtimeContext.scriptGenerationRetries+=r.attempts-1;const t=qe(s,{sessionId:e,phase:"script_generation",code:yt,message:`script generation retried ${r.attempts} attempts`,details:{errors:r.errors}});this.emitter.emit("diagnostic",t)}}catch(t){const n=De(t,"SCRIPT_GENERATION_FAILED");throw new Oe("SCRIPT_GENERATION_FAILED",`script generation failed: ${n.message}`,{sessionId:e,executionLogSize:s.executionLog.length,cause:n.message,...n.details})}s.running=!1,s.paused=!0,this.persistSession();const r={sessionId:s.sessionId,workflow:n,executionLog:[...s.executionLog],totalIterations:s.iteration,diagnostics:Ge(s)};return this.emitter.emit("complete",r),r}listSessions(e={}){return this.sessionStore?this.sessionStore.listSessions(e):[]}countSessions(e={}){return this.sessionStore?this.sessionStore.countSessions(e):0}getSessionSummary(e){return this.sessionStore?this.sessionStore.getSessionSummary(e):null}deleteSession(e){return!!this.sessionStore&&(this.session?.sessionId===e&&(this.stop(e),this.session=null,this.localToolContext={lastObserveRawDump:null,lastObserveNodes:[]}),this.sessionStore.deleteSession(e))}async destroy(){this.session&&this.stop(this.session.sessionId),await this.toolRegistry.destroy(),this.localToolContext={lastObserveRawDump:null,lastObserveNodes:[]},this.sessionStore&&(this.sessionStore.close(),this.sessionStore=null),this.emitter.clear()}async runPlanningStage(e){this.abortController=new AbortController;const t=this.abortController.signal;this.emitter.emit("planning",{sessionId:e.sessionId,status:"started"});let s=0,n=[];try{const r=await je(e.goal,e.provider,e.device,e.reliability,{signal:t,planningMessages:e.planningContext?.messages||[]});return s=r.attempts,n=r.errors,"paused"===r.status?(e.currentPhase="planning",e.planningContext?.messages.push({role:"assistant",content:r.text,timestamp:Date.now()}),this.applyPlanningDiagnostics(e,s,n),this.emitter.emit("planning",{sessionId:e.sessionId,status:"paused",text:r.text}),this.markSessionPaused(e,r.text,"planning"),"paused"):(Be(e,r.plan,r.screenDump),r.screenRawDump&&se(this.localToolContext,r.screenRawDump),this.applyPlanningDiagnostics(e,s,n),this.emitter.emit("planning",{sessionId:e.sessionId,status:"completed",plan:r.plan}),"completed")}catch(t){if("AbortError"===t.name)throw t;const r=De(t,"RUNTIME_LOOP_FAILED");return n.push(r.message),this.emitter.emit("error",{sessionId:e.sessionId,error:`task planning failed and was skipped: ${r.message}`,code:"RUNTIME_LOOP_FAILED",details:{phase:"planning",cause:r.message}}),Be(e),this.applyPlanningDiagnostics(e,s,n),"completed"}finally{this.abortController=null}}applyPlanningDiagnostics(e,t,s){if(t>1&&(e.runtimeContext.planningRetries+=t-1),0===s.length)return;const n=s[s.length-1],r=qe(e,{sessionId:e.sessionId,phase:"planning",code:t>1?mt:pt,message:`planning stage used retries or fallback: ${n}`,details:{attempts:t||1,errors:s}});this.emitter.emit("diagnostic",r)}async runLoop(){const e=this.session;if(!e)return;if(!this.provider)throw new Oe("INVALID_CONFIG","model provider has not been initialized");Ye(e),e.running=!0,e.paused=!1,e.currentPhase="runtime",this.abortController=new AbortController;const t=this.abortController.signal;try{for(;e.running;){const s=e.reliability.limits.maxIterations;if(s>0&&e.iteration>=s){e.runtimeContext.convergenceTriggers+=1;const t=qe(e,{sessionId:e.sessionId,phase:"policy",code:ft,message:`iteration limit reached: ${s}`,iteration:e.iteration});return this.emitter.emit("diagnostic",t),void this.markSessionPaused(e,"execution paused after hitting iteration limit; review goal or adjust strategy","policy")}e.iteration++;const n=at(e.messages),r=await this.provider.chatWithTools(n,this.toolRegistry.getModelTools(),t),o=r.content||"";o&&this.emitter.emit("thinking",{sessionId:e.sessionId,text:o,iteration:e.iteration});const i=this.toolRegistry.annotateToolCalls(r.toolCalls||[]);if(0===i.length)return o.trim().length>0&&Xe(e,o),void this.markSessionPaused(e,o,"runtime");Xe(e,o,i);for(const s of i){if(!e.running)break;this.emitter.emit("toolcall",{sessionId:e.sessionId,toolCall:s,iteration:e.iteration});const n=this.getVerifyGateError(e,s);if(n){const t={toolCallId:s.id,name:s.name,success:!1,error:n,errorCode:"RUNTIME_GUARD",retryable:!1,source:s.source,server:s.server},r=qe(e,{sessionId:e.sessionId,phase:"runtime",code:gt,message:n,iteration:e.iteration,details:{pendingVerify:e.runtimeContext.pendingVerifyAction,blockedTool:s.name}});this.emitter.emit("diagnostic",r),this.emitter.emit("toolresult",{sessionId:e.sessionId,result:t,iteration:e.iteration}),We(e,s.id,Se(t)),this.persistSession();continue}const r=Lt(s),o=Et(e.reliability,r),i=St(e.reliability,s.name),a=await Nt({toolCall:s,device:e.device,maxAttempts:o,timeoutMs:i,signal:t,execute:(e,t,s)=>this.toolRegistry.execute(e,t,s,this.localToolContext),onRetry:(t,n)=>{e.runtimeContext.toolRetries+=1;const r=qe(e,{sessionId:e.sessionId,phase:"runtime",code:_t,message:`tool retry: ${s.name}, attempt ${t+1}`,iteration:e.iteration,details:{attempt:t,maxAttempts:o,errorCode:n.errorCode,error:n.error}});this.emitter.emit("diagnostic",r)}});a.source||(a.source=s.source,a.server=s.server),this.emitter.emit("toolresult",{sessionId:e.sessionId,result:a,iteration:e.iteration});if(!a.success&&"VALIDATION"===a.errorCode||nt(e,s,a,this.toolRegistry.getCategory(s,a)),We(e,s.id,Se(a)),this.updateRuntimeStateAfterTool(e,s,a,r),this.localToolContext.softPendingVerify=e.runtimeContext.softPendingVerify,this.localToolContext.lastSearchContextTimestamp=e.runtimeContext.lastSearchContextTimestamp,this.persistSession(),!e.running)break}}}catch(t){if("AbortError"===t.name)return e.running=!1,e.paused=!1,void this.persistSession();e.running=!1,e.paused=!0;const s=De(t,"RUNTIME_LOOP_FAILED");this.emitter.emit("error",{sessionId:e.sessionId,error:s.message,code:s.code,details:s.details}),this.persistSession({lastError:s.message,lastErrorCode:s.code})}finally{this.abortController=null}}updateRuntimeStateAfterTool(e,s,n,r){const o=Ot(s);if(e.runtimeContext.lastToolFingerprint===o?e.runtimeContext.sameToolFingerprintCount+=1:(e.runtimeContext.lastToolFingerprint=o,e.runtimeContext.sameToolFingerprintCount=1),n.success)e.runtimeContext.consecutiveFailures=0;else{"VALIDATION"===n.errorCode||"RUNTIME_GUARD"===n.errorCode||(e.runtimeContext.consecutiveFailures+=1)}if(t.VERIFY_COMPLETION_TOOLS.has(s.name)&&n.success)e.runtimeContext.pendingVerifyAction=null,e.runtimeContext.softPendingVerify=null;else if("action"===r&&n.success&&t.VERIFY_REQUIRED_ACTIONS.has(s.name)){const t=e.executionLog.length;e.runtimeContext.pendingVerifyAction={index:t,toolName:s.name}}if(t.SOFT_VERIFY_ACTIONS.has(s.name)&&n.success&&(e.runtimeContext.softPendingVerify={index:e.executionLog.length,toolName:s.name}),"record_search_context"===s.name&&n.success&&(e.runtimeContext.lastSearchContextTimestamp=Date.now()),e.runtimeContext.consecutiveFailures>=e.reliability.limits.maxConsecutiveFailures){e.runtimeContext.convergenceTriggers+=1;const t=qe(e,{sessionId:e.sessionId,phase:"policy",code:ht,message:`consecutive failure limit reached: ${e.reliability.limits.maxConsecutiveFailures}`,iteration:e.iteration,details:{consecutiveFailures:e.runtimeContext.consecutiveFailures}});return this.emitter.emit("diagnostic",t),void this.markSessionPaused(e,"execution paused due to excessive consecutive failures","policy")}if(e.runtimeContext.sameToolFingerprintCount>=e.reliability.limits.maxSameToolFingerprint){e.runtimeContext.convergenceTriggers+=1;const t=qe(e,{sessionId:e.sessionId,phase:"policy",code:bt,message:`same tool fingerprint repeat limit reached: ${e.reliability.limits.maxSameToolFingerprint}`,iteration:e.iteration,details:{fingerprint:o,repeatCount:e.runtimeContext.sameToolFingerprintCount}});this.emitter.emit("diagnostic",t),this.markSessionPaused(e,"execution paused due to repeated non-converging tool calls","policy")}}getVerifyGateError(e,s){return function(e,s){return"local"!==s.source?null:e&&X(s.name)?t.VERIFY_GATE_ALLOWED_TOOLS.has(s.name)?null:`BLOCKED: #${e.index} ${e.toolName} 尚未验证。你必须先调用 observe_screen(wait_ms) 再调用 verify_ui_state,然后才能执行下一个动作。`:null}(e.runtimeContext.pendingVerifyAction,s)}markSessionPaused(e,t,s){e.running=!1,e.paused=!0,this.emitter.emit("paused",{sessionId:e.sessionId,text:t,iteration:e.iteration,phase:s}),this.persistSession()}persistSession(e){this.session&&this.sessionStore&&this.sessionStore.saveSession(this.session,e)}loadSessionIfNeeded(e){if(this.session&&this.session.sessionId===e)return Ye(this.session);if(!this.sessionStore)throw new Oe("SESSION_NOT_FOUND","session not found or expired");const t=this.sessionStore.loadSession(e);if(!t)throw new Oe("SESSION_NOT_FOUND","session not found or expired");return this.session=Ye(t),this.localToolContext={lastObserveRawDump:null,lastObserveNodes:[]},this.session}}exports.DEFAULT_PROVIDER_CONFIG=t.DEFAULT_PROVIDER_CONFIG,exports.DEFAULT_RELIABILITY_CONFIG=t.DEFAULT_RELIABILITY_CONFIG,exports.AgentRuntimeError=Oe,exports.AutomationAgentRuntime=Rt,exports.createAutomationAgentRuntime=function(e={}){return new Rt(e)},exports.optimizeIntent=async function(e,s){const n=t.createProvider(s),r=[{role:"system",content:t.buildOptimizeIntentPrompt(e)},{role:"user",content:e}];return(await n.chatWithTools(r,[])).content.trim()},exports.toRuntimeError=De;
@@ -1 +1 @@
1
- import{v4 as e}from"uuid";import{S as t,b as s,P as n,A as o,c as r,d as i,e as a,f as c,h as l,D as u,T as d,V as p,a as _,i as f,j as g,k as h,l as b}from"../chunks/scriptGenerator-Co2WLzwU.js";export{m as DEFAULT_PROVIDER_CONFIG}from"../chunks/scriptGenerator-Co2WLzwU.js";import y from"fs";import x from"os";import v from"path";import w from"better-sqlite3";import{z as I}from"zod";import{zodToJsonSchema as N}from"zod-to-json-schema";import{MultiServerMCPClient as T}from"@langchain/mcp-adapters";import"@langchain/core/prompts";import"@langchain/core/output_parsers";import"@langchain/core/runnables";import"@langchain/anthropic";import"@langchain/core/messages";import"@langchain/core/tools";import"@langchain/google-genai";import"@langchain/openai";import"crypto";function C(){return v.join(function(){const e=x.homedir();if(!e)throw new Error("Failed to resolve user home directory for session persistence");return v.join(e,".vmosedge")}(),"agent_sessions.db")}function S(e){return{sessionId:e.session_id,goal:e.goal,status:(t=e.status,"running"===t||"paused"===t||"stopped"===t?t:"stopped"),providerVendor:e.provider_vendor||"unknown",deviceId:e.device_id||"",totalIterations:e.total_iterations||0,lastError:e.last_error||void 0,lastErrorCode:e.last_error_code||void 0,createTime:e.create_time,updateTime:e.update_time};var t}class E{db;constructor(e){!function(e){const t=v.dirname(e);y.existsSync(t)||y.mkdirSync(t,{recursive:!0})}(e),this.db=new w(e),this.initPragmas(),this.initSchema()}initPragmas(){this.db.pragma("busy_timeout = 5000"),this.db.pragma("foreign_keys = ON"),this.db.pragma("synchronous = NORMAL");try{this.db.pragma("journal_mode = WAL")}catch{this.db.pragma("journal_mode = DELETE")}}initSchema(){this.db.exec("\n CREATE TABLE IF NOT EXISTS agent_meta (\n meta_key TEXT PRIMARY KEY,\n meta_value TEXT NOT NULL,\n update_time INTEGER NOT NULL\n );\n CREATE TABLE IF NOT EXISTS agent_sessions (\n session_id TEXT PRIMARY KEY,\n goal TEXT NOT NULL,\n status TEXT NOT NULL,\n provider_vendor TEXT NOT NULL DEFAULT '',\n device_id TEXT NOT NULL DEFAULT '',\n total_iterations INTEGER NOT NULL DEFAULT 0,\n last_error TEXT,\n last_error_code TEXT,\n state_json TEXT NOT NULL,\n create_time INTEGER NOT NULL,\n update_time INTEGER NOT NULL\n );\n "),this.ensureSessionColumns(),this.db.exec("\n CREATE INDEX IF NOT EXISTS idx_agent_sessions_update_time\n ON agent_sessions(update_time DESC);\n CREATE INDEX IF NOT EXISTS idx_agent_sessions_status_update_time\n ON agent_sessions(status, update_time DESC);\n CREATE INDEX IF NOT EXISTS idx_agent_sessions_device_update_time\n ON agent_sessions(device_id, update_time DESC);\n "),this.saveSchemaVersion("3")}ensureSessionColumns(){const e=this.db.prepare("PRAGMA table_info(agent_sessions)").all(),t=new Set(e.map(e=>e.name)),s=[{name:"provider_vendor",definition:"TEXT NOT NULL DEFAULT ''"},{name:"device_id",definition:"TEXT NOT NULL DEFAULT ''"},{name:"total_iterations",definition:"INTEGER NOT NULL DEFAULT 0"},{name:"last_error",definition:"TEXT"},{name:"last_error_code",definition:"TEXT"}];for(const e of s)t.has(e.name)||this.db.exec(`ALTER TABLE agent_sessions ADD COLUMN ${e.name} ${e.definition}`)}saveSchemaVersion(e){this.db.prepare("\n INSERT INTO agent_meta (meta_key, meta_value, update_time)\n VALUES (@meta_key, @meta_value, @update_time)\n ON CONFLICT(meta_key) DO UPDATE SET\n meta_value=excluded.meta_value,\n update_time=excluded.update_time\n ").run({meta_key:"schema_version",meta_value:e,update_time:Date.now()})}buildQueryFilter(e){const t=[],s={};e.status&&(t.push("status = @status"),s.status=e.status),e.deviceId&&(t.push("device_id = @device_id"),s.device_id=e.deviceId);const n=e.keyword?.trim();return n&&(t.push("goal LIKE @keyword"),s.keyword=`%${n}%`),{whereSql:t.length>0?`WHERE ${t.join(" AND ")}`:"",params:s}}saveSession(e,t){const s=Date.now(),n=e.running?"running":e.paused?"paused":"stopped",o=JSON.stringify(e);this.db.prepare("\n INSERT INTO agent_sessions (\n session_id, goal, status, provider_vendor, device_id,\n total_iterations,\n last_error, last_error_code, state_json, create_time, update_time\n )\n VALUES (\n @session_id, @goal, @status, @provider_vendor, @device_id,\n @total_iterations,\n @last_error, @last_error_code, @state_json, @create_time, @update_time\n )\n ON CONFLICT(session_id) DO UPDATE SET\n goal=excluded.goal,\n status=excluded.status,\n provider_vendor=excluded.provider_vendor,\n device_id=excluded.device_id,\n total_iterations=excluded.total_iterations,\n last_error=excluded.last_error,\n last_error_code=excluded.last_error_code,\n state_json=excluded.state_json,\n update_time=excluded.update_time\n ").run({session_id:e.sessionId,goal:e.goal,status:n,provider_vendor:e.provider.vendor,device_id:e.device.deviceId,total_iterations:e.iteration,last_error:t?.lastError||null,last_error_code:t?.lastErrorCode||null,state_json:o,create_time:s,update_time:s})}loadSession(e){const t=this.db.prepare("SELECT * FROM agent_sessions WHERE session_id = ? LIMIT 1").get(e);if(!t)return null;try{return JSON.parse(t.state_json)}catch{return null}}getSessionSummary(e){const t=this.db.prepare("\n SELECT\n session_id, goal, status, provider_vendor, device_id, total_iterations,\n last_error, last_error_code, create_time, update_time, state_json\n FROM agent_sessions\n WHERE session_id = ? LIMIT 1\n ").get(e);return t?S(t):null}listSessions(e={}){const{whereSql:t,params:s}=this.buildQueryFilter(e),n=function(e){return"number"!=typeof e||Number.isNaN(e)?50:Math.min(500,Math.max(1,Math.floor(e)))}(e.limit),o=function(e){return"number"!=typeof e||Number.isNaN(e)?0:Math.max(0,Math.floor(e))}(e.offset);return this.db.prepare(`\n SELECT\n session_id, goal, status, provider_vendor, device_id, total_iterations,\n last_error, last_error_code, create_time, update_time, state_json\n FROM agent_sessions\n ${t}\n ORDER BY update_time DESC\n LIMIT @limit OFFSET @offset\n `).all({...s,limit:n,offset:o}).map(S)}countSessions(e={}){const{whereSql:t,params:s}=this.buildQueryFilter(e),n=this.db.prepare(`\n SELECT COUNT(1) as total\n FROM agent_sessions\n ${t}\n `).get(s);return n?.total||0}deleteSession(e){return this.db.prepare("DELETE FROM agent_sessions WHERE session_id = ?").run(e).changes>0}close(){this.db.close()}}function A(e,t){const s=t.normalize("NFKC");return"text"===e||"contentDesc"===e?s.replace(/\s+/gu," ").trim():"bounds"===e?s.replace(/\s+/gu,""):s.trim()}function O(e,t,s){return A(e,t)===A(e,s)}function k(e,t){return(void 0===t.index||e.index===t.index)&&(!!(!t.resourceId||e.resourceId&&O("resourceId",t.resourceId,e.resourceId))&&(!!(!t.text||e.text&&O("text",t.text,e.text))&&(!!(!t.contentDesc||e.contentDesc&&O("contentDesc",t.contentDesc,e.contentDesc))&&(!!(!t.className||e.className&&O("className",t.className,e.className))&&!!(!t.bounds||e.bounds&&O("bounds",t.bounds,e.bounds))))))}function D(e,t){let s=0;for(const n of e)k(n,t)&&(s+=1);return s}function R(e,t,s){return void 0===t||void 0===s?t===s:O(e,t,s)}function L(e,t){return e===t||e.index===t.index&&R("resourceId",e.resourceId,t.resourceId)&&R("text",e.text,t.text)&&R("contentDesc",e.contentDesc,t.contentDesc)&&R("className",e.className,t.className)&&R("bounds",e.bounds,t.bounds)}function $(e,t,s){let n=0;for(const o of e)if(k(o,t)){if(L(o,s))return n;n+=1}}const M=e=>I.string().describe(e),j=e=>I.coerce.number().describe(e),F=e=>I.coerce.number().int().positive().describe(e),P=e=>{const t=I.coerce.number().int().positive().optional();return e?t.describe(e):t},U=e=>{const t=I.coerce.number().int().min(0).optional();return e?t.describe(e):t},q=e=>{const t=I.string().optional();return e?t.describe(e):t},G=(e,t)=>I.enum(e).optional().describe(t);function V(e){const t=N(e,{$refStrategy:"none"}),{$schema:s,definitions:n,...o}=t;return o}function X(e){return{name:e.name,description:e.description,category:e.category,schema:e.schema,parameters:V(e.schema)}}const W=I.object({resource_id:q("元素资源ID,如 com.example:id/btn"),text:q("元素文本,支持正则"),content_desc:q("元素内容描述,支持正则"),class_name:q("元素类名"),bounds:q("元素 bounds,格式 [x1,y1][x2,y2],仅用于兜底消歧,不建议直接进入脚本")}).strict(),K=G(["home","list","detail","dialog","search","form","unknown"],"可选:显式声明当前页面类型。仅在你有把握时填写;不确定则省略。"),z=G(["navigation","search_entry","action_button","input_field","content_item","generic_target"],"可选:显式声明目标元素角色。仅在你有把握时填写;不确定则省略。"),B=G(["critical","supporting","noise"],"可选:显式声明本轮动作对主流程的重要性。仅在你有把握时填写;不确定则省略。"),J=G(["main_flow","exception_handler_candidate","log_only"],"可选:显式声明该记录应进入主流程、异常处理还是仅日志。仅在你有把握时填写;不确定则省略。"),Y=G(["next_action","next_verified_transition","manual_close"],"可选:声明该上下文持续到何时结束。默认 next_verified_transition。"),H=I.object({step_intent:M("本次操作在任务流程中的目的和预期效果。必须说明为什么执行此操作、期望达到什么状态,而非重复元素属性描述。"),merge_key:q("可选:稳定的步骤合并键。用于把同类动作合并为同一脚本步骤,例如 open_search_page、scroll_result_list。仅在你能明确抽象出可复用步骤语义时填写。"),expected_result:q("可选:本次动作完成后预期达到的状态描述。建议使用比 step_intent 更直接的结果表述,例如“进入搜索页”“列表向下推进一屏”。"),wait_ms_hint:P("可选:建议回放时在主动作后等待或验证的毫秒数。仅在你明确知道该步骤通常需要额外等待时填写。"),page_kind:K,target_role:z,criticality_hint:B,include_mode_hint:J,anchor:I.object({desc:M("页面锚点元素描述,确认当前所在页面"),resource_id:q(),text:q(),content_desc:q(),class_name:q(),bounds:q()}).describe("页面锚点元素,必填"),target:I.object({desc:M("操作目标元素描述"),resource_id:q(),text:q(),content_desc:q(),class_name:q(),bounds:q()}).describe("本次操作的目标元素,必填"),post_verify:I.object({desc:M("操作成功后验证元素描述"),resource_id:q(),text:q(),content_desc:q(),class_name:q(),bounds:q()}).optional().describe("Phase 1 内部提示字段。告知 LLM 本次操作的最终预期状态,用于决定是否需要执行 observe_screen(wait_ms) + verify_ui_state。此字段不参与脚本生成;脚本的验证条件来自 verify_ui_state 的 verify_element。")}).describe("操作前必填的 UI 上下文,anchor+target+step_intent 强制要求;page_kind/target_role/criticality_hint/include_mode_hint 在明确时建议显式填写,用于录制质量约束和回放。"),Q=I.coerce.number().int().min(0).optional().describe("匹配结果动作索引(从 0 开始,仅 accessibility/node 使用)"),Z=I.object({wait_timeout:F("等待元素出现超时(ms)").optional(),wait_interval:F("重试间隔(ms)").optional()}),ee=X({name:"observe_screen",description:"获取当前屏幕的 UI 无障碍节点树。返回简化后的 UI 结构,包含每个可交互元素的 text、resource-id、content-desc、class、bounds 等属性。可选传 wait_ms:先等待指定毫秒再拉取,用于动作后「等待加载 + 获取新页面」一步完成。",schema:I.object({wait_ms:F("可选:先等待的毫秒数,再拉取 UI").optional()}),category:"observation"}),te=X({name:"get_installed_apps",description:"获取设备上已安装的应用列表,返回每个应用的包名(package_name)和应用名(app_name)。用于查找目标应用的包名以便启动。",schema:I.object({type:I.enum(["user","system","all"]).optional().describe("应用类型过滤:user(默认) / system / all")}),category:"observation"}),se=X({name:"get_top_activity",description:"获取当前前台应用的 Activity 信息,返回 package_name 和 class_name。用于判断当前在哪个应用的哪个页面。",schema:I.object({}),category:"observation"}),ne=X({name:"get_screen_info",description:"获取屏幕显示信息,返回 width、height、rotation、orientation。用于确定屏幕分辨率以计算滑动/点击坐标。",schema:I.object({}),category:"observation"}),oe=I.object({desc:M("元素简短描述"),resource_id:q("元素 resource-id,来自 observe_screen"),text:q("元素文本,来自 observe_screen"),content_desc:q("元素 content-desc"),class_name:q("元素类名,来自 observe_screen"),index:U("节点 index(来自 observe_screen 的 [index])")}),re=X({name:"verify_ui_state",description:"动作执行后,记录用于验证操作成功的标志性 UI 元素。必须先调用 observe_screen(可带 wait_ms)获取新界面,再调用本工具;verify_element 的 text/resource_id 必须来自当前界面真实节点,禁止编造。建议同时记录节点 index(observe_screen 的 [index])提高回放稳定性。当操作导致页面切换时,page_anchor 必须填写新页面的标志性元素。screen_desc 必填,用一句话描述当前页面;若你明确知道当前页面类型,可额外填写 page_kind;若该步骤回放通常需要额外等待,可填写 wait_ms_hint。",schema:I.object({step_desc:M("验证步骤描述"),page_kind:K,wait_ms_hint:P("可选:建议回放在验证前等待的毫秒数。仅在你明确知道该步骤通常需要额外等待时填写。"),verify_element:oe.describe("用于验证的 UI 元素,必须来自当前 observe_screen 结果"),screen_desc:M('当前页面的简要描述,用一句话概括所在页面(如"抖音首页信息流"、"微信聊天列表"、"系统设置-通用页面")。此字段帮助脚本生成 AI 理解操作上下文,必填。'),page_anchor:oe.optional().describe("动作后的页面锚点元素(如页面切换后的新页面标题/导航栏)。当操作导致页面切换时必填,用于脚本生成时确认导航成功。")}),category:"observation"}),ie=I.object({desc:M("元素描述"),resource_id:q(),text:q(),content_desc:q(),class_name:q(),bounds:q(),index:U("节点 index(来自 observe_screen 的 [index])")}),ae=X({name:"record_search_context",description:"在 swipe 或 press_key 前调用,记录本轮查找目标(target_desc + target_element)和当前页面锚点(anchor_element)。元素属性必须来自最近一次 observe_screen 的真实结果;step_intent、target_desc、anchor_element 必填。建议同时记录节点 index(observe_screen 的 [index])。",schema:I.object({step_intent:M("本次滑动/按键在任务流程中的目的和预期效果,说明为什么执行此操作、期望达到什么状态。"),merge_key:q("可选:稳定的步骤合并键。用于把同类滑动/按键动作合并为同一脚本步骤,例如 scroll_result_list、submit_search。"),expected_result:q("可选:本次动作完成后预期达到的状态描述,例如“结果列表继续向下推进”“进入详情页”。"),wait_ms_hint:P("可选:建议回放时在本轮动作后等待或验证的毫秒数。仅在你明确知道该步骤通常需要额外等待时填写。"),target_desc:M("本轮查找目标描述"),target_element:ie.optional().describe("查找目标元素,可选"),anchor_element:ie.describe("当前页面锚点元素,必填"),page_kind:K,target_role:z,criticality_hint:B,include_mode_hint:J,valid_until:Y}),category:"observation"});var ce;const le=[X({name:"start_app",description:"启动指定应用。需要 package_name。工具会在启动前自动执行 permission/set(grant_all=true)。",schema:I.object({package_name:M("应用包名")}),category:"action"}),X({name:"stop_app",description:"停止(强制关闭)指定应用。",schema:I.object({package_name:M("应用包名")}),category:"action"}),X({name:"tap",description:"在屏幕指定坐标点击。属于兜底方案:仅当按 resource_id -> text -> content_desc -> 最小组合 仍无法唯一定位元素时才允许使用。",schema:I.object({x:j("X 坐标"),y:j("Y 坐标")}),category:"action"}),X({name:"tap_element",description:"通过选择器查找并点击 UI 元素。这是推荐点击方式。\n\n调用规则:\n1. pre_context.anchor(页面锚点)和 pre_context.target(目标元素)必填,不可省略\n2. selector 遵循脚本优先策略:优先稳定 resource_id,其次稳定 text,再次稳定 content_desc,最后最小组合;bounds 仅作兜底消歧\n3. 序号点击使用 action_index(从 0 开始),严禁 selector.index\n4. 若点击会导致界面变化,最终验证必须:observe_screen(wait_ms) -> verify_ui_state",schema:I.object({pre_context:H,selector:W.describe("元素选择器"),action_index:Q,...Z.shape}).strict(),category:"action"}),X({name:"long_press_element",description:"通过选择器查找并长按 UI 元素。\n\n调用规则:\n1. pre_context.anchor 和 pre_context.target 必填,不可省略\n2. selector 遵循脚本优先策略:优先稳定 resource_id,其次稳定 text,再次稳定 content_desc,最后最小组合;bounds 仅作兜底消歧\n3. 序号操作使用 action_index(从 0 开始),严禁 selector.index\n4. 长按通常触发界面变化,最终验证必须:observe_screen(wait_ms) -> verify_ui_state",schema:I.object({pre_context:H,selector:W.describe("元素选择器"),action_index:Q,...Z.shape}).strict(),category:"action"}),X({name:"set_text",description:"通过选择器定位输入框并设置文本内容。\n\n调用规则:\n1. pre_context.anchor 和 pre_context.target 必填,不可省略\n2. 多个输入框时使用 action_index(从 0 开始),严禁 selector.index\n3. 若需要提交输入内容,紧接调用 press_key(66) 或等价提交动作\n4. 若后续操作导致界面变化,最终验证必须:observe_screen(wait_ms) -> verify_ui_state",schema:I.object({pre_context:H,selector:W.describe("输入框选择器"),action_index:Q,text:M("要输入的文本"),...Z.shape}).strict(),category:"action"}),X({name:"input_text",description:"向当前聚焦输入框直接输入文本,不需要 selector。",schema:I.object({text:M("要输入的文本")}),category:"action"}),X({name:"swipe",description:"执行坐标滑动手势,常用于滚动列表加载更多内容。\n\n调用规则:\n1. swipe 前先调用 record_search_context 记录查找目标与锚点\n2. swipe 后调用 observe_screen(wait_ms=500~1200),再根据结果决定是否继续滑动或调用 verify_ui_state\n3. 坐标依赖屏幕分辨率,调用前应已通过 get_screen_info 确认分辨率",schema:I.object({start_x:j("起点 X"),start_y:j("起点 Y"),end_x:j("终点 X"),end_y:j("终点 Y"),duration:j("持续时间(ms)").optional()}),category:"action"}),X({name:"press_key",description:"按下物理/虚拟按键。常用键码:3=Home, 4=Back, 66=Enter, 82=菜单等。调用前可先 record_search_context;按键会改变界面时,后续需 observe_screen(wait_ms) -> verify_ui_state。",schema:I.object({key_code:(ce="Android KeyCode",I.coerce.number().int().describe(ce))}),category:"action"}),X({name:"wait",description:"暂停指定时间。用于等待页面加载或动画完成。时长需按前一动作自适应:轻交互 300~800ms,滑动后 500~1200ms,页面切换/返回 1200~2500ms,启动应用 2000~4000ms;禁止全程固定同一时长。",schema:I.object({duration:j("等待时间(ms)")}),category:"action"})],ue=[...[ee,re,ae,te,se,ne],...le];function de(e){return le.some(t=>t.name===e)}const me=new Set(["btn","button","text","txt","img","image","icon","view","layout","container","content","header","footer","title","subtitle","label","input","edit","search","menu","nav","tab","bar","list","item","card","dialog","popup","modal","scroll","pager","recycler","grid","frame","root","main","action","toolbar","status","progress","loading","empty","error","divider","separator","wrapper","panel","avatar","profile","name","email","phone","password","submit","cancel","confirm","close","back","next","home","setting","notification","message","chat","comment","like","share","follow","post","feed","story","video","audio","play","pause","camera","photo","gallery","download","upload","save","delete","add","create","update","refresh","filter","sort","check","switch","toggle","radio","select","option","dropdown","picker","date","row","column","cell","section","group","block","region","area","top","bottom","left","right","center","start","end","inner","outer","overlay","badge","count","number","description","info","hint","placeholder","detail","summary","expand","collapse","more","viewpager","appbar","bottombar","snackbar","fab","chip","spinner","seekbar","slider","webview","mapview","banner","splash","widget"]);function pe(e){if(!e)return!1;const t=e.includes("/")?e.split("/").pop():e;return!!t&&(!e.startsWith("android:")&&(!!/obfuscated/i.test(t)||(!!/^\d+_/.test(t)||!function(e){const t=e.toLowerCase(),s=t.split("_");for(const e of s)if(e.length>=3&&me.has(e))return!0;for(const e of me)if(e.length>=4&&t.includes(e))return!0;return!1}(t)&&(t.length<=5||t.length<=10&&function(e){if(0===e.length)return 0;const t=new Map;for(const s of e)t.set(s,(t.get(s)||0)+1);let s=0;for(const n of t.values()){const t=n/e.length;s-=t*Math.log2(t)}return s}(t)>3))))}function _e(e){return e.normalize("NFKC").replace(/\s+/gu," ").trim()}function fe(e,t,s,n){const o=[],r=function(e,t,s){if(!s)return 0;const n=new Set;for(const o of e){if(o.resourceId!==s)continue;const e="text"===t?o.text:o.contentDesc;e&&n.add(_e(e))}return n.size}(s,"text"===n?"text":"contentDesc",t.resourceId),i=function(e){const t=_e(e);return!!t&&(/\b\d+\b/u.test(t)||/\d+[::]\d+/u.test(t)||/\d+[./-]\d+/u.test(t)||/[$¥¥]\s*\d/u.test(t)||/\brow\b|\bcolumn\b|行|列/u.test(t)||/@\w+/u.test(t))}(e),a=function(e){const t=_e(e);return t.length>24||/合集|日记|vlog|攻略|教程|推荐|详情|播放|第.+集/u.test(t)}(e),c=e.length<=12,l=1===D(s,"text"===n?{text:e}:{contentDesc:e});r>=2&&o.push("same resource_id carries multiple values on screen"),i&&o.push("contains dynamic segment"),a&&o.push("looks like content item text"),c||o.push("text is long or verbose"),l||o.push("not unique on current screen");const u=0===o.length||c&&l&&o.length<=1,d=u&&!i&&!a;return u&&t.clickable&&c&&o.push("short interactive label"),{stable:u,reusable:d,risk:d?"low":u?"medium":"high",reasons:o}}function ge(e,t){const s=[];if(e.resourceId){const t=function(e){const t=[];pe(e)?t.push("resource_id looks obfuscated"):t.push("resource_id contains semantic naming");const s=!pe(e);return{stable:s,reusable:s,risk:s?"low":"high",reasons:t}}(e.resourceId);s.push({field:"resource_id",value:e.resourceId,stable:t.stable,reusable:t.reusable,risk:t.risk,reasons:t.reasons})}if(e.text){const n=fe(e.text,e,t,"text");s.push({field:"text",value:e.text,stable:n.stable,reusable:n.reusable,risk:n.risk,reasons:n.reasons})}if(e.contentDesc){const n=fe(e.contentDesc,e,t,"content_desc");s.push({field:"content_desc",value:e.contentDesc,stable:n.stable,reusable:n.reusable,risk:n.risk,reasons:n.reasons})}if(e.className){const t={stable:!0,reusable:!1,risk:"medium",reasons:["class_name is only a disambiguation field, not a primary selector"]};s.push({field:"class_name",value:e.className,stable:t.stable,reusable:t.reusable,risk:t.risk,reasons:t.reasons})}if(e.bounds){const t={stable:!1,reusable:!1,risk:"high",reasons:[`bounds=${e.bounds} is screen-dependent and should stay debug-only`]};s.push({field:"bounds",value:e.bounds,stable:t.stable,reusable:t.reusable,risk:t.risk,reasons:t.reasons})}return s}function he(e){if(e.length<2)return!1;const t=e.map(e=>{const t=function(e){if(!e)return null;const t=e.match(/\[(\d+),(\d+)\]\[(\d+),(\d+)\]/);return t?{x1:Number(t[1]),y1:Number(t[2]),x2:Number(t[3]),y2:Number(t[4])}:null}(e.bounds);return t?{x:(t.x1+t.x2)/2,y:(t.y1+t.y2)/2}:null}).filter(e=>!!e);if(t.length!==e.length)return!1;const s=new Set(t.map(e=>Math.round(e.x/10))).size,n=new Set(t.map(e=>Math.round(e.y/10))).size;return s>1||n>1}function be(e,t,s,n){const o=D(t,{resourceId:s.resource_id,text:s.text,contentDesc:s.content_desc,className:s.class_name,bounds:s.bounds});if(0===o)return;if(n.requireUnique&&1!==o)return;if(!n.requireUnique&&o>1&&void 0===n.action_index)return;e.some(e=>JSON.stringify(e.selector)===JSON.stringify(s)&&e.action_index===n.action_index)||e.push({selector:s,action_index:n.action_index,stability:n.stability,reusable:n.reusable,matched_count:o,reason:n.reason,scope:n.scope,policy_decision:n.policy_decision,policy_reason:n.policy_reason})}function ye(e,t,s={}){if(!e||0===t.length)return null;const n=s.mode||"action",o=function(e){return"action"===e?{allowCollectionScope:!0,allowActionIndex:!0,allowContentCombos:!0,allowedScopes:["global","page","collection"],policyReason:"action selectors may use collection disambiguation"}:{allowCollectionScope:!1,allowActionIndex:!1,allowContentCombos:!1,allowedScopes:["global","page"],policyReason:`${e} selectors must stay single-field and non-positional`}}(n),r=ge(e,t),i=new Map;for(const e of r)i.set(e.field,e);const a=[],c=e.resourceId,l=e.text,u=e.contentDesc,d=e.className,m=e.bounds;if(function(e,t,s,n,o){s.resourceId&&!0===n.get("resource_id")?.reusable&&be(e,t,{resource_id:s.resourceId},{stability:"high",reusable:!0,reason:"unique_resource_id",scope:"global",requireUnique:!0,policy_decision:"approved",policy_reason:o.policyReason}),s.text&&!0===n.get("text")?.reusable&&be(e,t,{text:s.text},{stability:"high",reusable:!0,reason:"unique_reliable_text",scope:"global",requireUnique:!0,policy_decision:"approved",policy_reason:o.policyReason}),s.contentDesc&&!0===n.get("content_desc")?.reusable&&be(e,t,{content_desc:s.contentDesc},{stability:"high",reusable:!0,reason:"unique_reliable_content_desc",scope:"global",requireUnique:!0,policy_decision:"approved",policy_reason:o.policyReason})}(a,t,{resourceId:c,text:l,contentDesc:u},i,o),function(e,t,s,n,o){o.allowContentCombos&&(s.resourceId&&!0===n.get("resource_id")?.reusable&&s.text&&n.get("text")?.stable&&be(e,t,{resource_id:s.resourceId,text:s.text},{stability:"medium",reusable:!0===n.get("text")?.reusable,reason:"resource_id_text_combo",scope:"page",requireUnique:!0,policy_decision:"approved",policy_reason:o.policyReason}),s.resourceId&&!0===n.get("resource_id")?.reusable&&s.contentDesc&&n.get("content_desc")?.stable&&be(e,t,{resource_id:s.resourceId,content_desc:s.contentDesc},{stability:"medium",reusable:!0===n.get("content_desc")?.reusable,reason:"resource_id_content_desc_combo",scope:"page",requireUnique:!0,policy_decision:"approved",policy_reason:o.policyReason}),s.text&&s.className&&n.get("text")?.stable&&be(e,t,{text:s.text,class_name:s.className},{stability:"medium",reusable:!0===n.get("text")?.reusable,reason:"text_class_combo",scope:"page",requireUnique:!0,policy_decision:"approved",policy_reason:o.policyReason}),s.contentDesc&&s.className&&n.get("content_desc")?.stable&&be(e,t,{content_desc:s.contentDesc,class_name:s.className},{stability:"medium",reusable:!0===n.get("content_desc")?.reusable,reason:"content_desc_class_combo",scope:"page",requireUnique:!0,policy_decision:"approved",policy_reason:o.policyReason}))}(a,t,{resourceId:c,text:l,contentDesc:u,className:d},i,o),function(e,t,s,n,o,r,i){if(r.allowCollectionScope&&r.allowActionIndex){if(n.resourceId){const a={resourceId:n.resourceId},c=s.filter(e=>k(e,a));c.length>1&&he(c)&&be(e,s,{resource_id:n.resourceId},{action_index:i??$(s,a,t),stability:!0===o.get("resource_id")?.reusable?"medium":"low",reusable:!0===o.get("resource_id")?.reusable,reason:!0===o.get("resource_id")?.reusable?"resource_id_positional":"obfuscated_id_positional",scope:"collection",policy_decision:"approved",policy_reason:r.policyReason})}if(n.text){const a={text:n.text},c=s.filter(e=>k(e,a));c.length>1&&he(c)&&be(e,s,{text:n.text},{action_index:i??$(s,a,t),stability:"low",reusable:!0===o.get("text")?.reusable,reason:"text_positional",scope:"collection",policy_decision:"approved",policy_reason:r.policyReason})}if(n.contentDesc){const a={contentDesc:n.contentDesc},c=s.filter(e=>k(e,a));c.length>1&&he(c)&&be(e,s,{content_desc:n.contentDesc},{action_index:i??$(s,a,t),stability:"low",reusable:!0===o.get("content_desc")?.reusable,reason:"content_desc_positional",scope:"collection",policy_decision:"approved",policy_reason:r.policyReason})}n.className&&n.bounds&&be(e,s,{class_name:n.className,bounds:n.bounds},{stability:"low",reusable:!1,reason:"class_bounds_fallback",scope:"page",requireUnique:!0,policy_decision:"approved",policy_reason:r.policyReason})}}(a,e,t,{resourceId:c,text:l,contentDesc:u,className:d,bounds:m},i,o,s.requestedActionIndex),0===a.length){if("action"!==n)return null;const e=!0===i.get("resource_id")?.reusable,r=!0===i.get("text")?.reusable,p=!0===i.get("content_desc")?.reusable;c?be(a,t,{resource_id:c},{stability:e?"medium":"low",reusable:e,reason:e?"resource_id_positional":"obfuscated_id_positional",scope:"collection",action_index:s.requestedActionIndex,policy_decision:"approved",policy_reason:o.policyReason}):l?be(a,t,{text:l},{stability:"low",reusable:r,reason:"text_positional",scope:"collection",action_index:s.requestedActionIndex,policy_decision:"approved",policy_reason:o.policyReason}):u?be(a,t,{content_desc:u},{stability:"low",reusable:p,reason:"content_desc_positional",scope:"collection",action_index:s.requestedActionIndex,policy_decision:"approved",policy_reason:o.policyReason}):d&&m&&be(a,t,{class_name:d,bounds:m},{stability:"low",reusable:!1,reason:"class_bounds_fallback",scope:"page",requireUnique:!0,policy_decision:"approved",policy_reason:o.policyReason})}const p=a[0];return p?{mode:n,field_assessments:r,selector_candidates:a,script_selector:{selector:p.selector,action_index:p.action_index,stability:p.stability,reusable:p.reusable,reason:p.reason,scope:p.scope,policy_decision:p.policy_decision,policy_reason:p.policy_reason}}:null}function xe(e,t){return e.lastObserveRawDump=t,e.lastObserveNodes=function(e){const t=[],s=e.split("\n");for(const e of s){const s=e.trim(),n=s.match(/^\[(\d+)\]\s+(\S+)/);n&&t.push({index:Number(n[1]),className:n[2],resourceId:s.match(/\bresource-id="([^"]*)"/)?.[1]||void 0,text:s.match(/\btext="([^"]*)"/)?.[1]||void 0,contentDesc:s.match(/\bcontent-desc="([^"]*)"/)?.[1]||void 0,packageName:s.match(/\bpackage="([^"]*)"/)?.[1]||void 0,bounds:s.match(/\bbounds=(\[[^\]]*\]\[[^\]]*\])/)?.[1]||void 0,clickable:/\bclickable=true\b/.test(s),longClickable:/\blong-clickable=true\b/.test(s),scrollable:/\bscrollable=true\b/.test(s),focusable:/\bfocusable=true\b/.test(s),enabled:!/\benabled=false\b/.test(s),checked:/\bchecked=true\b/.test(s),selected:/\bselected=true\b/.test(s)})}return t}(t),e.lastObserveNodes}function ve(e,t,s={}){const n=ye(e,t,s);return n?{...n,selected_node:(o=e,{index:o.index,resource_id:o.resourceId,text:o.text,content_desc:o.contentDesc,class_name:o.className,bounds:o.bounds})}:null;var o}const we=new Set(["btn","button","text","txt","img","image","icon","view","layout","container","content","header","footer","title","subtitle","label","input","edit","search","menu","nav","tab","bar","list","item","card","dialog","popup","modal","scroll","pager","recycler","grid","frame","root","main","action","toolbar","status","progress","loading","empty","error","divider","separator","wrapper","panel","avatar","profile","name","email","phone","password","submit","cancel","confirm","close","back","next","home","setting","notification","message","chat","comment","like","share","follow","post","feed","story","video","audio","play","pause","camera","photo","gallery","download","upload","save","delete","add","create","update","refresh","filter","sort","check","switch","toggle","radio","select","option","dropdown","picker","date","row","column","cell","section","group","block","region","area","top","bottom","left","right","center","start","end","inner","outer","overlay","badge","count","number","description","info","hint","placeholder","detail","summary","expand","collapse","more","viewpager","appbar","bottombar","snackbar","fab","chip","spinner","seekbar","slider","webview","mapview","banner","splash","widget"]);function Ie(e){if(!e)return!1;const t=e.includes("/")?e.split("/").pop():e;return!!t&&(!e.startsWith("android:")&&(!!/obfuscated/i.test(t)||(!!/^\d+_/.test(t)||!function(e){const t=e.toLowerCase(),s=t.split("_");for(const e of s)if(e.length>=3&&we.has(e))return!0;for(const e of we)if(e.length>=4&&t.includes(e))return!0;return!1}(t)&&(t.length<=5||t.length<=10&&function(e){if(0===e.length)return 0;const t=new Map;for(const s of e)t.set(s,(t.get(s)||0)+1);let s=0;for(const n of t.values()){const t=n/e.length;s-=t*Math.log2(t)}return s}(t)>3))))}function Ne(e,t,s){let n=0;for(const o of e)if(Le(o,t)){if(n===s)return o;n++}return null}async function Te(e,t,s,n,o){const r=function(e){return`http://${e.hostIp}:18182/android_api/v2/${e.deviceId}`}(e),i=`${r}/${s}`,a={method:t,headers:{"Content-Type":"application/json"},signal:o};void 0!==n&&"POST"===t&&(a.body=JSON.stringify(n));const c=await fetch(i,a);if(!c.ok){const e=await c.text();throw new Error(`API ${s} failed: ${c.status} - ${e}`)}return await c.json()}function Ce(e){return!e||"object"!=typeof e||Array.isArray(e)?null:e}function Se(e,t){return Object.prototype.hasOwnProperty.call(e,t)}function Ee(e){if(null==e||""===e)return{ok:!0};const t="string"==typeof e?Number(e):e;return"number"==typeof t&&Number.isFinite(t)?!Number.isInteger(t)||t<0?{ok:!1}:{ok:!0,value:t}:{ok:!1}}function Ae(e,t){if(!Se(e,"index"))return null;return Ee(e.index).ok?null:`${t}.index 必须是从 0 开始的非负整数。`}function Oe(e){if("string"!=typeof e)return;const t=e.trim();return t.length>0?t:void 0}function ke(e){const t={};let s=!1;for(const[n,o]of Object.entries(e))null!=o&&""!==o&&(t[n]=o,s=!0);return s?t:void 0}function De(e){return ke({context_id:`ctx_${Date.now()}_${Math.floor(1e5*Math.random())}`,page_kind:Oe(e.page_kind),target_role:Oe(e.target_role),criticality_hint:Oe(e.criticality_hint),include_mode_hint:Oe(e.include_mode_hint),valid_until:Oe(e.valid_until)||"next_verified_transition"})||{context_id:`ctx_${Date.now()}_${Math.floor(1e5*Math.random())}`,valid_until:"next_verified_transition"}}function Re(e){if(!e||"object"!=typeof e||Array.isArray(e))return null;const t=e,s=Ee(t.index);if(!s.ok)return null;const n={index:s.value,resourceId:Oe(t.resource_id),text:Oe(t.text),contentDesc:Oe(t.content_desc),className:Oe(t.class_name),bounds:Oe(t.bounds)};return n.resourceId||n.text||n.contentDesc||n.className||n.bounds?n:null}function Le(e,t){return k(e,t)}function $e(e){const t=[];return e.resourceId&&t.push({key:"resource_id",value:e.resourceId}),e.text&&t.push({key:"text",value:e.text}),e.contentDesc&&t.push({key:"content_desc",value:e.contentDesc}),e.className&&t.push({key:"class_name",value:e.className}),e.bounds&&t.push({key:"bounds",value:e.bounds}),t}function Me(e,t){if(e.index!==t.index)return!1;const s=$e(e),n=$e(t);return s.length===n.length&&s.every(({key:e,value:s})=>{switch(e){case"resource_id":return!!t.resourceId&&O("resourceId",s,t.resourceId);case"text":return!!t.text&&O("text",s,t.text);case"content_desc":return!!t.contentDesc&&O("contentDesc",s,t.contentDesc);case"class_name":return!!t.className&&O("className",s,t.className);case"bounds":return!!t.bounds&&O("bounds",s,t.bounds)}})}function je(e,t){return D(e,t)}function Fe(e,t){const s=Re(e.selector);if(!s?.resourceId)return null;const n=function(e){const t=new Map;for(const s of e)s.resourceId&&t.set(s.resourceId,(t.get(s.resourceId)||0)+1);const s=new Set;for(const[e,n]of t)(Ie(e)||!e.startsWith("android:")&&n>=5)&&s.add(e);return s}(t);return n.has(s.resourceId)?`selector.resource_id="${s.resourceId}" 疑似混淆值,回放时可能不稳定。建议下次优先使用 text/content_desc/class_name 定位。`:null}function Pe(e,t,n){if(!s.has(e))return null;const o=Re(t.selector);if(!o)return"validation failed: selector is missing or invalid. Provide resource_id/text/content_desc/class_name/bounds.";const r=n?.lastObserveNodes;if(!r||0===r.length)return"validation failed: missing observe evidence. Call observe_screen before action tools.";const i=je(r,o);if(0===i)return"validation failed: selector has no match on latest observed screen.";const a=Ee(t.action_index);if(!a.ok)return"validation failed: action_index must be a non-negative integer.";if(void 0!==a.value&&a.value>=i)return`validation failed: action_index=${a.value} is out of range for matched_count=${i}.`;if(i>1&&void 0===a.value)return`validation failed: selector is ambiguous (matched_count=${i}). Refine selector or provide action_index.`;const c=function(e){if(!e||"object"!=typeof e||Array.isArray(e))return null;const t=e.target;if(!t||"object"!=typeof t||Array.isArray(t))return null;const s=t,n=Ee(s.index);if(!n.ok)return null;const o={index:n.value,resourceId:Oe(s.resource_id),text:Oe(s.text),contentDesc:Oe(s.content_desc),className:Oe(s.class_name),bounds:Oe(s.bounds)};return o.resourceId||o.text||o.contentDesc||o.className||o.bounds?o:null}(t.pre_context);return c&&!function(e,t){return void 0!==e.index&&void 0!==t.index&&e.index===t.index||!!(e.resourceId&&t.resourceId&&O("resourceId",e.resourceId,t.resourceId))||!!(e.text&&t.text&&O("text",e.text,t.text))||!!(e.contentDesc&&t.contentDesc&&O("contentDesc",e.contentDesc,t.contentDesc))||!!(e.className&&t.className&&O("className",e.className,t.className))||!!(e.bounds&&t.bounds&&O("bounds",e.bounds,t.bounds))}(o,c)?"validation failed: selector does not align with pre_context.target evidence.":null}function Ue(e,t){const s=Re(e.selector),n=Ce(t.script_selector),o=Re(n?.selector);if(!s||!o)return null;const r=Ee(e.action_index),i=Ee(n?.action_index),a=$e(s).map(e=>e.key),c=$e(o).map(e=>e.key);if(Me(s,o)&&(r.value??void 0)===(i.value??void 0))return{requested_is_minimal:!0,diff_type:"same",requested_selector_fields:a,script_selector_fields:c,script_selector:n?.selector,script_action_index:i.value};const l=function(e,t){const s=[];for(const{key:s,value:n}of $e(t))if(!(()=>{switch(s){case"resource_id":return!!e.resourceId&&O("resourceId",n,e.resourceId);case"text":return!!e.text&&O("text",n,e.text);case"content_desc":return!!e.contentDesc&&O("contentDesc",n,e.contentDesc);case"class_name":return!!e.className&&O("className",n,e.className);case"bounds":return!!e.bounds&&O("bounds",n,e.bounds)}})())return{matchesBase:!1,redundantFields:[]};for(const{key:n}of $e(e))(()=>{switch(n){case"resource_id":return!!t.resourceId;case"text":return!!t.text;case"content_desc":return!!t.contentDesc;case"class_name":return!!t.className;case"bounds":return!!t.bounds}})()||s.push(n);return{matchesBase:!0,redundantFields:s}}(s,o),u=void 0!==r.value&&void 0===i.value;return l.matchesBase&&(l.redundantFields.length>0||u)?{requested_is_minimal:!1,diff_type:"redundant_constraint",requested_selector_fields:a,script_selector_fields:c,redundant_selector_fields:l.redundantFields,requested_action_index:r.value,script_action_index:i.value,script_selector:n?.selector}:{requested_is_minimal:!1,diff_type:"different_contract",requested_selector_fields:a,script_selector_fields:c,requested_action_index:r.value,script_action_index:i.value,script_selector:n?.selector}}const qe={async observe_screen(e,t,s,n){const o="number"==typeof e.wait_ms&&e.wait_ms>0?e.wait_ms:0;if(o>0){const e=await Te(t,"POST","base/sleep",{duration:o},s);if(200!==e.code)return{success:!1,error:e.msg||"Wait before observe failed"}}const r=await Te(t,"POST","accessibility/dump_compact",{},s);return 200==r.code&&"string"==typeof r.data?(n&&xe(n,r.data),{success:!0,data:r.data}):{success:!1,error:r.msg||"Failed to get UI dump"}},async verify_ui_state(e,t,s,n){const o=Oe(e.step_desc);if(!o)return{success:!1,error:"verify_ui_state 缺少 step_desc。"};const r=Oe(e.screen_desc);if(!r)return{success:!1,error:"verify_ui_state 缺少 screen_desc。"};const i=function(e){return ke({page_kind:Oe(e.page_kind)})}(e),a=e.verify_element;if(!a||"object"!=typeof a||Array.isArray(a))return{success:!1,error:"verify_ui_state 缺少 verify_element 对象。"};const c=Ae(a,"verify_element");if(c)return{success:!1,error:c};const l=Re(a);if(!l)return{success:!1,error:"verify_element 必须提供 resource_id、text、content_desc、class_name 或 bounds 至少一个(可选 index),且需来自当前 observe_screen 结果。"};const u=e.page_anchor;if(u&&"object"==typeof u&&!Array.isArray(u)){const e=Ae(u,"page_anchor");if(e)return{success:!1,error:e}}const d=n?.lastObserveNodes;if(!d||0===d.length)return{success:!1,error:"请先调用 observe_screen(可带 wait_ms)获取当前界面,再调用 verify_ui_state。"};if(je(d,l)>0){const e={step_desc:o,screen_desc:r,verify_element:a,...i?{recorded_page:i}:{}},t=Ne(d,l,0);if(t){const s=ve(t,d,{mode:"verify"});s&&(e.verification_plan=s)}if(u&&"object"==typeof u&&!Array.isArray(u)){const t=Re(u);if(!t)return{success:!1,error:"page_anchor 必须提供 resource_id、text、content_desc、class_name 或 bounds 至少一个(可选 index)。"};if(0===je(d,t))return{success:!1,error:"page_anchor 必须来自当前 observe_screen 的真实节点,当前界面未匹配。"};e.page_anchor=u;const s=Ne(d,t,0);if(s){const t=ve(s,d,{mode:"anchor"});t&&(e.page_anchor_plan=t)}}return{success:!0,data:e}}const m=[void 0!==l.index&&`index=${l.index}`,l.resourceId&&`resource_id="${l.resourceId}"`,l.text&&`text="${l.text}"`,l.contentDesc&&`content_desc="${l.contentDesc}"`,l.className&&`class_name="${l.className}"`].filter(Boolean).join(", "),p=d.filter(e=>e.text||e.contentDesc||e.resourceId&&!Ie(e.resourceId)).slice(0,15).map(e=>{const t=[];return t.push(`index=${e.index}`),e.text&&t.push(`text="${e.text}"`),e.contentDesc&&t.push(`desc="${e.contentDesc}"`),e.resourceId&&!Ie(e.resourceId)&&t.push(`id="${e.resourceId}"`),t.push(`[${e.className.split(".").pop()}]`),` ${t.join(" ")}`});return{success:!1,error:`验证元素在当前界面中不存在:${m} 未匹配。请从当前界面重新选择 verify 元素。${p.length>0?`\n当前界面可用的验证候选元素(直接从中选择,无需再调 observe_screen):\n${p.join("\n")}`:""}`}},async record_search_context(e,t,s,n){const o=Oe(e.step_intent);if(!o)return{success:!1,error:"record_search_context 缺少 step_intent。"};const r=Oe(e.target_desc);if(!r)return{success:!1,error:"record_search_context 缺少 target_desc。"};const i=e.anchor_element;if(!i||"object"!=typeof i||Array.isArray(i))return{success:!1,error:"record_search_context 缺少 anchor_element(当前页面锚点),必填。"};const a=i,c=Ae(a,"anchor_element");if(c)return{success:!1,error:c};if(!function(e){const t=e.resource_id,s=e.text,n=e.content_desc,o=e.class_name,r=e.bounds;return"string"==typeof t&&t.trim().length>0||"string"==typeof s&&s.trim().length>0||"string"==typeof n&&n.trim().length>0||"string"==typeof o&&o.trim().length>0||"string"==typeof r&&r.trim().length>0}(a))return{success:!1,error:"anchor_element 必须包含 resource_id、text、content_desc、class_name 或 bounds 至少一个(可选 index)。"};const l=n?.lastObserveNodes;if(!l||0===l.length)return{success:!1,error:"record_search_context 前必须先调用 observe_screen 获取当前界面。"};const u=Re(i);if(!u)return{success:!1,error:"anchor_element 不是合法 selector。"};if(0===je(l,u))return{success:!1,error:"anchor_element 必须来自当前 observe_screen 的真实节点,当前界面未匹配。"};const d=e.target_element,m={...e,step_intent:o,target_desc:r,recorded_context:De({...e})},p=Ne(l,u,0);if(p){const e=ve(p,l,{mode:"anchor"});e&&(m.anchor_plan=e)}if(d&&"object"==typeof d&&!Array.isArray(d)){const e=Ae(d,"target_element");if(e)return{success:!1,error:e};const t=Re(d);if(!t)return{success:!1,error:"target_element 必须提供 resource_id、text、content_desc、class_name 或 bounds 至少一个(可选 index)。"};if(0===je(l,t))return{success:!1,error:"target_element 必须来自当前 observe_screen 的真实节点,当前界面未匹配。"};const s=Ne(l,t,0);if(s){const e=ve(s,l,{mode:"action"});e&&(m.target_plan=e)}}return{success:!0,data:m}},async get_installed_apps(e,t,s){const n=e.type||"user",o=await Te(t,"GET",`package/list?type=${n}`,void 0,s);if(200==o.code&&o.data?.packages){return{success:!0,data:o.data.packages.map(e=>`${e.app_name||"unknown"} = ${e.package_name||""}`).join("\n")}}return{success:!1,error:o.msg||"Failed to get app list"}},async get_top_activity(e,t,s){const n=await Te(t,"GET","activity/top_activity",void 0,s);return 200==n.code?{success:!0,data:n.data}:{success:!1,error:n.msg||"Failed to get top activity"}},async get_screen_info(e,t,s){const n=await Te(t,"GET","display/info",void 0,s);return 200==n.code?{success:!0,data:n.data}:{success:!1,error:n.msg||"Failed to get screen info"}},async start_app(e,t,s){const n=e.package_name;if(!n)return{success:!1,error:"Missing required param: package_name"};const o=await Te(t,"POST","permission/set",{package_name:n,grant_all:!0},s);if(200!==o.code)return{success:!1,error:`权限授权失败 (${n}): ${o.msg}。建议:通过 get_installed_apps 确认包名是否正确。`};const r=await Te(t,"POST","activity/start",{package_name:n},s);return 200==r.code?{success:!0,data:r.data}:{success:!1,error:r.msg||"Failed to start app"}},async stop_app(e,t,s){const n=e.package_name,o=await Te(t,"POST","activity/stop",{package_name:n},s);return 200==o.code?{success:!0,data:o.data}:{success:!1,error:o.msg||"Failed to stop app"}},async tap(e,t,s){const n=await Te(t,"POST","input/click",{x:e.x,y:e.y},s);return 200==n.code?{success:!0,data:n.data}:{success:!1,error:n.msg||"Tap failed"}},async tap_element(e,t,s){const n=Ee(e.action_index);if(!n.ok)return{success:!1,error:"Invalid action_index: expected non-negative integer"};const o={selector:e.selector,action:"click",wait_timeout:e.wait_timeout??5e3,wait_interval:e.wait_interval??1e3};void 0!==n.value&&(o.action_index=n.value);const r=await Te(t,"POST","accessibility/node",o,s);return 200==r.code?{success:!0,data:r.data}:{success:!1,error:r.msg?`元素操作失败: ${r.msg}。建议:重新 observe_screen 确认元素是否存在,检查 selector 唯一性,必要时补充 action_index。`:"元素未找到或点击失败,请重新 observe_screen 后检查 selector"}},async long_press_element(e,t,s){const n=Ee(e.action_index);if(!n.ok)return{success:!1,error:"Invalid action_index: expected non-negative integer"};const o={selector:e.selector,action:"long_click",wait_timeout:e.wait_timeout??5e3,wait_interval:e.wait_interval??1e3};void 0!==n.value&&(o.action_index=n.value);const r=await Te(t,"POST","accessibility/node",o,s);return 200==r.code?{success:!0,data:r.data}:{success:!1,error:r.msg||"Long press element failed"}},async set_text(e,t,s){const n=Ee(e.action_index);if(!n.ok)return{success:!1,error:"Invalid action_index: expected non-negative integer"};const o={selector:e.selector,action:"set_text",action_params:{text:e.text},wait_timeout:e.wait_timeout??5e3,wait_interval:e.wait_interval??1e3};void 0!==n.value&&(o.action_index=n.value);const r=await Te(t,"POST","accessibility/node",o,s);return 200==r.code?{success:!0,data:r.data}:{success:!1,error:r.msg?`文本输入失败: ${r.msg}。建议:确认输入框已聚焦,或尝试先 tap_element 聚焦后使用 input_text。`:"文本输入失败,请确认输入框已存在且可聚焦"}},async input_text(e,t,s){const n=await Te(t,"POST","input/text",{text:e.text},s);return 200==n.code?{success:!0,data:n.data}:{success:!1,error:n.msg||"Input text failed"}},async swipe(e,t,s){const n=await Te(t,"POST","input/scroll_bezier",{start_x:e.start_x,start_y:e.start_y,end_x:e.end_x,end_y:e.end_y,duration:e.duration??300},s);return 200==n.code?{success:!0,data:n.data}:{success:!1,error:n.msg||"Swipe failed"}},async press_key(e,t,s){const n=await Te(t,"POST","input/keyevent",{key_code:e.key_code},s);return 200==n.code?{success:!0,data:n.data}:{success:!1,error:n.msg||"Key event failed"}},async wait(e,t,s){const n=await Te(t,"POST","base/sleep",{duration:e.duration},s);return 200==n.code?{success:!0,data:!0}:{success:!1,error:n.msg||"Wait failed"}}};async function Ge(e,i,a,c){const l=qe[e.name],u=(d=e.name,ue.find(e=>e.name===d));var d;if(!l||!u)return{toolCallId:e.id,name:e.name,success:!1,error:`Unknown tool: ${e.name}`};const m=(p=e.arguments)&&"object"==typeof p&&!Array.isArray(p)?p:{};var p;const _=function(e,t){const s=Se(t,"index"),n=Se(t,"action_index"),r=Ce(t.selector),i=!!r&&Se(r,"index");return o.has(e)?s?"Invalid index usage: use action_index (top-level) for accessibility/node tools":i?"Invalid selector.index: selector does not support index, use action_index instead":null:s||n?"Invalid index usage: index/action_index are only allowed for accessibility/node tools":null}(e.name,m);if(_)return{toolCallId:e.id,name:e.name,success:!1,error:_};const f=u.schema.safeParse(m);if(!f.success&&t.has(e.name)){const t=f.error.issues.map(e=>`${e.path.join(".")}: ${e.message}`).join("; ");return{toolCallId:e.id,name:e.name,success:!1,error:`参数校验失败,拒绝执行: ${t}。请检查 pre_context.anchor 和 pre_context.target 是否已填写,selector 字段是否合法。`}}const g=f.success?f.data:m,h=function(e,t){if(!r.has(e))return null;const s=t.pre_context;if(!s||"object"!=typeof s||Array.isArray(s))return`pre_context 缺失:${e} 调用前必须提供 pre_context(anchor + target)。请先 observe_screen 确认页面状态,再填写 pre_context 后重新调用。`;const n=s,o=n.anchor;if(!o||"object"!=typeof o||Array.isArray(o))return"pre_context.anchor 缺失:必须提供页面锚点元素用于确认当前页面。anchor 应选择页面中稳定存在的框架级元素(如导航栏、标题栏)。";if(!Oe(o.desc))return"pre_context.anchor.desc 缺失:必须提供页面锚点描述,用于说明当前页面上下文。";const i=n.target;return!i||"object"!=typeof i||Array.isArray(i)?"pre_context.target 缺失:必须提供操作目标元素信息。target 应来自当前 observe_screen 的真实节点属性。":Oe(i.desc)?Oe(n.step_intent)?null:"pre_context.step_intent 缺失:必须说明本次操作在任务中的目的和预期效果,而非重复元素描述。此字段用于脚本生成的步骤命名和合并。":"pre_context.target.desc 缺失:必须提供目标元素描述,用于说明本次操作意图。"}(e.name,g);if(h)return{toolCallId:e.id,name:e.name,success:!1,error:h};const b=Pe(e.name,g,c);if(b)return{toolCallId:e.id,name:e.name,success:!1,error:b};try{const t=await l(g,i,a,c);if(t.success){const o=[];if(s.has(e.name)&&c?.lastObserveNodes?.length){const e=Re(g.selector),s=Ee(g.action_index);if(e){const n=Ne(c.lastObserveNodes,e,s.value??0);if(n){const e=ve(n,c.lastObserveNodes,{mode:"action",requestedActionIndex:s.value});if(e){const s=t.data&&"object"==typeof t.data&&!Array.isArray(t.data)?t.data:{},n=Ue(g,e);t.data={...s,_selector_recording_profile:e,...n?{_selector_request_analysis:n}:{}};const r=n?function(e){if(!0===e.requested_is_minimal)return null;if("redundant_constraint"===("string"==typeof e.diff_type?e.diff_type:"different_contract")){const t=Array.isArray(e.redundant_selector_fields)?e.redundant_selector_fields.filter(e=>"string"==typeof e):[],s=[...t.length>0?[`冗余字段: ${t.join(", ")}`]:[],void 0!==e.requested_action_index&&void 0===e.script_action_index?"冗余 action_index":void 0].filter(Boolean);return"selector 最小化提醒: 当前请求 selector 可执行,但不是最小稳定唯一方案。录制阶段批准的 script_selector 已足够定位。"+(s.length>0?`请去掉 ${s.join(",")}。`:"")}return"selector 对齐提醒: 当前请求 selector 与录制阶段批准的 script_selector 不一致。虽然本次执行成功,但后续应优先对齐为 recording-approved script_selector。"}(n):null;r&&o.push(r)}}}}if(s.has(e.name)&&c?.lastObserveNodes){const e=Fe(g,c.lastObserveNodes);e&&o.push(e)}if(s.has(e.name)){const e=function(e){const t=Ce(e.pre_context);if(!t)return null;const s=Re(t.anchor),n=Re(t.target);return s&&n&&Me(s,n)?"pre_context 质量提醒: anchor 与 target 使用了完全相同的定位证据。anchor 应优先描述当前页面或容器级稳定锚点,而不是与点击目标重合。":null}(g);e&&o.push(e)}if(c?.softPendingVerify&&de(e.name)){const e=c.softPendingVerify;o.push(`⚠️ 补录提醒: 上一步 #${e.index} ${e.toolName} 的 verify_ui_state 被跳过,录制信息不完整,将影响脚本生成质量。建议在当前操作完成后补充 observe_screen → verify_ui_state。`)}return n.has(e.name)&&o.push("录制提醒: 此动作可能改变界面,请执行 observe_screen(wait_ms) → verify_ui_state 记录验证信息。"),"swipe"===e.name&&(!c?.lastSearchContextTimestamp||Date.now()-c.lastSearchContextTimestamp>3e4)&&o.push("⚠️ 录制提醒: swipe 前未调用 record_search_context,录制信息将不完整。建议先调用 record_search_context 记录查找目标与页面锚点。"),{toolCallId:e.id,name:e.name,success:!0,data:t.data,warning:o.length>0?o.join("\n"):void 0}}return{toolCallId:e.id,name:e.name,success:t.success,data:t.data,error:t.error}}catch(t){return{toolCallId:e.id,name:e.name,success:!1,error:t.message||String(t)}}}const Ve=["search","explore","discover","for you","home","friends","inbox","profile","close","log in","sign up","comment","搜索","首页","消息","我的","关闭","登录","评论"];function Xe(e){const t=e.match(/\bbounds=\[(\d+),(\d+)\]\[(\d+),(\d+)\]/);if(!t)return null;const s=Number(t[2]),n=Number(t[4]);return Number.isFinite(s)&&Number.isFinite(n)?{y1:s,y2:n}:null}function We(e){if(0===e.length)return null;const t=function(e){const t=e.match(/^Screen\s+\d+x(\d+)/);if(!t)return null;const s=Number(t[1]);return Number.isFinite(s)&&s>0?s:null}(e[0]||"");if(!t)return null;let s=0,n=0,o=0;for(let r=1;r<e.length;r++){const i=e[r];if(!i.includes("clickable=true"))continue;const a=Xe(i);if(!a)continue;const c=(a.y1+a.y2)/2/t;c<.33?s++:c<=.67?n++:o++}return 0===s+n+o?null:`[可点击分布] 顶部:${s} 中部:${n} 底部:${o} (按 bounds center_y)`}function Ke(e){if(!e.success)return`Error: ${e.error||"operation failed"}`;const t=e.data;let s;if("verify_ui_state"===e.name&&t&&"object"==typeof t){const e=t,n=e.step_desc,o=e.verify_element;s=n?`verify_ui_state OK: ${n}`:"verify_ui_state OK",o&&(o.resource_id||o.text||o.content_desc)&&(s+=` (element: ${[o.resource_id,o.text,o.content_desc].filter(Boolean).join(" / ")})`)}else if("record_search_context"===e.name&&t&&"object"==typeof t){const e=t;s=e.target_desc?`record_search_context OK: ${e.target_desc}`:"record_search_context OK"}else if("observe_screen"===e.name&&"string"==typeof t){const e=t.split("\n"),n=(t.match(/clickable/g)||[]).length,o=function(e){if(e.length<=120)return{content:e.join("\n"),shownLines:e.length,truncated:!1};const t=new Map,s=s=>{s<0||s>=e.length||t.has(s)||t.set(s,e[s])};s(0);for(let t=1;t<Math.min(e.length,51);t++)s(t);for(let t=Math.max(1,e.length-40);t<e.length;t++)s(t);let n=0;for(let s=1;s<e.length&&n<30;s++){const o=e[s],r=o.toLowerCase(),i=Ve.some(e=>r.includes(e)),a=/\bbounds=\[/.test(o)&&(/\bclickable=true\b/.test(o)||/\bcontent-desc=/.test(o)||/\btext="/.test(o)||/\bresource-id=/.test(o));(i||a)&&(t.has(s)||(t.set(s,o),n++))}const o=Array.from(t.entries()).sort((e,t)=>e[0]-t[0]).slice(0,120).map(([,e])=>e);return{content:o.join("\n"),shownLines:o.length,truncated:!0}}(e),r=`[元素总行数: ${e.length}, 可点击: ${n}, 展示行: ${o.shownLines}]`,i=We(e),a="[位置规则] 仅依据 bounds=[x1,y1][x2,y2] + Screen WxH 判断顶部/底部;禁止按节点顺序或缩进推断位置。",c=o.truncated?"\n... (已按头部+尾部+关键词/可交互关键行压缩展示;定位不确定时请重新 observe_screen 并用 verify_ui_state 验证)":"";s=`${r}\n${a}${i?`\n${i}`:""}\n${o.content}${c}`}else s=function(e){if(!e||"object"!=typeof e||Array.isArray(e))return!1;const t=e;return"action_success"in t&&"nodes"in t&&Array.isArray(t.nodes)}(t)?function(e){const t=e,s="number"==typeof t.count?t.count:0,n="string"==typeof t.action?t.action:"find";return!0!==t.action_success?`${n} failed (matched ${s} nodes)`:s<=1?`${n} OK`:`${n} OK (注意: 匹配到 ${s} 个元素,操作了第 ${"number"==typeof t.action_index?t.action_index:0} 个)`}(t):"string"==typeof t?t:!0===t||void 0===t?"OK":JSON.stringify(t,null,2);return e.warning&&(s+=`\n⚠️ ${e.warning}`),s}class ze{handlers={planning:new Set,thinking:new Set,toolcall:new Set,toolresult:new Set,diagnostic:new Set,paused:new Set,scriptgenerating:new Set,complete:new Set,error:new Set};on(e,t){return this.handlers[e].add(t),()=>this.off(e,t)}off(e,t){this.handlers[e].delete(t)}emit(e,t){for(const s of Array.from(this.handlers[e]))s(t)}clear(){Object.keys(this.handlers).forEach(e=>{this.handlers[e].clear()})}}class Be extends Error{code;details;constructor(e,t,s){super(t),this.name="AgentRuntimeError",this.code=e,this.details=s}}const Je=["INVALID_CONFIG","SESSION_NOT_FOUND","SESSION_NOT_PAUSED","AGENT_RUNNING","EMPTY_EXECUTION_LOG","MCP_CONNECT_FAILED","SCRIPT_GENERATION_FAILED","RUNTIME_LOOP_FAILED","UNKNOWN"];function Ye(e){return!!e&&"object"==typeof e&&!Array.isArray(e)}function He(e,t="UNKNOWN"){if(e instanceof Be)return e;const s=function(e){return e instanceof Error&&e.message||Ye(e)&&"string"==typeof e.message?e.message:String(e)}(e);return Ye(e)?new Be("string"==typeof(n=e.code)&&Je.includes(n)?e.code:t,s,Ye(e.details)?e.details:void 0):new Be(t,s);var n}const Qe=I.object({goal:I.string().min(1),subtasks:I.array(I.object({id:I.number().int().positive(),description:I.string().min(1),successCriteria:I.string().min(1),estimatedActions:I.array(I.string().min(1)).default([])})).default([]),risks:I.array(I.string()).default([]),assumptions:I.array(I.string()).default([]),estimatedSteps:I.number().int().positive().default(1)}),Ze=I.object({status:I.enum(["completed","paused"]).default("completed"),goal:I.string().min(1).optional(),subtasks:Qe.shape.subtasks.default([]),risks:Qe.shape.risks.default([]),assumptions:Qe.shape.assumptions.default([]),estimatedSteps:I.number().int().positive().optional(),pauseMessage:I.string().min(1).optional(),pauseReason:I.string().min(1).optional()}).superRefine((e,t)=>{"paused"!==e.status?e.goal||t.addIssue({code:I.ZodIssueCode.custom,message:"goal is required when status=completed",path:["goal"]}):e.pauseMessage||t.addIssue({code:I.ZodIssueCode.custom,message:"pauseMessage is required when status=paused",path:["pauseMessage"]})});function et(e){const t=e.trim();if(t.startsWith("{")&&t.endsWith("}"))return t;const s=t.replace(/^```json\s*/i,"").replace(/^```\s*/i,"").replace(/\s*```$/,"").trim();if(s.startsWith("{")&&s.endsWith("}"))return s;throw new Error("plan output is not a valid standalone JSON object")}function tt(e,t){return"zh"===t?{goal:e,subtasks:[{id:1,description:"识别目标应用或目标页面入口",successCriteria:"当前界面可见可执行入口元素",estimatedActions:["observe_screen","tap_element"]},{id:2,description:"执行目标核心动作",successCriteria:"动作后出现预期的关键页面变化",estimatedActions:["tap_element","set_text","press_key"]},{id:3,description:"记录动作结果并验证状态",successCriteria:"verify_ui_state 成功且状态可复用",estimatedActions:["observe_screen","verify_ui_state"]}],risks:["页面元素可能动态变化","页面加载延迟导致观察结果滞后"],assumptions:["设备已连通且可调用 Android 控制 API"],estimatedSteps:8}:{goal:e,subtasks:[{id:1,description:"Identify the target app or the entry point screen",successCriteria:"A visible and actionable entry element is observed",estimatedActions:["observe_screen","tap_element"]},{id:2,description:"Execute the core target action",successCriteria:"Expected key screen transition appears after action",estimatedActions:["tap_element","set_text","press_key"]},{id:3,description:"Record outcome and verify resulting UI state",successCriteria:"verify_ui_state succeeds with reusable state evidence",estimatedActions:["observe_screen","verify_ui_state"]}],risks:["Dynamic UI elements may change during execution","Page loading delays can make observations stale"],assumptions:["Device is reachable and Android control APIs are available"],estimatedSteps:8}}function st(e,t,s){return e<=1?"":"zh"===s?`\n\n[上一次失败]\n${t[t.length-1]||"unknown"}\n请严格输出合法 JSON。`:`\n\n[PreviousFailure]\n${t[t.length-1]||"unknown"}\nOutput strict valid JSON only.`}function nt(e,t,s,n){return"zh"===n?`## 目标\n\n${e}\n\n## 当前设备屏幕\n\n${t}${s}`:`## Goal\n\n${e}\n\n## Current Device Screen\n\n${t}${s}`}async function ot(e,t,s,n,o={}){return async function(e,t,s,n,o={}){const r=c(e),l={lastObserveRawDump:null,lastObserveNodes:[]},u=o.planningMessages||[],d=!1!==o.allowPause,m=await Ge({id:"plan-observe-screen",name:"observe_screen",arguments:{},source:"local"},s,o.signal,l),p=m.success&&"string"==typeof m.data?m.data:Ke(m),_=l.lastObserveRawDump||void 0,f=i(t),g=[],h=Math.max(1,n.planner.maxAttempts);for(let t=1;t<=h;t++){const s=st(t,g,r),n=[{role:"system",content:a(e,d)},{role:"user",content:nt(e,p,s,r)},...u];try{let s;if(f.capabilities.structuredJson)s=await f.chatStructuredJson(n,Ze,o.signal);else{const e=await f.chatWithTools(n,[],o.signal);s=JSON.parse(et(e.content))}const r=Ze.parse(s);if("paused"===r.status){if(!d)throw new Error("planner requested clarification but pause is disabled for this entrypoint");return{status:"paused",text:r.pauseMessage||"planner requested clarification",reason:r.pauseReason,screenDump:p,screenRawDump:_,attempts:t,errors:g}}const i=Math.max(r.estimatedSteps||1,2*r.subtasks.length,1);return{status:"completed",plan:{goal:r.goal||e,subtasks:r.subtasks,risks:r.risks,assumptions:r.assumptions,estimatedSteps:i},screenDump:p,screenRawDump:_,attempts:t,errors:g}}catch(e){g.push(e.message||String(e))}}return{status:"completed",plan:tt(e,r),screenDump:p,screenRawDump:_,attempts:h,errors:g}}(e,t,s,n,o)}function rt(e,t){return t<=0?null:e/t}function it(e,t){const s={...t,timestamp:Date.now()};return e.diagnostics.push(s),s}function at(e){const t=[...e.diagnostics].reverse().find(e=>"runtime"===e.phase||"script_generation"===e.phase)?.code,s=function(e){let t=0,s=0,n=0;for(const o of e)o.result.success&&(t+=1),"action"===o.category&&(s+=1,o.result.success&&(n+=1));return{totalToolCalls:e.length,successfulToolCalls:t,runtimeSuccessRate:rt(t,e.length),totalActionCalls:s,successfulActionCalls:n,automationSuccessRate:rt(n,s)}}(e.executionLog),n={};let o=0;for(const t of e.executionLog){if(t.result.success)continue;o+=1;const e=t.result.errorCode||"UNKNOWN";n[e]=(n[e]||0)+1}return{planningRetries:e.runtimeContext.planningRetries,toolRetries:e.runtimeContext.toolRetries,scriptGenerationRetries:e.runtimeContext.scriptGenerationRetries,convergenceTriggers:e.runtimeContext.convergenceTriggers,totalToolCalls:s.totalToolCalls,successfulToolCalls:s.successfulToolCalls,runtimeSuccessRate:s.runtimeSuccessRate,totalActionCalls:s.totalActionCalls,successfulActionCalls:s.successfulActionCalls,automationSuccessRate:s.automationSuccessRate,totalFailures:o,failureByCode:n,finalFailureCode:t}}function ct(e,t,s,n=[]){const o=[{role:"system",content:l(e,t)},{role:"user",content:e}];for(const e of n)"assistant"!==e.role&&"user"!==e.role||o.push({role:e.role,content:e.content,timestamp:e.timestamp});return s&&(o.push({role:"assistant",content:"",toolCalls:[{id:"plan-initial-observe",name:"observe_screen",arguments:{},source:"local"}]}),o.push({role:"tool",content:s,toolCallId:"plan-initial-observe"})),o}function lt(e){return e.currentPhase||(e.currentPhase="runtime"),e.planningContext?Array.isArray(e.planningContext.messages)||(e.planningContext.messages=[]):e.planningContext={messages:[]},e}function ut(e,t,s){lt(e),e.plan=t,e.currentPhase="runtime",e.messages=ct(e.goal,t,s,e.planningContext?.messages||[])}function dt(e,t,s=[]){s.length>0?e.messages.push({role:"assistant",content:t,toolCalls:s,timestamp:Date.now()}):e.messages.push({role:"assistant",content:t,timestamp:Date.now()})}function mt(e,t,s){e.messages.push({role:"tool",content:s,toolCallId:t,timestamp:Date.now()})}const pt=new Set(["verify_ui_state","record_search_context","get_screen_info"]);function _t(e){return!e||"object"!=typeof e||Array.isArray(e)?null:e}function ft(e){if("number"==typeof e&&Number.isInteger(e)&&e>=0)return e;if("string"==typeof e&&e.trim().length>0){const t=Number(e);if(Number.isInteger(t)&&t>=0)return t}}function gt(e,t){for(const s of t){const t=e[s];if("string"==typeof t){const e=t.trim();if(e.length>0)return e}}}function ht(e){const t={};let s=!1;for(const[n,o]of Object.entries(e))null!=o&&""!==o&&(t[n]=o,s=!0);return s?t:void 0}function bt(e){const t=_t(e);if(!t)return;return ht({index:ft(t.index),resource_id:gt(t,["resource_id","resource-id","resourceId","id"]),text:gt(t,["text"]),content_desc:gt(t,["content_desc","content-desc","contentDesc"]),class_name:gt(t,["class_name","class","className"]),bounds:gt(t,["bounds"])})}function yt(e,t){if(!s.has(e.name)||!t.success)return null;const n=_t(e.arguments),o=_t(t.data);if(!n&&!o)return null;const r=ht(_t(n?.selector)||{}),i=ft(n?.action_index),a=Array.isArray(o?.nodes)?o.nodes:[],c=ft(o?.action_index),l=i??c??0,u=bt(a[l]??a[0]),d=a.slice(0,5).map(e=>bt(e)).filter(e=>!!e),m="number"==typeof o?.count&&Number.isFinite(o.count)?o.count:a.length,p={};r&&(p.requested_selector=r),void 0===i&&void 0===c||(p.action_index=l),m>=0&&(p.matched_count=m),u&&(p.selected_node=u),d.length>0&&(p.candidate_nodes=d);const _=function(e){const t=_t(e);if(!t)return;return ht({mode:"string"==typeof t.mode?t.mode:void 0,script_selector:_t(t.script_selector)||void 0,field_assessments:Array.isArray(t.field_assessments)?t.field_assessments.map(e=>_t(e)).filter(e=>!!e).slice(0,8):void 0,selector_candidates:Array.isArray(t.selector_candidates)?t.selector_candidates.map(e=>_t(e)).filter(e=>!!e).slice(0,8):void 0,selected_node:bt(t.selected_node)})}(o?._selector_recording_profile);if(_){p.selector_profile=_;const e=_t(_.script_selector);if(e){const t=_t(e.selector);t&&(p.recommended_selector=t);const s=ft(e.action_index);void 0!==s&&(p.recommended_action_index=s),"string"==typeof e.stability&&(p.selector_stability=e.stability),"string"==typeof e.reason&&(p.selector_reason=e.reason),"boolean"==typeof e.reusable&&(p.selector_reusable=e.reusable),"string"==typeof e.scope&&(p.selector_scope=e.scope)}}const f=_t(o?._selector_request_analysis);return f&&(p.selector_request_analysis=f),Object.keys(p).length>0?p:null}function xt(e,t){const s=e.name;if(pt.has(s))return t;if("observe_screen"===s&&t.success&&"string"==typeof t.data){const e=t.data.split("\n").length;return{...t,data:`(${e} lines)`}}const n=yt(e,t);if(n){const e={toolCallId:t.toolCallId,name:t.name,success:t.success,data:{runtime_selector_evidence:n},source:t.source,server:t.server};return t.error&&(e.error=t.error),t.warning&&(e.warning=t.warning),e}const o={toolCallId:t.toolCallId,name:t.name,success:t.success,source:t.source,server:t.server};return t.error&&(o.error=t.error),t.warning&&(o.warning=t.warning),o}function vt(e,t,s,n){e.executionLog.push({index:e.executionLog.length+1,toolName:t.name,arguments:t.arguments,result:xt(t,s),category:n,timestamp:Date.now()})}const wt=[/resource-id=/,/resource_id=/,/\btext="/,/content-desc=/,/content_desc=/,/clickable=true/,/bounds=\[/];function It(e){return e.startsWith("Screen ")||e.startsWith("[Package]")||e.startsWith("[元素总行数:")}function Nt(e){const t=e.split("\n");if(t.length<=7)return e;const s=t[0]||"",n=new Set,o=[];let r=0;for(let e=1;e<t.length;e++){const s=t[e];s.includes("clickable=true")&&r++,wt.some(e=>e.test(s))&&(n.has(s)||(n.add(s),o.push(s)))}return[s,...o.length>0?o.slice(0,6):t.slice(1,7),"[位置规则] 仅依据 bounds + Screen WxH 判断顶部/底部,禁止按节点顺序推断。",`[... 已压缩,原始 ${t.length} 行,关键候选 ${o.length} 行,可点击 ${r} 个]`].join("\n")}function Tt(e){if(e.length<=14)return e;const t=e.length-12,s=[],n=[],o=[];for(let t=0;t<e.length;t++){const s=e[t];"tool"===s.role&&It(s.content)&&n.push(t),"tool"===s.role&&(s.content.includes('"step_desc"')||s.content.includes('"target_desc"'))&&o.push(t)}const r=new Set(n.slice(-1)),i=new Set(o.slice(-3));for(let n=0;n<e.length;n++){const o=e[n];if(n<=1)s.push(o);else if(n>=t)s.push(o);else if("user"!==o.role)if("assistant"!==o.role)if(o.content.length<=200)s.push(o);else if(!o.content.includes('"step_desc"')&&!o.content.includes('"target_desc"')||i.has(n))if(It(o.content)){if(r.has(n)){s.push(o);continue}s.push({...o,content:Nt(o.content)})}else s.push({...o,content:o.content.substring(0,200)+"\n[... 已截断]"});else try{const e=JSON.parse(o.content);s.push({...o,content:JSON.stringify({step_desc:e.step_desc,target_desc:e.target_desc})})}catch{s.push({...o,content:o.content.substring(0,200)+"\n[... 已截断]"})}else o.content.length<=120?s.push(o):s.push({...o,content:o.content.substring(0,120)+"\n[... 已截断]"});else s.push(o)}return s}function Ct(e){return"stdio"===e.transport?{transport:"stdio",command:e.command,args:e.args||[],env:e.env||{}}:{transport:"streamable_http"===e.transport?"http":e.transport,url:e.url,headers:e.headers||{}}}class St{servers;client=null;constructor(e){this.servers=e}async connect(){if(!this.servers||0===Object.keys(this.servers).length)return[];if(!this.client){const e={};for(const[t,s]of Object.entries(this.servers))e[t]=Ct(s);this.client=new T({mcpServers:e})}const e=[];for(const t of Object.keys(this.servers)){const s=await this.client.getTools(t);for(const n of s)n.name&&e.push({name:n.name,tool:n,server:t})}return e}async close(){this.client&&(await this.client.close(),this.client=null)}}const Et=new Set(ue.map(e=>e.name));class At{mcpClient=null;mcpTools=new Map;modelTools=[...ue];getModelTools(){return this.modelTools}async prepare(e={}){if(await this.closeMcpClient(),this.mcpTools.clear(),this.modelTools=[...ue],0===Object.keys(e).length)return;const t=new St(e);try{const e=await t.connect();!function(e,t){const s=[],n=new Set(e),o=new Set;for(const e of t){const t=e.name;t&&(n.has(t)||o.has(t)?s.push(t):o.add(t))}if(s.length>0){const e=Array.from(new Set(s)).join(", ");throw new Error(`MCP tool name conflict detected: ${e}`)}}(ue.map(e=>e.name),e);for(const t of e)this.mcpTools.set(t.name,{tool:t.tool,server:t.server});this.modelTools=[...ue,...e.map(e=>e.tool)],this.mcpClient=t}catch(e){throw await t.close().catch(()=>{}),e}}annotateToolCalls(e){return e.map(e=>{const t=this.resolveTool(e.name);return{...e,source:t.source,server:t.server}})}getCategory(e,t){return"local"===t.source&&de(e.name)?"action":"observation"}async execute(e,t,s,n){const o=this.resolveTool(e.name);if("local"===o.source){return{...await Ge({id:e.id,name:e.name,arguments:e.arguments},t,s,n),source:"local"}}if(!o.tool)return{toolCallId:e.id,name:e.name,success:!1,error:`MCP tool not found: ${e.name}`,source:"mcp",server:o.server};try{const t=await o.tool.invoke(e.arguments);return{toolCallId:e.id,name:e.name,success:!0,data:t,source:"mcp",server:o.server}}catch(t){return{toolCallId:e.id,name:e.name,success:!1,error:t.message||String(t),source:"mcp",server:o.server}}}async destroy(){await this.closeMcpClient(),this.mcpTools.clear(),this.modelTools=[...ue]}resolveTool(e){if(Et.has(e))return{source:"local"};const t=this.mcpTools.get(e);return t?{source:"mcp",server:t.server,tool:t.tool}:{source:"local"}}async closeMcpClient(){this.mcpClient&&(await this.mcpClient.close(),this.mcpClient=null)}}const Ot="PLANNER_RETRY",kt="PLANNER_FALLBACK",Dt="TOOL_RETRY",Rt="VERIFY_GATE_BLOCKED",Lt="MAX_ITERATIONS_REACHED",$t="CONSECUTIVE_FAILURE_LIMIT_REACHED",Mt="SAME_TOOL_REPEAT_LIMIT_REACHED",jt="SCRIPT_RETRY",Ft=new Set(["TIMEOUT","NETWORK","HTTP_5XX"]);function Pt(e){if(e.errorCode)return{code:e.errorCode,retryable:e.retryable??Ft.has(e.errorCode)};const t=(e.error||"").toLowerCase();if(!t)return{code:"UNKNOWN",retryable:!1};if(t.includes("aborted"))return{code:"ABORTED",retryable:!1};if(t.includes("timeout")||t.includes("timed out"))return{code:"TIMEOUT",retryable:!0};if(t.includes("econnreset")||t.includes("enotfound")||t.includes("econnrefused")||t.includes("network")||t.includes("fetch failed"))return{code:"NETWORK",retryable:!0};const s=function(e){const t=e.match(/\b(?:status|failed:)\s*(\d{3})\b/i);if(!t)return null;const s=Number(t[1]);return Number.isFinite(s)?s:null}(t);if(null!==s){if(s>=500)return{code:"HTTP_5XX",retryable:!0};if(s>=400)return{code:"HTTP_4XX",retryable:!1}}return t.includes("unknown tool")?{code:"UNKNOWN_TOOL",retryable:!1}:t.includes("参数校验失败")||t.includes("validation")||t.includes("invalid")?{code:"VALIDATION",retryable:!1}:t.includes("拦截:上一步动作尚未验证")||t.includes("blocked by verify gate")?{code:"RUNTIME_GUARD",retryable:!1}:t.includes("plan")&&t.includes("invalid")?{code:"PLAN_VALIDATION_FAILED",retryable:!1}:t.includes("workflow")&&t.includes("invalid")?{code:"SCRIPT_VALIDATION_FAILED",retryable:!1}:{code:"UNKNOWN",retryable:!1}}function Ut(e,t){const s=new AbortController,n=()=>{s.abort(t?.reason)};t&&(t.aborted&&s.abort(t.reason),t.addEventListener("abort",n));const o=setTimeout(()=>{s.abort(new Error(`Tool timeout after ${e}ms`))},e);return{signal:s.signal,dispose:()=>{clearTimeout(o),t&&t.removeEventListener("abort",n)}}}function qt(e,t){return{toolCallId:e.id,name:e.name,success:!1,error:`Tool timeout after ${t}ms`,errorCode:"TIMEOUT",retryable:!0}}function Gt(e,t){return t instanceof Error&&/timeout/i.test(t.message)?qt(e,0):t instanceof Error&&"AbortError"===t.name?{toolCallId:e.id,name:e.name,success:!1,error:t.message||"Tool aborted",errorCode:"ABORTED",retryable:!1}:{toolCallId:e.id,name:e.name,success:!1,error:t?.message||String(t),errorCode:"UNKNOWN",retryable:!1}}async function Vt(e){const t=Math.max(1,e.maxAttempts);for(let s=1;s<=t;s++){const n=Date.now(),o=Ut(e.timeoutMs,e.signal);let r;try{r=await e.execute(e.toolCall,e.device,o.signal)}catch(t){const s=o.signal.reason instanceof Error?o.signal.reason.message:String(o.signal.reason||"");r=o.signal.aborted&&/timeout/i.test(s)?qt(e.toolCall,e.timeoutMs):Gt(e.toolCall,t)}finally{o.dispose()}if(r.latencyMs||(r.latencyMs=Date.now()-n),r.attempt=s,r.success)return r;const i=Pt(r);if(r.errorCode=r.errorCode??i.code,r.retryable=r.retryable??i.retryable,"ABORTED"===r.errorCode)return r;if(!r.retryable||s>=t)return r;e.onRetry?.(s,r)}return{toolCallId:e.toolCall.id,name:e.toolCall.name,success:!1,error:"Unreachable: retry loop terminated unexpectedly",errorCode:"UNKNOWN",retryable:!1}}function Xt(e,t){if(!Number.isFinite(e))return t;const s=Math.floor(e);return s<=0?t:s}function Wt(e,t){return"action"===t?e.retries.action:e.retries.observation}function Kt(e,t){const s=d[t];return s?e.timeouts[s]:e.timeouts.defaultMs}function zt(e){if(null==e)return String(e);if("object"!=typeof e)return JSON.stringify(e);if(Array.isArray(e))return`[${e.map(e=>zt(e)).join(",")}]`;return`{${Object.entries(e).sort(([e],[t])=>e.localeCompare(t)).map(([e,t])=>`${JSON.stringify(e)}:${zt(t)}`).join(",")}}`}function Bt(e){return`${e.name}:${zt(e.arguments||{})}`}function Jt(e){return"local"===e.source&&de(e.name)?"action":"observation"}class Yt{options;emitter=new ze;toolRegistry=new At;session=null;provider=null;abortController=null;sessionStore=null;localToolContext={lastObserveRawDump:null,lastObserveNodes:[]};constructor(e={}){this.options=e;if("sqlite"!==(e.persistence?.mode||"sqlite"))return;const t=C();try{this.sessionStore=new E(t)}catch(e){const s=He(e,"INVALID_CONFIG");throw new Be("INVALID_CONFIG",`session store initialization failed: ${s.message}`,{sqlitePath:t,cause:s.message})}}on(e,t){return this.emitter.on(e,t)}off(e,t){this.emitter.off(e,t)}async start(t){this.session?.running&&this.stop(this.session.sessionId);try{this.provider=i(t.provider)}catch(e){const s=He(e,"INVALID_CONFIG");throw new Be("INVALID_CONFIG",`invalid model provider configuration: ${s.message}`,{provider:{vendor:t.provider.vendor,baseUrl:t.provider.baseUrl,model:t.provider.model},cause:s.message})}try{await this.toolRegistry.prepare(this.options.mcpServers||{})}catch(e){const t=He(e,"MCP_CONNECT_FAILED");throw new Be("MCP_CONNECT_FAILED",`failed to connect MCP tools: ${t.message}`,{servers:Object.keys(this.options.mcpServers||{}),cause:t.message,...t.details})}const s=function(e){if(!e)return u;const t=u.scriptGeneration.waitRandomization||{},s=Xt(e.scriptGeneration?.waitRandomization?.minMs??t.minMs??400,t.minMs??400),n=Math.max(s,Xt(e.scriptGeneration?.waitRandomization?.maxMs??t.maxMs??3e4,t.maxMs??3e4));return{retries:{observation:Xt(e.retries?.observation??u.retries.observation,u.retries.observation),action:Xt(e.retries?.action??u.retries.action,u.retries.action)},timeouts:{defaultMs:Xt(e.timeouts?.defaultMs??u.timeouts.defaultMs,u.timeouts.defaultMs),observeScreenMs:Xt(e.timeouts?.observeScreenMs??u.timeouts.observeScreenMs,u.timeouts.observeScreenMs),startAppMs:Xt(e.timeouts?.startAppMs??u.timeouts.startAppMs,u.timeouts.startAppMs)},limits:{maxIterations:Xt(e.limits?.maxIterations??u.limits.maxIterations,u.limits.maxIterations),maxConsecutiveFailures:Xt(e.limits?.maxConsecutiveFailures??u.limits.maxConsecutiveFailures,u.limits.maxConsecutiveFailures),maxSameToolFingerprint:Xt(e.limits?.maxSameToolFingerprint??u.limits.maxSameToolFingerprint,u.limits.maxSameToolFingerprint)},planner:{maxAttempts:Xt(e.planner?.maxAttempts??u.planner.maxAttempts,u.planner.maxAttempts)},scriptGeneration:{maxAttempts:Xt(e.scriptGeneration?.maxAttempts??u.scriptGeneration.maxAttempts,u.scriptGeneration.maxAttempts),waitRandomization:{enabled:e.scriptGeneration?.waitRandomization?.enabled??t.enabled??!0,minMs:s,maxMs:n,jitterRatio:(o=e.scriptGeneration?.waitRandomization?.jitterRatio??t.jitterRatio,r=t.jitterRatio??.2,"number"==typeof o&&Number.isFinite(o)?o<0?0:o>1?1:o:r),roundingMs:Xt(e.scriptGeneration?.waitRandomization?.roundingMs??t.roundingMs??100,t.roundingMs??100)}}};var o,r}(t.reliability),n=t.sessionId||e();this.localToolContext={lastObserveRawDump:null,lastObserveNodes:[]},this.session=lt(function(e,t,s,n,o){return{sessionId:t,goal:e.goal,provider:e.provider,device:e.device,reliability:s,iteration:0,running:!1,paused:!1,messages:ct(e.goal,n,o),executionLog:[],diagnostics:[],runtimeContext:{lastObserveRawDump:null,lastObserveNodes:[],pendingVerifyAction:null,softPendingVerify:null,lastSearchContextTimestamp:null,consecutiveFailures:0,sameToolFingerprintCount:0,lastToolFingerprint:null,planningRetries:0,toolRetries:0,scriptGenerationRetries:0,convergenceTriggers:0},plan:n,currentPhase:"runtime",planningContext:{messages:[]}}}(t,n,s)),this.persistSession();if(!1!==t.enablePlanning){this.session.currentPhase="planning",this.persistSession();if("paused"===await this.runPlanningStage(this.session))return{sessionId:n}}else this.session.currentPhase="runtime";return this.persistSession(),await this.runLoop(),{sessionId:n}}async resume(e){if(!this.session||this.session.sessionId!==e.sessionId)throw new Be("SESSION_NOT_FOUND","session not found or expired");if(lt(this.session),!this.session.paused)throw new Be("SESSION_NOT_PAUSED","agent is not paused");if("planning"===this.session.currentPhase){this.session.planningContext?.messages.push({role:"user",content:e.message,timestamp:Date.now()}),this.session.paused=!1,this.persistSession();if("paused"===await this.runPlanningStage(this.session))return;return void await this.runLoop()}var t,s;t=this.session,s=e.message,t.messages.push({role:"user",content:s,timestamp:Date.now()}),this.persistSession(),await this.runLoop()}stop(e){this.session&&this.session.sessionId===e&&(this.session.running=!1,this.session.paused=!1,this.abortController&&(this.abortController.abort(),this.abortController=null),this.persistSession())}async generateScript(e){const t=this.loadSessionIfNeeded(e);if(lt(t),t.running)throw new Be("AGENT_RUNNING","agent is still running");if(0===t.executionLog.length)throw new Be("EMPTY_EXECUTION_LOG","no execution log available for script generation; run actions first");if(t.runtimeContext.pendingVerifyAction){const s=t.runtimeContext.pendingVerifyAction,n=[...t.diagnostics].reverse().find(e=>e.code===Rt);throw new Be("SCRIPT_GENERATION_FAILED",`script generation blocked: action #${s.index} ${s.toolName} has not been verified. Resume with observe_screen(wait_ms) -> verify_ui_state before generating script.`,{sessionId:e,pendingVerify:s,...n?{lastDiagnostic:n}:{}})}let s;t.currentPhase="script_generation",this.emitter.emit("scriptgenerating",{sessionId:t.sessionId});try{const n=await _(t.goal,t.executionLog,t.provider,t.reliability);if(s=n.workflow,n.attempts>1){t.runtimeContext.scriptGenerationRetries+=n.attempts-1;const s=it(t,{sessionId:e,phase:"script_generation",code:jt,message:`script generation retried ${n.attempts} attempts`,details:{errors:n.errors}});this.emitter.emit("diagnostic",s)}}catch(s){const n=He(s,"SCRIPT_GENERATION_FAILED");throw new Be("SCRIPT_GENERATION_FAILED",`script generation failed: ${n.message}`,{sessionId:e,executionLogSize:t.executionLog.length,cause:n.message,...n.details})}t.running=!1,t.paused=!0,this.persistSession();const n={sessionId:t.sessionId,workflow:s,executionLog:[...t.executionLog],totalIterations:t.iteration,diagnostics:at(t)};return this.emitter.emit("complete",n),n}listSessions(e={}){return this.sessionStore?this.sessionStore.listSessions(e):[]}countSessions(e={}){return this.sessionStore?this.sessionStore.countSessions(e):0}getSessionSummary(e){return this.sessionStore?this.sessionStore.getSessionSummary(e):null}deleteSession(e){return!!this.sessionStore&&(this.session?.sessionId===e&&(this.stop(e),this.session=null,this.localToolContext={lastObserveRawDump:null,lastObserveNodes:[]}),this.sessionStore.deleteSession(e))}async destroy(){this.session&&this.stop(this.session.sessionId),await this.toolRegistry.destroy(),this.localToolContext={lastObserveRawDump:null,lastObserveNodes:[]},this.sessionStore&&(this.sessionStore.close(),this.sessionStore=null),this.emitter.clear()}async runPlanningStage(e){this.abortController=new AbortController;const t=this.abortController.signal;this.emitter.emit("planning",{sessionId:e.sessionId,status:"started"});let s=0,n=[];try{const o=await ot(e.goal,e.provider,e.device,e.reliability,{signal:t,planningMessages:e.planningContext?.messages||[]});return s=o.attempts,n=o.errors,"paused"===o.status?(e.currentPhase="planning",e.planningContext?.messages.push({role:"assistant",content:o.text,timestamp:Date.now()}),this.applyPlanningDiagnostics(e,s,n),this.emitter.emit("planning",{sessionId:e.sessionId,status:"paused",text:o.text}),this.markSessionPaused(e,o.text,"planning"),"paused"):(ut(e,o.plan,o.screenDump),o.screenRawDump&&xe(this.localToolContext,o.screenRawDump),this.applyPlanningDiagnostics(e,s,n),this.emitter.emit("planning",{sessionId:e.sessionId,status:"completed",plan:o.plan}),"completed")}catch(t){if("AbortError"===t.name)throw t;const o=He(t,"RUNTIME_LOOP_FAILED");return n.push(o.message),this.emitter.emit("error",{sessionId:e.sessionId,error:`task planning failed and was skipped: ${o.message}`,code:"RUNTIME_LOOP_FAILED",details:{phase:"planning",cause:o.message}}),ut(e),this.applyPlanningDiagnostics(e,s,n),"completed"}finally{this.abortController=null}}applyPlanningDiagnostics(e,t,s){if(t>1&&(e.runtimeContext.planningRetries+=t-1),0===s.length)return;const n=s[s.length-1],o=it(e,{sessionId:e.sessionId,phase:"planning",code:t>1?Ot:kt,message:`planning stage used retries or fallback: ${n}`,details:{attempts:t||1,errors:s}});this.emitter.emit("diagnostic",o)}async runLoop(){const e=this.session;if(!e)return;if(!this.provider)throw new Be("INVALID_CONFIG","model provider has not been initialized");lt(e),e.running=!0,e.paused=!1,e.currentPhase="runtime",this.abortController=new AbortController;const t=this.abortController.signal;try{for(;e.running;){const s=e.reliability.limits.maxIterations;if(s>0&&e.iteration>=s){e.runtimeContext.convergenceTriggers+=1;const t=it(e,{sessionId:e.sessionId,phase:"policy",code:Lt,message:`iteration limit reached: ${s}`,iteration:e.iteration});return this.emitter.emit("diagnostic",t),void this.markSessionPaused(e,"execution paused after hitting iteration limit; review goal or adjust strategy","policy")}e.iteration++;const n=Tt(e.messages),o=await this.provider.chatWithTools(n,this.toolRegistry.getModelTools(),t),r=o.content||"";r&&this.emitter.emit("thinking",{sessionId:e.sessionId,text:r,iteration:e.iteration});const i=this.toolRegistry.annotateToolCalls(o.toolCalls||[]);if(0===i.length)return r.trim().length>0&&dt(e,r),void this.markSessionPaused(e,r,"runtime");dt(e,r,i);for(const s of i){if(!e.running)break;this.emitter.emit("toolcall",{sessionId:e.sessionId,toolCall:s,iteration:e.iteration});const n=this.getVerifyGateError(e,s);if(n){const t={toolCallId:s.id,name:s.name,success:!1,error:n,errorCode:"RUNTIME_GUARD",retryable:!1,source:s.source,server:s.server},o=it(e,{sessionId:e.sessionId,phase:"runtime",code:Rt,message:n,iteration:e.iteration,details:{pendingVerify:e.runtimeContext.pendingVerifyAction,blockedTool:s.name}});this.emitter.emit("diagnostic",o),this.emitter.emit("toolresult",{sessionId:e.sessionId,result:t,iteration:e.iteration}),mt(e,s.id,Ke(t)),this.persistSession();continue}const o=Jt(s),r=Wt(e.reliability,o),i=Kt(e.reliability,s.name),a=await Vt({toolCall:s,device:e.device,maxAttempts:r,timeoutMs:i,signal:t,execute:(e,t,s)=>this.toolRegistry.execute(e,t,s,this.localToolContext),onRetry:(t,n)=>{e.runtimeContext.toolRetries+=1;const o=it(e,{sessionId:e.sessionId,phase:"runtime",code:Dt,message:`tool retry: ${s.name}, attempt ${t+1}`,iteration:e.iteration,details:{attempt:t,maxAttempts:r,errorCode:n.errorCode,error:n.error}});this.emitter.emit("diagnostic",o)}});a.source||(a.source=s.source,a.server=s.server),this.emitter.emit("toolresult",{sessionId:e.sessionId,result:a,iteration:e.iteration});if(!a.success&&"VALIDATION"===a.errorCode||vt(e,s,a,this.toolRegistry.getCategory(s,a)),mt(e,s.id,Ke(a)),this.updateRuntimeStateAfterTool(e,s,a,o),this.localToolContext.softPendingVerify=e.runtimeContext.softPendingVerify,this.localToolContext.lastSearchContextTimestamp=e.runtimeContext.lastSearchContextTimestamp,this.persistSession(),!e.running)break}}}catch(t){if("AbortError"===t.name)return e.running=!1,e.paused=!1,void this.persistSession();e.running=!1,e.paused=!0;const s=He(t,"RUNTIME_LOOP_FAILED");this.emitter.emit("error",{sessionId:e.sessionId,error:s.message,code:s.code,details:s.details}),this.persistSession({lastError:s.message,lastErrorCode:s.code})}finally{this.abortController=null}}updateRuntimeStateAfterTool(e,t,s,n){const o=Bt(t);if(e.runtimeContext.lastToolFingerprint===o?e.runtimeContext.sameToolFingerprintCount+=1:(e.runtimeContext.lastToolFingerprint=o,e.runtimeContext.sameToolFingerprintCount=1),s.success)e.runtimeContext.consecutiveFailures=0;else{"VALIDATION"===s.errorCode||"RUNTIME_GUARD"===s.errorCode||(e.runtimeContext.consecutiveFailures+=1)}if(f.has(t.name)&&s.success)e.runtimeContext.pendingVerifyAction=null,e.runtimeContext.softPendingVerify=null;else if("action"===n&&s.success&&g.has(t.name)){const s=e.executionLog.length;e.runtimeContext.pendingVerifyAction={index:s,toolName:t.name}}if(h.has(t.name)&&s.success&&(e.runtimeContext.softPendingVerify={index:e.executionLog.length,toolName:t.name}),"record_search_context"===t.name&&s.success&&(e.runtimeContext.lastSearchContextTimestamp=Date.now()),e.runtimeContext.consecutiveFailures>=e.reliability.limits.maxConsecutiveFailures){e.runtimeContext.convergenceTriggers+=1;const t=it(e,{sessionId:e.sessionId,phase:"policy",code:$t,message:`consecutive failure limit reached: ${e.reliability.limits.maxConsecutiveFailures}`,iteration:e.iteration,details:{consecutiveFailures:e.runtimeContext.consecutiveFailures}});return this.emitter.emit("diagnostic",t),void this.markSessionPaused(e,"execution paused due to excessive consecutive failures","policy")}if(e.runtimeContext.sameToolFingerprintCount>=e.reliability.limits.maxSameToolFingerprint){e.runtimeContext.convergenceTriggers+=1;const t=it(e,{sessionId:e.sessionId,phase:"policy",code:Mt,message:`same tool fingerprint repeat limit reached: ${e.reliability.limits.maxSameToolFingerprint}`,iteration:e.iteration,details:{fingerprint:o,repeatCount:e.runtimeContext.sameToolFingerprintCount}});this.emitter.emit("diagnostic",t),this.markSessionPaused(e,"execution paused due to repeated non-converging tool calls","policy")}}getVerifyGateError(e,t){return function(e,t){return"local"!==t.source?null:e&&de(t.name)?p.has(t.name)?null:`BLOCKED: #${e.index} ${e.toolName} 尚未验证。你必须先调用 observe_screen(wait_ms) 再调用 verify_ui_state,然后才能执行下一个动作。`:null}(e.runtimeContext.pendingVerifyAction,t)}markSessionPaused(e,t,s){e.running=!1,e.paused=!0,this.emitter.emit("paused",{sessionId:e.sessionId,text:t,iteration:e.iteration,phase:s}),this.persistSession()}persistSession(e){this.session&&this.sessionStore&&this.sessionStore.saveSession(this.session,e)}loadSessionIfNeeded(e){if(this.session&&this.session.sessionId===e)return lt(this.session);if(!this.sessionStore)throw new Be("SESSION_NOT_FOUND","session not found or expired");const t=this.sessionStore.loadSession(e);if(!t)throw new Be("SESSION_NOT_FOUND","session not found or expired");return this.session=lt(t),this.localToolContext={lastObserveRawDump:null,lastObserveNodes:[]},this.session}}function Ht(e={}){return new Yt(e)}async function Qt(e,t){const s=i(t),n=[{role:"system",content:b(e)},{role:"user",content:e}];return(await s.chatWithTools(n,[])).content.trim()}export{Be as AgentRuntimeError,Yt as AutomationAgentRuntime,u as DEFAULT_RELIABILITY_CONFIG,Ht as createAutomationAgentRuntime,Qt as optimizeIntent,He as toRuntimeError};
1
+ import{v4 as e}from"uuid";import{S as t,b as s,P as n,A as o,c as r,d as i,e as a,f as c,h as l,D as u,T as d,V as p,a as _,i as f,j as g,k as h,l as b}from"../chunks/scriptGenerator-Co2WLzwU.js";export{m as DEFAULT_PROVIDER_CONFIG}from"../chunks/scriptGenerator-Co2WLzwU.js";import y from"fs";import x from"os";import v from"path";import w from"better-sqlite3";import{z as I}from"zod";import{zodToJsonSchema as N}from"zod-to-json-schema";import"@langchain/core/prompts";import"@langchain/core/output_parsers";import"@langchain/core/runnables";import"@langchain/anthropic";import"@langchain/core/messages";import"@langchain/core/tools";import"@langchain/google-genai";import"@langchain/openai";import"crypto";function T(){return v.join(function(){const e=x.homedir();if(!e)throw new Error("Failed to resolve user home directory for session persistence");return v.join(e,".vmosedge")}(),"agent_sessions.db")}function C(e){return{sessionId:e.session_id,goal:e.goal,status:(t=e.status,"running"===t||"paused"===t||"stopped"===t?t:"stopped"),providerVendor:e.provider_vendor||"unknown",deviceId:e.device_id||"",totalIterations:e.total_iterations||0,lastError:e.last_error||void 0,lastErrorCode:e.last_error_code||void 0,createTime:e.create_time,updateTime:e.update_time};var t}class S{db;constructor(e){!function(e){const t=v.dirname(e);y.existsSync(t)||y.mkdirSync(t,{recursive:!0})}(e),this.db=new w(e),this.initPragmas(),this.initSchema()}initPragmas(){this.db.pragma("busy_timeout = 5000"),this.db.pragma("foreign_keys = ON"),this.db.pragma("synchronous = NORMAL");try{this.db.pragma("journal_mode = WAL")}catch{this.db.pragma("journal_mode = DELETE")}}initSchema(){this.db.exec("\n CREATE TABLE IF NOT EXISTS agent_meta (\n meta_key TEXT PRIMARY KEY,\n meta_value TEXT NOT NULL,\n update_time INTEGER NOT NULL\n );\n CREATE TABLE IF NOT EXISTS agent_sessions (\n session_id TEXT PRIMARY KEY,\n goal TEXT NOT NULL,\n status TEXT NOT NULL,\n provider_vendor TEXT NOT NULL DEFAULT '',\n device_id TEXT NOT NULL DEFAULT '',\n total_iterations INTEGER NOT NULL DEFAULT 0,\n last_error TEXT,\n last_error_code TEXT,\n state_json TEXT NOT NULL,\n create_time INTEGER NOT NULL,\n update_time INTEGER NOT NULL\n );\n "),this.ensureSessionColumns(),this.db.exec("\n CREATE INDEX IF NOT EXISTS idx_agent_sessions_update_time\n ON agent_sessions(update_time DESC);\n CREATE INDEX IF NOT EXISTS idx_agent_sessions_status_update_time\n ON agent_sessions(status, update_time DESC);\n CREATE INDEX IF NOT EXISTS idx_agent_sessions_device_update_time\n ON agent_sessions(device_id, update_time DESC);\n "),this.saveSchemaVersion("3")}ensureSessionColumns(){const e=this.db.prepare("PRAGMA table_info(agent_sessions)").all(),t=new Set(e.map(e=>e.name)),s=[{name:"provider_vendor",definition:"TEXT NOT NULL DEFAULT ''"},{name:"device_id",definition:"TEXT NOT NULL DEFAULT ''"},{name:"total_iterations",definition:"INTEGER NOT NULL DEFAULT 0"},{name:"last_error",definition:"TEXT"},{name:"last_error_code",definition:"TEXT"}];for(const e of s)t.has(e.name)||this.db.exec(`ALTER TABLE agent_sessions ADD COLUMN ${e.name} ${e.definition}`)}saveSchemaVersion(e){this.db.prepare("\n INSERT INTO agent_meta (meta_key, meta_value, update_time)\n VALUES (@meta_key, @meta_value, @update_time)\n ON CONFLICT(meta_key) DO UPDATE SET\n meta_value=excluded.meta_value,\n update_time=excluded.update_time\n ").run({meta_key:"schema_version",meta_value:e,update_time:Date.now()})}buildQueryFilter(e){const t=[],s={};e.status&&(t.push("status = @status"),s.status=e.status),e.deviceId&&(t.push("device_id = @device_id"),s.device_id=e.deviceId);const n=e.keyword?.trim();return n&&(t.push("goal LIKE @keyword"),s.keyword=`%${n}%`),{whereSql:t.length>0?`WHERE ${t.join(" AND ")}`:"",params:s}}saveSession(e,t){const s=Date.now(),n=e.running?"running":e.paused?"paused":"stopped",o=JSON.stringify(e);this.db.prepare("\n INSERT INTO agent_sessions (\n session_id, goal, status, provider_vendor, device_id,\n total_iterations,\n last_error, last_error_code, state_json, create_time, update_time\n )\n VALUES (\n @session_id, @goal, @status, @provider_vendor, @device_id,\n @total_iterations,\n @last_error, @last_error_code, @state_json, @create_time, @update_time\n )\n ON CONFLICT(session_id) DO UPDATE SET\n goal=excluded.goal,\n status=excluded.status,\n provider_vendor=excluded.provider_vendor,\n device_id=excluded.device_id,\n total_iterations=excluded.total_iterations,\n last_error=excluded.last_error,\n last_error_code=excluded.last_error_code,\n state_json=excluded.state_json,\n update_time=excluded.update_time\n ").run({session_id:e.sessionId,goal:e.goal,status:n,provider_vendor:e.provider.vendor,device_id:e.device.deviceId,total_iterations:e.iteration,last_error:t?.lastError||null,last_error_code:t?.lastErrorCode||null,state_json:o,create_time:s,update_time:s})}loadSession(e){const t=this.db.prepare("SELECT * FROM agent_sessions WHERE session_id = ? LIMIT 1").get(e);if(!t)return null;try{return JSON.parse(t.state_json)}catch{return null}}getSessionSummary(e){const t=this.db.prepare("\n SELECT\n session_id, goal, status, provider_vendor, device_id, total_iterations,\n last_error, last_error_code, create_time, update_time, state_json\n FROM agent_sessions\n WHERE session_id = ? LIMIT 1\n ").get(e);return t?C(t):null}listSessions(e={}){const{whereSql:t,params:s}=this.buildQueryFilter(e),n=function(e){return"number"!=typeof e||Number.isNaN(e)?50:Math.min(500,Math.max(1,Math.floor(e)))}(e.limit),o=function(e){return"number"!=typeof e||Number.isNaN(e)?0:Math.max(0,Math.floor(e))}(e.offset);return this.db.prepare(`\n SELECT\n session_id, goal, status, provider_vendor, device_id, total_iterations,\n last_error, last_error_code, create_time, update_time, state_json\n FROM agent_sessions\n ${t}\n ORDER BY update_time DESC\n LIMIT @limit OFFSET @offset\n `).all({...s,limit:n,offset:o}).map(C)}countSessions(e={}){const{whereSql:t,params:s}=this.buildQueryFilter(e),n=this.db.prepare(`\n SELECT COUNT(1) as total\n FROM agent_sessions\n ${t}\n `).get(s);return n?.total||0}deleteSession(e){return this.db.prepare("DELETE FROM agent_sessions WHERE session_id = ?").run(e).changes>0}close(){this.db.close()}}function E(e,t){const s=t.normalize("NFKC");return"text"===e||"contentDesc"===e?s.replace(/\s+/gu," ").trim():"bounds"===e?s.replace(/\s+/gu,""):s.trim()}function A(e,t,s){return E(e,t)===E(e,s)}function O(e,t){return(void 0===t.index||e.index===t.index)&&(!!(!t.resourceId||e.resourceId&&A("resourceId",t.resourceId,e.resourceId))&&(!!(!t.text||e.text&&A("text",t.text,e.text))&&(!!(!t.contentDesc||e.contentDesc&&A("contentDesc",t.contentDesc,e.contentDesc))&&(!!(!t.className||e.className&&A("className",t.className,e.className))&&!!(!t.bounds||e.bounds&&A("bounds",t.bounds,e.bounds))))))}function k(e,t){let s=0;for(const n of e)O(n,t)&&(s+=1);return s}function D(e,t,s){return void 0===t||void 0===s?t===s:A(e,t,s)}function R(e,t){return e===t||e.index===t.index&&D("resourceId",e.resourceId,t.resourceId)&&D("text",e.text,t.text)&&D("contentDesc",e.contentDesc,t.contentDesc)&&D("className",e.className,t.className)&&D("bounds",e.bounds,t.bounds)}function L(e,t,s){let n=0;for(const o of e)if(O(o,t)){if(R(o,s))return n;n+=1}}const $=e=>I.string().describe(e),M=e=>I.coerce.number().describe(e),j=e=>I.coerce.number().int().positive().describe(e),F=e=>{const t=I.coerce.number().int().positive().optional();return e?t.describe(e):t},P=e=>{const t=I.coerce.number().int().min(0).optional();return e?t.describe(e):t},U=e=>{const t=I.string().optional();return e?t.describe(e):t},q=(e,t)=>I.enum(e).optional().describe(t);function G(e){const t=N(e,{$refStrategy:"none"}),{$schema:s,definitions:n,...o}=t;return o}function V(e){return{name:e.name,description:e.description,category:e.category,schema:e.schema,parameters:G(e.schema)}}const X=I.object({resource_id:U("元素资源ID,如 com.example:id/btn"),text:U("元素文本,支持正则"),content_desc:U("元素内容描述,支持正则"),class_name:U("元素类名"),bounds:U("元素 bounds,格式 [x1,y1][x2,y2],仅用于兜底消歧,不建议直接进入脚本")}).strict(),W=q(["home","list","detail","dialog","search","form","unknown"],"可选:显式声明当前页面类型。仅在你有把握时填写;不确定则省略。"),K=q(["navigation","search_entry","action_button","input_field","content_item","generic_target"],"可选:显式声明目标元素角色。仅在你有把握时填写;不确定则省略。"),z=q(["critical","supporting","noise"],"可选:显式声明本轮动作对主流程的重要性。仅在你有把握时填写;不确定则省略。"),B=q(["main_flow","exception_handler_candidate","log_only"],"可选:显式声明该记录应进入主流程、异常处理还是仅日志。仅在你有把握时填写;不确定则省略。"),J=q(["next_action","next_verified_transition","manual_close"],"可选:声明该上下文持续到何时结束。默认 next_verified_transition。"),Y=I.object({step_intent:$("本次操作在任务流程中的目的和预期效果。必须说明为什么执行此操作、期望达到什么状态,而非重复元素属性描述。"),merge_key:U("可选:稳定的步骤合并键。用于把同类动作合并为同一脚本步骤,例如 open_search_page、scroll_result_list。仅在你能明确抽象出可复用步骤语义时填写。"),expected_result:U("可选:本次动作完成后预期达到的状态描述。建议使用比 step_intent 更直接的结果表述,例如“进入搜索页”“列表向下推进一屏”。"),wait_ms_hint:F("可选:建议回放时在主动作后等待或验证的毫秒数。仅在你明确知道该步骤通常需要额外等待时填写。"),page_kind:W,target_role:K,criticality_hint:z,include_mode_hint:B,anchor:I.object({desc:$("页面锚点元素描述,确认当前所在页面"),resource_id:U(),text:U(),content_desc:U(),class_name:U(),bounds:U()}).describe("页面锚点元素,必填"),target:I.object({desc:$("操作目标元素描述"),resource_id:U(),text:U(),content_desc:U(),class_name:U(),bounds:U()}).describe("本次操作的目标元素,必填"),post_verify:I.object({desc:$("操作成功后验证元素描述"),resource_id:U(),text:U(),content_desc:U(),class_name:U(),bounds:U()}).optional().describe("Phase 1 内部提示字段。告知 LLM 本次操作的最终预期状态,用于决定是否需要执行 observe_screen(wait_ms) + verify_ui_state。此字段不参与脚本生成;脚本的验证条件来自 verify_ui_state 的 verify_element。")}).describe("操作前必填的 UI 上下文,anchor+target+step_intent 强制要求;page_kind/target_role/criticality_hint/include_mode_hint 在明确时建议显式填写,用于录制质量约束和回放。"),H=I.coerce.number().int().min(0).optional().describe("匹配结果动作索引(从 0 开始,仅 accessibility/node 使用)"),Q=I.object({wait_timeout:j("等待元素出现超时(ms)").optional(),wait_interval:j("重试间隔(ms)").optional()}),Z=V({name:"observe_screen",description:"获取当前屏幕的 UI 无障碍节点树。返回简化后的 UI 结构,包含每个可交互元素的 text、resource-id、content-desc、class、bounds 等属性。可选传 wait_ms:先等待指定毫秒再拉取,用于动作后「等待加载 + 获取新页面」一步完成。",schema:I.object({wait_ms:j("可选:先等待的毫秒数,再拉取 UI").optional()}),category:"observation"}),ee=V({name:"get_installed_apps",description:"获取设备上已安装的应用列表,返回每个应用的包名(package_name)和应用名(app_name)。用于查找目标应用的包名以便启动。",schema:I.object({type:I.enum(["user","system","all"]).optional().describe("应用类型过滤:user(默认) / system / all")}),category:"observation"}),te=V({name:"get_top_activity",description:"获取当前前台应用的 Activity 信息,返回 package_name 和 class_name。用于判断当前在哪个应用的哪个页面。",schema:I.object({}),category:"observation"}),se=V({name:"get_screen_info",description:"获取屏幕显示信息,返回 width、height、rotation、orientation。用于确定屏幕分辨率以计算滑动/点击坐标。",schema:I.object({}),category:"observation"}),ne=I.object({desc:$("元素简短描述"),resource_id:U("元素 resource-id,来自 observe_screen"),text:U("元素文本,来自 observe_screen"),content_desc:U("元素 content-desc"),class_name:U("元素类名,来自 observe_screen"),index:P("节点 index(来自 observe_screen 的 [index])")}),oe=V({name:"verify_ui_state",description:"动作执行后,记录用于验证操作成功的标志性 UI 元素。必须先调用 observe_screen(可带 wait_ms)获取新界面,再调用本工具;verify_element 的 text/resource_id 必须来自当前界面真实节点,禁止编造。建议同时记录节点 index(observe_screen 的 [index])提高回放稳定性。当操作导致页面切换时,page_anchor 必须填写新页面的标志性元素。screen_desc 必填,用一句话描述当前页面;若你明确知道当前页面类型,可额外填写 page_kind;若该步骤回放通常需要额外等待,可填写 wait_ms_hint。",schema:I.object({step_desc:$("验证步骤描述"),page_kind:W,wait_ms_hint:F("可选:建议回放在验证前等待的毫秒数。仅在你明确知道该步骤通常需要额外等待时填写。"),verify_element:ne.describe("用于验证的 UI 元素,必须来自当前 observe_screen 结果"),screen_desc:$('当前页面的简要描述,用一句话概括所在页面(如"抖音首页信息流"、"微信聊天列表"、"系统设置-通用页面")。此字段帮助脚本生成 AI 理解操作上下文,必填。'),page_anchor:ne.optional().describe("动作后的页面锚点元素(如页面切换后的新页面标题/导航栏)。当操作导致页面切换时必填,用于脚本生成时确认导航成功。")}),category:"observation"}),re=I.object({desc:$("元素描述"),resource_id:U(),text:U(),content_desc:U(),class_name:U(),bounds:U(),index:P("节点 index(来自 observe_screen 的 [index])")}),ie=V({name:"record_search_context",description:"在 swipe 或 press_key 前调用,记录本轮查找目标(target_desc + target_element)和当前页面锚点(anchor_element)。元素属性必须来自最近一次 observe_screen 的真实结果;step_intent、target_desc、anchor_element 必填。建议同时记录节点 index(observe_screen 的 [index])。",schema:I.object({step_intent:$("本次滑动/按键在任务流程中的目的和预期效果,说明为什么执行此操作、期望达到什么状态。"),merge_key:U("可选:稳定的步骤合并键。用于把同类滑动/按键动作合并为同一脚本步骤,例如 scroll_result_list、submit_search。"),expected_result:U("可选:本次动作完成后预期达到的状态描述,例如“结果列表继续向下推进”“进入详情页”。"),wait_ms_hint:F("可选:建议回放时在本轮动作后等待或验证的毫秒数。仅在你明确知道该步骤通常需要额外等待时填写。"),target_desc:$("本轮查找目标描述"),target_element:re.optional().describe("查找目标元素,可选"),anchor_element:re.describe("当前页面锚点元素,必填"),page_kind:W,target_role:K,criticality_hint:z,include_mode_hint:B,valid_until:J}),category:"observation"});var ae;const ce=[V({name:"start_app",description:"启动指定应用。需要 package_name。工具会在启动前自动执行 permission/set(grant_all=true)。",schema:I.object({package_name:$("应用包名")}),category:"action"}),V({name:"stop_app",description:"停止(强制关闭)指定应用。",schema:I.object({package_name:$("应用包名")}),category:"action"}),V({name:"tap",description:"在屏幕指定坐标点击。属于兜底方案:仅当按 resource_id -> text -> content_desc -> 最小组合 仍无法唯一定位元素时才允许使用。",schema:I.object({x:M("X 坐标"),y:M("Y 坐标")}),category:"action"}),V({name:"tap_element",description:"通过选择器查找并点击 UI 元素。这是推荐点击方式。\n\n调用规则:\n1. pre_context.anchor(页面锚点)和 pre_context.target(目标元素)必填,不可省略\n2. selector 遵循脚本优先策略:优先稳定 resource_id,其次稳定 text,再次稳定 content_desc,最后最小组合;bounds 仅作兜底消歧\n3. 序号点击使用 action_index(从 0 开始),严禁 selector.index\n4. 若点击会导致界面变化,最终验证必须:observe_screen(wait_ms) -> verify_ui_state",schema:I.object({pre_context:Y,selector:X.describe("元素选择器"),action_index:H,...Q.shape}).strict(),category:"action"}),V({name:"long_press_element",description:"通过选择器查找并长按 UI 元素。\n\n调用规则:\n1. pre_context.anchor 和 pre_context.target 必填,不可省略\n2. selector 遵循脚本优先策略:优先稳定 resource_id,其次稳定 text,再次稳定 content_desc,最后最小组合;bounds 仅作兜底消歧\n3. 序号操作使用 action_index(从 0 开始),严禁 selector.index\n4. 长按通常触发界面变化,最终验证必须:observe_screen(wait_ms) -> verify_ui_state",schema:I.object({pre_context:Y,selector:X.describe("元素选择器"),action_index:H,...Q.shape}).strict(),category:"action"}),V({name:"set_text",description:"通过选择器定位输入框并设置文本内容。\n\n调用规则:\n1. pre_context.anchor 和 pre_context.target 必填,不可省略\n2. 多个输入框时使用 action_index(从 0 开始),严禁 selector.index\n3. 若需要提交输入内容,紧接调用 press_key(66) 或等价提交动作\n4. 若后续操作导致界面变化,最终验证必须:observe_screen(wait_ms) -> verify_ui_state",schema:I.object({pre_context:Y,selector:X.describe("输入框选择器"),action_index:H,text:$("要输入的文本"),...Q.shape}).strict(),category:"action"}),V({name:"input_text",description:"向当前聚焦输入框直接输入文本,不需要 selector。",schema:I.object({text:$("要输入的文本")}),category:"action"}),V({name:"swipe",description:"执行坐标滑动手势,常用于滚动列表加载更多内容。\n\n调用规则:\n1. swipe 前先调用 record_search_context 记录查找目标与锚点\n2. swipe 后调用 observe_screen(wait_ms=500~1200),再根据结果决定是否继续滑动或调用 verify_ui_state\n3. 坐标依赖屏幕分辨率,调用前应已通过 get_screen_info 确认分辨率",schema:I.object({start_x:M("起点 X"),start_y:M("起点 Y"),end_x:M("终点 X"),end_y:M("终点 Y"),duration:M("持续时间(ms)").optional()}),category:"action"}),V({name:"press_key",description:"按下物理/虚拟按键。常用键码:3=Home, 4=Back, 66=Enter, 82=菜单等。调用前可先 record_search_context;按键会改变界面时,后续需 observe_screen(wait_ms) -> verify_ui_state。",schema:I.object({key_code:(ae="Android KeyCode",I.coerce.number().int().describe(ae))}),category:"action"}),V({name:"wait",description:"暂停指定时间。用于等待页面加载或动画完成。时长需按前一动作自适应:轻交互 300~800ms,滑动后 500~1200ms,页面切换/返回 1200~2500ms,启动应用 2000~4000ms;禁止全程固定同一时长。",schema:I.object({duration:M("等待时间(ms)")}),category:"action"})],le=[...[Z,oe,ie,ee,te,se],...ce];function ue(e){return ce.some(t=>t.name===e)}const de=new Set(["btn","button","text","txt","img","image","icon","view","layout","container","content","header","footer","title","subtitle","label","input","edit","search","menu","nav","tab","bar","list","item","card","dialog","popup","modal","scroll","pager","recycler","grid","frame","root","main","action","toolbar","status","progress","loading","empty","error","divider","separator","wrapper","panel","avatar","profile","name","email","phone","password","submit","cancel","confirm","close","back","next","home","setting","notification","message","chat","comment","like","share","follow","post","feed","story","video","audio","play","pause","camera","photo","gallery","download","upload","save","delete","add","create","update","refresh","filter","sort","check","switch","toggle","radio","select","option","dropdown","picker","date","row","column","cell","section","group","block","region","area","top","bottom","left","right","center","start","end","inner","outer","overlay","badge","count","number","description","info","hint","placeholder","detail","summary","expand","collapse","more","viewpager","appbar","bottombar","snackbar","fab","chip","spinner","seekbar","slider","webview","mapview","banner","splash","widget"]);function me(e){if(!e)return!1;const t=e.includes("/")?e.split("/").pop():e;return!!t&&(!e.startsWith("android:")&&(!!/obfuscated/i.test(t)||(!!/^\d+_/.test(t)||!function(e){const t=e.toLowerCase(),s=t.split("_");for(const e of s)if(e.length>=3&&de.has(e))return!0;for(const e of de)if(e.length>=4&&t.includes(e))return!0;return!1}(t)&&(t.length<=5||t.length<=10&&function(e){if(0===e.length)return 0;const t=new Map;for(const s of e)t.set(s,(t.get(s)||0)+1);let s=0;for(const n of t.values()){const t=n/e.length;s-=t*Math.log2(t)}return s}(t)>3))))}function pe(e){return e.normalize("NFKC").replace(/\s+/gu," ").trim()}function _e(e,t,s,n){const o=[],r=function(e,t,s){if(!s)return 0;const n=new Set;for(const o of e){if(o.resourceId!==s)continue;const e="text"===t?o.text:o.contentDesc;e&&n.add(pe(e))}return n.size}(s,"text"===n?"text":"contentDesc",t.resourceId),i=function(e){const t=pe(e);return!!t&&(/\b\d+\b/u.test(t)||/\d+[::]\d+/u.test(t)||/\d+[./-]\d+/u.test(t)||/[$¥¥]\s*\d/u.test(t)||/\brow\b|\bcolumn\b|行|列/u.test(t)||/@\w+/u.test(t))}(e),a=function(e){const t=pe(e);return t.length>24||/合集|日记|vlog|攻略|教程|推荐|详情|播放|第.+集/u.test(t)}(e),c=e.length<=12,l=1===k(s,"text"===n?{text:e}:{contentDesc:e});r>=2&&o.push("same resource_id carries multiple values on screen"),i&&o.push("contains dynamic segment"),a&&o.push("looks like content item text"),c||o.push("text is long or verbose"),l||o.push("not unique on current screen");const u=0===o.length||c&&l&&o.length<=1,d=u&&!i&&!a;return u&&t.clickable&&c&&o.push("short interactive label"),{stable:u,reusable:d,risk:d?"low":u?"medium":"high",reasons:o}}function fe(e,t){const s=[];if(e.resourceId){const t=function(e){const t=[];me(e)?t.push("resource_id looks obfuscated"):t.push("resource_id contains semantic naming");const s=!me(e);return{stable:s,reusable:s,risk:s?"low":"high",reasons:t}}(e.resourceId);s.push({field:"resource_id",value:e.resourceId,stable:t.stable,reusable:t.reusable,risk:t.risk,reasons:t.reasons})}if(e.text){const n=_e(e.text,e,t,"text");s.push({field:"text",value:e.text,stable:n.stable,reusable:n.reusable,risk:n.risk,reasons:n.reasons})}if(e.contentDesc){const n=_e(e.contentDesc,e,t,"content_desc");s.push({field:"content_desc",value:e.contentDesc,stable:n.stable,reusable:n.reusable,risk:n.risk,reasons:n.reasons})}if(e.className){const t={stable:!0,reusable:!1,risk:"medium",reasons:["class_name is only a disambiguation field, not a primary selector"]};s.push({field:"class_name",value:e.className,stable:t.stable,reusable:t.reusable,risk:t.risk,reasons:t.reasons})}if(e.bounds){const t={stable:!1,reusable:!1,risk:"high",reasons:[`bounds=${e.bounds} is screen-dependent and should stay debug-only`]};s.push({field:"bounds",value:e.bounds,stable:t.stable,reusable:t.reusable,risk:t.risk,reasons:t.reasons})}return s}function ge(e){if(e.length<2)return!1;const t=e.map(e=>{const t=function(e){if(!e)return null;const t=e.match(/\[(\d+),(\d+)\]\[(\d+),(\d+)\]/);return t?{x1:Number(t[1]),y1:Number(t[2]),x2:Number(t[3]),y2:Number(t[4])}:null}(e.bounds);return t?{x:(t.x1+t.x2)/2,y:(t.y1+t.y2)/2}:null}).filter(e=>!!e);if(t.length!==e.length)return!1;const s=new Set(t.map(e=>Math.round(e.x/10))).size,n=new Set(t.map(e=>Math.round(e.y/10))).size;return s>1||n>1}function he(e,t,s,n){const o=k(t,{resourceId:s.resource_id,text:s.text,contentDesc:s.content_desc,className:s.class_name,bounds:s.bounds});if(0===o)return;if(n.requireUnique&&1!==o)return;if(!n.requireUnique&&o>1&&void 0===n.action_index)return;e.some(e=>JSON.stringify(e.selector)===JSON.stringify(s)&&e.action_index===n.action_index)||e.push({selector:s,action_index:n.action_index,stability:n.stability,reusable:n.reusable,matched_count:o,reason:n.reason,scope:n.scope,policy_decision:n.policy_decision,policy_reason:n.policy_reason})}function be(e,t,s={}){if(!e||0===t.length)return null;const n=s.mode||"action",o=function(e){return"action"===e?{allowCollectionScope:!0,allowActionIndex:!0,allowContentCombos:!0,allowedScopes:["global","page","collection"],policyReason:"action selectors may use collection disambiguation"}:{allowCollectionScope:!1,allowActionIndex:!1,allowContentCombos:!1,allowedScopes:["global","page"],policyReason:`${e} selectors must stay single-field and non-positional`}}(n),r=fe(e,t),i=new Map;for(const e of r)i.set(e.field,e);const a=[],c=e.resourceId,l=e.text,u=e.contentDesc,d=e.className,m=e.bounds;if(function(e,t,s,n,o){s.resourceId&&!0===n.get("resource_id")?.reusable&&he(e,t,{resource_id:s.resourceId},{stability:"high",reusable:!0,reason:"unique_resource_id",scope:"global",requireUnique:!0,policy_decision:"approved",policy_reason:o.policyReason}),s.text&&!0===n.get("text")?.reusable&&he(e,t,{text:s.text},{stability:"high",reusable:!0,reason:"unique_reliable_text",scope:"global",requireUnique:!0,policy_decision:"approved",policy_reason:o.policyReason}),s.contentDesc&&!0===n.get("content_desc")?.reusable&&he(e,t,{content_desc:s.contentDesc},{stability:"high",reusable:!0,reason:"unique_reliable_content_desc",scope:"global",requireUnique:!0,policy_decision:"approved",policy_reason:o.policyReason})}(a,t,{resourceId:c,text:l,contentDesc:u},i,o),function(e,t,s,n,o){o.allowContentCombos&&(s.resourceId&&!0===n.get("resource_id")?.reusable&&s.text&&n.get("text")?.stable&&he(e,t,{resource_id:s.resourceId,text:s.text},{stability:"medium",reusable:!0===n.get("text")?.reusable,reason:"resource_id_text_combo",scope:"page",requireUnique:!0,policy_decision:"approved",policy_reason:o.policyReason}),s.resourceId&&!0===n.get("resource_id")?.reusable&&s.contentDesc&&n.get("content_desc")?.stable&&he(e,t,{resource_id:s.resourceId,content_desc:s.contentDesc},{stability:"medium",reusable:!0===n.get("content_desc")?.reusable,reason:"resource_id_content_desc_combo",scope:"page",requireUnique:!0,policy_decision:"approved",policy_reason:o.policyReason}),s.text&&s.className&&n.get("text")?.stable&&he(e,t,{text:s.text,class_name:s.className},{stability:"medium",reusable:!0===n.get("text")?.reusable,reason:"text_class_combo",scope:"page",requireUnique:!0,policy_decision:"approved",policy_reason:o.policyReason}),s.contentDesc&&s.className&&n.get("content_desc")?.stable&&he(e,t,{content_desc:s.contentDesc,class_name:s.className},{stability:"medium",reusable:!0===n.get("content_desc")?.reusable,reason:"content_desc_class_combo",scope:"page",requireUnique:!0,policy_decision:"approved",policy_reason:o.policyReason}))}(a,t,{resourceId:c,text:l,contentDesc:u,className:d},i,o),function(e,t,s,n,o,r,i){if(r.allowCollectionScope&&r.allowActionIndex){if(n.resourceId){const a={resourceId:n.resourceId},c=s.filter(e=>O(e,a));c.length>1&&ge(c)&&he(e,s,{resource_id:n.resourceId},{action_index:i??L(s,a,t),stability:!0===o.get("resource_id")?.reusable?"medium":"low",reusable:!0===o.get("resource_id")?.reusable,reason:!0===o.get("resource_id")?.reusable?"resource_id_positional":"obfuscated_id_positional",scope:"collection",policy_decision:"approved",policy_reason:r.policyReason})}if(n.text){const a={text:n.text},c=s.filter(e=>O(e,a));c.length>1&&ge(c)&&he(e,s,{text:n.text},{action_index:i??L(s,a,t),stability:"low",reusable:!0===o.get("text")?.reusable,reason:"text_positional",scope:"collection",policy_decision:"approved",policy_reason:r.policyReason})}if(n.contentDesc){const a={contentDesc:n.contentDesc},c=s.filter(e=>O(e,a));c.length>1&&ge(c)&&he(e,s,{content_desc:n.contentDesc},{action_index:i??L(s,a,t),stability:"low",reusable:!0===o.get("content_desc")?.reusable,reason:"content_desc_positional",scope:"collection",policy_decision:"approved",policy_reason:r.policyReason})}n.className&&n.bounds&&he(e,s,{class_name:n.className,bounds:n.bounds},{stability:"low",reusable:!1,reason:"class_bounds_fallback",scope:"page",requireUnique:!0,policy_decision:"approved",policy_reason:r.policyReason})}}(a,e,t,{resourceId:c,text:l,contentDesc:u,className:d,bounds:m},i,o,s.requestedActionIndex),0===a.length){if("action"!==n)return null;const e=!0===i.get("resource_id")?.reusable,r=!0===i.get("text")?.reusable,p=!0===i.get("content_desc")?.reusable;c?he(a,t,{resource_id:c},{stability:e?"medium":"low",reusable:e,reason:e?"resource_id_positional":"obfuscated_id_positional",scope:"collection",action_index:s.requestedActionIndex,policy_decision:"approved",policy_reason:o.policyReason}):l?he(a,t,{text:l},{stability:"low",reusable:r,reason:"text_positional",scope:"collection",action_index:s.requestedActionIndex,policy_decision:"approved",policy_reason:o.policyReason}):u?he(a,t,{content_desc:u},{stability:"low",reusable:p,reason:"content_desc_positional",scope:"collection",action_index:s.requestedActionIndex,policy_decision:"approved",policy_reason:o.policyReason}):d&&m&&he(a,t,{class_name:d,bounds:m},{stability:"low",reusable:!1,reason:"class_bounds_fallback",scope:"page",requireUnique:!0,policy_decision:"approved",policy_reason:o.policyReason})}const p=a[0];return p?{mode:n,field_assessments:r,selector_candidates:a,script_selector:{selector:p.selector,action_index:p.action_index,stability:p.stability,reusable:p.reusable,reason:p.reason,scope:p.scope,policy_decision:p.policy_decision,policy_reason:p.policy_reason}}:null}function ye(e,t){return e.lastObserveRawDump=t,e.lastObserveNodes=function(e){const t=[],s=e.split("\n");for(const e of s){const s=e.trim(),n=s.match(/^\[(\d+)\]\s+(\S+)/);n&&t.push({index:Number(n[1]),className:n[2],resourceId:s.match(/\bresource-id="([^"]*)"/)?.[1]||void 0,text:s.match(/\btext="([^"]*)"/)?.[1]||void 0,contentDesc:s.match(/\bcontent-desc="([^"]*)"/)?.[1]||void 0,packageName:s.match(/\bpackage="([^"]*)"/)?.[1]||void 0,bounds:s.match(/\bbounds=(\[[^\]]*\]\[[^\]]*\])/)?.[1]||void 0,clickable:/\bclickable=true\b/.test(s),longClickable:/\blong-clickable=true\b/.test(s),scrollable:/\bscrollable=true\b/.test(s),focusable:/\bfocusable=true\b/.test(s),enabled:!/\benabled=false\b/.test(s),checked:/\bchecked=true\b/.test(s),selected:/\bselected=true\b/.test(s)})}return t}(t),e.lastObserveNodes}function xe(e,t,s={}){const n=be(e,t,s);return n?{...n,selected_node:(o=e,{index:o.index,resource_id:o.resourceId,text:o.text,content_desc:o.contentDesc,class_name:o.className,bounds:o.bounds})}:null;var o}const ve=new Set(["btn","button","text","txt","img","image","icon","view","layout","container","content","header","footer","title","subtitle","label","input","edit","search","menu","nav","tab","bar","list","item","card","dialog","popup","modal","scroll","pager","recycler","grid","frame","root","main","action","toolbar","status","progress","loading","empty","error","divider","separator","wrapper","panel","avatar","profile","name","email","phone","password","submit","cancel","confirm","close","back","next","home","setting","notification","message","chat","comment","like","share","follow","post","feed","story","video","audio","play","pause","camera","photo","gallery","download","upload","save","delete","add","create","update","refresh","filter","sort","check","switch","toggle","radio","select","option","dropdown","picker","date","row","column","cell","section","group","block","region","area","top","bottom","left","right","center","start","end","inner","outer","overlay","badge","count","number","description","info","hint","placeholder","detail","summary","expand","collapse","more","viewpager","appbar","bottombar","snackbar","fab","chip","spinner","seekbar","slider","webview","mapview","banner","splash","widget"]);function we(e){if(!e)return!1;const t=e.includes("/")?e.split("/").pop():e;return!!t&&(!e.startsWith("android:")&&(!!/obfuscated/i.test(t)||(!!/^\d+_/.test(t)||!function(e){const t=e.toLowerCase(),s=t.split("_");for(const e of s)if(e.length>=3&&ve.has(e))return!0;for(const e of ve)if(e.length>=4&&t.includes(e))return!0;return!1}(t)&&(t.length<=5||t.length<=10&&function(e){if(0===e.length)return 0;const t=new Map;for(const s of e)t.set(s,(t.get(s)||0)+1);let s=0;for(const n of t.values()){const t=n/e.length;s-=t*Math.log2(t)}return s}(t)>3))))}function Ie(e,t,s){let n=0;for(const o of e)if(Re(o,t)){if(n===s)return o;n++}return null}async function Ne(e,t,s,n,o){const r=function(e){return`http://${e.hostIp}:18182/android_api/v2/${e.deviceId}`}(e),i=`${r}/${s}`,a={method:t,headers:{"Content-Type":"application/json"},signal:o};void 0!==n&&"POST"===t&&(a.body=JSON.stringify(n));const c=await fetch(i,a);if(!c.ok){const e=await c.text();throw new Error(`API ${s} failed: ${c.status} - ${e}`)}return await c.json()}function Te(e){return!e||"object"!=typeof e||Array.isArray(e)?null:e}function Ce(e,t){return Object.prototype.hasOwnProperty.call(e,t)}function Se(e){if(null==e||""===e)return{ok:!0};const t="string"==typeof e?Number(e):e;return"number"==typeof t&&Number.isFinite(t)?!Number.isInteger(t)||t<0?{ok:!1}:{ok:!0,value:t}:{ok:!1}}function Ee(e,t){if(!Ce(e,"index"))return null;return Se(e.index).ok?null:`${t}.index 必须是从 0 开始的非负整数。`}function Ae(e){if("string"!=typeof e)return;const t=e.trim();return t.length>0?t:void 0}function Oe(e){const t={};let s=!1;for(const[n,o]of Object.entries(e))null!=o&&""!==o&&(t[n]=o,s=!0);return s?t:void 0}function ke(e){return Oe({context_id:`ctx_${Date.now()}_${Math.floor(1e5*Math.random())}`,page_kind:Ae(e.page_kind),target_role:Ae(e.target_role),criticality_hint:Ae(e.criticality_hint),include_mode_hint:Ae(e.include_mode_hint),valid_until:Ae(e.valid_until)||"next_verified_transition"})||{context_id:`ctx_${Date.now()}_${Math.floor(1e5*Math.random())}`,valid_until:"next_verified_transition"}}function De(e){if(!e||"object"!=typeof e||Array.isArray(e))return null;const t=e,s=Se(t.index);if(!s.ok)return null;const n={index:s.value,resourceId:Ae(t.resource_id),text:Ae(t.text),contentDesc:Ae(t.content_desc),className:Ae(t.class_name),bounds:Ae(t.bounds)};return n.resourceId||n.text||n.contentDesc||n.className||n.bounds?n:null}function Re(e,t){return O(e,t)}function Le(e){const t=[];return e.resourceId&&t.push({key:"resource_id",value:e.resourceId}),e.text&&t.push({key:"text",value:e.text}),e.contentDesc&&t.push({key:"content_desc",value:e.contentDesc}),e.className&&t.push({key:"class_name",value:e.className}),e.bounds&&t.push({key:"bounds",value:e.bounds}),t}function $e(e,t){if(e.index!==t.index)return!1;const s=Le(e),n=Le(t);return s.length===n.length&&s.every(({key:e,value:s})=>{switch(e){case"resource_id":return!!t.resourceId&&A("resourceId",s,t.resourceId);case"text":return!!t.text&&A("text",s,t.text);case"content_desc":return!!t.contentDesc&&A("contentDesc",s,t.contentDesc);case"class_name":return!!t.className&&A("className",s,t.className);case"bounds":return!!t.bounds&&A("bounds",s,t.bounds)}})}function Me(e,t){return k(e,t)}function je(e,t){const s=De(e.selector);if(!s?.resourceId)return null;const n=function(e){const t=new Map;for(const s of e)s.resourceId&&t.set(s.resourceId,(t.get(s.resourceId)||0)+1);const s=new Set;for(const[e,n]of t)(we(e)||!e.startsWith("android:")&&n>=5)&&s.add(e);return s}(t);return n.has(s.resourceId)?`selector.resource_id="${s.resourceId}" 疑似混淆值,回放时可能不稳定。建议下次优先使用 text/content_desc/class_name 定位。`:null}function Fe(e,t,n){if(!s.has(e))return null;const o=De(t.selector);if(!o)return"validation failed: selector is missing or invalid. Provide resource_id/text/content_desc/class_name/bounds.";const r=n?.lastObserveNodes;if(!r||0===r.length)return"validation failed: missing observe evidence. Call observe_screen before action tools.";const i=Me(r,o);if(0===i)return"validation failed: selector has no match on latest observed screen.";const a=Se(t.action_index);if(!a.ok)return"validation failed: action_index must be a non-negative integer.";if(void 0!==a.value&&a.value>=i)return`validation failed: action_index=${a.value} is out of range for matched_count=${i}.`;if(i>1&&void 0===a.value)return`validation failed: selector is ambiguous (matched_count=${i}). Refine selector or provide action_index.`;const c=function(e){if(!e||"object"!=typeof e||Array.isArray(e))return null;const t=e.target;if(!t||"object"!=typeof t||Array.isArray(t))return null;const s=t,n=Se(s.index);if(!n.ok)return null;const o={index:n.value,resourceId:Ae(s.resource_id),text:Ae(s.text),contentDesc:Ae(s.content_desc),className:Ae(s.class_name),bounds:Ae(s.bounds)};return o.resourceId||o.text||o.contentDesc||o.className||o.bounds?o:null}(t.pre_context);return c&&!function(e,t){return void 0!==e.index&&void 0!==t.index&&e.index===t.index||!!(e.resourceId&&t.resourceId&&A("resourceId",e.resourceId,t.resourceId))||!!(e.text&&t.text&&A("text",e.text,t.text))||!!(e.contentDesc&&t.contentDesc&&A("contentDesc",e.contentDesc,t.contentDesc))||!!(e.className&&t.className&&A("className",e.className,t.className))||!!(e.bounds&&t.bounds&&A("bounds",e.bounds,t.bounds))}(o,c)?"validation failed: selector does not align with pre_context.target evidence.":null}function Pe(e,t){const s=De(e.selector),n=Te(t.script_selector),o=De(n?.selector);if(!s||!o)return null;const r=Se(e.action_index),i=Se(n?.action_index),a=Le(s).map(e=>e.key),c=Le(o).map(e=>e.key);if($e(s,o)&&(r.value??void 0)===(i.value??void 0))return{requested_is_minimal:!0,diff_type:"same",requested_selector_fields:a,script_selector_fields:c,script_selector:n?.selector,script_action_index:i.value};const l=function(e,t){const s=[];for(const{key:s,value:n}of Le(t))if(!(()=>{switch(s){case"resource_id":return!!e.resourceId&&A("resourceId",n,e.resourceId);case"text":return!!e.text&&A("text",n,e.text);case"content_desc":return!!e.contentDesc&&A("contentDesc",n,e.contentDesc);case"class_name":return!!e.className&&A("className",n,e.className);case"bounds":return!!e.bounds&&A("bounds",n,e.bounds)}})())return{matchesBase:!1,redundantFields:[]};for(const{key:n}of Le(e))(()=>{switch(n){case"resource_id":return!!t.resourceId;case"text":return!!t.text;case"content_desc":return!!t.contentDesc;case"class_name":return!!t.className;case"bounds":return!!t.bounds}})()||s.push(n);return{matchesBase:!0,redundantFields:s}}(s,o),u=void 0!==r.value&&void 0===i.value;return l.matchesBase&&(l.redundantFields.length>0||u)?{requested_is_minimal:!1,diff_type:"redundant_constraint",requested_selector_fields:a,script_selector_fields:c,redundant_selector_fields:l.redundantFields,requested_action_index:r.value,script_action_index:i.value,script_selector:n?.selector}:{requested_is_minimal:!1,diff_type:"different_contract",requested_selector_fields:a,script_selector_fields:c,requested_action_index:r.value,script_action_index:i.value,script_selector:n?.selector}}const Ue={async observe_screen(e,t,s,n){const o="number"==typeof e.wait_ms&&e.wait_ms>0?e.wait_ms:0;if(o>0){const e=await Ne(t,"POST","base/sleep",{duration:o},s);if(200!==e.code)return{success:!1,error:e.msg||"Wait before observe failed"}}const r=await Ne(t,"POST","accessibility/dump_compact",{},s);return 200==r.code&&"string"==typeof r.data?(n&&ye(n,r.data),{success:!0,data:r.data}):{success:!1,error:r.msg||"Failed to get UI dump"}},async verify_ui_state(e,t,s,n){const o=Ae(e.step_desc);if(!o)return{success:!1,error:"verify_ui_state 缺少 step_desc。"};const r=Ae(e.screen_desc);if(!r)return{success:!1,error:"verify_ui_state 缺少 screen_desc。"};const i=function(e){return Oe({page_kind:Ae(e.page_kind)})}(e),a=e.verify_element;if(!a||"object"!=typeof a||Array.isArray(a))return{success:!1,error:"verify_ui_state 缺少 verify_element 对象。"};const c=Ee(a,"verify_element");if(c)return{success:!1,error:c};const l=De(a);if(!l)return{success:!1,error:"verify_element 必须提供 resource_id、text、content_desc、class_name 或 bounds 至少一个(可选 index),且需来自当前 observe_screen 结果。"};const u=e.page_anchor;if(u&&"object"==typeof u&&!Array.isArray(u)){const e=Ee(u,"page_anchor");if(e)return{success:!1,error:e}}const d=n?.lastObserveNodes;if(!d||0===d.length)return{success:!1,error:"请先调用 observe_screen(可带 wait_ms)获取当前界面,再调用 verify_ui_state。"};if(Me(d,l)>0){const e={step_desc:o,screen_desc:r,verify_element:a,...i?{recorded_page:i}:{}},t=Ie(d,l,0);if(t){const s=xe(t,d,{mode:"verify"});s&&(e.verification_plan=s)}if(u&&"object"==typeof u&&!Array.isArray(u)){const t=De(u);if(!t)return{success:!1,error:"page_anchor 必须提供 resource_id、text、content_desc、class_name 或 bounds 至少一个(可选 index)。"};if(0===Me(d,t))return{success:!1,error:"page_anchor 必须来自当前 observe_screen 的真实节点,当前界面未匹配。"};e.page_anchor=u;const s=Ie(d,t,0);if(s){const t=xe(s,d,{mode:"anchor"});t&&(e.page_anchor_plan=t)}}return{success:!0,data:e}}const m=[void 0!==l.index&&`index=${l.index}`,l.resourceId&&`resource_id="${l.resourceId}"`,l.text&&`text="${l.text}"`,l.contentDesc&&`content_desc="${l.contentDesc}"`,l.className&&`class_name="${l.className}"`].filter(Boolean).join(", "),p=d.filter(e=>e.text||e.contentDesc||e.resourceId&&!we(e.resourceId)).slice(0,15).map(e=>{const t=[];return t.push(`index=${e.index}`),e.text&&t.push(`text="${e.text}"`),e.contentDesc&&t.push(`desc="${e.contentDesc}"`),e.resourceId&&!we(e.resourceId)&&t.push(`id="${e.resourceId}"`),t.push(`[${e.className.split(".").pop()}]`),` ${t.join(" ")}`});return{success:!1,error:`验证元素在当前界面中不存在:${m} 未匹配。请从当前界面重新选择 verify 元素。${p.length>0?`\n当前界面可用的验证候选元素(直接从中选择,无需再调 observe_screen):\n${p.join("\n")}`:""}`}},async record_search_context(e,t,s,n){const o=Ae(e.step_intent);if(!o)return{success:!1,error:"record_search_context 缺少 step_intent。"};const r=Ae(e.target_desc);if(!r)return{success:!1,error:"record_search_context 缺少 target_desc。"};const i=e.anchor_element;if(!i||"object"!=typeof i||Array.isArray(i))return{success:!1,error:"record_search_context 缺少 anchor_element(当前页面锚点),必填。"};const a=i,c=Ee(a,"anchor_element");if(c)return{success:!1,error:c};if(!function(e){const t=e.resource_id,s=e.text,n=e.content_desc,o=e.class_name,r=e.bounds;return"string"==typeof t&&t.trim().length>0||"string"==typeof s&&s.trim().length>0||"string"==typeof n&&n.trim().length>0||"string"==typeof o&&o.trim().length>0||"string"==typeof r&&r.trim().length>0}(a))return{success:!1,error:"anchor_element 必须包含 resource_id、text、content_desc、class_name 或 bounds 至少一个(可选 index)。"};const l=n?.lastObserveNodes;if(!l||0===l.length)return{success:!1,error:"record_search_context 前必须先调用 observe_screen 获取当前界面。"};const u=De(i);if(!u)return{success:!1,error:"anchor_element 不是合法 selector。"};if(0===Me(l,u))return{success:!1,error:"anchor_element 必须来自当前 observe_screen 的真实节点,当前界面未匹配。"};const d=e.target_element,m={...e,step_intent:o,target_desc:r,recorded_context:ke({...e})},p=Ie(l,u,0);if(p){const e=xe(p,l,{mode:"anchor"});e&&(m.anchor_plan=e)}if(d&&"object"==typeof d&&!Array.isArray(d)){const e=Ee(d,"target_element");if(e)return{success:!1,error:e};const t=De(d);if(!t)return{success:!1,error:"target_element 必须提供 resource_id、text、content_desc、class_name 或 bounds 至少一个(可选 index)。"};if(0===Me(l,t))return{success:!1,error:"target_element 必须来自当前 observe_screen 的真实节点,当前界面未匹配。"};const s=Ie(l,t,0);if(s){const e=xe(s,l,{mode:"action"});e&&(m.target_plan=e)}}return{success:!0,data:m}},async get_installed_apps(e,t,s){const n=e.type||"user",o=await Ne(t,"GET",`package/list?type=${n}`,void 0,s);if(200==o.code&&o.data?.packages){return{success:!0,data:o.data.packages.map(e=>`${e.app_name||"unknown"} = ${e.package_name||""}`).join("\n")}}return{success:!1,error:o.msg||"Failed to get app list"}},async get_top_activity(e,t,s){const n=await Ne(t,"GET","activity/top_activity",void 0,s);return 200==n.code?{success:!0,data:n.data}:{success:!1,error:n.msg||"Failed to get top activity"}},async get_screen_info(e,t,s){const n=await Ne(t,"GET","display/info",void 0,s);return 200==n.code?{success:!0,data:n.data}:{success:!1,error:n.msg||"Failed to get screen info"}},async start_app(e,t,s){const n=e.package_name;if(!n)return{success:!1,error:"Missing required param: package_name"};const o=await Ne(t,"POST","permission/set",{package_name:n,grant_all:!0},s);if(200!==o.code)return{success:!1,error:`权限授权失败 (${n}): ${o.msg}。建议:通过 get_installed_apps 确认包名是否正确。`};const r=await Ne(t,"POST","activity/start",{package_name:n},s);return 200==r.code?{success:!0,data:r.data}:{success:!1,error:r.msg||"Failed to start app"}},async stop_app(e,t,s){const n=e.package_name,o=await Ne(t,"POST","activity/stop",{package_name:n},s);return 200==o.code?{success:!0,data:o.data}:{success:!1,error:o.msg||"Failed to stop app"}},async tap(e,t,s){const n=await Ne(t,"POST","input/click",{x:e.x,y:e.y},s);return 200==n.code?{success:!0,data:n.data}:{success:!1,error:n.msg||"Tap failed"}},async tap_element(e,t,s){const n=Se(e.action_index);if(!n.ok)return{success:!1,error:"Invalid action_index: expected non-negative integer"};const o={selector:e.selector,action:"click",wait_timeout:e.wait_timeout??5e3,wait_interval:e.wait_interval??1e3};void 0!==n.value&&(o.action_index=n.value);const r=await Ne(t,"POST","accessibility/node",o,s);return 200==r.code?{success:!0,data:r.data}:{success:!1,error:r.msg?`元素操作失败: ${r.msg}。建议:重新 observe_screen 确认元素是否存在,检查 selector 唯一性,必要时补充 action_index。`:"元素未找到或点击失败,请重新 observe_screen 后检查 selector"}},async long_press_element(e,t,s){const n=Se(e.action_index);if(!n.ok)return{success:!1,error:"Invalid action_index: expected non-negative integer"};const o={selector:e.selector,action:"long_click",wait_timeout:e.wait_timeout??5e3,wait_interval:e.wait_interval??1e3};void 0!==n.value&&(o.action_index=n.value);const r=await Ne(t,"POST","accessibility/node",o,s);return 200==r.code?{success:!0,data:r.data}:{success:!1,error:r.msg||"Long press element failed"}},async set_text(e,t,s){const n=Se(e.action_index);if(!n.ok)return{success:!1,error:"Invalid action_index: expected non-negative integer"};const o={selector:e.selector,action:"set_text",action_params:{text:e.text},wait_timeout:e.wait_timeout??5e3,wait_interval:e.wait_interval??1e3};void 0!==n.value&&(o.action_index=n.value);const r=await Ne(t,"POST","accessibility/node",o,s);return 200==r.code?{success:!0,data:r.data}:{success:!1,error:r.msg?`文本输入失败: ${r.msg}。建议:确认输入框已聚焦,或尝试先 tap_element 聚焦后使用 input_text。`:"文本输入失败,请确认输入框已存在且可聚焦"}},async input_text(e,t,s){const n=await Ne(t,"POST","input/text",{text:e.text},s);return 200==n.code?{success:!0,data:n.data}:{success:!1,error:n.msg||"Input text failed"}},async swipe(e,t,s){const n=await Ne(t,"POST","input/scroll_bezier",{start_x:e.start_x,start_y:e.start_y,end_x:e.end_x,end_y:e.end_y,duration:e.duration??300},s);return 200==n.code?{success:!0,data:n.data}:{success:!1,error:n.msg||"Swipe failed"}},async press_key(e,t,s){const n=await Ne(t,"POST","input/keyevent",{key_code:e.key_code},s);return 200==n.code?{success:!0,data:n.data}:{success:!1,error:n.msg||"Key event failed"}},async wait(e,t,s){const n=await Ne(t,"POST","base/sleep",{duration:e.duration},s);return 200==n.code?{success:!0,data:!0}:{success:!1,error:n.msg||"Wait failed"}}};async function qe(e,i,a,c){const l=Ue[e.name],u=(d=e.name,le.find(e=>e.name===d));var d;if(!l||!u)return{toolCallId:e.id,name:e.name,success:!1,error:`Unknown tool: ${e.name}`};const m=(p=e.arguments)&&"object"==typeof p&&!Array.isArray(p)?p:{};var p;const _=function(e,t){const s=Ce(t,"index"),n=Ce(t,"action_index"),r=Te(t.selector),i=!!r&&Ce(r,"index");return o.has(e)?s?"Invalid index usage: use action_index (top-level) for accessibility/node tools":i?"Invalid selector.index: selector does not support index, use action_index instead":null:s||n?"Invalid index usage: index/action_index are only allowed for accessibility/node tools":null}(e.name,m);if(_)return{toolCallId:e.id,name:e.name,success:!1,error:_};const f=u.schema.safeParse(m);if(!f.success&&t.has(e.name)){const t=f.error.issues.map(e=>`${e.path.join(".")}: ${e.message}`).join("; ");return{toolCallId:e.id,name:e.name,success:!1,error:`参数校验失败,拒绝执行: ${t}。请检查 pre_context.anchor 和 pre_context.target 是否已填写,selector 字段是否合法。`}}const g=f.success?f.data:m,h=function(e,t){if(!r.has(e))return null;const s=t.pre_context;if(!s||"object"!=typeof s||Array.isArray(s))return`pre_context 缺失:${e} 调用前必须提供 pre_context(anchor + target)。请先 observe_screen 确认页面状态,再填写 pre_context 后重新调用。`;const n=s,o=n.anchor;if(!o||"object"!=typeof o||Array.isArray(o))return"pre_context.anchor 缺失:必须提供页面锚点元素用于确认当前页面。anchor 应选择页面中稳定存在的框架级元素(如导航栏、标题栏)。";if(!Ae(o.desc))return"pre_context.anchor.desc 缺失:必须提供页面锚点描述,用于说明当前页面上下文。";const i=n.target;return!i||"object"!=typeof i||Array.isArray(i)?"pre_context.target 缺失:必须提供操作目标元素信息。target 应来自当前 observe_screen 的真实节点属性。":Ae(i.desc)?Ae(n.step_intent)?null:"pre_context.step_intent 缺失:必须说明本次操作在任务中的目的和预期效果,而非重复元素描述。此字段用于脚本生成的步骤命名和合并。":"pre_context.target.desc 缺失:必须提供目标元素描述,用于说明本次操作意图。"}(e.name,g);if(h)return{toolCallId:e.id,name:e.name,success:!1,error:h};const b=Fe(e.name,g,c);if(b)return{toolCallId:e.id,name:e.name,success:!1,error:b};try{const t=await l(g,i,a,c);if(t.success){const o=[];if(s.has(e.name)&&c?.lastObserveNodes?.length){const e=De(g.selector),s=Se(g.action_index);if(e){const n=Ie(c.lastObserveNodes,e,s.value??0);if(n){const e=xe(n,c.lastObserveNodes,{mode:"action",requestedActionIndex:s.value});if(e){const s=t.data&&"object"==typeof t.data&&!Array.isArray(t.data)?t.data:{},n=Pe(g,e);t.data={...s,_selector_recording_profile:e,...n?{_selector_request_analysis:n}:{}};const r=n?function(e){if(!0===e.requested_is_minimal)return null;if("redundant_constraint"===("string"==typeof e.diff_type?e.diff_type:"different_contract")){const t=Array.isArray(e.redundant_selector_fields)?e.redundant_selector_fields.filter(e=>"string"==typeof e):[],s=[...t.length>0?[`冗余字段: ${t.join(", ")}`]:[],void 0!==e.requested_action_index&&void 0===e.script_action_index?"冗余 action_index":void 0].filter(Boolean);return"selector 最小化提醒: 当前请求 selector 可执行,但不是最小稳定唯一方案。录制阶段批准的 script_selector 已足够定位。"+(s.length>0?`请去掉 ${s.join(",")}。`:"")}return"selector 对齐提醒: 当前请求 selector 与录制阶段批准的 script_selector 不一致。虽然本次执行成功,但后续应优先对齐为 recording-approved script_selector。"}(n):null;r&&o.push(r)}}}}if(s.has(e.name)&&c?.lastObserveNodes){const e=je(g,c.lastObserveNodes);e&&o.push(e)}if(s.has(e.name)){const e=function(e){const t=Te(e.pre_context);if(!t)return null;const s=De(t.anchor),n=De(t.target);return s&&n&&$e(s,n)?"pre_context 质量提醒: anchor 与 target 使用了完全相同的定位证据。anchor 应优先描述当前页面或容器级稳定锚点,而不是与点击目标重合。":null}(g);e&&o.push(e)}if(c?.softPendingVerify&&ue(e.name)){const e=c.softPendingVerify;o.push(`⚠️ 补录提醒: 上一步 #${e.index} ${e.toolName} 的 verify_ui_state 被跳过,录制信息不完整,将影响脚本生成质量。建议在当前操作完成后补充 observe_screen → verify_ui_state。`)}return n.has(e.name)&&o.push("录制提醒: 此动作可能改变界面,请执行 observe_screen(wait_ms) → verify_ui_state 记录验证信息。"),"swipe"===e.name&&(!c?.lastSearchContextTimestamp||Date.now()-c.lastSearchContextTimestamp>3e4)&&o.push("⚠️ 录制提醒: swipe 前未调用 record_search_context,录制信息将不完整。建议先调用 record_search_context 记录查找目标与页面锚点。"),{toolCallId:e.id,name:e.name,success:!0,data:t.data,warning:o.length>0?o.join("\n"):void 0}}return{toolCallId:e.id,name:e.name,success:t.success,data:t.data,error:t.error}}catch(t){return{toolCallId:e.id,name:e.name,success:!1,error:t.message||String(t)}}}const Ge=["search","explore","discover","for you","home","friends","inbox","profile","close","log in","sign up","comment","搜索","首页","消息","我的","关闭","登录","评论"];function Ve(e){const t=e.match(/\bbounds=\[(\d+),(\d+)\]\[(\d+),(\d+)\]/);if(!t)return null;const s=Number(t[2]),n=Number(t[4]);return Number.isFinite(s)&&Number.isFinite(n)?{y1:s,y2:n}:null}function Xe(e){if(0===e.length)return null;const t=function(e){const t=e.match(/^Screen\s+\d+x(\d+)/);if(!t)return null;const s=Number(t[1]);return Number.isFinite(s)&&s>0?s:null}(e[0]||"");if(!t)return null;let s=0,n=0,o=0;for(let r=1;r<e.length;r++){const i=e[r];if(!i.includes("clickable=true"))continue;const a=Ve(i);if(!a)continue;const c=(a.y1+a.y2)/2/t;c<.33?s++:c<=.67?n++:o++}return 0===s+n+o?null:`[可点击分布] 顶部:${s} 中部:${n} 底部:${o} (按 bounds center_y)`}function We(e){if(!e.success)return`Error: ${e.error||"operation failed"}`;const t=e.data;let s;if("verify_ui_state"===e.name&&t&&"object"==typeof t){const e=t,n=e.step_desc,o=e.verify_element;s=n?`verify_ui_state OK: ${n}`:"verify_ui_state OK",o&&(o.resource_id||o.text||o.content_desc)&&(s+=` (element: ${[o.resource_id,o.text,o.content_desc].filter(Boolean).join(" / ")})`)}else if("record_search_context"===e.name&&t&&"object"==typeof t){const e=t;s=e.target_desc?`record_search_context OK: ${e.target_desc}`:"record_search_context OK"}else if("observe_screen"===e.name&&"string"==typeof t){const e=t.split("\n"),n=(t.match(/clickable/g)||[]).length,o=function(e){if(e.length<=120)return{content:e.join("\n"),shownLines:e.length,truncated:!1};const t=new Map,s=s=>{s<0||s>=e.length||t.has(s)||t.set(s,e[s])};s(0);for(let t=1;t<Math.min(e.length,51);t++)s(t);for(let t=Math.max(1,e.length-40);t<e.length;t++)s(t);let n=0;for(let s=1;s<e.length&&n<30;s++){const o=e[s],r=o.toLowerCase(),i=Ge.some(e=>r.includes(e)),a=/\bbounds=\[/.test(o)&&(/\bclickable=true\b/.test(o)||/\bcontent-desc=/.test(o)||/\btext="/.test(o)||/\bresource-id=/.test(o));(i||a)&&(t.has(s)||(t.set(s,o),n++))}const o=Array.from(t.entries()).sort((e,t)=>e[0]-t[0]).slice(0,120).map(([,e])=>e);return{content:o.join("\n"),shownLines:o.length,truncated:!0}}(e),r=`[元素总行数: ${e.length}, 可点击: ${n}, 展示行: ${o.shownLines}]`,i=Xe(e),a="[位置规则] 仅依据 bounds=[x1,y1][x2,y2] + Screen WxH 判断顶部/底部;禁止按节点顺序或缩进推断位置。",c=o.truncated?"\n... (已按头部+尾部+关键词/可交互关键行压缩展示;定位不确定时请重新 observe_screen 并用 verify_ui_state 验证)":"";s=`${r}\n${a}${i?`\n${i}`:""}\n${o.content}${c}`}else s=function(e){if(!e||"object"!=typeof e||Array.isArray(e))return!1;const t=e;return"action_success"in t&&"nodes"in t&&Array.isArray(t.nodes)}(t)?function(e){const t=e,s="number"==typeof t.count?t.count:0,n="string"==typeof t.action?t.action:"find";return!0!==t.action_success?`${n} failed (matched ${s} nodes)`:s<=1?`${n} OK`:`${n} OK (注意: 匹配到 ${s} 个元素,操作了第 ${"number"==typeof t.action_index?t.action_index:0} 个)`}(t):"string"==typeof t?t:!0===t||void 0===t?"OK":JSON.stringify(t,null,2);return e.warning&&(s+=`\n⚠️ ${e.warning}`),s}class Ke{handlers={planning:new Set,thinking:new Set,toolcall:new Set,toolresult:new Set,diagnostic:new Set,paused:new Set,scriptgenerating:new Set,complete:new Set,error:new Set};on(e,t){return this.handlers[e].add(t),()=>this.off(e,t)}off(e,t){this.handlers[e].delete(t)}emit(e,t){for(const s of Array.from(this.handlers[e]))s(t)}clear(){Object.keys(this.handlers).forEach(e=>{this.handlers[e].clear()})}}class ze extends Error{code;details;constructor(e,t,s){super(t),this.name="AgentRuntimeError",this.code=e,this.details=s}}const Be=["INVALID_CONFIG","SESSION_NOT_FOUND","SESSION_NOT_PAUSED","AGENT_RUNNING","EMPTY_EXECUTION_LOG","MCP_CONNECT_FAILED","SCRIPT_GENERATION_FAILED","RUNTIME_LOOP_FAILED","UNKNOWN"];function Je(e){return!!e&&"object"==typeof e&&!Array.isArray(e)}function Ye(e,t="UNKNOWN"){if(e instanceof ze)return e;const s=function(e){return e instanceof Error&&e.message||Je(e)&&"string"==typeof e.message?e.message:String(e)}(e);return Je(e)?new ze("string"==typeof(n=e.code)&&Be.includes(n)?e.code:t,s,Je(e.details)?e.details:void 0):new ze(t,s);var n}const He=I.object({goal:I.string().min(1),subtasks:I.array(I.object({id:I.number().int().positive(),description:I.string().min(1),successCriteria:I.string().min(1),estimatedActions:I.array(I.string().min(1)).default([])})).default([]),risks:I.array(I.string()).default([]),assumptions:I.array(I.string()).default([]),estimatedSteps:I.number().int().positive().default(1)}),Qe=I.object({status:I.enum(["completed","paused"]).default("completed"),goal:I.string().min(1).optional(),subtasks:He.shape.subtasks.default([]),risks:He.shape.risks.default([]),assumptions:He.shape.assumptions.default([]),estimatedSteps:I.number().int().positive().optional(),pauseMessage:I.string().min(1).optional(),pauseReason:I.string().min(1).optional()}).superRefine((e,t)=>{"paused"!==e.status?e.goal||t.addIssue({code:I.ZodIssueCode.custom,message:"goal is required when status=completed",path:["goal"]}):e.pauseMessage||t.addIssue({code:I.ZodIssueCode.custom,message:"pauseMessage is required when status=paused",path:["pauseMessage"]})});function Ze(e){const t=e.trim();if(t.startsWith("{")&&t.endsWith("}"))return t;const s=t.replace(/^```json\s*/i,"").replace(/^```\s*/i,"").replace(/\s*```$/,"").trim();if(s.startsWith("{")&&s.endsWith("}"))return s;throw new Error("plan output is not a valid standalone JSON object")}function et(e,t){return"zh"===t?{goal:e,subtasks:[{id:1,description:"识别目标应用或目标页面入口",successCriteria:"当前界面可见可执行入口元素",estimatedActions:["observe_screen","tap_element"]},{id:2,description:"执行目标核心动作",successCriteria:"动作后出现预期的关键页面变化",estimatedActions:["tap_element","set_text","press_key"]},{id:3,description:"记录动作结果并验证状态",successCriteria:"verify_ui_state 成功且状态可复用",estimatedActions:["observe_screen","verify_ui_state"]}],risks:["页面元素可能动态变化","页面加载延迟导致观察结果滞后"],assumptions:["设备已连通且可调用 Android 控制 API"],estimatedSteps:8}:{goal:e,subtasks:[{id:1,description:"Identify the target app or the entry point screen",successCriteria:"A visible and actionable entry element is observed",estimatedActions:["observe_screen","tap_element"]},{id:2,description:"Execute the core target action",successCriteria:"Expected key screen transition appears after action",estimatedActions:["tap_element","set_text","press_key"]},{id:3,description:"Record outcome and verify resulting UI state",successCriteria:"verify_ui_state succeeds with reusable state evidence",estimatedActions:["observe_screen","verify_ui_state"]}],risks:["Dynamic UI elements may change during execution","Page loading delays can make observations stale"],assumptions:["Device is reachable and Android control APIs are available"],estimatedSteps:8}}function tt(e,t,s){return e<=1?"":"zh"===s?`\n\n[上一次失败]\n${t[t.length-1]||"unknown"}\n请严格输出合法 JSON。`:`\n\n[PreviousFailure]\n${t[t.length-1]||"unknown"}\nOutput strict valid JSON only.`}function st(e,t,s,n){return"zh"===n?`## 目标\n\n${e}\n\n## 当前设备屏幕\n\n${t}${s}`:`## Goal\n\n${e}\n\n## Current Device Screen\n\n${t}${s}`}async function nt(e,t,s,n,o={}){return async function(e,t,s,n,o={}){const r=c(e),l={lastObserveRawDump:null,lastObserveNodes:[]},u=o.planningMessages||[],d=!1!==o.allowPause,m=await qe({id:"plan-observe-screen",name:"observe_screen",arguments:{},source:"local"},s,o.signal,l),p=m.success&&"string"==typeof m.data?m.data:We(m),_=l.lastObserveRawDump||void 0,f=i(t),g=[],h=Math.max(1,n.planner.maxAttempts);for(let t=1;t<=h;t++){const s=tt(t,g,r),n=[{role:"system",content:a(e,d)},{role:"user",content:st(e,p,s,r)},...u];try{let s;if(f.capabilities.structuredJson)s=await f.chatStructuredJson(n,Qe,o.signal);else{const e=await f.chatWithTools(n,[],o.signal);s=JSON.parse(Ze(e.content))}const r=Qe.parse(s);if("paused"===r.status){if(!d)throw new Error("planner requested clarification but pause is disabled for this entrypoint");return{status:"paused",text:r.pauseMessage||"planner requested clarification",reason:r.pauseReason,screenDump:p,screenRawDump:_,attempts:t,errors:g}}const i=Math.max(r.estimatedSteps||1,2*r.subtasks.length,1);return{status:"completed",plan:{goal:r.goal||e,subtasks:r.subtasks,risks:r.risks,assumptions:r.assumptions,estimatedSteps:i},screenDump:p,screenRawDump:_,attempts:t,errors:g}}catch(e){g.push(e.message||String(e))}}return{status:"completed",plan:et(e,r),screenDump:p,screenRawDump:_,attempts:h,errors:g}}(e,t,s,n,o)}function ot(e,t){return t<=0?null:e/t}function rt(e,t){const s={...t,timestamp:Date.now()};return e.diagnostics.push(s),s}function it(e){const t=[...e.diagnostics].reverse().find(e=>"runtime"===e.phase||"script_generation"===e.phase)?.code,s=function(e){let t=0,s=0,n=0;for(const o of e)o.result.success&&(t+=1),"action"===o.category&&(s+=1,o.result.success&&(n+=1));return{totalToolCalls:e.length,successfulToolCalls:t,runtimeSuccessRate:ot(t,e.length),totalActionCalls:s,successfulActionCalls:n,automationSuccessRate:ot(n,s)}}(e.executionLog),n={};let o=0;for(const t of e.executionLog){if(t.result.success)continue;o+=1;const e=t.result.errorCode||"UNKNOWN";n[e]=(n[e]||0)+1}return{planningRetries:e.runtimeContext.planningRetries,toolRetries:e.runtimeContext.toolRetries,scriptGenerationRetries:e.runtimeContext.scriptGenerationRetries,convergenceTriggers:e.runtimeContext.convergenceTriggers,totalToolCalls:s.totalToolCalls,successfulToolCalls:s.successfulToolCalls,runtimeSuccessRate:s.runtimeSuccessRate,totalActionCalls:s.totalActionCalls,successfulActionCalls:s.successfulActionCalls,automationSuccessRate:s.automationSuccessRate,totalFailures:o,failureByCode:n,finalFailureCode:t}}function at(e,t,s,n=[]){const o=[{role:"system",content:l(e,t)},{role:"user",content:e}];for(const e of n)"assistant"!==e.role&&"user"!==e.role||o.push({role:e.role,content:e.content,timestamp:e.timestamp});return s&&(o.push({role:"assistant",content:"",toolCalls:[{id:"plan-initial-observe",name:"observe_screen",arguments:{},source:"local"}]}),o.push({role:"tool",content:s,toolCallId:"plan-initial-observe"})),o}function ct(e){return e.currentPhase||(e.currentPhase="runtime"),e.planningContext?Array.isArray(e.planningContext.messages)||(e.planningContext.messages=[]):e.planningContext={messages:[]},e}function lt(e,t,s){ct(e),e.plan=t,e.currentPhase="runtime",e.messages=at(e.goal,t,s,e.planningContext?.messages||[])}function ut(e,t,s=[]){s.length>0?e.messages.push({role:"assistant",content:t,toolCalls:s,timestamp:Date.now()}):e.messages.push({role:"assistant",content:t,timestamp:Date.now()})}function dt(e,t,s){e.messages.push({role:"tool",content:s,toolCallId:t,timestamp:Date.now()})}const mt=new Set(["verify_ui_state","record_search_context","get_screen_info"]);function pt(e){return!e||"object"!=typeof e||Array.isArray(e)?null:e}function _t(e){if("number"==typeof e&&Number.isInteger(e)&&e>=0)return e;if("string"==typeof e&&e.trim().length>0){const t=Number(e);if(Number.isInteger(t)&&t>=0)return t}}function ft(e,t){for(const s of t){const t=e[s];if("string"==typeof t){const e=t.trim();if(e.length>0)return e}}}function gt(e){const t={};let s=!1;for(const[n,o]of Object.entries(e))null!=o&&""!==o&&(t[n]=o,s=!0);return s?t:void 0}function ht(e){const t=pt(e);if(!t)return;return gt({index:_t(t.index),resource_id:ft(t,["resource_id","resource-id","resourceId","id"]),text:ft(t,["text"]),content_desc:ft(t,["content_desc","content-desc","contentDesc"]),class_name:ft(t,["class_name","class","className"]),bounds:ft(t,["bounds"])})}function bt(e,t){if(!s.has(e.name)||!t.success)return null;const n=pt(e.arguments),o=pt(t.data);if(!n&&!o)return null;const r=gt(pt(n?.selector)||{}),i=_t(n?.action_index),a=Array.isArray(o?.nodes)?o.nodes:[],c=_t(o?.action_index),l=i??c??0,u=ht(a[l]??a[0]),d=a.slice(0,5).map(e=>ht(e)).filter(e=>!!e),m="number"==typeof o?.count&&Number.isFinite(o.count)?o.count:a.length,p={};r&&(p.requested_selector=r),void 0===i&&void 0===c||(p.action_index=l),m>=0&&(p.matched_count=m),u&&(p.selected_node=u),d.length>0&&(p.candidate_nodes=d);const _=function(e){const t=pt(e);if(!t)return;return gt({mode:"string"==typeof t.mode?t.mode:void 0,script_selector:pt(t.script_selector)||void 0,field_assessments:Array.isArray(t.field_assessments)?t.field_assessments.map(e=>pt(e)).filter(e=>!!e).slice(0,8):void 0,selector_candidates:Array.isArray(t.selector_candidates)?t.selector_candidates.map(e=>pt(e)).filter(e=>!!e).slice(0,8):void 0,selected_node:ht(t.selected_node)})}(o?._selector_recording_profile);if(_){p.selector_profile=_;const e=pt(_.script_selector);if(e){const t=pt(e.selector);t&&(p.recommended_selector=t);const s=_t(e.action_index);void 0!==s&&(p.recommended_action_index=s),"string"==typeof e.stability&&(p.selector_stability=e.stability),"string"==typeof e.reason&&(p.selector_reason=e.reason),"boolean"==typeof e.reusable&&(p.selector_reusable=e.reusable),"string"==typeof e.scope&&(p.selector_scope=e.scope)}}const f=pt(o?._selector_request_analysis);return f&&(p.selector_request_analysis=f),Object.keys(p).length>0?p:null}function yt(e,t){const s=e.name;if(mt.has(s))return t;if("observe_screen"===s&&t.success&&"string"==typeof t.data){const e=t.data.split("\n").length;return{...t,data:`(${e} lines)`}}const n=bt(e,t);if(n){const e={toolCallId:t.toolCallId,name:t.name,success:t.success,data:{runtime_selector_evidence:n},source:t.source,server:t.server};return t.error&&(e.error=t.error),t.warning&&(e.warning=t.warning),e}const o={toolCallId:t.toolCallId,name:t.name,success:t.success,source:t.source,server:t.server};return t.error&&(o.error=t.error),t.warning&&(o.warning=t.warning),o}function xt(e,t,s,n){e.executionLog.push({index:e.executionLog.length+1,toolName:t.name,arguments:t.arguments,result:yt(t,s),category:n,timestamp:Date.now()})}const vt=[/resource-id=/,/resource_id=/,/\btext="/,/content-desc=/,/content_desc=/,/clickable=true/,/bounds=\[/];function wt(e){return e.startsWith("Screen ")||e.startsWith("[Package]")||e.startsWith("[元素总行数:")}function It(e){const t=e.split("\n");if(t.length<=7)return e;const s=t[0]||"",n=new Set,o=[];let r=0;for(let e=1;e<t.length;e++){const s=t[e];s.includes("clickable=true")&&r++,vt.some(e=>e.test(s))&&(n.has(s)||(n.add(s),o.push(s)))}return[s,...o.length>0?o.slice(0,6):t.slice(1,7),"[位置规则] 仅依据 bounds + Screen WxH 判断顶部/底部,禁止按节点顺序推断。",`[... 已压缩,原始 ${t.length} 行,关键候选 ${o.length} 行,可点击 ${r} 个]`].join("\n")}function Nt(e){if(e.length<=14)return e;const t=e.length-12,s=[],n=[],o=[];for(let t=0;t<e.length;t++){const s=e[t];"tool"===s.role&&wt(s.content)&&n.push(t),"tool"===s.role&&(s.content.includes('"step_desc"')||s.content.includes('"target_desc"'))&&o.push(t)}const r=new Set(n.slice(-1)),i=new Set(o.slice(-3));for(let n=0;n<e.length;n++){const o=e[n];if(n<=1)s.push(o);else if(n>=t)s.push(o);else if("user"!==o.role)if("assistant"!==o.role)if(o.content.length<=200)s.push(o);else if(!o.content.includes('"step_desc"')&&!o.content.includes('"target_desc"')||i.has(n))if(wt(o.content)){if(r.has(n)){s.push(o);continue}s.push({...o,content:It(o.content)})}else s.push({...o,content:o.content.substring(0,200)+"\n[... 已截断]"});else try{const e=JSON.parse(o.content);s.push({...o,content:JSON.stringify({step_desc:e.step_desc,target_desc:e.target_desc})})}catch{s.push({...o,content:o.content.substring(0,200)+"\n[... 已截断]"})}else o.content.length<=120?s.push(o):s.push({...o,content:o.content.substring(0,120)+"\n[... 已截断]"});else s.push(o)}return s}function Tt(e){return"stdio"===e.transport?{transport:"stdio",command:e.command,args:e.args||[],env:e.env||{}}:{transport:"streamable_http"===e.transport?"http":e.transport,url:e.url,headers:e.headers||{}}}class Ct{servers;client=null;constructor(e){this.servers=e}async connect(){if(!this.servers||0===Object.keys(this.servers).length)return[];if(!this.client){const{MultiServerMCPClient:e}=await async function(){try{return await import("@langchain/mcp-adapters")}catch(e){const t=e instanceof Error?e.message:String(e);throw new Error(`failed to load MCP runtime dependency @langchain/mcp-adapters: ${t}; ensure @langchain/langgraph is installed`)}}(),t={};for(const[e,s]of Object.entries(this.servers))t[e]=Tt(s);this.client=new e({mcpServers:t})}const e=[];for(const t of Object.keys(this.servers)){const s=await this.client.getTools(t);for(const n of s)n.name&&e.push({name:n.name,tool:n,server:t})}return e}async close(){this.client&&(await this.client.close(),this.client=null)}}const St=new Set(le.map(e=>e.name));class Et{mcpClient=null;mcpTools=new Map;modelTools=[...le];getModelTools(){return this.modelTools}async prepare(e={}){if(await this.closeMcpClient(),this.mcpTools.clear(),this.modelTools=[...le],0===Object.keys(e).length)return;const t=new Ct(e);try{const e=await t.connect();!function(e,t){const s=[],n=new Set(e),o=new Set;for(const e of t){const t=e.name;t&&(n.has(t)||o.has(t)?s.push(t):o.add(t))}if(s.length>0){const e=Array.from(new Set(s)).join(", ");throw new Error(`MCP tool name conflict detected: ${e}`)}}(le.map(e=>e.name),e);for(const t of e)this.mcpTools.set(t.name,{tool:t.tool,server:t.server});this.modelTools=[...le,...e.map(e=>e.tool)],this.mcpClient=t}catch(e){throw await t.close().catch(()=>{}),e}}annotateToolCalls(e){return e.map(e=>{const t=this.resolveTool(e.name);return{...e,source:t.source,server:t.server}})}getCategory(e,t){return"local"===t.source&&ue(e.name)?"action":"observation"}async execute(e,t,s,n){const o=this.resolveTool(e.name);if("local"===o.source){return{...await qe({id:e.id,name:e.name,arguments:e.arguments},t,s,n),source:"local"}}if(!o.tool)return{toolCallId:e.id,name:e.name,success:!1,error:`MCP tool not found: ${e.name}`,source:"mcp",server:o.server};try{const t=await o.tool.invoke(e.arguments);return{toolCallId:e.id,name:e.name,success:!0,data:t,source:"mcp",server:o.server}}catch(t){return{toolCallId:e.id,name:e.name,success:!1,error:t.message||String(t),source:"mcp",server:o.server}}}async destroy(){await this.closeMcpClient(),this.mcpTools.clear(),this.modelTools=[...le]}resolveTool(e){if(St.has(e))return{source:"local"};const t=this.mcpTools.get(e);return t?{source:"mcp",server:t.server,tool:t.tool}:{source:"local"}}async closeMcpClient(){this.mcpClient&&(await this.mcpClient.close(),this.mcpClient=null)}}const At="PLANNER_RETRY",Ot="PLANNER_FALLBACK",kt="TOOL_RETRY",Dt="VERIFY_GATE_BLOCKED",Rt="MAX_ITERATIONS_REACHED",Lt="CONSECUTIVE_FAILURE_LIMIT_REACHED",$t="SAME_TOOL_REPEAT_LIMIT_REACHED",Mt="SCRIPT_RETRY",jt=new Set(["TIMEOUT","NETWORK","HTTP_5XX"]);function Ft(e){if(e.errorCode)return{code:e.errorCode,retryable:e.retryable??jt.has(e.errorCode)};const t=(e.error||"").toLowerCase();if(!t)return{code:"UNKNOWN",retryable:!1};if(t.includes("aborted"))return{code:"ABORTED",retryable:!1};if(t.includes("timeout")||t.includes("timed out"))return{code:"TIMEOUT",retryable:!0};if(t.includes("econnreset")||t.includes("enotfound")||t.includes("econnrefused")||t.includes("network")||t.includes("fetch failed"))return{code:"NETWORK",retryable:!0};const s=function(e){const t=e.match(/\b(?:status|failed:)\s*(\d{3})\b/i);if(!t)return null;const s=Number(t[1]);return Number.isFinite(s)?s:null}(t);if(null!==s){if(s>=500)return{code:"HTTP_5XX",retryable:!0};if(s>=400)return{code:"HTTP_4XX",retryable:!1}}return t.includes("unknown tool")?{code:"UNKNOWN_TOOL",retryable:!1}:t.includes("参数校验失败")||t.includes("validation")||t.includes("invalid")?{code:"VALIDATION",retryable:!1}:t.includes("拦截:上一步动作尚未验证")||t.includes("blocked by verify gate")?{code:"RUNTIME_GUARD",retryable:!1}:t.includes("plan")&&t.includes("invalid")?{code:"PLAN_VALIDATION_FAILED",retryable:!1}:t.includes("workflow")&&t.includes("invalid")?{code:"SCRIPT_VALIDATION_FAILED",retryable:!1}:{code:"UNKNOWN",retryable:!1}}function Pt(e,t){const s=new AbortController,n=()=>{s.abort(t?.reason)};t&&(t.aborted&&s.abort(t.reason),t.addEventListener("abort",n));const o=setTimeout(()=>{s.abort(new Error(`Tool timeout after ${e}ms`))},e);return{signal:s.signal,dispose:()=>{clearTimeout(o),t&&t.removeEventListener("abort",n)}}}function Ut(e,t){return{toolCallId:e.id,name:e.name,success:!1,error:`Tool timeout after ${t}ms`,errorCode:"TIMEOUT",retryable:!0}}function qt(e,t){return t instanceof Error&&/timeout/i.test(t.message)?Ut(e,0):t instanceof Error&&"AbortError"===t.name?{toolCallId:e.id,name:e.name,success:!1,error:t.message||"Tool aborted",errorCode:"ABORTED",retryable:!1}:{toolCallId:e.id,name:e.name,success:!1,error:t?.message||String(t),errorCode:"UNKNOWN",retryable:!1}}async function Gt(e){const t=Math.max(1,e.maxAttempts);for(let s=1;s<=t;s++){const n=Date.now(),o=Pt(e.timeoutMs,e.signal);let r;try{r=await e.execute(e.toolCall,e.device,o.signal)}catch(t){const s=o.signal.reason instanceof Error?o.signal.reason.message:String(o.signal.reason||"");r=o.signal.aborted&&/timeout/i.test(s)?Ut(e.toolCall,e.timeoutMs):qt(e.toolCall,t)}finally{o.dispose()}if(r.latencyMs||(r.latencyMs=Date.now()-n),r.attempt=s,r.success)return r;const i=Ft(r);if(r.errorCode=r.errorCode??i.code,r.retryable=r.retryable??i.retryable,"ABORTED"===r.errorCode)return r;if(!r.retryable||s>=t)return r;e.onRetry?.(s,r)}return{toolCallId:e.toolCall.id,name:e.toolCall.name,success:!1,error:"Unreachable: retry loop terminated unexpectedly",errorCode:"UNKNOWN",retryable:!1}}function Vt(e,t){if(!Number.isFinite(e))return t;const s=Math.floor(e);return s<=0?t:s}function Xt(e,t){return"action"===t?e.retries.action:e.retries.observation}function Wt(e,t){const s=d[t];return s?e.timeouts[s]:e.timeouts.defaultMs}function Kt(e){if(null==e)return String(e);if("object"!=typeof e)return JSON.stringify(e);if(Array.isArray(e))return`[${e.map(e=>Kt(e)).join(",")}]`;return`{${Object.entries(e).sort(([e],[t])=>e.localeCompare(t)).map(([e,t])=>`${JSON.stringify(e)}:${Kt(t)}`).join(",")}}`}function zt(e){return`${e.name}:${Kt(e.arguments||{})}`}function Bt(e){return"local"===e.source&&ue(e.name)?"action":"observation"}class Jt{options;emitter=new Ke;toolRegistry=new Et;session=null;provider=null;abortController=null;sessionStore=null;localToolContext={lastObserveRawDump:null,lastObserveNodes:[]};constructor(e={}){this.options=e;if("sqlite"!==(e.persistence?.mode||"sqlite"))return;const t=T();try{this.sessionStore=new S(t)}catch(e){const s=Ye(e,"INVALID_CONFIG");throw new ze("INVALID_CONFIG",`session store initialization failed: ${s.message}`,{sqlitePath:t,cause:s.message})}}on(e,t){return this.emitter.on(e,t)}off(e,t){this.emitter.off(e,t)}async start(t){this.session?.running&&this.stop(this.session.sessionId);try{this.provider=i(t.provider)}catch(e){const s=Ye(e,"INVALID_CONFIG");throw new ze("INVALID_CONFIG",`invalid model provider configuration: ${s.message}`,{provider:{vendor:t.provider.vendor,baseUrl:t.provider.baseUrl,model:t.provider.model},cause:s.message})}try{await this.toolRegistry.prepare(this.options.mcpServers||{})}catch(e){const t=Ye(e,"MCP_CONNECT_FAILED");throw new ze("MCP_CONNECT_FAILED",`failed to connect MCP tools: ${t.message}`,{servers:Object.keys(this.options.mcpServers||{}),cause:t.message,...t.details})}const s=function(e){if(!e)return u;const t=u.scriptGeneration.waitRandomization||{},s=Vt(e.scriptGeneration?.waitRandomization?.minMs??t.minMs??400,t.minMs??400),n=Math.max(s,Vt(e.scriptGeneration?.waitRandomization?.maxMs??t.maxMs??3e4,t.maxMs??3e4));return{retries:{observation:Vt(e.retries?.observation??u.retries.observation,u.retries.observation),action:Vt(e.retries?.action??u.retries.action,u.retries.action)},timeouts:{defaultMs:Vt(e.timeouts?.defaultMs??u.timeouts.defaultMs,u.timeouts.defaultMs),observeScreenMs:Vt(e.timeouts?.observeScreenMs??u.timeouts.observeScreenMs,u.timeouts.observeScreenMs),startAppMs:Vt(e.timeouts?.startAppMs??u.timeouts.startAppMs,u.timeouts.startAppMs)},limits:{maxIterations:Vt(e.limits?.maxIterations??u.limits.maxIterations,u.limits.maxIterations),maxConsecutiveFailures:Vt(e.limits?.maxConsecutiveFailures??u.limits.maxConsecutiveFailures,u.limits.maxConsecutiveFailures),maxSameToolFingerprint:Vt(e.limits?.maxSameToolFingerprint??u.limits.maxSameToolFingerprint,u.limits.maxSameToolFingerprint)},planner:{maxAttempts:Vt(e.planner?.maxAttempts??u.planner.maxAttempts,u.planner.maxAttempts)},scriptGeneration:{maxAttempts:Vt(e.scriptGeneration?.maxAttempts??u.scriptGeneration.maxAttempts,u.scriptGeneration.maxAttempts),waitRandomization:{enabled:e.scriptGeneration?.waitRandomization?.enabled??t.enabled??!0,minMs:s,maxMs:n,jitterRatio:(o=e.scriptGeneration?.waitRandomization?.jitterRatio??t.jitterRatio,r=t.jitterRatio??.2,"number"==typeof o&&Number.isFinite(o)?o<0?0:o>1?1:o:r),roundingMs:Vt(e.scriptGeneration?.waitRandomization?.roundingMs??t.roundingMs??100,t.roundingMs??100)}}};var o,r}(t.reliability),n=t.sessionId||e();this.localToolContext={lastObserveRawDump:null,lastObserveNodes:[]},this.session=ct(function(e,t,s,n,o){return{sessionId:t,goal:e.goal,provider:e.provider,device:e.device,reliability:s,iteration:0,running:!1,paused:!1,messages:at(e.goal,n,o),executionLog:[],diagnostics:[],runtimeContext:{lastObserveRawDump:null,lastObserveNodes:[],pendingVerifyAction:null,softPendingVerify:null,lastSearchContextTimestamp:null,consecutiveFailures:0,sameToolFingerprintCount:0,lastToolFingerprint:null,planningRetries:0,toolRetries:0,scriptGenerationRetries:0,convergenceTriggers:0},plan:n,currentPhase:"runtime",planningContext:{messages:[]}}}(t,n,s)),this.persistSession();if(!1!==t.enablePlanning){this.session.currentPhase="planning",this.persistSession();if("paused"===await this.runPlanningStage(this.session))return{sessionId:n}}else this.session.currentPhase="runtime";return this.persistSession(),await this.runLoop(),{sessionId:n}}async resume(e){if(!this.session||this.session.sessionId!==e.sessionId)throw new ze("SESSION_NOT_FOUND","session not found or expired");if(ct(this.session),!this.session.paused)throw new ze("SESSION_NOT_PAUSED","agent is not paused");if("planning"===this.session.currentPhase){this.session.planningContext?.messages.push({role:"user",content:e.message,timestamp:Date.now()}),this.session.paused=!1,this.persistSession();if("paused"===await this.runPlanningStage(this.session))return;return void await this.runLoop()}var t,s;t=this.session,s=e.message,t.messages.push({role:"user",content:s,timestamp:Date.now()}),this.persistSession(),await this.runLoop()}stop(e){this.session&&this.session.sessionId===e&&(this.session.running=!1,this.session.paused=!1,this.abortController&&(this.abortController.abort(),this.abortController=null),this.persistSession())}async generateScript(e){const t=this.loadSessionIfNeeded(e);if(ct(t),t.running)throw new ze("AGENT_RUNNING","agent is still running");if(0===t.executionLog.length)throw new ze("EMPTY_EXECUTION_LOG","no execution log available for script generation; run actions first");if(t.runtimeContext.pendingVerifyAction){const s=t.runtimeContext.pendingVerifyAction,n=[...t.diagnostics].reverse().find(e=>e.code===Dt);throw new ze("SCRIPT_GENERATION_FAILED",`script generation blocked: action #${s.index} ${s.toolName} has not been verified. Resume with observe_screen(wait_ms) -> verify_ui_state before generating script.`,{sessionId:e,pendingVerify:s,...n?{lastDiagnostic:n}:{}})}let s;t.currentPhase="script_generation",this.emitter.emit("scriptgenerating",{sessionId:t.sessionId});try{const n=await _(t.goal,t.executionLog,t.provider,t.reliability);if(s=n.workflow,n.attempts>1){t.runtimeContext.scriptGenerationRetries+=n.attempts-1;const s=rt(t,{sessionId:e,phase:"script_generation",code:Mt,message:`script generation retried ${n.attempts} attempts`,details:{errors:n.errors}});this.emitter.emit("diagnostic",s)}}catch(s){const n=Ye(s,"SCRIPT_GENERATION_FAILED");throw new ze("SCRIPT_GENERATION_FAILED",`script generation failed: ${n.message}`,{sessionId:e,executionLogSize:t.executionLog.length,cause:n.message,...n.details})}t.running=!1,t.paused=!0,this.persistSession();const n={sessionId:t.sessionId,workflow:s,executionLog:[...t.executionLog],totalIterations:t.iteration,diagnostics:it(t)};return this.emitter.emit("complete",n),n}listSessions(e={}){return this.sessionStore?this.sessionStore.listSessions(e):[]}countSessions(e={}){return this.sessionStore?this.sessionStore.countSessions(e):0}getSessionSummary(e){return this.sessionStore?this.sessionStore.getSessionSummary(e):null}deleteSession(e){return!!this.sessionStore&&(this.session?.sessionId===e&&(this.stop(e),this.session=null,this.localToolContext={lastObserveRawDump:null,lastObserveNodes:[]}),this.sessionStore.deleteSession(e))}async destroy(){this.session&&this.stop(this.session.sessionId),await this.toolRegistry.destroy(),this.localToolContext={lastObserveRawDump:null,lastObserveNodes:[]},this.sessionStore&&(this.sessionStore.close(),this.sessionStore=null),this.emitter.clear()}async runPlanningStage(e){this.abortController=new AbortController;const t=this.abortController.signal;this.emitter.emit("planning",{sessionId:e.sessionId,status:"started"});let s=0,n=[];try{const o=await nt(e.goal,e.provider,e.device,e.reliability,{signal:t,planningMessages:e.planningContext?.messages||[]});return s=o.attempts,n=o.errors,"paused"===o.status?(e.currentPhase="planning",e.planningContext?.messages.push({role:"assistant",content:o.text,timestamp:Date.now()}),this.applyPlanningDiagnostics(e,s,n),this.emitter.emit("planning",{sessionId:e.sessionId,status:"paused",text:o.text}),this.markSessionPaused(e,o.text,"planning"),"paused"):(lt(e,o.plan,o.screenDump),o.screenRawDump&&ye(this.localToolContext,o.screenRawDump),this.applyPlanningDiagnostics(e,s,n),this.emitter.emit("planning",{sessionId:e.sessionId,status:"completed",plan:o.plan}),"completed")}catch(t){if("AbortError"===t.name)throw t;const o=Ye(t,"RUNTIME_LOOP_FAILED");return n.push(o.message),this.emitter.emit("error",{sessionId:e.sessionId,error:`task planning failed and was skipped: ${o.message}`,code:"RUNTIME_LOOP_FAILED",details:{phase:"planning",cause:o.message}}),lt(e),this.applyPlanningDiagnostics(e,s,n),"completed"}finally{this.abortController=null}}applyPlanningDiagnostics(e,t,s){if(t>1&&(e.runtimeContext.planningRetries+=t-1),0===s.length)return;const n=s[s.length-1],o=rt(e,{sessionId:e.sessionId,phase:"planning",code:t>1?At:Ot,message:`planning stage used retries or fallback: ${n}`,details:{attempts:t||1,errors:s}});this.emitter.emit("diagnostic",o)}async runLoop(){const e=this.session;if(!e)return;if(!this.provider)throw new ze("INVALID_CONFIG","model provider has not been initialized");ct(e),e.running=!0,e.paused=!1,e.currentPhase="runtime",this.abortController=new AbortController;const t=this.abortController.signal;try{for(;e.running;){const s=e.reliability.limits.maxIterations;if(s>0&&e.iteration>=s){e.runtimeContext.convergenceTriggers+=1;const t=rt(e,{sessionId:e.sessionId,phase:"policy",code:Rt,message:`iteration limit reached: ${s}`,iteration:e.iteration});return this.emitter.emit("diagnostic",t),void this.markSessionPaused(e,"execution paused after hitting iteration limit; review goal or adjust strategy","policy")}e.iteration++;const n=Nt(e.messages),o=await this.provider.chatWithTools(n,this.toolRegistry.getModelTools(),t),r=o.content||"";r&&this.emitter.emit("thinking",{sessionId:e.sessionId,text:r,iteration:e.iteration});const i=this.toolRegistry.annotateToolCalls(o.toolCalls||[]);if(0===i.length)return r.trim().length>0&&ut(e,r),void this.markSessionPaused(e,r,"runtime");ut(e,r,i);for(const s of i){if(!e.running)break;this.emitter.emit("toolcall",{sessionId:e.sessionId,toolCall:s,iteration:e.iteration});const n=this.getVerifyGateError(e,s);if(n){const t={toolCallId:s.id,name:s.name,success:!1,error:n,errorCode:"RUNTIME_GUARD",retryable:!1,source:s.source,server:s.server},o=rt(e,{sessionId:e.sessionId,phase:"runtime",code:Dt,message:n,iteration:e.iteration,details:{pendingVerify:e.runtimeContext.pendingVerifyAction,blockedTool:s.name}});this.emitter.emit("diagnostic",o),this.emitter.emit("toolresult",{sessionId:e.sessionId,result:t,iteration:e.iteration}),dt(e,s.id,We(t)),this.persistSession();continue}const o=Bt(s),r=Xt(e.reliability,o),i=Wt(e.reliability,s.name),a=await Gt({toolCall:s,device:e.device,maxAttempts:r,timeoutMs:i,signal:t,execute:(e,t,s)=>this.toolRegistry.execute(e,t,s,this.localToolContext),onRetry:(t,n)=>{e.runtimeContext.toolRetries+=1;const o=rt(e,{sessionId:e.sessionId,phase:"runtime",code:kt,message:`tool retry: ${s.name}, attempt ${t+1}`,iteration:e.iteration,details:{attempt:t,maxAttempts:r,errorCode:n.errorCode,error:n.error}});this.emitter.emit("diagnostic",o)}});a.source||(a.source=s.source,a.server=s.server),this.emitter.emit("toolresult",{sessionId:e.sessionId,result:a,iteration:e.iteration});if(!a.success&&"VALIDATION"===a.errorCode||xt(e,s,a,this.toolRegistry.getCategory(s,a)),dt(e,s.id,We(a)),this.updateRuntimeStateAfterTool(e,s,a,o),this.localToolContext.softPendingVerify=e.runtimeContext.softPendingVerify,this.localToolContext.lastSearchContextTimestamp=e.runtimeContext.lastSearchContextTimestamp,this.persistSession(),!e.running)break}}}catch(t){if("AbortError"===t.name)return e.running=!1,e.paused=!1,void this.persistSession();e.running=!1,e.paused=!0;const s=Ye(t,"RUNTIME_LOOP_FAILED");this.emitter.emit("error",{sessionId:e.sessionId,error:s.message,code:s.code,details:s.details}),this.persistSession({lastError:s.message,lastErrorCode:s.code})}finally{this.abortController=null}}updateRuntimeStateAfterTool(e,t,s,n){const o=zt(t);if(e.runtimeContext.lastToolFingerprint===o?e.runtimeContext.sameToolFingerprintCount+=1:(e.runtimeContext.lastToolFingerprint=o,e.runtimeContext.sameToolFingerprintCount=1),s.success)e.runtimeContext.consecutiveFailures=0;else{"VALIDATION"===s.errorCode||"RUNTIME_GUARD"===s.errorCode||(e.runtimeContext.consecutiveFailures+=1)}if(f.has(t.name)&&s.success)e.runtimeContext.pendingVerifyAction=null,e.runtimeContext.softPendingVerify=null;else if("action"===n&&s.success&&g.has(t.name)){const s=e.executionLog.length;e.runtimeContext.pendingVerifyAction={index:s,toolName:t.name}}if(h.has(t.name)&&s.success&&(e.runtimeContext.softPendingVerify={index:e.executionLog.length,toolName:t.name}),"record_search_context"===t.name&&s.success&&(e.runtimeContext.lastSearchContextTimestamp=Date.now()),e.runtimeContext.consecutiveFailures>=e.reliability.limits.maxConsecutiveFailures){e.runtimeContext.convergenceTriggers+=1;const t=rt(e,{sessionId:e.sessionId,phase:"policy",code:Lt,message:`consecutive failure limit reached: ${e.reliability.limits.maxConsecutiveFailures}`,iteration:e.iteration,details:{consecutiveFailures:e.runtimeContext.consecutiveFailures}});return this.emitter.emit("diagnostic",t),void this.markSessionPaused(e,"execution paused due to excessive consecutive failures","policy")}if(e.runtimeContext.sameToolFingerprintCount>=e.reliability.limits.maxSameToolFingerprint){e.runtimeContext.convergenceTriggers+=1;const t=rt(e,{sessionId:e.sessionId,phase:"policy",code:$t,message:`same tool fingerprint repeat limit reached: ${e.reliability.limits.maxSameToolFingerprint}`,iteration:e.iteration,details:{fingerprint:o,repeatCount:e.runtimeContext.sameToolFingerprintCount}});this.emitter.emit("diagnostic",t),this.markSessionPaused(e,"execution paused due to repeated non-converging tool calls","policy")}}getVerifyGateError(e,t){return function(e,t){return"local"!==t.source?null:e&&ue(t.name)?p.has(t.name)?null:`BLOCKED: #${e.index} ${e.toolName} 尚未验证。你必须先调用 observe_screen(wait_ms) 再调用 verify_ui_state,然后才能执行下一个动作。`:null}(e.runtimeContext.pendingVerifyAction,t)}markSessionPaused(e,t,s){e.running=!1,e.paused=!0,this.emitter.emit("paused",{sessionId:e.sessionId,text:t,iteration:e.iteration,phase:s}),this.persistSession()}persistSession(e){this.session&&this.sessionStore&&this.sessionStore.saveSession(this.session,e)}loadSessionIfNeeded(e){if(this.session&&this.session.sessionId===e)return ct(this.session);if(!this.sessionStore)throw new ze("SESSION_NOT_FOUND","session not found or expired");const t=this.sessionStore.loadSession(e);if(!t)throw new ze("SESSION_NOT_FOUND","session not found or expired");return this.session=ct(t),this.localToolContext={lastObserveRawDump:null,lastObserveNodes:[]},this.session}}function Yt(e={}){return new Jt(e)}async function Ht(e,t){const s=i(t),n=[{role:"system",content:b(e)},{role:"user",content:e}];return(await s.chatWithTools(n,[])).content.trim()}export{ze as AgentRuntimeError,Jt as AutomationAgentRuntime,u as DEFAULT_RELIABILITY_CONFIG,Yt as createAutomationAgentRuntime,Ht as optimizeIntent,Ye as toRuntimeError};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vmosedge/workflow-agent-sdk",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "VMOS Edge 自动化 SDK,提供任务规划、设备执行与工作流脚本生成能力",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -48,10 +48,21 @@
48
48
  "langchain",
49
49
  "workflow"
50
50
  ],
51
+ "scripts": {
52
+ "clean": "rm -rf dist .rollup-tmp",
53
+ "build:js": "tsc -p tsconfig.rollup.json",
54
+ "build:bundle": "rollup -c rollup.config.mjs",
55
+ "build:types": "rollup -c rollup.config.types.mjs",
56
+ "build": "pnpm run clean && pnpm run build:js && pnpm run build:bundle && pnpm run build:types && rm -rf .rollup-tmp",
57
+ "prepublishOnly": "pnpm run build && npm pack --dry-run",
58
+ "typecheck": "tsc --noEmit -p tsconfig.json",
59
+ "test": "vitest run"
60
+ },
51
61
  "dependencies": {
52
62
  "@langchain/anthropic": "1.3.21",
53
63
  "@langchain/core": "^1.1.29",
54
64
  "@langchain/google-genai": "2.1.23",
65
+ "@langchain/langgraph": "^1.2.0",
55
66
  "@langchain/mcp-adapters": "^1.1.3",
56
67
  "@langchain/openai": "^1.2.10",
57
68
  "better-sqlite3": "^12.5.0",
@@ -67,14 +78,5 @@
67
78
  "rollup-plugin-dts": "^6.3.0",
68
79
  "typescript": "^5.9.2",
69
80
  "vitest": "^3.2.4"
70
- },
71
- "scripts": {
72
- "clean": "rm -rf dist .rollup-tmp",
73
- "build:js": "tsc -p tsconfig.rollup.json",
74
- "build:bundle": "rollup -c rollup.config.mjs",
75
- "build:types": "rollup -c rollup.config.types.mjs",
76
- "build": "pnpm run clean && pnpm run build:js && pnpm run build:bundle && pnpm run build:types && rm -rf .rollup-tmp",
77
- "typecheck": "tsc --noEmit -p tsconfig.json",
78
- "test": "vitest run"
79
81
  }
80
- }
82
+ }