@vmosedge/workflow-agent-sdk 0.1.0

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.
@@ -0,0 +1,143 @@
1
+ type AIVendor = 'deepseek' | 'openai' | 'anthropic' | 'claude' | 'google' | 'gemini' | 'azure' | 'custom';
2
+ interface AIProviderConfig {
3
+ vendor: AIVendor;
4
+ baseUrl?: string;
5
+ apiKey: string;
6
+ model: string;
7
+ headers?: Record<string, string>;
8
+ }
9
+ type ToolCategory = 'observation' | 'action';
10
+ type ToolErrorCode = 'TIMEOUT' | 'NETWORK' | 'HTTP_4XX' | 'HTTP_5XX' | 'VALIDATION' | 'UNKNOWN_TOOL' | 'ABORTED' | 'RUNTIME_GUARD' | 'MODEL_OUTPUT_INVALID' | 'PLAN_VALIDATION_FAILED' | 'SCRIPT_VALIDATION_FAILED' | 'UNKNOWN';
11
+ interface ToolResult {
12
+ toolCallId: string;
13
+ name: string;
14
+ success: boolean;
15
+ data?: unknown;
16
+ error?: string;
17
+ warning?: string;
18
+ errorCode?: ToolErrorCode;
19
+ retryable?: boolean;
20
+ attempt?: number;
21
+ latencyMs?: number;
22
+ source?: 'local' | 'mcp';
23
+ server?: string;
24
+ }
25
+ interface ExecutionLogEntry {
26
+ index: number;
27
+ toolName: string;
28
+ arguments: Record<string, unknown>;
29
+ result: ToolResult;
30
+ category: ToolCategory;
31
+ timestamp: number;
32
+ }
33
+ interface WorkflowScript {
34
+ id: string;
35
+ name: string;
36
+ version: string;
37
+ steps: Record<string, WorkflowStep>;
38
+ flow: string[];
39
+ description?: string;
40
+ timeout?: number;
41
+ exception_handlers?: ExceptionHandler[];
42
+ }
43
+ interface ExceptionHandler {
44
+ description?: string;
45
+ name?: string;
46
+ selector: Record<string, unknown>;
47
+ action: string;
48
+ action_params?: Record<string, unknown>;
49
+ max_trigger_count?: number;
50
+ }
51
+ interface WorkflowStep {
52
+ description?: string;
53
+ completed?: string;
54
+ loop?: {
55
+ count: number;
56
+ interval?: number;
57
+ } | {
58
+ max_count: number;
59
+ interval?: number;
60
+ };
61
+ actions: WorkflowAction[];
62
+ }
63
+ interface WorkflowAction {
64
+ path: string;
65
+ params?: Record<string, unknown>;
66
+ throw_if_empty?: string[];
67
+ }
68
+ interface TaskPlanSubtask {
69
+ id: number;
70
+ description: string;
71
+ successCriteria: string;
72
+ estimatedActions: string[];
73
+ }
74
+ interface TaskPlan {
75
+ goal: string;
76
+ subtasks: TaskPlanSubtask[];
77
+ risks: string[];
78
+ assumptions: string[];
79
+ estimatedSteps: number;
80
+ }
81
+ interface ToolRetryPolicy {
82
+ observation: number;
83
+ action: number;
84
+ }
85
+ interface ToolTimeoutPolicy {
86
+ defaultMs: number;
87
+ observeScreenMs: number;
88
+ startAppMs: number;
89
+ }
90
+ interface RuntimeLimitPolicy {
91
+ /** Positive hard cap on dialogue iterations. */
92
+ maxIterations: number;
93
+ maxConsecutiveFailures: number;
94
+ maxSameToolFingerprint: number;
95
+ }
96
+ interface PlannerPolicy {
97
+ maxAttempts: number;
98
+ }
99
+ interface ScriptGenerationPolicy {
100
+ maxAttempts: number;
101
+ }
102
+ interface ReliabilityConfig {
103
+ retries: ToolRetryPolicy;
104
+ timeouts: ToolTimeoutPolicy;
105
+ limits: RuntimeLimitPolicy;
106
+ planner: PlannerPolicy;
107
+ scriptGeneration: ScriptGenerationPolicy;
108
+ }
109
+ interface RuntimeDiagnosticsSummary {
110
+ planningRetries: number;
111
+ toolRetries: number;
112
+ scriptGenerationRetries: number;
113
+ convergenceTriggers: number;
114
+ totalToolCalls: number;
115
+ successfulToolCalls: number;
116
+ runtimeSuccessRate: number | null;
117
+ totalActionCalls: number;
118
+ successfulActionCalls: number;
119
+ automationSuccessRate: number | null;
120
+ totalFailures: number;
121
+ failureByCode: Record<string, number>;
122
+ finalFailureCode?: string;
123
+ }
124
+ interface AgentPlanningData {
125
+ sessionId: string;
126
+ status: 'started' | 'completed';
127
+ plan?: TaskPlan;
128
+ }
129
+ type McpServerConfig = {
130
+ transport: 'stdio';
131
+ command: string;
132
+ args?: string[];
133
+ env?: Record<string, string>;
134
+ } | {
135
+ transport: 'http' | 'streamable_http' | 'sse';
136
+ url: string;
137
+ headers?: Record<string, string>;
138
+ };
139
+
140
+ declare function generateScript(goal: string, executionLog: ExecutionLogEntry[], providerConfig: AIProviderConfig, signal?: AbortSignal): Promise<WorkflowScript>;
141
+
142
+ export { generateScript };
143
+ export type { AIProviderConfig, AIVendor, AgentPlanningData, ExecutionLogEntry, McpServerConfig, ReliabilityConfig, RuntimeDiagnosticsSummary, TaskPlan, TaskPlanSubtask, WorkflowScript };
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ export{g as generateScript}from"./chunks/scriptGenerator-D5dNotBm.js";import"@langchain/core/prompts";import"@langchain/core/output_parsers";import"@langchain/core/runnables";import"zod";import"@langchain/anthropic";import"@langchain/core/messages";import"@langchain/core/tools";import"@langchain/google-genai";import"@langchain/openai";import"crypto";
@@ -0,0 +1 @@
1
+ "use strict";var e=require("uuid"),t=require("../chunks/scriptGenerator-0SXH7-UK.cjs"),s=require("fs"),n=require("os"),r=require("path"),o=require("better-sqlite3"),i=require("zod"),a=require("zod-to-json-schema"),c=require("@langchain/mcp-adapters");require("@langchain/core/prompts"),require("@langchain/core/output_parsers"),require("@langchain/core/runnables"),require("@langchain/anthropic"),require("@langchain/core/messages"),require("@langchain/core/tools"),require("@langchain/google-genai"),require("@langchain/openai"),require("crypto");function l(){return r.join(function(){const e=n.homedir();if(!e)throw new Error("Failed to resolve user home directory for session persistence");return r.join(e,".vmosedge")}(),"agent_sessions.db")}function u(e){return{sessionId:e.session_id,goal:e.goal,status:(t=e.status,"running"===t||"paused"===t||"stopped"===t?t:"stopped"),providerVendor:e.provider_vendor||"unknown",deviceId:e.device_id||"",totalIterations:e.total_iterations||0,lastError:e.last_error||void 0,lastErrorCode:e.last_error_code||void 0,createTime:e.create_time,updateTime:e.update_time};var t}class d{db;constructor(e){!function(e){const t=r.dirname(e);s.existsSync(t)||s.mkdirSync(t,{recursive:!0})}(e),this.db=new o(e),this.initPragmas(),this.initSchema()}initPragmas(){this.db.pragma("busy_timeout = 5000"),this.db.pragma("foreign_keys = ON"),this.db.pragma("synchronous = NORMAL");try{this.db.pragma("journal_mode = WAL")}catch{this.db.pragma("journal_mode = DELETE")}}initSchema(){this.db.exec("\n CREATE TABLE IF NOT EXISTS agent_meta (\n meta_key TEXT PRIMARY KEY,\n meta_value TEXT NOT NULL,\n update_time INTEGER NOT NULL\n );\n CREATE TABLE IF NOT EXISTS agent_sessions (\n session_id TEXT PRIMARY KEY,\n goal TEXT NOT NULL,\n status TEXT NOT NULL,\n provider_vendor TEXT NOT NULL DEFAULT '',\n device_id TEXT NOT NULL DEFAULT '',\n total_iterations INTEGER NOT NULL DEFAULT 0,\n last_error TEXT,\n last_error_code TEXT,\n state_json TEXT NOT NULL,\n create_time INTEGER NOT NULL,\n update_time INTEGER NOT NULL\n );\n "),this.ensureSessionColumns(),this.db.exec("\n CREATE INDEX IF NOT EXISTS idx_agent_sessions_update_time\n ON agent_sessions(update_time DESC);\n CREATE INDEX IF NOT EXISTS idx_agent_sessions_status_update_time\n ON agent_sessions(status, update_time DESC);\n CREATE INDEX IF NOT EXISTS idx_agent_sessions_device_update_time\n ON agent_sessions(device_id, update_time DESC);\n "),this.saveSchemaVersion("3")}ensureSessionColumns(){const e=this.db.prepare("PRAGMA table_info(agent_sessions)").all(),t=new Set(e.map(e=>e.name)),s=[{name:"provider_vendor",definition:"TEXT NOT NULL DEFAULT ''"},{name:"device_id",definition:"TEXT NOT NULL DEFAULT ''"},{name:"total_iterations",definition:"INTEGER NOT NULL DEFAULT 0"},{name:"last_error",definition:"TEXT"},{name:"last_error_code",definition:"TEXT"}];for(const e of s)t.has(e.name)||this.db.exec(`ALTER TABLE agent_sessions ADD COLUMN ${e.name} ${e.definition}`)}saveSchemaVersion(e){this.db.prepare("\n INSERT INTO agent_meta (meta_key, meta_value, update_time)\n VALUES (@meta_key, @meta_value, @update_time)\n ON CONFLICT(meta_key) DO UPDATE SET\n meta_value=excluded.meta_value,\n update_time=excluded.update_time\n ").run({meta_key:"schema_version",meta_value:e,update_time:Date.now()})}buildQueryFilter(e){const t=[],s={};e.status&&(t.push("status = @status"),s.status=e.status),e.deviceId&&(t.push("device_id = @device_id"),s.device_id=e.deviceId);const n=e.keyword?.trim();return n&&(t.push("goal LIKE @keyword"),s.keyword=`%${n}%`),{whereSql:t.length>0?`WHERE ${t.join(" AND ")}`:"",params:s}}saveSession(e,t){const s=Date.now(),n=e.running?"running":e.paused?"paused":"stopped",r=JSON.stringify(e);this.db.prepare("\n INSERT INTO agent_sessions (\n session_id, goal, status, provider_vendor, device_id,\n total_iterations,\n last_error, last_error_code, state_json, create_time, update_time\n )\n VALUES (\n @session_id, @goal, @status, @provider_vendor, @device_id,\n @total_iterations,\n @last_error, @last_error_code, @state_json, @create_time, @update_time\n )\n ON CONFLICT(session_id) DO UPDATE SET\n goal=excluded.goal,\n status=excluded.status,\n provider_vendor=excluded.provider_vendor,\n device_id=excluded.device_id,\n total_iterations=excluded.total_iterations,\n last_error=excluded.last_error,\n last_error_code=excluded.last_error_code,\n state_json=excluded.state_json,\n update_time=excluded.update_time\n ").run({session_id:e.sessionId,goal:e.goal,status:n,provider_vendor:e.provider.vendor,device_id:e.device.deviceId,total_iterations:e.iteration,last_error:t?.lastError||null,last_error_code:t?.lastErrorCode||null,state_json:r,create_time:s,update_time:s})}loadSession(e){const t=this.db.prepare("SELECT * FROM agent_sessions WHERE session_id = ? LIMIT 1").get(e);if(!t)return null;try{return JSON.parse(t.state_json)}catch{return null}}getSessionSummary(e){const t=this.db.prepare("\n SELECT\n session_id, goal, status, provider_vendor, device_id, total_iterations,\n last_error, last_error_code, create_time, update_time, state_json\n FROM agent_sessions\n WHERE session_id = ? LIMIT 1\n ").get(e);return t?u(t):null}listSessions(e={}){const{whereSql:t,params:s}=this.buildQueryFilter(e),n=function(e){return"number"!=typeof e||Number.isNaN(e)?50:Math.min(500,Math.max(1,Math.floor(e)))}(e.limit),r=function(e){return"number"!=typeof e||Number.isNaN(e)?0:Math.max(0,Math.floor(e))}(e.offset);return this.db.prepare(`\n SELECT\n session_id, goal, status, provider_vendor, device_id, total_iterations,\n last_error, last_error_code, create_time, update_time, state_json\n FROM agent_sessions\n ${t}\n ORDER BY update_time DESC\n LIMIT @limit OFFSET @offset\n `).all({...s,limit:n,offset:r}).map(u)}countSessions(e={}){const{whereSql:t,params:s}=this.buildQueryFilter(e),n=this.db.prepare(`\n SELECT COUNT(1) as total\n FROM agent_sessions\n ${t}\n `).get(s);return n?.total||0}deleteSession(e){return this.db.prepare("DELETE FROM agent_sessions WHERE session_id = ?").run(e).changes>0}close(){this.db.close()}}function _(e,t){const s=t.normalize("NFKC");return"text"===e||"contentDesc"===e?s.replace(/\s+/gu," ").trim():"bounds"===e?s.replace(/\s+/gu,""):s.trim()}function m(e,t,s){return _(e,t)===_(e,s)}function p(e,t){return(void 0===t.index||e.index===t.index)&&(!!(!t.resourceId||e.resourceId&&m("resourceId",t.resourceId,e.resourceId))&&(!!(!t.text||e.text&&m("text",t.text,e.text))&&(!!(!t.contentDesc||e.contentDesc&&m("contentDesc",t.contentDesc,e.contentDesc))&&(!!(!t.className||e.className&&m("className",t.className,e.className))&&!!(!t.bounds||e.bounds&&m("bounds",t.bounds,e.bounds))))))}function f(e,t){let s=0;for(const n of e)p(n,t)&&(s+=1);return s}function g(e,t,s){let n=0;for(const r of e)if(p(r,t)){if(r.index===s.index)return n;n+=1}}const h=e=>i.z.string().describe(e),b=e=>i.z.coerce.number().describe(e),y=e=>i.z.coerce.number().int().positive().describe(e),v=e=>{const t=i.z.coerce.number().int().positive().optional();return e?t.describe(e):t},x=e=>{const t=i.z.coerce.number().int().min(0).optional();return e?t.describe(e):t},I=e=>{const t=i.z.string().optional();return e?t.describe(e):t},T=(e,t)=>i.z.enum(e).optional().describe(t);function N(e){const t=a.zodToJsonSchema(e,{$refStrategy:"none"}),{$schema:s,definitions:n,...r}=t;return r}function w(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:I("元素资源ID,如 com.example:id/btn"),text:I("元素文本,支持正则"),content_desc:I("元素内容描述,支持正则"),class_name:I("元素类名"),bounds:I("元素 bounds,格式 [x1,y1][x2,y2],仅用于兜底消歧,不建议直接进入脚本")}).strict(),C=T(["home","list","detail","dialog","search","form","unknown"],"可选:显式声明当前页面类型。仅在你有把握时填写;不确定则省略。"),A=T(["navigation","search_entry","action_button","input_field","content_item","generic_target"],"可选:显式声明目标元素角色。仅在你有把握时填写;不确定则省略。"),O=T(["critical","supporting","noise"],"可选:显式声明本轮动作对主流程的重要性。仅在你有把握时填写;不确定则省略。"),S=T(["main_flow","exception_handler_candidate","log_only"],"可选:显式声明该记录应进入主流程、异常处理还是仅日志。仅在你有把握时填写;不确定则省略。"),L=T(["next_action","next_verified_transition","manual_close"],"可选:声明该上下文持续到何时结束。默认 next_verified_transition。"),R=i.z.object({step_intent:h("本次操作在任务流程中的目的和预期效果。必须说明为什么执行此操作、期望达到什么状态,而非重复元素属性描述。"),merge_key:I("可选:稳定的步骤合并键。用于把同类动作合并为同一脚本步骤,例如 open_search_page、scroll_result_list。仅在你能明确抽象出可复用步骤语义时填写。"),expected_result:I("可选:本次动作完成后预期达到的状态描述。建议使用比 step_intent 更直接的结果表述,例如“进入搜索页”“列表向下推进一屏”。"),wait_ms_hint:v("可选:建议回放时在主动作后等待或验证的毫秒数。仅在你明确知道该步骤通常需要额外等待时填写。"),page_kind:C,target_role:A,criticality_hint:O,include_mode_hint:S,anchor:i.z.object({desc:h("页面锚点元素描述,确认当前所在页面"),resource_id:I(),text:I(),content_desc:I(),class_name:I(),bounds:I()}).describe("页面锚点元素,必填"),target:i.z.object({desc:h("操作目标元素描述"),resource_id:I(),text:I(),content_desc:I(),class_name:I(),bounds:I()}).describe("本次操作的目标元素,必填"),post_verify:i.z.object({desc:h("操作成功后验证元素描述"),resource_id:I(),text:I(),content_desc:I(),class_name:I(),bounds:I()}).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 在明确时建议显式填写,用于录制质量约束和回放。"),D=i.z.coerce.number().int().min(0).optional().describe("匹配结果动作索引(从 0 开始,仅 accessibility/node 使用)"),k=i.z.object({wait_timeout:y("等待元素出现超时(ms)").optional(),wait_interval:y("重试间隔(ms)").optional()}),F=w({name:"observe_screen",description:"获取当前屏幕的 UI 无障碍节点树。返回简化后的 UI 结构,包含每个可交互元素的 text、resource-id、content-desc、class、bounds 等属性。可选传 wait_ms:先等待指定毫秒再拉取,用于动作后「等待加载 + 获取新页面」一步完成。",schema:i.z.object({wait_ms:y("可选:先等待的毫秒数,再拉取 UI").optional()}),category:"observation"}),$=w({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"}),U=w({name:"get_top_activity",description:"获取当前前台应用的 Activity 信息,返回 package_name 和 class_name。用于判断当前在哪个应用的哪个页面。",schema:i.z.object({}),category:"observation"}),j=w({name:"get_screen_info",description:"获取屏幕显示信息,返回 width、height、rotation、orientation。用于确定屏幕分辨率以计算滑动/点击坐标。",schema:i.z.object({}),category:"observation"}),M=i.z.object({desc:h("元素简短描述"),resource_id:I("元素 resource-id,来自 observe_screen"),text:I("元素文本,来自 observe_screen"),content_desc:I("元素 content-desc"),class_name:I("元素类名,来自 observe_screen"),index:x("节点 index(来自 observe_screen 的 [index])")}),P=w({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:h("验证步骤描述"),page_kind:C,wait_ms_hint:v("可选:建议回放在验证前等待的毫秒数。仅在你明确知道该步骤通常需要额外等待时填写。"),verify_element:M.describe("用于验证的 UI 元素,必须来自当前 observe_screen 结果"),screen_desc:h('当前页面的简要描述,用一句话概括所在页面(如"抖音首页信息流"、"微信聊天列表"、"系统设置-通用页面")。此字段帮助脚本生成 AI 理解操作上下文,必填。'),page_anchor:M.optional().describe("动作后的页面锚点元素(如页面切换后的新页面标题/导航栏)。当操作导致页面切换时必填,用于脚本生成时确认导航成功。")}),category:"observation"}),q=i.z.object({desc:h("元素描述"),resource_id:I(),text:I(),content_desc:I(),class_name:I(),bounds:I(),index:x("节点 index(来自 observe_screen 的 [index])")}),G=w({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:h("本次滑动/按键在任务流程中的目的和预期效果,说明为什么执行此操作、期望达到什么状态。"),merge_key:I("可选:稳定的步骤合并键。用于把同类滑动/按键动作合并为同一脚本步骤,例如 scroll_result_list、submit_search。"),expected_result:I("可选:本次动作完成后预期达到的状态描述,例如“结果列表继续向下推进”“进入详情页”。"),wait_ms_hint:v("可选:建议回放时在本轮动作后等待或验证的毫秒数。仅在你明确知道该步骤通常需要额外等待时填写。"),target_desc:h("本轮查找目标描述"),target_element:q.optional().describe("查找目标元素,可选"),anchor_element:q.describe("当前页面锚点元素,必填"),page_kind:C,target_role:A,criticality_hint:O,include_mode_hint:S,valid_until:L}),category:"observation"});var z;const V=[w({name:"start_app",description:"启动指定应用。需要 package_name。工具会在启动前自动执行 permission/set(grant_all=true)。",schema:i.z.object({package_name:h("应用包名")}),category:"action"}),w({name:"stop_app",description:"停止(强制关闭)指定应用。",schema:i.z.object({package_name:h("应用包名")}),category:"action"}),w({name:"tap",description:"在屏幕指定坐标点击。属于兜底方案:仅当按 resource_id -> text -> content_desc -> 最小组合 仍无法唯一定位元素时才允许使用。",schema:i.z.object({x:b("X 坐标"),y:b("Y 坐标")}),category:"action"}),w({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:D,...k.shape}).strict(),category:"action"}),w({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:D,...k.shape}).strict(),category:"action"}),w({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:D,text:h("要输入的文本"),...k.shape}).strict(),category:"action"}),w({name:"input_text",description:"向当前聚焦输入框直接输入文本,不需要 selector。",schema:i.z.object({text:h("要输入的文本")}),category:"action"}),w({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:b("起点 X"),start_y:b("起点 Y"),end_x:b("终点 X"),end_y:b("终点 Y"),duration:b("持续时间(ms)").optional()}),category:"action"}),w({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:(z="Android KeyCode",i.z.coerce.number().int().describe(z))}),category:"action"}),w({name:"wait",description:"暂停指定时间。用于等待页面加载或动画完成。时长需按前一动作自适应:轻交互 300~800ms,滑动后 500~1200ms,页面切换/返回 1200~2500ms,启动应用 2000~4000ms;禁止全程固定同一时长。",schema:i.z.object({duration:b("等待时间(ms)")}),category:"action"})],Y=[...[F,P,G,$,U,j],...V];function B(e){return V.some(t=>t.name===e)}const X=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 W(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&&X.has(e))return!0;for(const e of X)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 K(e){return e.normalize("NFKC").replace(/\s+/gu," ").trim()}function J(e,t,s,n){const r=[],o=function(e,t,s){if(!s)return 0;const n=new Set;for(const r of e){if(r.resourceId!==s)continue;const e="text"===t?r.text:r.contentDesc;e&&n.add(K(e))}return n.size}(s,"text"===n?"text":"contentDesc",t.resourceId),i=function(e){const t=K(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=K(e);return t.length>24||/合集|日记|vlog|攻略|教程|推荐|详情|播放|第.+集/u.test(t)}(e),c=e.length<=12,l=1===f(s,"text"===n?{text:e}:{contentDesc:e});o>=2&&r.push("same resource_id carries multiple values on screen"),i&&r.push("contains dynamic segment"),a&&r.push("looks like content item text"),c||r.push("text is long or verbose"),l||r.push("not unique on current screen");const u=0===r.length||c&&l&&r.length<=1,d=u&&!i&&!a;return u&&t.clickable&&c&&r.push("short interactive label"),{stable:u,reusable:d,risk:d?"low":u?"medium":"high",reasons:r}}function H(e,t){const s=[];if(e.resourceId){const t=function(e){const t=[];W(e)?t.push("resource_id looks obfuscated"):t.push("resource_id contains semantic naming");const s=!W(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=J(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=J(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 Q(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 Z(e,t,s,n){const r=f(t,{resourceId:s.resource_id,text:s.text,contentDesc:s.content_desc,className:s.class_name,bounds:s.bounds});if(0===r)return;if(n.requireUnique&&1!==r)return;if(!n.requireUnique&&r>1&&void 0===n.action_index)return;e.some(e=>JSON.stringify(e.selector)===JSON.stringify(s)&&e.action_index===n.action_index)||e.push({selector:s,action_index:n.action_index,stability:n.stability,reusable:n.reusable,matched_count:r,reason:n.reason,scope:n.scope,policy_decision:n.policy_decision,policy_reason:n.policy_reason})}function ee(e,t,s={}){if(!e||0===t.length)return null;const n=s.mode||"action",r=function(e){return"action"===e?{allowCollectionScope:!0,allowActionIndex:!0,allowContentCombos:!0,allowedScopes:["global","page","collection"],policyReason:"action selectors may use collection disambiguation"}:{allowCollectionScope:!1,allowActionIndex:!1,allowContentCombos:!1,allowedScopes:["global","page"],policyReason:`${e} selectors must stay single-field and non-positional`}}(n),o=H(e,t),i=new Map;for(const e of o)i.set(e.field,e);const a=[],c=e.resourceId,l=e.text,u=e.contentDesc,d=e.className,_=e.bounds;if(function(e,t,s,n,r){s.resourceId&&!0===n.get("resource_id")?.reusable&&Z(e,t,{resource_id:s.resourceId},{stability:"high",reusable:!0,reason:"unique_resource_id",scope:"global",requireUnique:!0,policy_decision:"approved",policy_reason:r.policyReason}),s.text&&!0===n.get("text")?.reusable&&Z(e,t,{text:s.text},{stability:"high",reusable:!0,reason:"unique_reliable_text",scope:"global",requireUnique:!0,policy_decision:"approved",policy_reason:r.policyReason}),s.contentDesc&&!0===n.get("content_desc")?.reusable&&Z(e,t,{content_desc:s.contentDesc},{stability:"high",reusable:!0,reason:"unique_reliable_content_desc",scope:"global",requireUnique:!0,policy_decision:"approved",policy_reason:r.policyReason})}(a,t,{resourceId:c,text:l,contentDesc:u},i,r),function(e,t,s,n,r){r.allowContentCombos&&(s.resourceId&&!0===n.get("resource_id")?.reusable&&s.text&&n.get("text")?.stable&&Z(e,t,{resource_id:s.resourceId,text:s.text},{stability:"medium",reusable:!0===n.get("text")?.reusable,reason:"resource_id_text_combo",scope:"page",requireUnique:!0,policy_decision:"approved",policy_reason:r.policyReason}),s.resourceId&&!0===n.get("resource_id")?.reusable&&s.contentDesc&&n.get("content_desc")?.stable&&Z(e,t,{resource_id:s.resourceId,content_desc:s.contentDesc},{stability:"medium",reusable:!0===n.get("content_desc")?.reusable,reason:"resource_id_content_desc_combo",scope:"page",requireUnique:!0,policy_decision:"approved",policy_reason:r.policyReason}),s.text&&s.className&&n.get("text")?.stable&&Z(e,t,{text:s.text,class_name:s.className},{stability:"medium",reusable:!0===n.get("text")?.reusable,reason:"text_class_combo",scope:"page",requireUnique:!0,policy_decision:"approved",policy_reason:r.policyReason}),s.contentDesc&&s.className&&n.get("content_desc")?.stable&&Z(e,t,{content_desc:s.contentDesc,class_name:s.className},{stability:"medium",reusable:!0===n.get("content_desc")?.reusable,reason:"content_desc_class_combo",scope:"page",requireUnique:!0,policy_decision:"approved",policy_reason:r.policyReason}))}(a,t,{resourceId:c,text:l,contentDesc:u,className:d},i,r),function(e,t,s,n,r,o,i){if(o.allowCollectionScope&&o.allowActionIndex){if(n.resourceId){const a={resourceId:n.resourceId},c=s.filter(e=>p(e,a));c.length>1&&Q(c)&&Z(e,s,{resource_id:n.resourceId},{action_index:i??g(s,a,t),stability:!0===r.get("resource_id")?.reusable?"medium":"low",reusable:!0===r.get("resource_id")?.reusable,reason:!0===r.get("resource_id")?.reusable?"resource_id_positional":"obfuscated_id_positional",scope:"collection",policy_decision:"approved",policy_reason:o.policyReason})}if(n.text){const a={text:n.text},c=s.filter(e=>p(e,a));c.length>1&&Q(c)&&Z(e,s,{text:n.text},{action_index:i??g(s,a,t),stability:"low",reusable:!0===r.get("text")?.reusable,reason:"text_positional",scope:"collection",policy_decision:"approved",policy_reason:o.policyReason})}if(n.contentDesc){const a={contentDesc:n.contentDesc},c=s.filter(e=>p(e,a));c.length>1&&Q(c)&&Z(e,s,{content_desc:n.contentDesc},{action_index:i??g(s,a,t),stability:"low",reusable:!0===r.get("content_desc")?.reusable,reason:"content_desc_positional",scope:"collection",policy_decision:"approved",policy_reason:o.policyReason})}n.className&&n.bounds&&Z(e,s,{class_name:n.className,bounds:n.bounds},{stability:"low",reusable:!1,reason:"class_bounds_fallback",scope:"page",requireUnique:!0,policy_decision:"approved",policy_reason:o.policyReason})}}(a,e,t,{resourceId:c,text:l,contentDesc:u,className:d,bounds:_},i,r,s.requestedActionIndex),0===a.length){if("action"!==n)return null;const e=!0===i.get("resource_id")?.reusable,o=!0===i.get("text")?.reusable,m=!0===i.get("content_desc")?.reusable;c?Z(a,t,{resource_id:c},{stability:e?"medium":"low",reusable:e,reason:e?"resource_id_positional":"obfuscated_id_positional",scope:"collection",action_index:s.requestedActionIndex,policy_decision:"approved",policy_reason:r.policyReason}):l?Z(a,t,{text:l},{stability:"low",reusable:o,reason:"text_positional",scope:"collection",action_index:s.requestedActionIndex,policy_decision:"approved",policy_reason:r.policyReason}):u?Z(a,t,{content_desc:u},{stability:"low",reusable:m,reason:"content_desc_positional",scope:"collection",action_index:s.requestedActionIndex,policy_decision:"approved",policy_reason:r.policyReason}):d&&_&&Z(a,t,{class_name:d,bounds:_},{stability:"low",reusable:!1,reason:"class_bounds_fallback",scope:"page",requireUnique:!0,policy_decision:"approved",policy_reason:r.policyReason})}const m=a[0];return m?{mode:n,field_assessments:o,selector_candidates:a,script_selector:{selector:m.selector,action_index:m.action_index,stability:m.stability,reusable:m.reusable,reason:m.reason,scope:m.scope,policy_decision:m.policy_decision,policy_reason:m.policy_reason}}:null}function te(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 se(e,t,s={}){const n=ee(e,t,s);return n?{...n,selected_node:(r=e,{index:r.index,resource_id:r.resourceId,text:r.text,content_desc:r.contentDesc,class_name:r.className,bounds:r.bounds})}:null;var r}const ne=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&&ne.has(e))return!0;for(const e of ne)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 oe(e,t,s){let n=0;for(const r of e)if(fe(r,t)){if(n===s)return r;n++}return null}async function ie(e,t,s,n,r){const o=function(e){return`http://${e.hostIp}:18182/android_api/v2/${e.deviceId}`}(e),i=`${o}/${s}`,a={method:t,headers:{"Content-Type":"application/json"},signal:r};void 0!==n&&"POST"===t&&(a.body=JSON.stringify(n));const c=await fetch(i,a);if(!c.ok){const e=await c.text();throw new Error(`API ${s} failed: ${c.status} - ${e}`)}return await c.json()}function ae(e){return!e||"object"!=typeof e||Array.isArray(e)?null:e}function ce(e,t){return Object.prototype.hasOwnProperty.call(e,t)}function le(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 ue(e,t){if(!ce(e,"index"))return null;return le(e.index).ok?null:`${t}.index 必须是从 0 开始的非负整数。`}function de(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,r]of Object.entries(e))null!=r&&""!==r&&(t[n]=r,s=!0);return s?t:void 0}function me(e){return _e({context_id:`ctx_${Date.now()}_${Math.floor(1e5*Math.random())}`,page_kind:de(e.page_kind),target_role:de(e.target_role),criticality_hint:de(e.criticality_hint),include_mode_hint:de(e.include_mode_hint),valid_until:de(e.valid_until)||"next_verified_transition"})||{context_id:`ctx_${Date.now()}_${Math.floor(1e5*Math.random())}`,valid_until:"next_verified_transition"}}function pe(e){if(!e||"object"!=typeof e||Array.isArray(e))return null;const t=e,s=le(t.index);if(!s.ok)return null;const n={index:s.value,resourceId:de(t.resource_id),text:de(t.text),contentDesc:de(t.content_desc),className:de(t.class_name),bounds:de(t.bounds)};return n.resourceId||n.text||n.contentDesc||n.className||n.bounds?n:null}function fe(e,t){return p(e,t)}function ge(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 he(e,t){if(e.index!==t.index)return!1;const s=ge(e),n=ge(t);return s.length===n.length&&s.every(({key:e,value:s})=>{switch(e){case"resource_id":return!!t.resourceId&&m("resourceId",s,t.resourceId);case"text":return!!t.text&&m("text",s,t.text);case"content_desc":return!!t.contentDesc&&m("contentDesc",s,t.contentDesc);case"class_name":return!!t.className&&m("className",s,t.className);case"bounds":return!!t.bounds&&m("bounds",s,t.bounds)}})}function be(e,t){return f(e,t)}function ye(e,t){const s=pe(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 ve(e,s,n){if(!t.SELECTOR_ACTION_TOOLS.has(e))return null;const r=pe(s.selector);if(!r)return"validation failed: selector is missing or invalid. Provide resource_id/text/content_desc/class_name/bounds.";const o=n?.lastObserveNodes;if(!o||0===o.length)return"validation failed: missing observe evidence. Call observe_screen before action tools.";const i=be(o,r);if(0===i)return"validation failed: selector has no match on latest observed screen.";const a=le(s.action_index);if(!a.ok)return"validation failed: action_index must be a non-negative integer.";if(void 0!==a.value&&a.value>=i)return`validation failed: action_index=${a.value} is out of range for matched_count=${i}.`;if(i>1&&void 0===a.value)return`validation failed: selector is ambiguous (matched_count=${i}). Refine selector or provide action_index.`;const c=function(e){if(!e||"object"!=typeof e||Array.isArray(e))return null;const t=e.target;if(!t||"object"!=typeof t||Array.isArray(t))return null;const s=t,n=le(s.index);if(!n.ok)return null;const r={index:n.value,resourceId:de(s.resource_id),text:de(s.text),contentDesc:de(s.content_desc),className:de(s.class_name),bounds:de(s.bounds)};return r.resourceId||r.text||r.contentDesc||r.className||r.bounds?r:null}(s.pre_context);return c&&!function(e,t){return void 0!==e.index&&void 0!==t.index&&e.index===t.index||!!(e.resourceId&&t.resourceId&&m("resourceId",e.resourceId,t.resourceId))||!!(e.text&&t.text&&m("text",e.text,t.text))||!!(e.contentDesc&&t.contentDesc&&m("contentDesc",e.contentDesc,t.contentDesc))||!!(e.className&&t.className&&m("className",e.className,t.className))||!!(e.bounds&&t.bounds&&m("bounds",e.bounds,t.bounds))}(r,c)?"validation failed: selector does not align with pre_context.target evidence.":null}function xe(e,t){const s=pe(e.selector),n=ae(t.script_selector),r=pe(n?.selector);if(!s||!r)return null;const o=le(e.action_index),i=le(n?.action_index),a=ge(s).map(e=>e.key),c=ge(r).map(e=>e.key);if(he(s,r)&&(o.value??void 0)===(i.value??void 0))return{requested_is_minimal:!0,diff_type:"same",requested_selector_fields:a,script_selector_fields:c,script_selector:n?.selector,script_action_index:i.value};const l=function(e,t){const s=[];for(const{key:s,value:n}of ge(t))if(!(()=>{switch(s){case"resource_id":return!!e.resourceId&&m("resourceId",n,e.resourceId);case"text":return!!e.text&&m("text",n,e.text);case"content_desc":return!!e.contentDesc&&m("contentDesc",n,e.contentDesc);case"class_name":return!!e.className&&m("className",n,e.className);case"bounds":return!!e.bounds&&m("bounds",n,e.bounds)}})())return{matchesBase:!1,redundantFields:[]};for(const{key:n}of ge(e))(()=>{switch(n){case"resource_id":return!!t.resourceId;case"text":return!!t.text;case"content_desc":return!!t.contentDesc;case"class_name":return!!t.className;case"bounds":return!!t.bounds}})()||s.push(n);return{matchesBase:!0,redundantFields:s}}(s,r),u=void 0!==o.value&&void 0===i.value;return l.matchesBase&&(l.redundantFields.length>0||u)?{requested_is_minimal:!1,diff_type:"redundant_constraint",requested_selector_fields:a,script_selector_fields:c,redundant_selector_fields:l.redundantFields,requested_action_index:o.value,script_action_index:i.value,script_selector:n?.selector}:{requested_is_minimal:!1,diff_type:"different_contract",requested_selector_fields:a,script_selector_fields:c,requested_action_index:o.value,script_action_index:i.value,script_selector:n?.selector}}const Ie={async observe_screen(e,t,s,n){const r="number"==typeof e.wait_ms&&e.wait_ms>0?e.wait_ms:0;if(r>0){const e=await ie(t,"POST","base/sleep",{duration:r},s);if(200!==e.code)return{success:!1,error:e.msg||"Wait before observe failed"}}const o=await ie(t,"POST","accessibility/dump_compact",{},s);return 200==o.code&&"string"==typeof o.data?(n&&te(n,o.data),{success:!0,data:o.data}):{success:!1,error:o.msg||"Failed to get UI dump"}},async verify_ui_state(e,t,s,n){const r=de(e.step_desc);if(!r)return{success:!1,error:"verify_ui_state 缺少 step_desc。"};const o=de(e.screen_desc);if(!o)return{success:!1,error:"verify_ui_state 缺少 screen_desc。"};const i=function(e){return _e({page_kind:de(e.page_kind)})}(e),a=e.verify_element;if(!a||"object"!=typeof a||Array.isArray(a))return{success:!1,error:"verify_ui_state 缺少 verify_element 对象。"};const c=ue(a,"verify_element");if(c)return{success:!1,error:c};const l=pe(a);if(!l)return{success:!1,error:"verify_element 必须提供 resource_id、text、content_desc、class_name 或 bounds 至少一个(可选 index),且需来自当前 observe_screen 结果。"};const u=e.page_anchor;if(u&&"object"==typeof u&&!Array.isArray(u)){const e=ue(u,"page_anchor");if(e)return{success:!1,error:e}}const d=n?.lastObserveNodes;if(!d||0===d.length)return{success:!1,error:"请先调用 observe_screen(可带 wait_ms)获取当前界面,再调用 verify_ui_state。"};if(be(d,l)>0){const e={step_desc:r,screen_desc:o,verify_element:a,...i?{recorded_page:i}:{}},t=oe(d,l,0);if(t){const s=se(t,d,{mode:"verify"});s&&(e.verification_plan=s)}if(u&&"object"==typeof u&&!Array.isArray(u)){const t=pe(u);if(!t)return{success:!1,error:"page_anchor 必须提供 resource_id、text、content_desc、class_name 或 bounds 至少一个(可选 index)。"};if(0===be(d,t))return{success:!1,error:"page_anchor 必须来自当前 observe_screen 的真实节点,当前界面未匹配。"};e.page_anchor=u;const s=oe(d,t,0);if(s){const t=se(s,d,{mode:"anchor"});t&&(e.page_anchor_plan=t)}}return{success:!0,data:e}}const _=[void 0!==l.index&&`index=${l.index}`,l.resourceId&&`resource_id="${l.resourceId}"`,l.text&&`text="${l.text}"`,l.contentDesc&&`content_desc="${l.contentDesc}"`,l.className&&`class_name="${l.className}"`].filter(Boolean).join(", "),m=d.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(" ")}`});return{success:!1,error:`验证元素在当前界面中不存在:${_} 未匹配。请从当前界面重新选择 verify 元素。${m.length>0?`\n当前界面可用的验证候选元素(直接从中选择,无需再调 observe_screen):\n${m.join("\n")}`:""}`}},async record_search_context(e,t,s,n){const r=de(e.step_intent);if(!r)return{success:!1,error:"record_search_context 缺少 step_intent。"};const o=de(e.target_desc);if(!o)return{success:!1,error:"record_search_context 缺少 target_desc。"};const i=e.anchor_element;if(!i||"object"!=typeof i||Array.isArray(i))return{success:!1,error:"record_search_context 缺少 anchor_element(当前页面锚点),必填。"};const a=i,c=ue(a,"anchor_element");if(c)return{success:!1,error:c};if(!function(e){const t=e.resource_id,s=e.text,n=e.content_desc,r=e.class_name,o=e.bounds;return"string"==typeof t&&t.trim().length>0||"string"==typeof s&&s.trim().length>0||"string"==typeof n&&n.trim().length>0||"string"==typeof r&&r.trim().length>0||"string"==typeof o&&o.trim().length>0}(a))return{success:!1,error:"anchor_element 必须包含 resource_id、text、content_desc、class_name 或 bounds 至少一个(可选 index)。"};const l=n?.lastObserveNodes;if(!l||0===l.length)return{success:!1,error:"record_search_context 前必须先调用 observe_screen 获取当前界面。"};const u=pe(i);if(!u)return{success:!1,error:"anchor_element 不是合法 selector。"};if(0===be(l,u))return{success:!1,error:"anchor_element 必须来自当前 observe_screen 的真实节点,当前界面未匹配。"};const d=e.target_element,_={...e,step_intent:r,target_desc:o,recorded_context:me({...e})},m=oe(l,u,0);if(m){const e=se(m,l,{mode:"anchor"});e&&(_.anchor_plan=e)}if(d&&"object"==typeof d&&!Array.isArray(d)){const e=ue(d,"target_element");if(e)return{success:!1,error:e};const t=pe(d);if(!t)return{success:!1,error:"target_element 必须提供 resource_id、text、content_desc、class_name 或 bounds 至少一个(可选 index)。"};if(0===be(l,t))return{success:!1,error:"target_element 必须来自当前 observe_screen 的真实节点,当前界面未匹配。"};const s=oe(l,t,0);if(s){const e=se(s,l,{mode:"action"});e&&(_.target_plan=e)}}return{success:!0,data:_}},async get_installed_apps(e,t,s){const n=e.type||"user",r=await ie(t,"GET",`package/list?type=${n}`,void 0,s);if(200==r.code&&r.data?.packages){return{success:!0,data:r.data.packages.map(e=>`${e.app_name||"unknown"} = ${e.package_name||""}`).join("\n")}}return{success:!1,error:r.msg||"Failed to get app list"}},async get_top_activity(e,t,s){const n=await ie(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 ie(t,"GET","display/info",void 0,s);return 200==n.code?{success:!0,data:n.data}:{success:!1,error:n.msg||"Failed to get screen info"}},async start_app(e,t,s){const n=e.package_name;if(!n)return{success:!1,error:"Missing required param: package_name"};const r=await ie(t,"POST","permission/set",{package_name:n,grant_all:!0},s);if(200!==r.code)return{success:!1,error:`权限授权失败 (${n}): ${r.msg}。建议:通过 get_installed_apps 确认包名是否正确。`};const o=await ie(t,"POST","activity/start",{package_name:n},s);return 200==o.code?{success:!0,data:o.data}:{success:!1,error:o.msg||"Failed to start app"}},async stop_app(e,t,s){const n=e.package_name,r=await ie(t,"POST","activity/stop",{package_name:n},s);return 200==r.code?{success:!0,data:r.data}:{success:!1,error:r.msg||"Failed to stop app"}},async tap(e,t,s){const n=await ie(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=le(e.action_index);if(!n.ok)return{success:!1,error:"Invalid action_index: expected non-negative integer"};const r={selector:e.selector,action:"click",wait_timeout:e.wait_timeout??5e3,wait_interval:e.wait_interval??1e3};void 0!==n.value&&(r.action_index=n.value);const o=await ie(t,"POST","accessibility/node",r,s);return 200==o.code?{success:!0,data:o.data}:{success:!1,error:o.msg?`元素操作失败: ${o.msg}。建议:重新 observe_screen 确认元素是否存在,检查 selector 唯一性,必要时补充 action_index。`:"元素未找到或点击失败,请重新 observe_screen 后检查 selector"}},async long_press_element(e,t,s){const n=le(e.action_index);if(!n.ok)return{success:!1,error:"Invalid action_index: expected non-negative integer"};const r={selector:e.selector,action:"long_click",wait_timeout:e.wait_timeout??5e3,wait_interval:e.wait_interval??1e3};void 0!==n.value&&(r.action_index=n.value);const o=await ie(t,"POST","accessibility/node",r,s);return 200==o.code?{success:!0,data:o.data}:{success:!1,error:o.msg||"Long press element failed"}},async set_text(e,t,s){const n=le(e.action_index);if(!n.ok)return{success:!1,error:"Invalid action_index: expected non-negative integer"};const r={selector:e.selector,action:"set_text",action_params:{text:e.text},wait_timeout:e.wait_timeout??5e3,wait_interval:e.wait_interval??1e3};void 0!==n.value&&(r.action_index=n.value);const o=await ie(t,"POST","accessibility/node",r,s);return 200==o.code?{success:!0,data:o.data}:{success:!1,error:o.msg?`文本输入失败: ${o.msg}。建议:确认输入框已聚焦,或尝试先 tap_element 聚焦后使用 input_text。`:"文本输入失败,请确认输入框已存在且可聚焦"}},async input_text(e,t,s){const n=await ie(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 ie(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 ie(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 ie(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 Te(e,s,n,r){const o=Ie[e.name],i=(a=e.name,Y.find(e=>e.name===a));var a;if(!o||!i)return{toolCallId:e.id,name:e.name,success:!1,error:`Unknown tool: ${e.name}`};const c=(l=e.arguments)&&"object"==typeof l&&!Array.isArray(l)?l:{};var l;const u=function(e,s){const n=ce(s,"index"),r=ce(s,"action_index"),o=ae(s.selector),i=!!o&&ce(o,"index");return t.ACCESSIBILITY_NODE_TOOLS.has(e)?n?"Invalid index usage: use action_index (top-level) for accessibility/node tools":i?"Invalid selector.index: selector does not support index, use action_index instead":null:n||r?"Invalid index usage: index/action_index are only allowed for accessibility/node tools":null}(e.name,c);if(u)return{toolCallId:e.id,name:e.name,success:!1,error:u};const d=i.schema.safeParse(c);if(!d.success&&t.STRICT_SCHEMA_TOOLS.has(e.name)){const t=d.error.issues.map(e=>`${e.path.join(".")}: ${e.message}`).join("; ");return{toolCallId:e.id,name:e.name,success:!1,error:`参数校验失败,拒绝执行: ${t}。请检查 pre_context.anchor 和 pre_context.target 是否已填写,selector 字段是否合法。`}}const _=d.success?d.data:c,m=function(e,s){if(!t.PRE_CONTEXT_REQUIRED_TOOLS.has(e))return null;const n=s.pre_context;if(!n||"object"!=typeof n||Array.isArray(n))return`pre_context 缺失:${e} 调用前必须提供 pre_context(anchor + target)。请先 observe_screen 确认页面状态,再填写 pre_context 后重新调用。`;const r=n,o=r.anchor;if(!o||"object"!=typeof o||Array.isArray(o))return"pre_context.anchor 缺失:必须提供页面锚点元素用于确认当前页面。anchor 应选择页面中稳定存在的框架级元素(如导航栏、标题栏)。";if(!de(o.desc))return"pre_context.anchor.desc 缺失:必须提供页面锚点描述,用于说明当前页面上下文。";const i=r.target;return!i||"object"!=typeof i||Array.isArray(i)?"pre_context.target 缺失:必须提供操作目标元素信息。target 应来自当前 observe_screen 的真实节点属性。":de(i.desc)?de(r.step_intent)?null:"pre_context.step_intent 缺失:必须说明本次操作在任务中的目的和预期效果,而非重复元素描述。此字段用于脚本生成的步骤命名和合并。":"pre_context.target.desc 缺失:必须提供目标元素描述,用于说明本次操作意图。"}(e.name,_);if(m)return{toolCallId:e.id,name:e.name,success:!1,error:m};const p=ve(e.name,_,r);if(p)return{toolCallId:e.id,name:e.name,success:!1,error:p};try{const i=await o(_,s,n,r);if(i.success){const s=[];if(t.SELECTOR_ACTION_TOOLS.has(e.name)&&r?.lastObserveNodes?.length){const e=pe(_.selector),t=le(_.action_index);if(e){const n=oe(r.lastObserveNodes,e,t.value??0);if(n){const e=se(n,r.lastObserveNodes,{mode:"action",requestedActionIndex:t.value});if(e){const t=i.data&&"object"==typeof i.data&&!Array.isArray(i.data)?i.data:{},n=xe(_,e);i.data={...t,_selector_recording_profile:e,...n?{_selector_request_analysis:n}:{}};const r=n?function(e){if(!0===e.requested_is_minimal)return null;if("redundant_constraint"===("string"==typeof e.diff_type?e.diff_type:"different_contract")){const t=Array.isArray(e.redundant_selector_fields)?e.redundant_selector_fields.filter(e=>"string"==typeof e):[],s=[...t.length>0?[`冗余字段: ${t.join(", ")}`]:[],void 0!==e.requested_action_index&&void 0===e.script_action_index?"冗余 action_index":void 0].filter(Boolean);return"selector 最小化提醒: 当前请求 selector 可执行,但不是最小稳定唯一方案。录制阶段批准的 script_selector 已足够定位。"+(s.length>0?`请去掉 ${s.join(",")}。`:"")}return"selector 对齐提醒: 当前请求 selector 与录制阶段批准的 script_selector 不一致。虽然本次执行成功,但后续应优先对齐为 recording-approved script_selector。"}(n):null;r&&s.push(r)}}}}if(t.SELECTOR_ACTION_TOOLS.has(e.name)&&r?.lastObserveNodes){const e=ye(_,r.lastObserveNodes);e&&s.push(e)}if(t.SELECTOR_ACTION_TOOLS.has(e.name)){const e=function(e){const t=ae(e.pre_context);if(!t)return null;const s=pe(t.anchor),n=pe(t.target);return s&&n&&he(s,n)?"pre_context 质量提醒: anchor 与 target 使用了完全相同的定位证据。anchor 应优先描述当前页面或容器级稳定锚点,而不是与点击目标重合。":null}(_);e&&s.push(e)}if(r?.softPendingVerify&&B(e.name)){const e=r.softPendingVerify;s.push(`⚠️ 补录提醒: 上一步 #${e.index} ${e.toolName} 的 verify_ui_state 被跳过,录制信息不完整,将影响脚本生成质量。建议在当前操作完成后补充 observe_screen → verify_ui_state。`)}return t.POST_VERIFY_HINT_TOOLS.has(e.name)&&s.push("录制提醒: 此动作可能改变界面,请执行 observe_screen(wait_ms) → verify_ui_state 记录验证信息。"),"swipe"===e.name&&(!r?.lastSearchContextTimestamp||Date.now()-r.lastSearchContextTimestamp>3e4)&&s.push("⚠️ 录制提醒: swipe 前未调用 record_search_context,录制信息将不完整。建议先调用 record_search_context 记录查找目标与页面锚点。"),{toolCallId:e.id,name:e.name,success:!0,data:i.data,warning:s.length>0?s.join("\n"):void 0}}return{toolCallId:e.id,name:e.name,success:i.success,data:i.data,error:i.error}}catch(t){return{toolCallId:e.id,name:e.name,success:!1,error:t.message||String(t)}}}const Ne=["search","explore","discover","for you","home","friends","inbox","profile","close","log in","sign up","comment","搜索","首页","消息","我的","关闭","登录","评论"];function we(e){const t=e.match(/\bbounds=\[(\d+),(\d+)\]\[(\d+),(\d+)\]/);if(!t)return null;const s=Number(t[2]),n=Number(t[4]);return Number.isFinite(s)&&Number.isFinite(n)?{y1:s,y2:n}:null}function Ee(e){if(0===e.length)return null;const t=function(e){const t=e.match(/^Screen\s+\d+x(\d+)/);if(!t)return null;const s=Number(t[1]);return Number.isFinite(s)&&s>0?s:null}(e[0]||"");if(!t)return null;let s=0,n=0,r=0;for(let o=1;o<e.length;o++){const i=e[o];if(!i.includes("clickable=true"))continue;const a=we(i);if(!a)continue;const c=(a.y1+a.y2)/2/t;c<.33?s++:c<=.67?n++:r++}return 0===s+n+r?null:`[可点击分布] 顶部:${s} 中部:${n} 底部:${r} (按 bounds center_y)`}function Ce(e){if(!e.success)return`Error: ${e.error||"operation failed"}`;const t=e.data;let s;if("verify_ui_state"===e.name&&t&&"object"==typeof t){const e=t,n=e.step_desc,r=e.verify_element;s=n?`verify_ui_state OK: ${n}`:"verify_ui_state OK",r&&(r.resource_id||r.text||r.content_desc)&&(s+=` (element: ${[r.resource_id,r.text,r.content_desc].filter(Boolean).join(" / ")})`)}else if("record_search_context"===e.name&&t&&"object"==typeof t){const e=t;s=e.target_desc?`record_search_context OK: ${e.target_desc}`:"record_search_context OK"}else if("observe_screen"===e.name&&"string"==typeof t){const e=t.split("\n"),n=(t.match(/clickable/g)||[]).length,r=function(e){if(e.length<=120)return{content:e.join("\n"),shownLines:e.length,truncated:!1};const t=new Map,s=s=>{s<0||s>=e.length||t.has(s)||t.set(s,e[s])};s(0);for(let t=1;t<Math.min(e.length,51);t++)s(t);for(let t=Math.max(1,e.length-40);t<e.length;t++)s(t);let n=0;for(let s=1;s<e.length&&n<30;s++){const r=e[s],o=r.toLowerCase(),i=Ne.some(e=>o.includes(e)),a=/\bbounds=\[/.test(r)&&(/\bclickable=true\b/.test(r)||/\bcontent-desc=/.test(r)||/\btext="/.test(r)||/\bresource-id=/.test(r));(i||a)&&(t.has(s)||(t.set(s,r),n++))}const r=Array.from(t.entries()).sort((e,t)=>e[0]-t[0]).slice(0,120).map(([,e])=>e);return{content:r.join("\n"),shownLines:r.length,truncated:!0}}(e),o=`[元素总行数: ${e.length}, 可点击: ${n}, 展示行: ${r.shownLines}]`,i=Ee(e),a="[位置规则] 仅依据 bounds=[x1,y1][x2,y2] + Screen WxH 判断顶部/底部;禁止按节点顺序或缩进推断位置。",c=r.truncated?"\n... (已按头部+尾部+关键词/可交互关键行压缩展示;定位不确定时请重新 observe_screen 并用 verify_ui_state 验证)":"";s=`${o}\n${a}${i?`\n${i}`:""}\n${r.content}${c}`}else s=function(e){if(!e||"object"!=typeof e||Array.isArray(e))return!1;const t=e;return"action_success"in t&&"nodes"in t&&Array.isArray(t.nodes)}(t)?function(e){const t=e,s="number"==typeof t.count?t.count:0,n="string"==typeof t.action?t.action:"find";return!0!==t.action_success?`${n} failed (matched ${s} nodes)`:s<=1?`${n} OK`:`${n} OK (注意: 匹配到 ${s} 个元素,操作了第 ${"number"==typeof t.action_index?t.action_index:0} 个)`}(t):"string"==typeof t?t:!0===t||void 0===t?"OK":JSON.stringify(t,null,2);return e.warning&&(s+=`\n⚠️ ${e.warning}`),s}class Ae{handlers={planning:new Set,thinking:new Set,toolcall:new Set,toolresult:new Set,diagnostic:new Set,paused:new Set,scriptgenerating:new Set,complete:new Set,error:new Set};on(e,t){return this.handlers[e].add(t),()=>this.off(e,t)}off(e,t){this.handlers[e].delete(t)}emit(e,t){for(const s of Array.from(this.handlers[e]))s(t)}clear(){Object.keys(this.handlers).forEach(e=>{this.handlers[e].clear()})}}class Oe extends Error{code;details;constructor(e,t,s){super(t),this.name="AgentRuntimeError",this.code=e,this.details=s}}const Se=["INVALID_CONFIG","SESSION_NOT_FOUND","SESSION_NOT_PAUSED","AGENT_RUNNING","EMPTY_EXECUTION_LOG","MCP_CONNECT_FAILED","SCRIPT_GENERATION_FAILED","RUNTIME_LOOP_FAILED","UNKNOWN"];function Le(e){return!!e&&"object"==typeof e&&!Array.isArray(e)}function Re(e,t="UNKNOWN"){if(e instanceof Oe)return e;const s=function(e){return e instanceof Error&&e.message||Le(e)&&"string"==typeof e.message?e.message:String(e)}(e);return Le(e)?new Oe("string"==typeof(n=e.code)&&Se.includes(n)?e.code:t,s,Le(e.details)?e.details:void 0):new Oe(t,s);var n}const De=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)});function ke(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 Fe(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 $e(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 Ue(e,t,s,n){return"zh"===n?`## 目标\n\n${e}\n\n## 当前设备屏幕\n\n${t}${s}`:`## Goal\n\n${e}\n\n## Current Device Screen\n\n${t}${s}`}async function je(e,s,n,r,o){return async function(e,s,n,r,o){const i=t.detectPromptLocale(e),a={lastObserveRawDump:null,lastObserveNodes:[]},c=await Te({id:"plan-observe-screen",name:"observe_screen",arguments:{},source:"local"},n,o,a),l=c.success&&"string"==typeof c.data?c.data:Ce(c),u=a.lastObserveRawDump||void 0,d=t.createProvider(s),_=[],m=Math.max(1,r.planner.maxAttempts);for(let s=1;s<=m;s++){const n=$e(s,_,i),r=[{role:"system",content:t.buildTaskPlanPrompt(e)},{role:"user",content:Ue(e,l,n,i)}];try{let t;if(d.capabilities.structuredJson)t=await d.chatStructuredJson(r,De,o);else{const e=await d.chatWithTools(r,[],o);t=JSON.parse(ke(e.content))}const n=De.parse(t),i=Math.max(n.estimatedSteps,2*n.subtasks.length,1);return{plan:{goal:n.goal||e,subtasks:n.subtasks,risks:n.risks,assumptions:n.assumptions,estimatedSteps:i},screenDump:l,screenRawDump:u,attempts:s,errors:_}}catch(e){_.push(e.message||String(e))}}return{plan:Fe(e,i),screenDump:l,screenRawDump:u,attempts:m,errors:_}}(e,s,n,r,o)}function Me(e,t){return t<=0?null:e/t}function Pe(e,t){const s={...t,timestamp:Date.now()};return e.diagnostics.push(s),s}function qe(e){const t=[...e.diagnostics].reverse().find(e=>"runtime"===e.phase||"script_generation"===e.phase)?.code,s=function(e){let t=0,s=0,n=0;for(const r of e)r.result.success&&(t+=1),"action"===r.category&&(s+=1,r.result.success&&(n+=1));return{totalToolCalls:e.length,successfulToolCalls:t,runtimeSuccessRate:Me(t,e.length),totalActionCalls:s,successfulActionCalls:n,automationSuccessRate:Me(n,s)}}(e.executionLog),n={};let r=0;for(const t of e.executionLog){if(t.result.success)continue;r+=1;const e=t.result.errorCode||"UNKNOWN";n[e]=(n[e]||0)+1}return{planningRetries:e.runtimeContext.planningRetries,toolRetries:e.runtimeContext.toolRetries,scriptGenerationRetries:e.runtimeContext.scriptGenerationRetries,convergenceTriggers:e.runtimeContext.convergenceTriggers,totalToolCalls:s.totalToolCalls,successfulToolCalls:s.successfulToolCalls,runtimeSuccessRate:s.runtimeSuccessRate,totalActionCalls:s.totalActionCalls,successfulActionCalls:s.successfulActionCalls,automationSuccessRate:s.automationSuccessRate,totalFailures:r,failureByCode:n,finalFailureCode:t}}function Ge(e,s,n,r,o){const i=[{role:"system",content:t.buildAgentSystemPrompt(e.goal,r)},{role:"user",content:e.goal}];return o&&(i.push({role:"assistant",content:"",toolCalls:[{id:"plan-initial-observe",name:"observe_screen",arguments:{},source:"local"}]}),i.push({role:"tool",content:o,toolCallId:"plan-initial-observe"})),{sessionId:s,goal:e.goal,provider:e.provider,device:e.device,reliability:n,iteration:0,running:!1,paused:!1,messages:i,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:r}}function ze(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 Ve(e,t,s){e.messages.push({role:"tool",content:s,toolCallId:t,timestamp:Date.now()})}const Ye=new Set(["verify_ui_state","record_search_context","get_screen_info"]);function Be(e){return!e||"object"!=typeof e||Array.isArray(e)?null:e}function Xe(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 We(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 Ke(e){const t={};let s=!1;for(const[n,r]of Object.entries(e))null!=r&&""!==r&&(t[n]=r,s=!0);return s?t:void 0}function Je(e){const t=Be(e);if(!t)return;return Ke({index:Xe(t.index),resource_id:We(t,["resource_id","resource-id","resourceId","id"]),text:We(t,["text"]),content_desc:We(t,["content_desc","content-desc","contentDesc"]),class_name:We(t,["class_name","class","className"]),bounds:We(t,["bounds"])})}function He(e,s){if(!t.SELECTOR_ACTION_TOOLS.has(e.name)||!s.success)return null;const n=Be(e.arguments),r=Be(s.data);if(!n&&!r)return null;const o=Ke(Be(n?.selector)||{}),i=Xe(n?.action_index),a=Array.isArray(r?.nodes)?r.nodes:[],c=Xe(r?.action_index),l=i??c??0,u=Je(a[l]??a[0]),d=a.slice(0,5).map(e=>Je(e)).filter(e=>!!e),_="number"==typeof r?.count&&Number.isFinite(r.count)?r.count:a.length,m={};o&&(m.requested_selector=o),void 0===i&&void 0===c||(m.action_index=l),_>=0&&(m.matched_count=_),u&&(m.selected_node=u),d.length>0&&(m.candidate_nodes=d);const p=function(e){const t=Be(e);if(!t)return;return Ke({mode:"string"==typeof t.mode?t.mode:void 0,script_selector:Be(t.script_selector)||void 0,field_assessments:Array.isArray(t.field_assessments)?t.field_assessments.map(e=>Be(e)).filter(e=>!!e).slice(0,8):void 0,selector_candidates:Array.isArray(t.selector_candidates)?t.selector_candidates.map(e=>Be(e)).filter(e=>!!e).slice(0,8):void 0,selected_node:Je(t.selected_node)})}(r?._selector_recording_profile);if(p){m.selector_profile=p;const e=Be(p.script_selector);if(e){const t=Be(e.selector);t&&(m.recommended_selector=t);const s=Xe(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 f=Be(r?._selector_request_analysis);return f&&(m.selector_request_analysis=f),Object.keys(m).length>0?m:null}function Qe(e,t){const s=e.name;if(Ye.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=He(e,t);if(n){const e={toolCallId:t.toolCallId,name:t.name,success:t.success,data:{runtime_selector_evidence:n},source:t.source,server:t.server};return t.error&&(e.error=t.error),t.warning&&(e.warning=t.warning),e}const r={toolCallId:t.toolCallId,name:t.name,success:t.success,source:t.source,server:t.server};return t.error&&(r.error=t.error),t.warning&&(r.warning=t.warning),r}function Ze(e,t,s,n){e.executionLog.push({index:e.executionLog.length+1,toolName:t.name,arguments:t.arguments,result:Qe(t,s),category:n,timestamp:Date.now()})}const et=[/resource-id=/,/resource_id=/,/\btext="/,/content-desc=/,/content_desc=/,/clickable=true/,/bounds=\[/];function tt(e){return e.startsWith("Screen ")||e.startsWith("[Package]")||e.startsWith("[元素总行数:")}function st(e){const t=e.split("\n");if(t.length<=7)return e;const s=t[0]||"",n=new Set,r=[];let o=0;for(let e=1;e<t.length;e++){const s=t[e];s.includes("clickable=true")&&o++,et.some(e=>e.test(s))&&(n.has(s)||(n.add(s),r.push(s)))}return[s,...r.length>0?r.slice(0,6):t.slice(1,7),"[位置规则] 仅依据 bounds + Screen WxH 判断顶部/底部,禁止按节点顺序推断。",`[... 已压缩,原始 ${t.length} 行,关键候选 ${r.length} 行,可点击 ${o} 个]`].join("\n")}function nt(e){if(e.length<=14)return e;const t=e.length-12,s=[],n=[],r=[];for(let t=0;t<e.length;t++){const s=e[t];"tool"===s.role&&tt(s.content)&&n.push(t),"tool"===s.role&&(s.content.includes('"step_desc"')||s.content.includes('"target_desc"'))&&r.push(t)}const o=new Set(n.slice(-1)),i=new Set(r.slice(-3));for(let n=0;n<e.length;n++){const r=e[n];if(n<=1)s.push(r);else if(n>=t)s.push(r);else if("user"!==r.role)if("assistant"!==r.role)if(r.content.length<=200)s.push(r);else if(!r.content.includes('"step_desc"')&&!r.content.includes('"target_desc"')||i.has(n))if(tt(r.content)){if(o.has(n)){s.push(r);continue}s.push({...r,content:st(r.content)})}else s.push({...r,content:r.content.substring(0,200)+"\n[... 已截断]"});else try{const e=JSON.parse(r.content);s.push({...r,content:JSON.stringify({step_desc:e.step_desc,target_desc:e.target_desc})})}catch{s.push({...r,content:r.content.substring(0,200)+"\n[... 已截断]"})}else r.content.length<=120?s.push(r):s.push({...r,content:r.content.substring(0,120)+"\n[... 已截断]"});else s.push(r)}return s}function rt(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 ot{servers;client=null;constructor(e){this.servers=e}async connect(){if(!this.servers||0===Object.keys(this.servers).length)return[];if(!this.client){const e={};for(const[t,s]of Object.entries(this.servers))e[t]=rt(s);this.client=new c.MultiServerMCPClient({mcpServers:e})}const e=[];for(const t of Object.keys(this.servers)){const s=await this.client.getTools(t);for(const n of s)n.name&&e.push({name:n.name,tool:n,server:t})}return e}async close(){this.client&&(await this.client.close(),this.client=null)}}const it=new Set(Y.map(e=>e.name));class at{mcpClient=null;mcpTools=new Map;modelTools=[...Y];getModelTools(){return this.modelTools}async prepare(e={}){if(await this.closeMcpClient(),this.mcpTools.clear(),this.modelTools=[...Y],0===Object.keys(e).length)return;const t=new ot(e);try{const e=await t.connect();!function(e,t){const s=[],n=new Set(e),r=new Set;for(const e of t){const t=e.name;t&&(n.has(t)||r.has(t)?s.push(t):r.add(t))}if(s.length>0){const e=Array.from(new Set(s)).join(", ");throw new Error(`MCP tool name conflict detected: ${e}`)}}(Y.map(e=>e.name),e);for(const t of e)this.mcpTools.set(t.name,{tool:t.tool,server:t.server});this.modelTools=[...Y,...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&&B(e.name)?"action":"observation"}async execute(e,t,s,n){const r=this.resolveTool(e.name);if("local"===r.source){return{...await Te({id:e.id,name:e.name,arguments:e.arguments},t,s,n),source:"local"}}if(!r.tool)return{toolCallId:e.id,name:e.name,success:!1,error:`MCP tool not found: ${e.name}`,source:"mcp",server:r.server};try{const t=await r.tool.invoke(e.arguments);return{toolCallId:e.id,name:e.name,success:!0,data:t,source:"mcp",server:r.server}}catch(t){return{toolCallId:e.id,name:e.name,success:!1,error:t.message||String(t),source:"mcp",server:r.server}}}async destroy(){await this.closeMcpClient(),this.mcpTools.clear(),this.modelTools=[...Y]}resolveTool(e){if(it.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 ct="PLANNER_RETRY",lt="PLANNER_FALLBACK",ut="TOOL_RETRY",dt="VERIFY_GATE_BLOCKED",_t="MAX_ITERATIONS_REACHED",mt="CONSECUTIVE_FAILURE_LIMIT_REACHED",pt="SAME_TOOL_REPEAT_LIMIT_REACHED",ft="SCRIPT_RETRY",gt=new Set(["TIMEOUT","NETWORK","HTTP_5XX"]);function ht(e){if(e.errorCode)return{code:e.errorCode,retryable:e.retryable??gt.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 bt(e,t){const s=new AbortController,n=()=>{s.abort(t?.reason)};t&&(t.aborted&&s.abort(t.reason),t.addEventListener("abort",n));const r=setTimeout(()=>{s.abort(new Error(`Tool timeout after ${e}ms`))},e);return{signal:s.signal,dispose:()=>{clearTimeout(r),t&&t.removeEventListener("abort",n)}}}function yt(e,t){return{toolCallId:e.id,name:e.name,success:!1,error:`Tool timeout after ${t}ms`,errorCode:"TIMEOUT",retryable:!0}}function vt(e,t){return t instanceof Error&&/timeout/i.test(t.message)?yt(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 xt(e){const t=Math.max(1,e.maxAttempts);for(let s=1;s<=t;s++){const n=Date.now(),r=bt(e.timeoutMs,e.signal);let o;try{o=await e.execute(e.toolCall,e.device,r.signal)}catch(t){const s=r.signal.reason instanceof Error?r.signal.reason.message:String(r.signal.reason||"");o=r.signal.aborted&&/timeout/i.test(s)?yt(e.toolCall,e.timeoutMs):vt(e.toolCall,t)}finally{r.dispose()}if(o.latencyMs||(o.latencyMs=Date.now()-n),o.attempt=s,o.success)return o;const i=ht(o);if(o.errorCode=o.errorCode??i.code,o.retryable=o.retryable??i.retryable,"ABORTED"===o.errorCode)return o;if(!o.retryable||s>=t)return o;e.onRetry?.(s,o)}return{toolCallId:e.toolCall.id,name:e.toolCall.name,success:!1,error:"Unreachable: retry loop terminated unexpectedly",errorCode:"UNKNOWN",retryable:!1}}function It(e,t){if(!Number.isFinite(e))return t;const s=Math.floor(e);return s<=0?t:s}function Tt(e,t){return"action"===t?e.retries.action:e.retries.observation}function Nt(e,s){const n=t.TOOL_TIMEOUT_OVERRIDES_MS[s];return n?e.timeouts[n]:e.timeouts.defaultMs}function wt(e){if(null==e)return String(e);if("object"!=typeof e)return JSON.stringify(e);if(Array.isArray(e))return`[${e.map(e=>wt(e)).join(",")}]`;return`{${Object.entries(e).sort(([e],[t])=>e.localeCompare(t)).map(([e,t])=>`${JSON.stringify(e)}:${wt(t)}`).join(",")}}`}function Et(e){return`${e.name}:${wt(e.arguments||{})}`}function Ct(e){return"local"===e.source&&B(e.name)?"action":"observation"}class At{options;emitter=new Ae;toolRegistry=new at;session=null;provider=null;abortController=null;sessionStore=null;localToolContext={lastObserveRawDump:null,lastObserveNodes:[]};constructor(e={}){this.options=e;if("sqlite"!==(e.persistence?.mode||"sqlite"))return;const t=l();try{this.sessionStore=new d(t)}catch(e){const s=Re(e,"INVALID_CONFIG");throw new Oe("INVALID_CONFIG",`session store initialization failed: ${s.message}`,{sqlitePath:t,cause:s.message})}}on(e,t){return this.emitter.on(e,t)}off(e,t){this.emitter.off(e,t)}async start(s){this.session?.running&&this.stop(this.session.sessionId);try{this.provider=t.createProvider(s.provider)}catch(e){const t=Re(e,"INVALID_CONFIG");throw new Oe("INVALID_CONFIG",`invalid model provider configuration: ${t.message}`,{provider:{vendor:s.provider.vendor,baseUrl:s.provider.baseUrl,model:s.provider.model},cause:t.message})}try{await this.toolRegistry.prepare(this.options.mcpServers||{})}catch(e){const t=Re(e,"MCP_CONNECT_FAILED");throw new Oe("MCP_CONNECT_FAILED",`failed to connect MCP tools: ${t.message}`,{servers:Object.keys(this.options.mcpServers||{}),cause:t.message,...t.details})}const n=(r=s.reliability)?{retries:{observation:It(r.retries?.observation??t.DEFAULT_RELIABILITY_CONFIG.retries.observation,t.DEFAULT_RELIABILITY_CONFIG.retries.observation),action:It(r.retries?.action??t.DEFAULT_RELIABILITY_CONFIG.retries.action,t.DEFAULT_RELIABILITY_CONFIG.retries.action)},timeouts:{defaultMs:It(r.timeouts?.defaultMs??t.DEFAULT_RELIABILITY_CONFIG.timeouts.defaultMs,t.DEFAULT_RELIABILITY_CONFIG.timeouts.defaultMs),observeScreenMs:It(r.timeouts?.observeScreenMs??t.DEFAULT_RELIABILITY_CONFIG.timeouts.observeScreenMs,t.DEFAULT_RELIABILITY_CONFIG.timeouts.observeScreenMs),startAppMs:It(r.timeouts?.startAppMs??t.DEFAULT_RELIABILITY_CONFIG.timeouts.startAppMs,t.DEFAULT_RELIABILITY_CONFIG.timeouts.startAppMs)},limits:{maxIterations:It(r.limits?.maxIterations??t.DEFAULT_RELIABILITY_CONFIG.limits.maxIterations,t.DEFAULT_RELIABILITY_CONFIG.limits.maxIterations),maxConsecutiveFailures:It(r.limits?.maxConsecutiveFailures??t.DEFAULT_RELIABILITY_CONFIG.limits.maxConsecutiveFailures,t.DEFAULT_RELIABILITY_CONFIG.limits.maxConsecutiveFailures),maxSameToolFingerprint:It(r.limits?.maxSameToolFingerprint??t.DEFAULT_RELIABILITY_CONFIG.limits.maxSameToolFingerprint,t.DEFAULT_RELIABILITY_CONFIG.limits.maxSameToolFingerprint)},planner:{maxAttempts:It(r.planner?.maxAttempts??t.DEFAULT_RELIABILITY_CONFIG.planner.maxAttempts,t.DEFAULT_RELIABILITY_CONFIG.planner.maxAttempts)},scriptGeneration:{maxAttempts:It(r.scriptGeneration?.maxAttempts??t.DEFAULT_RELIABILITY_CONFIG.scriptGeneration.maxAttempts,t.DEFAULT_RELIABILITY_CONFIG.scriptGeneration.maxAttempts)}}:t.DEFAULT_RELIABILITY_CONFIG;var r;const o=s.sessionId||e.v4();let i,a,c;this.localToolContext={lastObserveRawDump:null,lastObserveNodes:[]},this.session=Ge(s,o,n),this.persistSession();let l=0,u=[];if(!1!==s.enablePlanning){this.abortController=new AbortController;const e=this.abortController.signal;this.emitter.emit("planning",{sessionId:o,status:"started"});try{const t=await je(s.goal,s.provider,s.device,n,e);i=t.plan,a=t.screenDump,c=t.screenRawDump,l=t.attempts,u=t.errors,this.emitter.emit("planning",{sessionId:o,status:"completed",plan:i})}catch(e){if("AbortError"===e.name)throw e;const t=Re(e,"RUNTIME_LOOP_FAILED");u.push(t.message),this.emitter.emit("error",{sessionId:o,error:`task planning failed and was skipped: ${t.message}`,code:"RUNTIME_LOOP_FAILED",details:{phase:"planning",cause:t.message}})}finally{this.abortController=null}}if(this.session=Ge(s,o,n,i,a),c&&te(this.localToolContext,c),l>1&&(this.session.runtimeContext.planningRetries+=l-1),u.length>0){const e=u[u.length-1],t=Pe(this.session,{sessionId:o,phase:"planning",code:l>1?ct:lt,message:`planning stage used retries or fallback: ${e}`,details:{attempts:l||1,errors:u}});this.emitter.emit("diagnostic",t)}return this.persistSession(),await this.runLoop(),{sessionId:o}}async resume(e){if(!this.session||this.session.sessionId!==e.sessionId)throw new Oe("SESSION_NOT_FOUND","session not found or expired");if(!this.session.paused)throw new Oe("SESSION_NOT_PAUSED","agent is not paused");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(s.running)throw new Oe("AGENT_RUNNING","agent is still running");if(0===s.executionLog.length)throw new Oe("EMPTY_EXECUTION_LOG","no execution log available for script generation; run actions first");if(s.runtimeContext.pendingVerifyAction){const t=s.runtimeContext.pendingVerifyAction,n=[...s.diagnostics].reverse().find(e=>e.code===dt);throw new Oe("SCRIPT_GENERATION_FAILED",`script generation blocked: action #${t.index} ${t.toolName} has not been verified. Resume with observe_screen(wait_ms) -> verify_ui_state before generating script.`,{sessionId:e,pendingVerify:t,...n?{lastDiagnostic:n}:{}})}let n;this.emitter.emit("scriptgenerating",{sessionId:s.sessionId});try{const r=await t.generateScriptWithReliability(s.goal,s.executionLog,s.provider,s.reliability);if(n=r.workflow,r.attempts>1){s.runtimeContext.scriptGenerationRetries+=r.attempts-1;const t=Pe(s,{sessionId:e,phase:"script_generation",code:ft,message:`script generation retried ${r.attempts} attempts`,details:{errors:r.errors}});this.emitter.emit("diagnostic",t)}}catch(t){const n=Re(t,"SCRIPT_GENERATION_FAILED");throw new Oe("SCRIPT_GENERATION_FAILED",`script generation failed: ${n.message}`,{sessionId:e,executionLogSize:s.executionLog.length,cause:n.message,...n.details})}s.running=!1,s.paused=!0,this.persistSession();const r={sessionId:s.sessionId,workflow:n,executionLog:[...s.executionLog],totalIterations:s.iteration,diagnostics:qe(s)};return this.emitter.emit("complete",r),r}listSessions(e={}){return this.sessionStore?this.sessionStore.listSessions(e):[]}countSessions(e={}){return this.sessionStore?this.sessionStore.countSessions(e):0}getSessionSummary(e){return this.sessionStore?this.sessionStore.getSessionSummary(e):null}deleteSession(e){return!!this.sessionStore&&(this.session?.sessionId===e&&(this.stop(e),this.session=null,this.localToolContext={lastObserveRawDump:null,lastObserveNodes:[]}),this.sessionStore.deleteSession(e))}async destroy(){this.session&&this.stop(this.session.sessionId),await this.toolRegistry.destroy(),this.localToolContext={lastObserveRawDump:null,lastObserveNodes:[]},this.sessionStore&&(this.sessionStore.close(),this.sessionStore=null),this.emitter.clear()}async runLoop(){const e=this.session;if(!e)return;if(!this.provider)throw new Oe("INVALID_CONFIG","model provider has not been initialized");e.running=!0,e.paused=!1,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=Pe(e,{sessionId:e.sessionId,phase:"policy",code:_t,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")}e.iteration++;const n=nt(e.messages),r=await this.provider.chatWithTools(n,this.toolRegistry.getModelTools(),t),o=r.content||"";o&&this.emitter.emit("thinking",{sessionId:e.sessionId,text:o,iteration:e.iteration});const i=this.toolRegistry.annotateToolCalls(r.toolCalls||[]);if(0===i.length)return o.trim().length>0&&ze(e,o),void this.markSessionPaused(e,o);ze(e,o,i);for(const s of i){if(!e.running)break;this.emitter.emit("toolcall",{sessionId:e.sessionId,toolCall:s,iteration:e.iteration});const n=this.getVerifyGateError(e,s);if(n){const t={toolCallId:s.id,name:s.name,success:!1,error:n,errorCode:"RUNTIME_GUARD",retryable:!1,source:s.source,server:s.server},r=Pe(e,{sessionId:e.sessionId,phase:"runtime",code:dt,message:n,iteration:e.iteration,details:{pendingVerify:e.runtimeContext.pendingVerifyAction,blockedTool:s.name}});this.emitter.emit("diagnostic",r),this.emitter.emit("toolresult",{sessionId:e.sessionId,result:t,iteration:e.iteration}),Ve(e,s.id,Ce(t)),this.persistSession();continue}const r=Ct(s),o=Tt(e.reliability,r),i=Nt(e.reliability,s.name),a=await xt({toolCall:s,device:e.device,maxAttempts:o,timeoutMs:i,signal:t,execute:(e,t,s)=>this.toolRegistry.execute(e,t,s,this.localToolContext),onRetry:(t,n)=>{e.runtimeContext.toolRetries+=1;const r=Pe(e,{sessionId:e.sessionId,phase:"runtime",code:ut,message:`tool retry: ${s.name}, attempt ${t+1}`,iteration:e.iteration,details:{attempt:t,maxAttempts:o,errorCode:n.errorCode,error:n.error}});this.emitter.emit("diagnostic",r)}});a.source||(a.source=s.source,a.server=s.server),this.emitter.emit("toolresult",{sessionId:e.sessionId,result:a,iteration:e.iteration});if(!a.success&&"VALIDATION"===a.errorCode||Ze(e,s,a,this.toolRegistry.getCategory(s,a)),Ve(e,s.id,Ce(a)),this.updateRuntimeStateAfterTool(e,s,a,r),this.localToolContext.softPendingVerify=e.runtimeContext.softPendingVerify,this.localToolContext.lastSearchContextTimestamp=e.runtimeContext.lastSearchContextTimestamp,this.persistSession(),!e.running)break}}}catch(t){if("AbortError"===t.name)return e.running=!1,e.paused=!1,void this.persistSession();e.running=!1,e.paused=!0;const s=Re(t,"RUNTIME_LOOP_FAILED");this.emitter.emit("error",{sessionId:e.sessionId,error:s.message,code:s.code,details:s.details}),this.persistSession({lastError:s.message,lastErrorCode:s.code})}finally{this.abortController=null}}updateRuntimeStateAfterTool(e,s,n,r){const o=Et(s);if(e.runtimeContext.lastToolFingerprint===o?e.runtimeContext.sameToolFingerprintCount+=1:(e.runtimeContext.lastToolFingerprint=o,e.runtimeContext.sameToolFingerprintCount=1),n.success)e.runtimeContext.consecutiveFailures=0;else{"VALIDATION"===n.errorCode||"RUNTIME_GUARD"===n.errorCode||(e.runtimeContext.consecutiveFailures+=1)}if(t.VERIFY_COMPLETION_TOOLS.has(s.name)&&n.success)e.runtimeContext.pendingVerifyAction=null,e.runtimeContext.softPendingVerify=null;else if("action"===r&&n.success&&t.VERIFY_REQUIRED_ACTIONS.has(s.name)){const t=e.executionLog.length;e.runtimeContext.pendingVerifyAction={index:t,toolName:s.name}}if(t.SOFT_VERIFY_ACTIONS.has(s.name)&&n.success&&(e.runtimeContext.softPendingVerify={index:e.executionLog.length,toolName:s.name}),"record_search_context"===s.name&&n.success&&(e.runtimeContext.lastSearchContextTimestamp=Date.now()),e.runtimeContext.consecutiveFailures>=e.reliability.limits.maxConsecutiveFailures){e.runtimeContext.convergenceTriggers+=1;const t=Pe(e,{sessionId:e.sessionId,phase:"policy",code:mt,message:`consecutive failure limit reached: ${e.reliability.limits.maxConsecutiveFailures}`,iteration:e.iteration,details:{consecutiveFailures:e.runtimeContext.consecutiveFailures}});return this.emitter.emit("diagnostic",t),void this.markSessionPaused(e,"execution paused due to excessive consecutive failures")}if(e.runtimeContext.sameToolFingerprintCount>=e.reliability.limits.maxSameToolFingerprint){e.runtimeContext.convergenceTriggers+=1;const t=Pe(e,{sessionId:e.sessionId,phase:"policy",code:pt,message:`same tool fingerprint repeat limit reached: ${e.reliability.limits.maxSameToolFingerprint}`,iteration:e.iteration,details:{fingerprint:o,repeatCount:e.runtimeContext.sameToolFingerprintCount}});this.emitter.emit("diagnostic",t),this.markSessionPaused(e,"execution paused due to repeated non-converging tool calls")}}getVerifyGateError(e,s){return function(e,s){return"local"!==s.source?null:e&&B(s.name)?t.VERIFY_GATE_ALLOWED_TOOLS.has(s.name)?null:`BLOCKED: #${e.index} ${e.toolName} 尚未验证。你必须先调用 observe_screen(wait_ms) 再调用 verify_ui_state,然后才能执行下一个动作。`:null}(e.runtimeContext.pendingVerifyAction,s)}markSessionPaused(e,t){e.running=!1,e.paused=!0,this.emitter.emit("paused",{sessionId:e.sessionId,text:t,iteration:e.iteration}),this.persistSession()}persistSession(e){this.session&&this.sessionStore&&this.sessionStore.saveSession(this.session,e)}loadSessionIfNeeded(e){if(this.session&&this.session.sessionId===e)return this.session;if(!this.sessionStore)throw new Oe("SESSION_NOT_FOUND","session not found or expired");const t=this.sessionStore.loadSession(e);if(!t)throw new Oe("SESSION_NOT_FOUND","session not found or expired");return this.session=t,this.localToolContext={lastObserveRawDump:null,lastObserveNodes:[]},t}}exports.DEFAULT_PROVIDER_CONFIG=t.DEFAULT_PROVIDER_CONFIG,exports.DEFAULT_RELIABILITY_CONFIG=t.DEFAULT_RELIABILITY_CONFIG,exports.AgentRuntimeError=Oe,exports.AutomationAgentRuntime=At,exports.createAutomationAgentRuntime=function(e={}){return new At(e)},exports.optimizeIntent=async function(e,s){const n=t.createProvider(s),r=[{role:"system",content:t.buildOptimizeIntentPrompt(e)},{role:"user",content:e}];return(await n.chatWithTools(r,[])).content.trim()},exports.toRuntimeError=Re;