diff --git a/skills/agent-device/references/session-management.md b/skills/agent-device/references/session-management.md index 705563cf9..044983147 100644 --- a/skills/agent-device/references/session-management.md +++ b/skills/agent-device/references/session-management.md @@ -13,7 +13,7 @@ Sessions isolate device context. A device can only be held by one session at a t - Name sessions semantically. - Close sessions when done. -- Use separate devices for parallel work. +- Use separate sessions for parallel work. ## Listing sessions diff --git a/src/core/dispatch.ts b/src/core/dispatch.ts index 026a7b29b..b98668114 100644 --- a/src/core/dispatch.ts +++ b/src/core/dispatch.ts @@ -76,6 +76,7 @@ export async function dispatchCommand( outPath?: string, context?: { appBundleId?: string; + activity?: string; verbose?: boolean; logPath?: string; traceLogPath?: string; diff --git a/src/daemon.ts b/src/daemon.ts index 525eb1c50..4ab452279 100644 --- a/src/daemon.ts +++ b/src/daemon.ts @@ -843,7 +843,11 @@ async function handleRequest(req: DaemonRequest): Promise { if (!node) { return { ok: false, error: { code: 'COMMAND_FAILED', message: 'find did not match any element' } }; } - const ref = `@${node.ref}`; + const resolvedNode = + action === 'click' || action === 'focus' || action === 'fill' || action === 'type' + ? findNearestHittableAncestor(nodes, node) ?? node + : node; + const ref = `@${resolvedNode.ref}`; const actionFlags = { ...(req.flags ?? {}), noRecord: true }; if (action === 'exists') { if (session) { @@ -1686,6 +1690,24 @@ function normalizeType(type: string): string { return value; } +function findNearestHittableAncestor( + nodes: SnapshotState['nodes'], + node: SnapshotState['nodes'][number], +): SnapshotState['nodes'][number] | null { + if (node.hittable) return node; + let current = node; + const visited = new Set(); + while (current.parentIndex !== undefined) { + if (visited.has(current.ref)) break; + visited.add(current.ref); + const parent = nodes[current.parentIndex]; + if (!parent) break; + if (parent.hittable) return parent; + current = parent; + } + return null; +} + function readVersion(): string { try { const root = findProjectRoot(); diff --git a/src/platforms/android/index.ts b/src/platforms/android/index.ts index 0d548f7af..c09122cda 100644 --- a/src/platforms/android/index.ts +++ b/src/platforms/android/index.ts @@ -493,7 +493,7 @@ function parseUiHierarchy( const scopedRoot = options.scope ? findScopeNode(tree, options.scope) : null; const roots = scopedRoot ? [scopedRoot] : tree.children; - const walk = (node: AndroidNode, depth: number) => { + const walk = (node: AndroidNode, depth: number, parentIndex?: number) => { if (nodes.length >= maxNodes) { truncated = true; return; @@ -501,9 +501,11 @@ function parseUiHierarchy( if (depth > maxDepth) return; const include = options.raw ? true : shouldIncludeAndroidNode(node, options); + let currentIndex = parentIndex; if (include) { + currentIndex = nodes.length; nodes.push({ - index: nodes.length, + index: currentIndex, type: node.type ?? undefined, label: node.label ?? undefined, value: node.value ?? undefined, @@ -512,17 +514,17 @@ function parseUiHierarchy( enabled: node.enabled, hittable: node.hittable, depth, - parentIndex: node.parentIndex, + parentIndex, }); } for (const child of node.children) { - walk(child, depth + 1); + walk(child, depth + 1, currentIndex); if (truncated) return; } }; for (const root of roots) { - walk(root, 0); + walk(root, 0, undefined); if (truncated) break; } diff --git a/website/docs/docs/selectors.md b/website/docs/docs/selectors.md index cecf118b7..9e52a349b 100644 --- a/website/docs/docs/selectors.md +++ b/website/docs/docs/selectors.md @@ -19,3 +19,4 @@ Tips: - Use `find ... wait ` to wait for UI to appear. - Combine with scoped snapshots using `snapshot -s "