mirror of
https://github.com/stablyai/orca.git
synced 2026-11-14 18:07: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
122 lines
3.7 KiB
TypeScript
122 lines
3.7 KiB
TypeScript
import { describe, expect, it, vi } from 'vitest'
|
|
import type { ComputerActionResult } from '../shared/runtime-types'
|
|
import { formatComputerAction, prepareComputerCliJsonResult } from './computer-format'
|
|
import { printResult } from './format'
|
|
|
|
describe('prepareComputerCliJsonResult', () => {
|
|
it.each([
|
|
'Physical size: 1440x3200\n',
|
|
'33',
|
|
'screenshotStatus',
|
|
'',
|
|
42,
|
|
-1,
|
|
0,
|
|
true,
|
|
false,
|
|
null
|
|
])('preserves successful primitive JSON output for %j', (result) => {
|
|
const response = {
|
|
id: 'req-primitive',
|
|
ok: true as const,
|
|
result,
|
|
_meta: { runtimeId: 'runtime-1' }
|
|
}
|
|
const formatter = vi.fn(() => 'unused')
|
|
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => undefined)
|
|
|
|
try {
|
|
expect(prepareComputerCliJsonResult(response)).toBe(response)
|
|
printResult(response, true, formatter)
|
|
expect(logSpy).toHaveBeenCalledOnce()
|
|
expect(logSpy).toHaveBeenCalledWith(JSON.stringify(response, null, 2))
|
|
expect(formatter).not.toHaveBeenCalled()
|
|
} finally {
|
|
logSpy.mockRestore()
|
|
}
|
|
})
|
|
|
|
it.each([
|
|
{ result: {} },
|
|
{ result: [] },
|
|
{ result: { screenshotStatus: {} } },
|
|
{ result: { screenshot: { data: 'cG5n', format: 'png' } } }
|
|
])('preserves object results without a computer screenshot: %j', ({ result }) => {
|
|
const response = {
|
|
id: 'req-object',
|
|
ok: true as const,
|
|
result,
|
|
_meta: { runtimeId: 'runtime-1' }
|
|
}
|
|
|
|
expect(prepareComputerCliJsonResult(response)).toBe(response)
|
|
})
|
|
})
|
|
|
|
describe('formatComputerAction', () => {
|
|
it.each([
|
|
{ kind: 'full tree', treeText: 'tree', treeDiff: undefined },
|
|
{
|
|
kind: 'diff',
|
|
treeText: '~ 1 button Save',
|
|
treeDiff: { baseSnapshotId: 'snap-before', changed: 1, added: 0, removed: 0 }
|
|
},
|
|
{ kind: 'empty tree', treeText: '', treeDiff: undefined }
|
|
])('prints $kind without claiming unverified actions completed', ({ treeText, treeDiff }) => {
|
|
const result: ComputerActionResult = {
|
|
snapshot: {
|
|
id: 'snap-1',
|
|
app: { name: 'Finder', bundleId: 'com.apple.finder', pid: 100 },
|
|
window: { title: 'Finder', id: 42, width: 800, height: 600 },
|
|
coordinateSpace: 'window',
|
|
treeText,
|
|
treeDiff,
|
|
elementCount: 5,
|
|
focusedElementId: null
|
|
},
|
|
screenshot: null,
|
|
screenshotStatus: { state: 'skipped', reason: 'no_screenshot_flag' }
|
|
}
|
|
|
|
const output = formatComputerAction('click', result)
|
|
|
|
expect(output).toContain('Click attempted, unverified (verification metadata unavailable)')
|
|
expect(output).toContain('Inspect with the command above')
|
|
expect(output).not.toContain('Click completed')
|
|
if (treeText) {
|
|
expect(output).toContain(`\n\n${treeText}`)
|
|
} else {
|
|
expect(output).not.toContain('\n\n')
|
|
}
|
|
})
|
|
|
|
it('does not treat accessibility actions without verification as completed', () => {
|
|
const result: ComputerActionResult = {
|
|
snapshot: {
|
|
id: 'snap-1',
|
|
app: { name: 'Finder', bundleId: 'com.apple.finder', pid: 100 },
|
|
window: { title: 'Finder', id: 42, width: 800, height: 600 },
|
|
coordinateSpace: 'window',
|
|
treeText: 'tree',
|
|
elementCount: 5,
|
|
focusedElementId: null
|
|
},
|
|
screenshot: null,
|
|
screenshotStatus: { state: 'skipped', reason: 'no_screenshot_flag' },
|
|
action: {
|
|
path: 'accessibility',
|
|
actionName: 'click',
|
|
targetWindowId: 42
|
|
}
|
|
}
|
|
|
|
const output = formatComputerAction('click', result)
|
|
|
|
expect(output).toContain(
|
|
'Click attempted via accessibility, unverified (accessibility action unasserted)'
|
|
)
|
|
expect(output).toContain('Inspect with the command above')
|
|
expect(output).not.toContain('Click completed')
|
|
})
|
|
})
|