mirror of
https://github.com/stablyai/orca.git
synced 2026-10-31 00:57:45 +00:00
* feat(computer): send tree diffs after actions and add get-app-state --query Action results now carry only the accessibility-tree lines that changed since the caller's last view of the same window (~ changed, + added, - removed as index ranges, or an explicit no-change line), plus the focused element. The runtime sidecar keeps the last tree per session/worktree and app, so all three providers get it. The CLI opts in with treeDiff; older runtimes strip the param and keep returning the full tree, and --full-tree opts out. The macOS helper now keeps an element's index across snapshots of a window: AX identity first, then the next unclaimed index with the same signature for views that rebuild their AX objects, and never reuses a removed index. get-app-state --query prints matching elements with their ancestors and --max-elements caps the output, both in the CLI over the rendered tree. Custom action names with raw newlines no longer split an element over lines. * fix(computer): preserve parent changes in snapshot diffs * fix(computer): refuse stale index reuse for lookalike controls * fix(computer): print full trees in action replies * fix(computer): keep controls located in snapshot replies
41 lines
1.8 KiB
Swift
41 lines
1.8 KiB
Swift
import Foundation
|
|
|
|
/// Keeps an element's index across snapshots of the same window, so action results can send only
|
|
/// the lines that changed.
|
|
public enum StableElementIndexes {
|
|
/// Only matching AX identities keep their indexes; all other elements receive fresh indexes.
|
|
public static func assign<Key: Hashable>(
|
|
current: [Key],
|
|
previous: [(key: Key, index: Int)],
|
|
previousNextFreshIndex: Int
|
|
) -> (indexes: [Int], nextFreshIndex: Int) {
|
|
let previousIndexByKey = Dictionary(previous.map { ($0.key, $0.index) }, uniquingKeysWith: { first, _ in first })
|
|
var claimed = Set<Int>()
|
|
var assigned = current.map { element -> Int? in
|
|
guard let index = previousIndexByKey[element], claimed.insert(index).inserted else { return nil }
|
|
return index
|
|
}
|
|
var nextFreshIndex = max(previousNextFreshIndex, (previous.map(\.index).max() ?? -1) + 1)
|
|
for position in assigned.indices where assigned[position] == nil {
|
|
assigned[position] = nextFreshIndex
|
|
nextFreshIndex += 1
|
|
}
|
|
return (assigned.compactMap { $0 }, nextFreshIndex)
|
|
}
|
|
|
|
/// Rewrites the leading `<tabs><index>` of a rendered element line; other lines pass through.
|
|
public static func renumber(line: String, indexes: [Int: Int]) -> String {
|
|
let tabs = line.prefix { $0 == "\t" }
|
|
let rest = line.dropFirst(tabs.count)
|
|
let digits = rest.prefix { $0.isASCII && $0.isNumber }
|
|
guard !digits.isEmpty,
|
|
rest.dropFirst(digits.count).first.map({ $0 == " " }) ?? true,
|
|
let old = Int(digits),
|
|
let new = indexes[old]
|
|
else {
|
|
return line
|
|
}
|
|
return "\(tabs)\(new)\(rest.dropFirst(digits.count))"
|
|
}
|
|
}
|