diff --git a/CHANGELOG.md b/CHANGELOG.md index 7fde883601..575f584935 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,8 @@ had no disclosure at all, so an agent read a screen missing its footer, tab bar, or the items after a long list as complete — the backends walk the tree in document order, so what falls off is what comes last, on screen or not. One shared warning renders from the shared flag; the - limit and dimension stay backend-side. + limit and dimension stay backend-side. The depth-cap warning no longer suggests `--scope` as a + way to read deeper: on iOS, scope narrows the presented view and acquisition stays scope-blind. - Changed: the iOS Simulator AX bridge caps a capture at 5000 nodes, up from 1500, the Android helper's bound. Measured on a synthetic 600-row screen, acquisition time did not move with the cap (the native read fetches the whole tree; the cap only stops conversion) while the 1500 cut diff --git a/packages/capture-kit/src/ios-snapshot-engine/engine.test.ts b/packages/capture-kit/src/ios-snapshot-engine/engine.test.ts index 59864a0c7c..cb0d1cd2c3 100644 --- a/packages/capture-kit/src/ios-snapshot-engine/engine.test.ts +++ b/packages/capture-kit/src/ios-snapshot-engine/engine.test.ts @@ -377,7 +377,7 @@ function acquisition( truncated: false, viewport: { kind: 'reported', rect: viewport }, lineage: { targetId: 'simulator-1', generation: 'generation-1' }, - residue: [{ kind: 'truncated', dimension: 'payload', limit: 2000 }], + residue: [{ kind: 'truncated' }], }; } @@ -391,7 +391,7 @@ function validationFacts(request: IosSnapshotRequest): IosSnapshotValidationFact viewport: { kind: 'reported', rect: viewport }, hittability: { kind: 'available' }, lineage: { targetId: 'simulator-1', generation: 'generation-1' }, - residue: [{ kind: 'truncated', dimension: 'payload', limit: 2000 }], + residue: [{ kind: 'truncated' }], }; } diff --git a/packages/capture-kit/src/ios-snapshot-planning.test.ts b/packages/capture-kit/src/ios-snapshot-planning.test.ts index d06e26d967..99b4e29731 100644 --- a/packages/capture-kit/src/ios-snapshot-planning.test.ts +++ b/packages/capture-kit/src/ios-snapshot-planning.test.ts @@ -117,7 +117,7 @@ test('comparison identity rejects every identity axis and residue mismatch', () { ...base, intent: 'surface-observation' }, { ...base, lineage: { targetId: 'simulator-1', generation: 'generation-2' } }, { ...base, presentationKey: { ...base.presentationKey, depth: 1 } }, - { ...base, residue: [{ kind: 'truncated', dimension: 'nodes' }] }, + { ...base, residue: [{ kind: 'truncated' }] }, ]; assert.equal(areIosSnapshotComparisonIdentitiesEqual(base, { ...base }), true); assert.equal( diff --git a/packages/capture-kit/src/ios-snapshot-planning.ts b/packages/capture-kit/src/ios-snapshot-planning.ts index 317cd695d7..dc047a82c7 100644 --- a/packages/capture-kit/src/ios-snapshot-planning.ts +++ b/packages/capture-kit/src/ios-snapshot-planning.ts @@ -126,11 +126,7 @@ function residueIdentity(residue: IosAcquisitionResidue): string { case 'missing-viewport': return JSON.stringify({ kind: residue.kind, reason: residue.reason }); case 'truncated': - return JSON.stringify({ - kind: residue.kind, - dimension: residue.dimension, - limit: residue.limit, - }); + return JSON.stringify({ kind: residue.kind }); case 'stale-generation': return JSON.stringify({ kind: residue.kind, diff --git a/packages/capture-kit/src/snapshot/snapshot-presentation/quality-warnings.ts b/packages/capture-kit/src/snapshot/snapshot-presentation/quality-warnings.ts index 49c090a1ed..0f79547323 100644 --- a/packages/capture-kit/src/snapshot/snapshot-presentation/quality-warnings.ts +++ b/packages/capture-kit/src/snapshot/snapshot-presentation/quality-warnings.ts @@ -84,7 +84,7 @@ function appAccessibilityDefectWarning(verdict: SnapshotQualityVerdict): string[ function depthWarning(verdict: SnapshotQualityVerdict): string[] { if (verdict.effectiveDepth === undefined) return []; return [ - `Some deeper accessibility nodes were omitted; this tree is capped at depth ${verdict.effectiveDepth}. Re-run with --depth ${verdict.effectiveDepth} --scope only if you need deeper content.`, + `Some deeper accessibility nodes were omitted; the accessibility backend capped this tree at depth ${verdict.effectiveDepth}. Navigate so the content you need sits higher in the tree, and use screenshot as visual truth for the rest.`, ]; } diff --git a/packages/capture-kit/src/snapshot/snapshot-presentation/warnings.test.ts b/packages/capture-kit/src/snapshot/snapshot-presentation/warnings.test.ts index ce541e4154..978b53969b 100644 --- a/packages/capture-kit/src/snapshot/snapshot-presentation/warnings.test.ts +++ b/packages/capture-kit/src/snapshot/snapshot-presentation/warnings.test.ts @@ -35,7 +35,7 @@ test('penalty-deferred recovered captures suppress the fallback warning but keep ); assert.deepEqual(warnings, [ - 'Some deeper accessibility nodes were omitted; this tree is capped at depth 56. Re-run with --depth 56 --scope only if you need deeper content.', + 'Some deeper accessibility nodes were omitted; the accessibility backend capped this tree at depth 56. Navigate so the content you need sits higher in the tree, and use screenshot as visual truth for the rest.', ]); }); @@ -53,7 +53,7 @@ test('non-presentation recovery keeps the generic warning for the same reason te assert.deepEqual(warnings, [ 'Detected an overly complex or slow accessibility tree. Fell back to the private-ax snapshot backend. It is OK to continue; use --json to inspect snapshotQuality.reason if you need recovery details.', - 'Some deeper accessibility nodes were omitted; this tree is capped at depth 56. Re-run with --depth 56 --scope only if you need deeper content.', + 'Some deeper accessibility nodes were omitted; the accessibility backend capped this tree at depth 56. Navigate so the content you need sits higher in the tree, and use screenshot as visual truth for the rest.', ]); }); @@ -71,7 +71,7 @@ test('presentation failures identify a runner bug and preserve composed warnings assert.deepEqual(warnings, [ 'Agent Device could not safely present the captured accessibility tree and fell back to the private-ax snapshot backend. This is an Agent Device runner bug, not an app accessibility-tree issue. Use screenshot as visual truth and report snapshotQuality.reason with the screenshot.', - 'Some deeper accessibility nodes were omitted; this tree is capped at depth 56. Re-run with --depth 56 --scope only if you need deeper content.', + 'Some deeper accessibility nodes were omitted; the accessibility backend capped this tree at depth 56. Navigate so the content you need sits higher in the tree, and use screenshot as visual truth for the rest.', ]); }); diff --git a/packages/contracts/src/ios-snapshot.ts b/packages/contracts/src/ios-snapshot.ts index ac3c0b88c9..992388edf1 100644 --- a/packages/contracts/src/ios-snapshot.ts +++ b/packages/contracts/src/ios-snapshot.ts @@ -114,7 +114,6 @@ export type IosHittabilityEvidence = }>; export type IosProviderPrunedField = 'nodes' | 'depth' | 'scope' | 'interactive-only'; -export type IosTruncationDimension = 'nodes' | 'depth' | 'payload'; export type IosAcquisitionResidue = | Readonly<{ @@ -127,8 +126,6 @@ export type IosAcquisitionResidue = }> | Readonly<{ kind: 'truncated'; - dimension: IosTruncationDimension; - limit?: number; }> | Readonly<{ kind: 'stale-generation'; diff --git a/packages/platform-apple/src/snapshot-source/adapter.test.ts b/packages/platform-apple/src/snapshot-source/adapter.test.ts index 05bcbb9349..23c48e8298 100644 --- a/packages/platform-apple/src/snapshot-source/adapter.test.ts +++ b/packages/platform-apple/src/snapshot-source/adapter.test.ts @@ -78,11 +78,7 @@ test('the Simulator AX source returns raw acquisition facts and discloses unsupp }); assert.equal(rawDepthOne.stage, 'acquired'); assert.equal(fixture.requestedDepths.at(-1), 1); - assert.ok( - rawDepthOne.acquisition.residue.some( - (item) => item.kind === 'truncated' && item.dimension === 'depth', - ), - ); + assert.ok(rawDepthOne.acquisition.residue.some((item) => item.kind === 'truncated')); fixture.responsePid = 999; const outcome = await source.acquire({ diff --git a/packages/platform-apple/src/snapshot-source/adapter.ts b/packages/platform-apple/src/snapshot-source/adapter.ts index 6e3b2b9ad5..a45cdab7ab 100644 --- a/packages/platform-apple/src/snapshot-source/adapter.ts +++ b/packages/platform-apple/src/snapshot-source/adapter.ts @@ -1,5 +1,9 @@ import { AppError } from '@agent-device/kernel/errors'; -import type { CaptureHint, IosSnapshotAcquisition } from '@agent-device/contracts/ios-snapshot'; +import type { + CaptureHint, + IosSnapshotAcquisition, + IosViewportEvidence, +} from '@agent-device/contracts/ios-snapshot'; import { ensureSnapshotBridgeBinary } from './cache.ts'; import { createSnapshotSourceDeadline, remainingSnapshotSourceMs } from './deadline.ts'; import { AcceptedDepthHints, type DepthHintDecision } from './depth-hints.ts'; @@ -94,13 +98,7 @@ export function createSimulatorSnapshotSource( deadline, }); remainingSnapshotSourceMs(deadline, 'snapshot-decode-deadline'); - const acquisition = createAcquisition( - request.hint, - request.target, - envelope, - limits, - maxDepth, - ); + const acquisition = createAcquisition(request.hint, request.target, envelope, limits); recordRecovery( host, depthHints, @@ -216,7 +214,6 @@ function createAcquisition( target: SnapshotSourceRequest['target'], envelope: SnapshotBridgeEnvelope, limits: SnapshotSourceLimits, - maxDepth: number, ): IosSnapshotAcquisition { if (envelope.automationEnabled !== true) { throw snapshotSourceError('unsupported', 'automation-mode-unavailable'); @@ -243,14 +240,7 @@ function createAcquisition( const nodes = Object.freeze( decoded.nodes.map((node) => Object.freeze({ ...node, pid: target.pid })), ); - const residue = createAcquisitionResidue( - hint, - truncated, - decoded, - limits, - maxDepth, - nodes.length, - ); + const residue = createAcquisitionResidue(hint, truncated, decoded.viewport); const lineage = Object.freeze({ ...(target.targetId ? { targetId: target.targetId } : {}), generation, @@ -276,36 +266,16 @@ function createAcquisition( function createAcquisitionResidue( hint: CaptureHint, truncated: boolean, - decoded: ReturnType, - limits: SnapshotSourceLimits, - maxDepth: number, - nodeCount: number, + viewport: IosViewportEvidence, ) { return Object.freeze([ { kind: 'unavailable-fact', fact: 'hittability' } as const, ...(hint.interactiveOnly ? ([{ kind: 'unavailable-fact', fact: 'interactive-query' }] as const) : []), - ...(truncated - ? [truncationResidue(decoded.maxTraversalDepth, nodeCount, limits, maxDepth)] - : []), - ...(decoded.viewport.kind === 'missing' - ? ([{ kind: 'missing-viewport', reason: decoded.viewport.reason }] as const) + ...(truncated ? ([{ kind: 'truncated' }] as const) : []), + ...(viewport.kind === 'missing' + ? ([{ kind: 'missing-viewport', reason: viewport.reason }] as const) : []), ]); } - -function truncationResidue( - maxTraversalDepth: number, - nodeCount: number, - limits: SnapshotSourceLimits, - maxDepth: number, -): { kind: 'truncated'; dimension: 'nodes' | 'depth' | 'payload'; limit?: number } { - if (nodeCount >= limits.maxNodes) { - return { kind: 'truncated', dimension: 'nodes', limit: limits.maxNodes }; - } - if (maxTraversalDepth >= maxDepth) { - return { kind: 'truncated', dimension: 'depth', limit: maxDepth }; - } - return { kind: 'truncated', dimension: 'payload', limit: limits.maxResponseBytes }; -}