mirror of
https://github.com/stablyai/orca.git
synced 2026-11-15 02:17:31 +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
51 lines
1.8 KiB
TypeScript
51 lines
1.8 KiB
TypeScript
// Every provider renders `<tabs><index> <role> ...` element lines between a header and a footer.
|
|
export type ComputerTreeElement = {
|
|
index: number
|
|
depth: number
|
|
// The element line plus any unindexed lines that follow it, such as omitted-tab notes.
|
|
lines: string[]
|
|
}
|
|
|
|
export type ComputerTreeText = {
|
|
header: string[]
|
|
elements: ComputerTreeElement[]
|
|
footer: string[]
|
|
}
|
|
|
|
const ELEMENT_LINE = /^(\t*)(\d+)(?= |$)/u
|
|
|
|
export function parseComputerTreeText(treeText: string): ComputerTreeText {
|
|
const lines = treeText.split('\n')
|
|
const first = lines.findIndex((line) => ELEMENT_LINE.test(line))
|
|
if (first === -1) {
|
|
return { header: lines, elements: [], footer: [] }
|
|
}
|
|
const lastElement = lines.findLastIndex((line) => ELEMENT_LINE.test(line))
|
|
// Why: the footer starts at the first blank line after the last element; anything else between
|
|
// elements belongs to the element above, including names that carried raw newlines.
|
|
const blank = lines.indexOf('', lastElement)
|
|
const footerStart = blank === -1 ? lines.length : blank
|
|
const elements: ComputerTreeElement[] = []
|
|
for (const line of lines.slice(first, footerStart)) {
|
|
const match = ELEMENT_LINE.exec(line)
|
|
if (match) {
|
|
elements.push({ index: Number(match[2]), depth: match[1].length, lines: [line] })
|
|
} else {
|
|
elements.at(-1)?.lines.push(line)
|
|
}
|
|
}
|
|
return { header: lines.slice(0, first), elements, footer: lines.slice(footerStart) }
|
|
}
|
|
|
|
export function joinComputerTreeText(header: string[], body: string[], footer: string[]): string {
|
|
return [...trimTrailingBlank(header), '', ...body, ...footer].join('\n')
|
|
}
|
|
|
|
function trimTrailingBlank(lines: string[]): string[] {
|
|
let end = lines.length
|
|
while (end > 0 && lines[end - 1] === '') {
|
|
end -= 1
|
|
}
|
|
return lines.slice(0, end)
|
|
}
|