@vmosedge/workflow-agent-sdk 1.0.2 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunks/scriptGenerator-CtSwPsED.js +1 -0
- package/dist/chunks/scriptGenerator-D_hIqEQc.cjs +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.js +1 -1
- package/dist/runtime/index.cjs +1 -1
- package/dist/runtime/index.d.ts +7 -2
- package/dist/runtime/index.js +1 -1
- package/dist/types/index.d.ts +6 -0
- package/package.json +1 -1
- package/dist/chunks/scriptGenerator-CgLLATgN.js +0 -1
- package/dist/chunks/scriptGenerator-DzkshEpz.cjs +0 -1
package/dist/runtime/index.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var e=require("uuid"),t=require("../chunks/scriptGenerator-DzkshEpz.cjs"),s=require("fs"),n=require("os"),o=require("path"),r=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 o.join(function(){const e=n.homedir();if(!e)throw new Error("Failed to resolve user home directory for session persistence");return o.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=o.dirname(e);s.existsSync(t)||s.mkdirSync(t,{recursive:!0})}(e),this.db=new r(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?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),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(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 p(e,t,s){return d(e,t)===d(e,s)}function m(e,t){return(void 0===t.index||e.index===t.index)&&(!!(!t.resourceId||e.resourceId&&p("resourceId",t.resourceId,e.resourceId))&&(!!(!t.text||e.text&&p("text",t.text,e.text))&&(!!(!t.contentDesc||e.contentDesc&&p("contentDesc",t.contentDesc,e.contentDesc))&&(!!(!t.className||e.className&&p("className",t.className,e.className))&&!!(!t.bounds||e.bounds&&p("bounds",t.bounds,e.bounds))))))}function _(e,t){let s=0;for(const n of e)m(n,t)&&(s+=1);return s}function g(e,t,s){return void 0===t||void 0===s?t===s:p(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 o of e)if(m(o,t)){if(f(o,s))return n;n+=1}}const b=e=>i.z.string().describe(e),y=e=>i.z.coerce.number().describe(e),v=e=>i.z.coerce.number().int().positive().describe(e),x=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,...o}=t;return o}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"],"可选:显式声明该记录应进入主流程、异常处理还是仅日志。仅在你有把握时填写;不确定则省略。"),D=w(["next_action","next_verified_transition","manual_close"],"可选:声明该上下文持续到何时结束。默认 next_verified_transition。"),R=i.z.object({step_intent:b("本次操作在任务流程中的目的和预期效果。必须说明为什么执行此操作、期望达到什么状态,而非重复元素属性描述。"),merge_key:T("可选:稳定的步骤合并键。用于把同类动作合并为同一脚本步骤,例如 open_search_page、scroll_result_list。仅在你能明确抽象出可复用步骤语义时填写。"),expected_result:T("可选:本次动作完成后预期达到的状态描述。建议使用比 step_intent 更直接的结果表述,例如“进入搜索页”“列表向下推进一屏”。"),wait_ms_hint:x("可选:建议回放时在主动作后等待或验证的毫秒数。仅在你明确知道该步骤通常需要额外等待时填写。"),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:v("等待元素出现超时(ms,最小 3000)").optional(),wait_interval:v("重试间隔(ms)").optional()}),$=C({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"}),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"}),P=C({name:"get_top_activity",description:"获取当前前台应用的 Activity 信息,返回 package_name 和 class_name。用于判断当前在哪个应用的哪个页面。",schema:i.z.object({}),category:"observation"}),j=C({name:"get_screen_info",description:"获取屏幕显示信息,返回 width、height、rotation、orientation。用于确定屏幕分辨率以计算滑动/点击坐标。",schema:i.z.object({}),category:"observation"}),M=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])")}),q=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:x("可选:建议回放在验证前等待的毫秒数。仅在你明确知道该步骤通常需要额外等待时填写。"),verify_element:M.describe("用于验证的 UI 元素,必须来自当前 observe_screen 结果"),screen_desc:b('当前页面的简要描述,用一句话概括所在页面(如"抖音首页信息流"、"微信聊天列表"、"系统设置-通用页面")。此字段帮助脚本生成 AI 理解操作上下文,必填。'),page_anchor:M.optional().describe("动作后的页面锚点元素(如页面切换后的新页面标题/导航栏)。当操作导致页面切换时优先填写,用于脚本生成时确认导航成功。")}),category:"observation"}),z=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:x("可选:建议回放时在本轮动作后等待或验证的毫秒数。仅在你明确知道该步骤通常需要额外等待时填写。"),target_desc:b("本轮查找目标描述"),target_element:z.optional().describe("查找目标元素,可选"),anchor_element:z.describe("当前页面锚点元素,必填"),page_kind:S,target_role:A,criticality_hint:O,include_mode_hint:L,valid_until:D}),category:"observation"});var Y;const B=[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:R,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:R,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:R,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:(Y="Android KeyCode",i.z.coerce.number().int().describe(Y))}),category:"action"}),C({name:"wait",description:"暂停指定时间。用于等待页面加载或动画完成。时长需按前一动作自适应:轻交互 300~800ms,滑动后 500~1200ms,页面切换/返回 1200~2500ms,启动应用 2000~4000ms;禁止全程固定同一时长。",schema:i.z.object({duration:y("等待时间(ms)")}),category:"action"})],V=[...[$,q,G,U,P,j],...B];function X(e){return B.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 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(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});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 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 o=_(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 te(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=Q(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,p=e.bounds;if(function(e,t,s,n,o){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:o.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:o.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: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&&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:o.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:o.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:o.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: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=>m(e,a));c.length>1&&Z(c)&&ee(e,s,{resource_id:n.resourceId},{action_index:i??h(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=>m(e,a));c.length>1&&Z(c)&&ee(e,s,{text:n.text},{action_index:i??h(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=>m(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===o.get("content_desc")?.reusable,reason:"content_desc_positional",scope:"collection",policy_decision:"approved",policy_reason:r.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:r.policyReason})}}(a,e,t,{resourceId:c,text:l,contentDesc:u,className:d,bounds:p},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,m=!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:o.policyReason}):l?ee(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?ee(a,t,{content_desc:u},{stability:"low",reusable:m,reason:"content_desc_positional",scope:"collection",action_index:s.requestedActionIndex,policy_decision:"approved",policy_reason:o.policyReason}):d&&p&&ee(a,t,{class_name:d,bounds:p},{stability:"low",reusable:!1,reason:"class_bounds_fallback",scope:"page",requireUnique:!0,policy_decision:"approved",policy_reason:o.policyReason})}const _=a[0];return _?{mode:n,field_assessments:r,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:(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 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 re(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 ie(e,t,s){let n=0;for(const o of e)if(he(o,t)){if(n===s)return o;n++}return null}async function ae(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 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){if(null==e||""===e)return 5e3;const t="string"==typeof e?Number(e):e;return"number"!=typeof t||!Number.isFinite(t)||t<=0?5e3:Math.max(3e3,Math.trunc(t))}function pe(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 _e(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 ge(e){return _e({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 fe(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 he(e,t){return m(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&&p("resourceId",s,t.resourceId);case"text":return!!t.text&&p("text",s,t.text);case"content_desc":return!!t.contentDesc&&p("contentDesc",s,t.contentDesc);case"class_name":return!!t.className&&p("className",s,t.className);case"bounds":return!!t.bounds&&p("bounds",s,t.bounds)}})}function ve(e,t){return _(e,t)}function xe(e,t){const s=fe(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)(re(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,t){const s=fe(e.selector),n=ce(t.script_selector),o=fe(n?.selector);if(!s||!o)return null;const r=ue(e.action_index),i=ue(n?.action_index),a=be(s).map(e=>e.key),c=be(o).map(e=>e.key);if(ye(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 be(t))if(!(()=>{switch(s){case"resource_id":return!!e.resourceId&&p("resourceId",n,e.resourceId);case"text":return!!e.text&&p("text",n,e.text);case"content_desc":return!!e.contentDesc&&p("contentDesc",n,e.contentDesc);case"class_name":return!!e.className&&p("className",n,e.className);case"bounds":return!!e.bounds&&p("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,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}}function Te(e,s){if(!t.SELECTOR_ACTION_TOOLS.has(e))return null;const n=fe(s.selector),o=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 o={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 o.resourceId||o.text||o.contentDesc||o.className||o.bounds?o:null}(s.pre_context);return n&&o?function(e,t){return void 0!==e.index&&void 0!==t.index&&e.index===t.index||!!(e.resourceId&&t.resourceId&&p("resourceId",e.resourceId,t.resourceId))||!!(e.text&&t.text&&p("text",e.text,t.text))||!!(e.contentDesc&&t.contentDesc&&p("contentDesc",e.contentDesc,t.contentDesc))||!!(e.className&&t.className&&p("className",e.className,t.className))||!!(e.bounds&&t.bounds&&p("bounds",e.bounds,t.bounds))}(n,o)?null:"pre_context.target 对齐提醒: selector 与 pre_context.target 证据不一致。本次执行仍继续,但录制语义可能不准确。":null}const we={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 ae(t,"POST","base/sleep",{duration:o},s);if(200!==e.code)return{success:!1,error:e.msg||"Wait before observe failed"}}const r=await ae(t,"POST","accessibility/dump_compact",{},s);return 200==r.code&&"string"==typeof r.data?(n&&se(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=[],r=me(e.step_desc);r||o.push("录制提醒: verify_ui_state 未填写 step_desc,步骤摘要会缺失。");const i=me(e.screen_desc);i||o.push("录制提醒: verify_ui_state 未填写 screen_desc,页面上下文会缺失。");const a=function(e){return _e({page_kind:me(e.page_kind)})}(e),c={...r?{step_desc:r}:{},...i?{screen_desc:i}:{},...a?{recorded_page:a}:{}},l=e.verify_element;if(!l||"object"!=typeof l||Array.isArray(l))return o.push("录制提醒: verify_ui_state 未提供 verify_element,未记录验证元素。"),{success:!0,data:c,warning:o.join("\n")};const u=pe(l,"verify_element");if(u)return o.push(`录制提醒: ${u}`),{success:!0,data:c,warning:o.join("\n")};const d=fe(l);if(!d)return o.push("录制提醒: verify_element 未提供可用 selector 字段,未形成验证记录。"),{success:!0,data:c,warning:o.join("\n")};const p=e.page_anchor;if(p&&"object"==typeof p&&!Array.isArray(p)){const e=pe(p,"page_anchor");e&&o.push(`录制提醒: ${e}`)}const m=n?.lastObserveNodes;if(!m||0===m.length)return o.push("录制提醒: verify_ui_state 前未调用 observe_screen,未记录验证快照。"),{success:!0,data:c,warning:o.join("\n")};if(ve(m,d)>0){c._verification_matched=!0,c.verify_element=l;const e=ie(m,d,0);if(e){const t=ne(e,m,{mode:"verify"});t&&(c.verification_plan=t)}if(p&&"object"==typeof p&&!Array.isArray(p)){const e=fe(p);if(e){if(0===ve(m,e))o.push("录制提醒: page_anchor 未匹配当前 observe_screen,未记录页面锚点。");else{c.page_anchor=p;const t=ie(m,e,0);if(t){const e=ne(t,m,{mode:"anchor"});e&&(c.page_anchor_plan=e)}}}else o.push("录制提醒: page_anchor 未提供可用 selector 字段,未记录页面锚点。")}return!!c.verification_plan||!!c.page_anchor_plan||o.push("补录提醒: 当前 verify_ui_state 虽然匹配成功,但未形成可回放 verification_plan。请优先重新 observe_screen,并改用单字段且唯一的 verify_element(resource_id -> text -> content_desc);若发生页面切换,还可补充可唯一定位的 page_anchor。若主流程已明确,也可以继续执行,但录制质量会下降。"),{success:!0,data:c,...o.length>0?{warning:o.join("\n")}:{}}}const _=[void 0!==d.index&&`index=${d.index}`,d.resourceId&&`resource_id="${d.resourceId}"`,d.text&&`text="${d.text}"`,d.contentDesc&&`content_desc="${d.contentDesc}"`,d.className&&`class_name="${d.className}"`].filter(Boolean).join(", "),g=m.filter(e=>e.text||e.contentDesc||e.resourceId&&!re(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&&!re(e.resourceId)&&t.push(`id="${e.resourceId}"`),t.push(`[${e.className.split(".").pop()}]`),` ${t.join(" ")}`}),f=g.length>0?`\n当前界面可用的验证候选元素(直接从中选择,无需再调 observe_screen):\n${g.join("\n")}`:"";return o.push(`验证元素在当前界面中不存在:${_} 未匹配。请从当前界面重新选择 verify 元素。${f}`),{success:!0,data:{...c,_verification_matched:!1,verify_element:l},warning:o.join("\n")}},async record_search_context(e,t,s,n){const o=[],r=me(e.step_intent);r||o.push("录制提醒: record_search_context 未填写 step_intent。");const i=me(e.target_desc);i||o.push("录制提醒: record_search_context 未填写 target_desc。");const a={...e,...r?{step_intent:r}:{},...i?{target_desc:i}:{},recorded_context:ge({...e,...r?{step_intent:r}:{},...i?{target_desc:i}:{}})},c=n?.lastObserveNodes;if(!c||0===c.length)return o.push("录制提醒: record_search_context 前未调用 observe_screen,未记录页面锚点快照。"),{success:!0,data:a,warning:o.join("\n")};const l=e.anchor_element;if(!l||"object"!=typeof l||Array.isArray(l))o.push("录制提醒: record_search_context 未提供 anchor_element。");else{const e=l,t=pe(e,"anchor_element");if(t)o.push(`录制提醒: ${t}`);else 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}(e)){const e=fe(l);if(e)if(0===ve(c,e))o.push("录制提醒: anchor_element 未匹配当前 observe_screen,未记录页面锚点。");else{const t=ie(c,e,0);if(t){const e=ne(t,c,{mode:"anchor"});e&&(a.anchor_plan=e)}}else o.push("录制提醒: anchor_element 不是合法 selector。")}else o.push("录制提醒: anchor_element 未提供可用 selector 字段。")}const u=e.target_element;if(u&&"object"==typeof u&&!Array.isArray(u)){const e=pe(u,"target_element");if(e)o.push(`录制提醒: ${e}`);else{const e=fe(u);if(e)if(0===ve(c,e))o.push("录制提醒: target_element 未匹配当前 observe_screen,未记录目标快照。");else{const t=ie(c,e,0);if(t){const e=ne(t,c,{mode:"action"});e&&(a.target_plan=e)}}else o.push("录制提醒: target_element 未提供可用 selector 字段。")}}return{success:!0,data:a,...o.length>0?{warning:o.join("\n")}:{}}},async get_installed_apps(e,t,s){const n=e.type||"user",o=await ae(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 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 o=await ae(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 ae(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 ae(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 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 o={selector:e.selector,action:"click",wait_timeout:de(e.wait_timeout),wait_interval:e.wait_interval??1e3};void 0!==n.value&&(o.action_index=n.value);const r=await ae(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=ue(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:de(e.wait_timeout),wait_interval:e.wait_interval??1e3};void 0!==n.value&&(o.action_index=n.value);const r=await ae(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=ue(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:de(e.wait_timeout),wait_interval:e.wait_interval??1e3};void 0!==n.value&&(o.action_index=n.value);const r=await ae(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 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 Ne(e,s,n,o){const r=we[e.name],i=(a=e.name,V.find(e=>e.name===a));var a;if(!r||!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"),o=le(s,"action_index"),r=ce(s.selector),i=!!r&&le(r,"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||o?"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),p=d.success?d.data:c,m=[];if(!d.success){const t=d.error.issues.map(e=>`${e.path.join(".")}: ${e.message}`).join("; ");m.push(`参数补录提醒: ${e.name} 参数未完全符合推荐 schema,已按原始参数继续执行。${t}`)}m.push(...function(e,s){if(!t.PRE_CONTEXT_REQUIRED_TOOLS.has(e))return[];const n=[],o=s.pre_context;if(!o||"object"!=typeof o||Array.isArray(o))return n.push(`pre_context 缺失:${e} 调用前必须提供 pre_context(anchor + target)。本次执行仍继续,但录制语义会缺失。`),n;const r=o,i=r.anchor;!i||"object"!=typeof i||Array.isArray(i)?n.push("pre_context.anchor 缺失:必须提供页面锚点元素用于确认当前页面。本次执行仍继续,但录制语义会缺失。"):me(i.desc)||n.push("pre_context.anchor.desc 缺失:必须提供页面锚点描述,用于说明当前页面上下文。");const a=r.target;!a||"object"!=typeof a||Array.isArray(a)?n.push("pre_context.target 缺失:必须提供操作目标元素信息。本次执行仍继续,但录制语义会缺失。"):me(a.desc)||n.push("pre_context.target.desc 缺失:必须提供目标元素描述,用于说明本次操作意图。");return me(r.step_intent)||n.push("pre_context.step_intent 缺失:必须说明本次操作在任务中的目的和预期效果,而非重复元素描述。此字段用于脚本生成的步骤命名和合并。"),n}(e.name,p));const _=Te(e.name,p);_&&m.push(_);const g=function(e,s,n){if(!t.SELECTOR_ACTION_TOOLS.has(e))return null;const o=fe(s.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=ve(r,o);if(0===i)return"validation failed: selector has no match on latest observed screen.";const a=ue(s.action_index);return a.ok?void 0!==a.value&&a.value>=i?`validation failed: action_index=${a.value} is out of range for matched_count=${i}.`:i>1&&void 0===a.value?`validation failed: selector is ambiguous (matched_count=${i}). Refine selector or provide action_index.`:null:"validation failed: action_index must be a non-negative integer."}(e.name,p,o);if(g)return{toolCallId:e.id,name:e.name,success:!1,error:g};try{const i=await r(p,s,n,o);if(i.success){const s=[...m];if(t.SELECTOR_ACTION_TOOLS.has(e.name)&&o?.lastObserveNodes?.length){const e=fe(p.selector),t=ue(p.action_index);if(e){const n=ie(o.lastObserveNodes,e,t.value??0);if(n){const e=ne(n,o.lastObserveNodes,{mode:"action",requestedActionIndex:t.value});if(e){const t=i.data&&"object"==typeof i.data&&!Array.isArray(i.data)?i.data:{},n=Ie(p,e);i.data={...t,_selector_recording_profile:e,...n?{_selector_request_analysis:n}:{}};const o=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;o&&s.push(o)}}}}if(t.SELECTOR_ACTION_TOOLS.has(e.name)&&o?.lastObserveNodes){const e=xe(p,o.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=fe(t.anchor),n=fe(t.target);return s&&n&&ye(s,n)?"pre_context 质量提醒: anchor 与 target 使用了完全相同的定位证据。anchor 应优先描述当前页面或容器级稳定锚点,而不是与点击目标重合。":null}(p);e&&s.push(e)}if(o?.softPendingVerify&&X(e.name)){const e=o.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&&(!o?.lastSearchContextTimestamp||Date.now()-o.lastSearchContextTimestamp>3e4)&&s.push("⚠️ 录制提醒: swipe 前未调用 record_search_context,录制信息将不完整。建议先调用 record_search_context 记录查找目标与页面锚点。"),{toolCallId:e.id,name:e.name,success:!0,data:i.data,warning:[i.warning,...s].filter(e=>!!e).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 Se(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=Ee(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 Ae(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=Ce.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=Se(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 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 De=["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 ke(e,t="UNKNOWN"){if(e instanceof Le)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 Le("string"==typeof(n=e.code)&&De.includes(n)?e.code:t,s,Re(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 Pe(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 je(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 Me(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 qe(e,s,n,o,r={}){return async function(e,s,n,o,r={}){const i=t.detectPromptLocale(e),a={lastObserveRawDump:null,lastObserveNodes:[]},c=r.planningMessages||[],l=!1!==r.allowPause,u=await Ne({id:"plan-observe-screen",name:"observe_screen",arguments:{},source:"local"},n,r.signal,a),d=u.success&&"string"==typeof u.data?u.data:Ae(u),p=a.lastObserveRawDump||void 0,m=t.createProvider(s),_=[],g=Math.max(1,o.planner.maxAttempts);for(let s=1;s<=g;s++){const n=je(s,_,i),o=[{role:"system",content:t.buildTaskPlanPrompt(e,l)},{role:"user",content:Me(e,d,n,i)},...c];try{let t;if(m.capabilities.structuredJson)t=await m.chatStructuredJson(o,$e,r.signal);else{const e=await m.chatWithTools(o,[],r.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:p,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:p,attempts:s,errors:_}}catch(e){_.push(e.message||String(e))}}return{status:"completed",plan:Pe(e,i),screenDump:d,screenRawDump:p,attempts:g,errors:_}}(e,s,n,o,r)}function ze(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 Ye(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:ze(t,e.length),totalActionCalls:s,successfulActionCalls:n,automationSuccessRate:ze(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 Be(e,s,n,o=[]){const r=[{role:"system",content:t.buildAgentSystemPrompt(e,s)},{role:"user",content:e}];for(const e of o)"assistant"!==e.role&&"user"!==e.role||r.push({role:e.role,content:e.content,timestamp:e.timestamp});return n&&(r.push({role:"assistant",content:"",toolCalls:[{id:"plan-initial-observe",name:"observe_screen",arguments:{},source:"local"}]}),r.push({role:"tool",content:n,toolCallId:"plan-initial-observe"})),r}function Ve(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){Ve(e),e.plan=t,e.currentPhase="runtime",e.messages=Be(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,o]of Object.entries(e))null!=o&&""!==o&&(t[n]=o,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),o=He(s.data);if(!n&&!o)return null;const r=et(He(n?.selector)||{}),i=Qe(n?.action_index),a=Array.isArray(o?.nodes)?o.nodes:[],c=Qe(o?.action_index),l=i??c??0,u=tt(a[l]??a[0]),d=a.slice(0,5).map(e=>tt(e)).filter(e=>!!e),p="number"==typeof o?.count&&Number.isFinite(o.count)?o.count:a.length,m={};r&&(m.requested_selector=r),void 0===i&&void 0===c||(m.action_index=l),p>=0&&(m.matched_count=p),u&&(m.selected_node=u),d.length>0&&(m.candidate_nodes=d);const _=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)})}(o?._selector_recording_profile);if(_){m.selector_profile=_;const e=He(_.script_selector);if(e){const t=He(e.selector);t&&(m.recommended_selector=t);const s=Qe(e.action_index);void 0!==s&&(m.recommended_action_index=s),"string"==typeof e.stability&&(m.selector_stability=e.stability),"string"==typeof e.reason&&(m.selector_reason=e.reason),"boolean"==typeof e.reusable&&(m.selector_reusable=e.reusable),"string"==typeof e.scope&&(m.selector_scope=e.scope)}}const g=He(o?._selector_request_analysis);return g&&(m.selector_request_analysis=g),Object.keys(m).length>0?m: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 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 ot(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 rt=[/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,o=[];let r=0;for(let e=1;e<t.length;e++){const s=t[e];s.includes("clickable=true")&&r++,rt.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 ct(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:at(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 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{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]=lt(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 dt=new Set(V.map(e=>e.name));class pt{mcpClient=null;mcpTools=new Map;modelTools=[...V];getModelTools(){return this.modelTools}async prepare(e={}){if(await this.closeMcpClient(),this.mcpTools.clear(),this.modelTools=[...V],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),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}`)}}(V.map(e=>e.name),e);for(const t of e)this.mcpTools.set(t.name,{tool:t.tool,server:t.server});this.modelTools=[...V,...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 o=this.resolveTool(e.name);if("local"===o.source){return{...await Ne({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=[...V]}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 mt="PLANNER_RETRY",_t="PLANNER_FALLBACK",gt="TOOL_RETRY",ft="VERIFY_GATE_BLOCKED",ht="MAX_ITERATIONS_REACHED",bt="CONSECUTIVE_FAILURE_LIMIT_REACHED",yt="SAME_TOOL_REPEAT_LIMIT_REACHED",vt="SCRIPT_RETRY",xt=new Set(["TIMEOUT","NETWORK","HTTP_5XX"]);function It(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 Tt(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 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(),o=Tt(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)?wt(e.toolCall,e.timeoutMs):Nt(e.toolCall,t)}finally{o.dispose()}if(r.latencyMs||(r.latencyMs=Date.now()-n),r.attempt=s,r.success)return r;const i=It(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 Et(e,t){if(!Number.isFinite(e))return t;const s=Math.floor(e);return s<=0?t:s}function St(e,t){return"action"===t?e.retries.action:e.retries.observation}function At(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 Dt(e){return"local"===e.source&&X(e.name)?"action":"observation"}class Rt{options;emitter=new Oe;toolRegistry=new pt;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=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=(o=s.reliability)?{retries:{observation:Et(o.retries?.observation??t.DEFAULT_RELIABILITY_CONFIG.retries.observation,t.DEFAULT_RELIABILITY_CONFIG.retries.observation),action:Et(o.retries?.action??t.DEFAULT_RELIABILITY_CONFIG.retries.action,t.DEFAULT_RELIABILITY_CONFIG.retries.action)},timeouts:{defaultMs:Et(o.timeouts?.defaultMs??t.DEFAULT_RELIABILITY_CONFIG.timeouts.defaultMs,t.DEFAULT_RELIABILITY_CONFIG.timeouts.defaultMs),observeScreenMs:Et(o.timeouts?.observeScreenMs??t.DEFAULT_RELIABILITY_CONFIG.timeouts.observeScreenMs,t.DEFAULT_RELIABILITY_CONFIG.timeouts.observeScreenMs),startAppMs:Et(o.timeouts?.startAppMs??t.DEFAULT_RELIABILITY_CONFIG.timeouts.startAppMs,t.DEFAULT_RELIABILITY_CONFIG.timeouts.startAppMs)},limits:{maxIterations:Et(o.limits?.maxIterations??t.DEFAULT_RELIABILITY_CONFIG.limits.maxIterations,t.DEFAULT_RELIABILITY_CONFIG.limits.maxIterations),maxConsecutiveFailures:Et(o.limits?.maxConsecutiveFailures??t.DEFAULT_RELIABILITY_CONFIG.limits.maxConsecutiveFailures,t.DEFAULT_RELIABILITY_CONFIG.limits.maxConsecutiveFailures),maxSameToolFingerprint:Et(o.limits?.maxSameToolFingerprint??t.DEFAULT_RELIABILITY_CONFIG.limits.maxSameToolFingerprint,t.DEFAULT_RELIABILITY_CONFIG.limits.maxSameToolFingerprint)},planner:{maxAttempts:Et(o.planner?.maxAttempts??t.DEFAULT_RELIABILITY_CONFIG.planner.maxAttempts,t.DEFAULT_RELIABILITY_CONFIG.planner.maxAttempts)},scriptGeneration:{maxAttempts:Et(o.scriptGeneration?.maxAttempts??t.DEFAULT_RELIABILITY_CONFIG.scriptGeneration.maxAttempts,t.DEFAULT_RELIABILITY_CONFIG.scriptGeneration.maxAttempts)}}:t.DEFAULT_RELIABILITY_CONFIG;var o;const r=s.sessionId||e.v4();this.localToolContext={lastObserveRawDump:null,lastObserveNodes:[]},this.session=Ve(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:Be(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:[]}}}(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(Ve(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(Ve(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");let n;s.currentPhase="script_generation",this.emitter.emit("scriptgenerating",{sessionId:s.sessionId});try{const o=await t.generateScriptWithReliability(s.goal,s.executionLog,s.provider,s.reliability);if(n=o.workflow,o.attempts>1){s.runtimeContext.scriptGenerationRetries+=o.attempts-1;const t=Ge(s,{sessionId:e,phase:"script_generation",code:vt,message:`script generation retried ${o.attempts} attempts`,details:{errors:o.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 o={sessionId:s.sessionId,workflow:n,executionLog:[...s.executionLog],totalIterations:s.iteration,diagnostics:Ye(s)};return this.emitter.emit("complete",o),o}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 qe(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"):(Xe(e,o.plan,o.screenDump),o.screenRawDump&&se(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=ke(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}}),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],o=Ge(e,{sessionId:e.sessionId,phase:"planning",code:t>1?mt:_t,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 Le("INVALID_CONFIG","model provider has not been initialized");Ve(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),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&&We(e,r),void this.markSessionPaused(e,r,"runtime");We(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=Ge(e,{sessionId:e.sessionId,phase:"runtime",code:ft,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}),Ke(e,s.id,Ae(t)),this.persistSession();continue}const o=Dt(s),r=St(e.reliability,o),i=At(e.reliability,s.name),a=await Ct({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=Ge(e,{sessionId:e.sessionId,phase:"runtime",code:gt,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||ot(e,s,a,this.toolRegistry.getCategory(s,a)),Ke(e,s.id,Ae(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=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,o){const r=Lt(s);if(e.runtimeContext.lastToolFingerprint===r?e.runtimeContext.sameToolFingerprintCount+=1:(e.runtimeContext.lastToolFingerprint=r,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"===o&&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 for recovery after excessive consecutive failures","recovery")}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:r,repeatCount:e.runtimeContext.sameToolFingerprintCount}});this.emitter.emit("diagnostic",t),this.markSessionPaused(e,"execution paused for recovery after repeated non-converging tool calls","recovery")}}getVerifyGateError(e,t){return e.runtimeContext.pendingVerifyAction,null}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 Ve(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=Ve(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=Rt,exports.createAutomationAgentRuntime=function(e={}){return new Rt(e)},exports.optimizeIntent=async function(e,s){const n=t.createProvider(s),o=[{role:"system",content:t.buildOptimizeIntentPrompt(e)},{role:"user",content:e}];return(await n.chatWithTools(o,[])).content.trim()},exports.toRuntimeError=ke;
|
|
1
|
+
"use strict";var e=require("uuid"),t=require("../chunks/scriptGenerator-D_hIqEQc.cjs"),s=require("fs"),n=require("os"),o=require("path"),r=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");function c(){return o.join(function(){const e=n.homedir();if(!e)throw new Error("Failed to resolve user home directory for session persistence");return o.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=o.dirname(e);s.existsSync(t)||s.mkdirSync(t,{recursive:!0})}(e),this.db=new r(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?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),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(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 p(e,t,s){return d(e,t)===d(e,s)}function m(e,t){return(void 0===t.index||e.index===t.index)&&(!!(!t.resourceId||e.resourceId&&p("resourceId",t.resourceId,e.resourceId))&&(!!(!t.text||e.text&&p("text",t.text,e.text))&&(!!(!t.contentDesc||e.contentDesc&&p("contentDesc",t.contentDesc,e.contentDesc))&&(!!(!t.className||e.className&&p("className",t.className,e.className))&&!!(!t.bounds||e.bounds&&p("bounds",t.bounds,e.bounds))))))}function _(e,t){let s=0;for(const n of e)m(n,t)&&(s+=1);return s}function g(e,t,s){return void 0===t||void 0===s?t===s:p(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 o of e)if(m(o,t)){if(f(o,s))return n;n+=1}}const b=e=>i.z.string().describe(e),y=e=>i.z.coerce.number().describe(e),v=e=>i.z.coerce.number().int().positive().describe(e),x=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,...o}=t;return o}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"],"可选:显式声明该记录应进入主流程、异常处理还是仅日志。仅在你有把握时填写;不确定则省略。"),D=w(["next_action","next_verified_transition","manual_close"],"可选:声明该上下文持续到何时结束。默认 next_verified_transition。"),R=i.z.object({step_intent:b("本次操作在任务流程中的目的和预期效果。必须说明为什么执行此操作、期望达到什么状态,而非重复元素属性描述。"),merge_key:T("可选:稳定的步骤合并键。用于把同类动作合并为同一脚本步骤,例如 open_search_page、scroll_result_list。仅在你能明确抽象出可复用步骤语义时填写。"),expected_result:T("可选:本次动作完成后预期达到的状态描述。建议使用比 step_intent 更直接的结果表述,例如“进入搜索页”“列表向下推进一屏”。"),wait_ms_hint:x("可选:建议回放时在主动作后等待或验证的毫秒数。仅在你明确知道该步骤通常需要额外等待时填写。"),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:v("等待元素出现超时(ms,最小 3000)").optional(),wait_interval:v("重试间隔(ms)").optional()}),$=C({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"}),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"}),j=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"}),M=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])")}),q=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:x("可选:建议回放在验证前等待的毫秒数。仅在你明确知道该步骤通常需要额外等待时填写。"),verify_element:M.describe("用于验证的 UI 元素,必须来自当前 observe_screen 结果"),screen_desc:b('当前页面的简要描述,用一句话概括所在页面(如"抖音首页信息流"、"微信聊天列表"、"系统设置-通用页面")。此字段帮助脚本生成 AI 理解操作上下文,必填。'),page_anchor:M.optional().describe("动作后的页面锚点元素(如页面切换后的新页面标题/导航栏)。当操作导致页面切换时优先填写,用于脚本生成时确认导航成功。")}),category:"observation"}),z=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:x("可选:建议回放时在本轮动作后等待或验证的毫秒数。仅在你明确知道该步骤通常需要额外等待时填写。"),target_desc:b("本轮查找目标描述"),target_element:z.optional().describe("查找目标元素,可选"),anchor_element:z.describe("当前页面锚点元素,必填"),page_kind:S,target_role:A,criticality_hint:O,include_mode_hint:L,valid_until:D}),category:"observation"});var Y;const B=[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:R,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:R,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:R,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:(Y="Android KeyCode",i.z.coerce.number().int().describe(Y))}),category:"action"}),C({name:"wait",description:"暂停指定时间。用于等待页面加载或动画完成。时长需按前一动作自适应:轻交互 300~800ms,滑动后 500~1200ms,页面切换/返回 1200~2500ms,启动应用 2000~4000ms;禁止全程固定同一时长。",schema:i.z.object({duration:y("等待时间(ms)")}),category:"action"})],V=[...[$,q,G,U,j,P],...B];function X(e){return B.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 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(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});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 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 o=_(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 te(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=Q(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,p=e.bounds;if(function(e,t,s,n,o){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:o.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:o.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: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&&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:o.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:o.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:o.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: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=>m(e,a));c.length>1&&Z(c)&&ee(e,s,{resource_id:n.resourceId},{action_index:i??h(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=>m(e,a));c.length>1&&Z(c)&&ee(e,s,{text:n.text},{action_index:i??h(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=>m(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===o.get("content_desc")?.reusable,reason:"content_desc_positional",scope:"collection",policy_decision:"approved",policy_reason:r.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:r.policyReason})}}(a,e,t,{resourceId:c,text:l,contentDesc:u,className:d,bounds:p},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,m=!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:o.policyReason}):l?ee(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?ee(a,t,{content_desc:u},{stability:"low",reusable:m,reason:"content_desc_positional",scope:"collection",action_index:s.requestedActionIndex,policy_decision:"approved",policy_reason:o.policyReason}):d&&p&&ee(a,t,{class_name:d,bounds:p},{stability:"low",reusable:!1,reason:"class_bounds_fallback",scope:"page",requireUnique:!0,policy_decision:"approved",policy_reason:o.policyReason})}const _=a[0];return _?{mode:n,field_assessments:r,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:(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 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 re(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 ie(e,t,s){let n=0;for(const o of e)if(he(o,t)){if(n===s)return o;n++}return null}async function ae(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 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){if(null==e||""===e)return 5e3;const t="string"==typeof e?Number(e):e;return"number"!=typeof t||!Number.isFinite(t)||t<=0?5e3:Math.max(3e3,Math.trunc(t))}function pe(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 _e(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 ge(e){return _e({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 fe(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 he(e,t){return m(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&&p("resourceId",s,t.resourceId);case"text":return!!t.text&&p("text",s,t.text);case"content_desc":return!!t.contentDesc&&p("contentDesc",s,t.contentDesc);case"class_name":return!!t.className&&p("className",s,t.className);case"bounds":return!!t.bounds&&p("bounds",s,t.bounds)}})}function ve(e,t){return _(e,t)}function xe(e,t){const s=fe(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)(re(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,t){const s=fe(e.selector),n=ce(t.script_selector),o=fe(n?.selector);if(!s||!o)return null;const r=ue(e.action_index),i=ue(n?.action_index),a=be(s).map(e=>e.key),c=be(o).map(e=>e.key);if(ye(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 be(t))if(!(()=>{switch(s){case"resource_id":return!!e.resourceId&&p("resourceId",n,e.resourceId);case"text":return!!e.text&&p("text",n,e.text);case"content_desc":return!!e.contentDesc&&p("contentDesc",n,e.contentDesc);case"class_name":return!!e.className&&p("className",n,e.className);case"bounds":return!!e.bounds&&p("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,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}}function Te(e,s){if(!t.SELECTOR_ACTION_TOOLS.has(e))return null;const n=fe(s.selector),o=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 o={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 o.resourceId||o.text||o.contentDesc||o.className||o.bounds?o:null}(s.pre_context);return n&&o?function(e,t){return void 0!==e.index&&void 0!==t.index&&e.index===t.index||!!(e.resourceId&&t.resourceId&&p("resourceId",e.resourceId,t.resourceId))||!!(e.text&&t.text&&p("text",e.text,t.text))||!!(e.contentDesc&&t.contentDesc&&p("contentDesc",e.contentDesc,t.contentDesc))||!!(e.className&&t.className&&p("className",e.className,t.className))||!!(e.bounds&&t.bounds&&p("bounds",e.bounds,t.bounds))}(n,o)?null:"pre_context.target 对齐提醒: selector 与 pre_context.target 证据不一致。本次执行仍继续,但录制语义可能不准确。":null}const we={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 ae(t,"POST","base/sleep",{duration:o},s);if(200!==e.code)return{success:!1,error:e.msg||"Wait before observe failed"}}const r=await ae(t,"POST","accessibility/dump_compact",{},s);return 200==r.code&&"string"==typeof r.data?(n&&se(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=[],r=me(e.step_desc);r||o.push("录制提醒: verify_ui_state 未填写 step_desc,步骤摘要会缺失。");const i=me(e.screen_desc);i||o.push("录制提醒: verify_ui_state 未填写 screen_desc,页面上下文会缺失。");const a=function(e){return _e({page_kind:me(e.page_kind)})}(e),c={...r?{step_desc:r}:{},...i?{screen_desc:i}:{},...a?{recorded_page:a}:{}},l=e.verify_element;if(!l||"object"!=typeof l||Array.isArray(l))return o.push("录制提醒: verify_ui_state 未提供 verify_element,未记录验证元素。"),{success:!0,data:c,warning:o.join("\n")};const u=pe(l,"verify_element");if(u)return o.push(`录制提醒: ${u}`),{success:!0,data:c,warning:o.join("\n")};const d=fe(l);if(!d)return o.push("录制提醒: verify_element 未提供可用 selector 字段,未形成验证记录。"),{success:!0,data:c,warning:o.join("\n")};const p=e.page_anchor;if(p&&"object"==typeof p&&!Array.isArray(p)){const e=pe(p,"page_anchor");e&&o.push(`录制提醒: ${e}`)}const m=n?.lastObserveNodes;if(!m||0===m.length)return o.push("录制提醒: verify_ui_state 前未调用 observe_screen,未记录验证快照。"),{success:!0,data:c,warning:o.join("\n")};if(ve(m,d)>0){c._verification_matched=!0,c.verify_element=l;const e=ie(m,d,0);if(e){const t=ne(e,m,{mode:"verify"});t&&(c.verification_plan=t)}if(p&&"object"==typeof p&&!Array.isArray(p)){const e=fe(p);if(e){if(0===ve(m,e))o.push("录制提醒: page_anchor 未匹配当前 observe_screen,未记录页面锚点。");else{c.page_anchor=p;const t=ie(m,e,0);if(t){const e=ne(t,m,{mode:"anchor"});e&&(c.page_anchor_plan=e)}}}else o.push("录制提醒: page_anchor 未提供可用 selector 字段,未记录页面锚点。")}return!!c.verification_plan||!!c.page_anchor_plan||o.push("补录提醒: 当前 verify_ui_state 虽然匹配成功,但未形成可回放 verification_plan。请优先重新 observe_screen,并改用单字段且唯一的 verify_element(resource_id -> text -> content_desc);若发生页面切换,还可补充可唯一定位的 page_anchor。若主流程已明确,也可以继续执行,但录制质量会下降。"),{success:!0,data:c,...o.length>0?{warning:o.join("\n")}:{}}}const _=[void 0!==d.index&&`index=${d.index}`,d.resourceId&&`resource_id="${d.resourceId}"`,d.text&&`text="${d.text}"`,d.contentDesc&&`content_desc="${d.contentDesc}"`,d.className&&`class_name="${d.className}"`].filter(Boolean).join(", "),g=m.filter(e=>e.text||e.contentDesc||e.resourceId&&!re(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&&!re(e.resourceId)&&t.push(`id="${e.resourceId}"`),t.push(`[${e.className.split(".").pop()}]`),` ${t.join(" ")}`}),f=g.length>0?`\n当前界面可用的验证候选元素(直接从中选择,无需再调 observe_screen):\n${g.join("\n")}`:"";return o.push(`验证元素在当前界面中不存在:${_} 未匹配。请从当前界面重新选择 verify 元素。${f}`),{success:!0,data:{...c,_verification_matched:!1,verify_element:l},warning:o.join("\n")}},async record_search_context(e,t,s,n){const o=[],r=me(e.step_intent);r||o.push("录制提醒: record_search_context 未填写 step_intent。");const i=me(e.target_desc);i||o.push("录制提醒: record_search_context 未填写 target_desc。");const a={...e,...r?{step_intent:r}:{},...i?{target_desc:i}:{},recorded_context:ge({...e,...r?{step_intent:r}:{},...i?{target_desc:i}:{}})},c=n?.lastObserveNodes;if(!c||0===c.length)return o.push("录制提醒: record_search_context 前未调用 observe_screen,未记录页面锚点快照。"),{success:!0,data:a,warning:o.join("\n")};const l=e.anchor_element;if(!l||"object"!=typeof l||Array.isArray(l))o.push("录制提醒: record_search_context 未提供 anchor_element。");else{const e=l,t=pe(e,"anchor_element");if(t)o.push(`录制提醒: ${t}`);else 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}(e)){const e=fe(l);if(e)if(0===ve(c,e))o.push("录制提醒: anchor_element 未匹配当前 observe_screen,未记录页面锚点。");else{const t=ie(c,e,0);if(t){const e=ne(t,c,{mode:"anchor"});e&&(a.anchor_plan=e)}}else o.push("录制提醒: anchor_element 不是合法 selector。")}else o.push("录制提醒: anchor_element 未提供可用 selector 字段。")}const u=e.target_element;if(u&&"object"==typeof u&&!Array.isArray(u)){const e=pe(u,"target_element");if(e)o.push(`录制提醒: ${e}`);else{const e=fe(u);if(e)if(0===ve(c,e))o.push("录制提醒: target_element 未匹配当前 observe_screen,未记录目标快照。");else{const t=ie(c,e,0);if(t){const e=ne(t,c,{mode:"action"});e&&(a.target_plan=e)}}else o.push("录制提醒: target_element 未提供可用 selector 字段。")}}return{success:!0,data:a,...o.length>0?{warning:o.join("\n")}:{}}},async get_installed_apps(e,t,s){const n=e.type||"user",o=await ae(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 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 o=await ae(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 ae(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 ae(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 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 o={selector:e.selector,action:"click",wait_timeout:de(e.wait_timeout),wait_interval:e.wait_interval??1e3};void 0!==n.value&&(o.action_index=n.value);const r=await ae(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=ue(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:de(e.wait_timeout),wait_interval:e.wait_interval??1e3};void 0!==n.value&&(o.action_index=n.value);const r=await ae(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=ue(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:de(e.wait_timeout),wait_interval:e.wait_interval??1e3};void 0!==n.value&&(o.action_index=n.value);const r=await ae(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 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 Ne(e,s,n,o){const r=we[e.name],i=(a=e.name,V.find(e=>e.name===a));var a;if(!r||!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"),o=le(s,"action_index"),r=ce(s.selector),i=!!r&&le(r,"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||o?"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),p=d.success?d.data:c,m=[];if(!d.success){const t=d.error.issues.map(e=>`${e.path.join(".")}: ${e.message}`).join("; ");m.push(`参数补录提醒: ${e.name} 参数未完全符合推荐 schema,已按原始参数继续执行。${t}`)}m.push(...function(e,s){if(!t.PRE_CONTEXT_REQUIRED_TOOLS.has(e))return[];const n=[],o=s.pre_context;if(!o||"object"!=typeof o||Array.isArray(o))return n.push(`pre_context 缺失:${e} 调用前必须提供 pre_context(anchor + target)。本次执行仍继续,但录制语义会缺失。`),n;const r=o,i=r.anchor;!i||"object"!=typeof i||Array.isArray(i)?n.push("pre_context.anchor 缺失:必须提供页面锚点元素用于确认当前页面。本次执行仍继续,但录制语义会缺失。"):me(i.desc)||n.push("pre_context.anchor.desc 缺失:必须提供页面锚点描述,用于说明当前页面上下文。");const a=r.target;!a||"object"!=typeof a||Array.isArray(a)?n.push("pre_context.target 缺失:必须提供操作目标元素信息。本次执行仍继续,但录制语义会缺失。"):me(a.desc)||n.push("pre_context.target.desc 缺失:必须提供目标元素描述,用于说明本次操作意图。");return me(r.step_intent)||n.push("pre_context.step_intent 缺失:必须说明本次操作在任务中的目的和预期效果,而非重复元素描述。此字段用于脚本生成的步骤命名和合并。"),n}(e.name,p));const _=Te(e.name,p);_&&m.push(_);const g=function(e,s,n){if(!t.SELECTOR_ACTION_TOOLS.has(e))return null;const o=fe(s.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=ve(r,o);if(0===i)return"validation failed: selector has no match on latest observed screen.";const a=ue(s.action_index);return a.ok?void 0!==a.value&&a.value>=i?`validation failed: action_index=${a.value} is out of range for matched_count=${i}.`:i>1&&void 0===a.value?`validation failed: selector is ambiguous (matched_count=${i}). Refine selector or provide action_index.`:null:"validation failed: action_index must be a non-negative integer."}(e.name,p,o);if(g)return{toolCallId:e.id,name:e.name,success:!1,error:g};try{const i=await r(p,s,n,o);if(i.success){const s=[...m];if(t.SELECTOR_ACTION_TOOLS.has(e.name)&&o?.lastObserveNodes?.length){const e=fe(p.selector),t=ue(p.action_index);if(e){const n=ie(o.lastObserveNodes,e,t.value??0);if(n){const e=ne(n,o.lastObserveNodes,{mode:"action",requestedActionIndex:t.value});if(e){const t=i.data&&"object"==typeof i.data&&!Array.isArray(i.data)?i.data:{},n=Ie(p,e);i.data={...t,_selector_recording_profile:e,...n?{_selector_request_analysis:n}:{}};const o=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;o&&s.push(o)}}}}if(t.SELECTOR_ACTION_TOOLS.has(e.name)&&o?.lastObserveNodes){const e=xe(p,o.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=fe(t.anchor),n=fe(t.target);return s&&n&&ye(s,n)?"pre_context 质量提醒: anchor 与 target 使用了完全相同的定位证据。anchor 应优先描述当前页面或容器级稳定锚点,而不是与点击目标重合。":null}(p);e&&s.push(e)}if(o?.softPendingVerify&&X(e.name)){const e=o.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&&(!o?.lastSearchContextTimestamp||Date.now()-o.lastSearchContextTimestamp>3e4)&&s.push("⚠️ 录制提醒: swipe 前未调用 record_search_context,录制信息将不完整。建议先调用 record_search_context 记录查找目标与页面锚点。"),{toolCallId:e.id,name:e.name,success:!0,data:i.data,warning:[i.warning,...s].filter(e=>!!e).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 Se(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=Ee(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 Ae(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=Ce.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=Se(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 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 De=["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 ke(e,t="UNKNOWN"){if(e instanceof Le)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 Le("string"==typeof(n=e.code)&&De.includes(n)?e.code:t,s,Re(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 je(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 Me(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}`}function qe(e,t){return t<=0?null:e/t}function ze(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 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:qe(t,e.length),totalActionCalls:s,successfulActionCalls:n,automationSuccessRate:qe(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 Ye(e,s,n,o=[]){const r=[{role:"system",content:t.buildAgentSystemPrompt(e,s)},{role:"user",content:e}];for(const e of o)"assistant"!==e.role&&"user"!==e.role||r.push({role:e.role,content:e.content,timestamp:e.timestamp});return n&&(r.push({role:"assistant",content:"",toolCalls:[{id:"plan-initial-observe",name:"observe_screen",arguments:{},source:"local"}]}),r.push({role:"tool",content:n,toolCallId:"plan-initial-observe"})),r}function Be(e){return e.currentPhase||(e.currentPhase="runtime"),e.planningContext?Array.isArray(e.planningContext.messages)||(e.planningContext.messages=[]):e.planningContext={messages:[]},e}function Ve(e,t,s){Be(e),e.plan=t,e.currentPhase="runtime",e.messages=Ye(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,o]of Object.entries(e))null!=o&&""!==o&&(t[n]=o,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),o=Je(s.data);if(!n&&!o)return null;const r=Ze(Je(n?.selector)||{}),i=He(n?.action_index),a=Array.isArray(o?.nodes)?o.nodes:[],c=He(o?.action_index),l=i??c??0,u=et(a[l]??a[0]),d=a.slice(0,5).map(e=>et(e)).filter(e=>!!e),p="number"==typeof o?.count&&Number.isFinite(o.count)?o.count:a.length,m={};r&&(m.requested_selector=r),void 0===i&&void 0===c||(m.action_index=l),p>=0&&(m.matched_count=p),u&&(m.selected_node=u),d.length>0&&(m.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)})}(o?._selector_recording_profile);if(_){m.selector_profile=_;const e=Je(_.script_selector);if(e){const t=Je(e.selector);t&&(m.recommended_selector=t);const s=He(e.action_index);void 0!==s&&(m.recommended_action_index=s),"string"==typeof e.stability&&(m.selector_stability=e.stability),"string"==typeof e.reason&&(m.selector_reason=e.reason),"boolean"==typeof e.reusable&&(m.selector_reusable=e.reusable),"string"==typeof e.scope&&(m.selector_scope=e.scope)}}const g=Je(o?._selector_request_analysis);return g&&(m.selector_request_analysis=g),Object.keys(m).length>0?m: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 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 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 ot=[/resource-id=/,/resource_id=/,/\btext="/,/content-desc=/,/content_desc=/,/clickable=true/,/bounds=\[/];function rt(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++,ot.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 at(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&&rt(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(rt(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 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(V.map(e=>e.name));class dt{mcpClient=null;mcpTools=new Map;modelTools=[...V];getModelTools(){return this.modelTools}async prepare(e={}){if(await this.closeMcpClient(),this.mcpTools.clear(),this.modelTools=[...V],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),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}`)}}(V.map(e=>e.name),e);for(const t of e)this.mcpTools.set(t.name,{tool:t.tool,server:t.server});this.modelTools=[...V,...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 o=this.resolveTool(e.name);if("local"===o.source){return{...await Ne({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=[...V]}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 pt="PLANNER_RETRY",mt="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",vt=new Set(["TIMEOUT","NETWORK","HTTP_5XX"]);function xt(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 It(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 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(),o=It(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)?Tt(e.toolCall,e.timeoutMs):wt(e.toolCall,t)}finally{o.dispose()}if(r.latencyMs||(r.latencyMs=Date.now()-n),r.attempt=s,r.success)return r;const i=xt(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 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 Dt{options;emitter=new Oe;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=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=(o=s.reliability)?{retries:{observation:Ct(o.retries?.observation??t.DEFAULT_RELIABILITY_CONFIG.retries.observation,t.DEFAULT_RELIABILITY_CONFIG.retries.observation),action:Ct(o.retries?.action??t.DEFAULT_RELIABILITY_CONFIG.retries.action,t.DEFAULT_RELIABILITY_CONFIG.retries.action)},timeouts:{defaultMs:Ct(o.timeouts?.defaultMs??t.DEFAULT_RELIABILITY_CONFIG.timeouts.defaultMs,t.DEFAULT_RELIABILITY_CONFIG.timeouts.defaultMs),observeScreenMs:Ct(o.timeouts?.observeScreenMs??t.DEFAULT_RELIABILITY_CONFIG.timeouts.observeScreenMs,t.DEFAULT_RELIABILITY_CONFIG.timeouts.observeScreenMs),startAppMs:Ct(o.timeouts?.startAppMs??t.DEFAULT_RELIABILITY_CONFIG.timeouts.startAppMs,t.DEFAULT_RELIABILITY_CONFIG.timeouts.startAppMs)},limits:{maxIterations:Ct(o.limits?.maxIterations??t.DEFAULT_RELIABILITY_CONFIG.limits.maxIterations,t.DEFAULT_RELIABILITY_CONFIG.limits.maxIterations),maxConsecutiveFailures:Ct(o.limits?.maxConsecutiveFailures??t.DEFAULT_RELIABILITY_CONFIG.limits.maxConsecutiveFailures,t.DEFAULT_RELIABILITY_CONFIG.limits.maxConsecutiveFailures),maxSameToolFingerprint:Ct(o.limits?.maxSameToolFingerprint??t.DEFAULT_RELIABILITY_CONFIG.limits.maxSameToolFingerprint,t.DEFAULT_RELIABILITY_CONFIG.limits.maxSameToolFingerprint)},planner:{maxAttempts:Ct(o.planner?.maxAttempts??t.DEFAULT_RELIABILITY_CONFIG.planner.maxAttempts,t.DEFAULT_RELIABILITY_CONFIG.planner.maxAttempts)},scriptGeneration:{maxAttempts:Ct(o.scriptGeneration?.maxAttempts??t.DEFAULT_RELIABILITY_CONFIG.scriptGeneration.maxAttempts,t.DEFAULT_RELIABILITY_CONFIG.scriptGeneration.maxAttempts)}}:t.DEFAULT_RELIABILITY_CONFIG;var o;const r=s.sessionId||e.v4();this.localToolContext={lastObserveRawDump:null,lastObserveNodes:[]},this.session=Be(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:Ye(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:[]}}}(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");let n;s.currentPhase="script_generation",this.emitter.emit("scriptgenerating",{sessionId:s.sessionId});try{const o=await t.generateScriptWithReliability(s.goal,s.executionLog,s.provider,s.reliability);if(n=o.workflow,o.attempts>1){s.runtimeContext.scriptGenerationRetries+=o.attempts-1;const t=ze(s,{sessionId:e,phase:"script_generation",code:yt,message:`script generation retried ${o.attempts} attempts`,details:{errors:o.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 o={sessionId:s.sessionId,workflow:n,executionLog:[...s.executionLog],totalIterations:s.iteration,diagnostics:Ge(s)};return this.emitter.emit("complete",o),o}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 s=this.abortController.signal;this.emitter.emit("planning",{sessionId:e.sessionId,status:"started"});let n=0,o=[];try{const r=await async function(e,s,n,o,r={}){const i=t.detectPromptLocale(e),a={lastObserveRawDump:null,lastObserveNodes:[]},c=r.planningMessages||[],l=!1!==r.allowPause,u=await Ne({id:"plan-observe-screen",name:"observe_screen",arguments:{},source:"local"},n,r.signal,a),d=u.success&&"string"==typeof u.data?u.data:Ae(u),p=a.lastObserveRawDump||void 0,m=t.createProvider(s),_=[],g=Math.max(1,o.planner.maxAttempts);for(let s=1;s<=g;s++){const n=Pe(s,_,i),o=[{role:"system",content:t.buildTaskPlanPrompt(e,l)},{role:"user",content:Me(e,d,n,i)},...c];try{let t;if(m.capabilities.structuredJson)t=await m.chatStructuredJson(o,$e,r.signal);else{const e=await m.chatWithTools(o,[],r.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:p,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:p,attempts:s,errors:_}}catch(e){_.push(e.message||String(e))}}return{status:"completed",plan:je(e,i),screenDump:d,screenRawDump:p,attempts:g,errors:_}}(e.goal,e.provider,e.device,e.reliability,{signal:s,planningMessages:e.planningContext?.messages||[]});return n=r.attempts,o=r.errors,"paused"===r.status?(e.currentPhase="planning",e.planningContext?.messages.push({role:"assistant",content:r.text,timestamp:Date.now()}),this.applyPlanningDiagnostics(e,n,o),this.emitter.emit("planning",{sessionId:e.sessionId,status:"paused",text:r.text}),this.markSessionPaused(e,r.text,"planning"),"paused"):(Ve(e,r.plan,r.screenDump),r.screenRawDump&&se(this.localToolContext,r.screenRawDump),this.applyPlanningDiagnostics(e,n,o),this.emitter.emit("planning",{sessionId:e.sessionId,status:"completed",plan:r.plan}),"completed")}catch(t){if("AbortError"===t.name)throw t;const s=ke(t,"RUNTIME_LOOP_FAILED");return o.push(s.message),this.emitter.emit("error",{sessionId:e.sessionId,error:`task planning failed and was skipped: ${s.message}`,code:"RUNTIME_LOOP_FAILED",details:{phase:"planning",cause:s.message}}),Ve(e),this.applyPlanningDiagnostics(e,n,o),"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=ze(e,{sessionId:e.sessionId,phase:"planning",code:t>1?pt:mt,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 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=ze(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),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&&Xe(e,r),void this.markSessionPaused(e,r,"runtime");Xe(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=ze(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",o),this.emitter.emit("toolresult",{sessionId:e.sessionId,result:t,iteration:e.iteration}),We(e,s.id,Ae(t)),this.persistSession();continue}const o=Lt(s),r=Et(e.reliability,o),i=St(e.reliability,s.name),a=await Nt({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=ze(e,{sessionId:e.sessionId,phase:"runtime",code:_t,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||nt(e,s,a,this.toolRegistry.getCategory(s,a)),We(e,s.id,Ae(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=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,o){const r=Ot(s);if(e.runtimeContext.lastToolFingerprint===r?e.runtimeContext.sameToolFingerprintCount+=1:(e.runtimeContext.lastToolFingerprint=r,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"===o&&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=ze(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 for recovery after excessive consecutive failures","recovery")}if(e.runtimeContext.sameToolFingerprintCount>=e.reliability.limits.maxSameToolFingerprint){e.runtimeContext.convergenceTriggers+=1;const t=ze(e,{sessionId:e.sessionId,phase:"policy",code:bt,message:`same tool fingerprint repeat limit reached: ${e.reliability.limits.maxSameToolFingerprint}`,iteration:e.iteration,details:{fingerprint:r,repeatCount:e.runtimeContext.sameToolFingerprintCount}});this.emitter.emit("diagnostic",t),this.markSessionPaused(e,"execution paused for recovery after repeated non-converging tool calls","recovery")}}getVerifyGateError(e,t){return e.runtimeContext.pendingVerifyAction,null}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_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),o=[{role:"system",content:t.buildOptimizeIntentPrompt(e)},{role:"user",content:e}];return(await n.chatWithTools(o,[])).content.trim()},exports.toRuntimeError=ke;
|
package/dist/runtime/index.d.ts
CHANGED
|
@@ -62,6 +62,12 @@ interface ExceptionHandler {
|
|
|
62
62
|
interface WorkflowStep {
|
|
63
63
|
description?: string;
|
|
64
64
|
completed?: string;
|
|
65
|
+
/**
|
|
66
|
+
* 循环配置,两种模式二选一:
|
|
67
|
+
* - count: 固定次数循环(-1 表示无限循环,需外部取消)
|
|
68
|
+
* - max_count + completed="success": 条件循环(-1 表示无限循环直到成功)
|
|
69
|
+
* - interval: 循环间隔毫秒数,默认 0
|
|
70
|
+
*/
|
|
65
71
|
loop?: {
|
|
66
72
|
count: number;
|
|
67
73
|
interval?: number;
|
|
@@ -284,7 +290,6 @@ declare class AutomationAgentRuntime {
|
|
|
284
290
|
}
|
|
285
291
|
declare function createAutomationAgentRuntime(options?: AgentRuntimeOptions): AutomationAgentRuntime;
|
|
286
292
|
|
|
287
|
-
declare const DEFAULT_PROVIDER_CONFIG: AIProviderConfig;
|
|
288
293
|
declare const DEFAULT_RELIABILITY_CONFIG: ReliabilityConfig;
|
|
289
294
|
|
|
290
295
|
declare class AgentRuntimeError extends Error {
|
|
@@ -296,4 +301,4 @@ declare function toRuntimeError(error: unknown, fallbackCode?: AgentErrorCode):
|
|
|
296
301
|
|
|
297
302
|
declare function optimizeIntent(input: string, providerConfig: AgentRuntimeStartParams['provider']): Promise<string>;
|
|
298
303
|
|
|
299
|
-
export { AgentRuntimeError, AutomationAgentRuntime,
|
|
304
|
+
export { AgentRuntimeError, AutomationAgentRuntime, DEFAULT_RELIABILITY_CONFIG, createAutomationAgentRuntime, optimizeIntent, toRuntimeError };
|