Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions packages/capture-kit/src/ios-snapshot-engine/engine.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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' }],
};
}

Expand All @@ -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' }],
};
}

Expand Down
2 changes: 1 addition & 1 deletion packages/capture-kit/src/ios-snapshot-planning.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
6 changes: 1 addition & 5 deletions packages/capture-kit/src/ios-snapshot-planning.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 <container> 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.`,
];
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 <container> 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.',
]);
});

Expand All @@ -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 <container> 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.',
]);
});

Expand All @@ -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 <container> 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.',
]);
});

Expand Down
3 changes: 0 additions & 3 deletions packages/contracts/src/ios-snapshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<{
Expand All @@ -127,8 +126,6 @@ export type IosAcquisitionResidue =
}>
| Readonly<{
kind: 'truncated';
dimension: IosTruncationDimension;
limit?: number;
}>
| Readonly<{
kind: 'stale-generation';
Expand Down
6 changes: 1 addition & 5 deletions packages/platform-apple/src/snapshot-source/adapter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down
52 changes: 11 additions & 41 deletions packages/platform-apple/src/snapshot-source/adapter.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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');
Expand All @@ -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,
Expand All @@ -276,36 +266,16 @@ function createAcquisition(
function createAcquisitionResidue(
hint: CaptureHint,
truncated: boolean,
decoded: ReturnType<typeof decodeSnapshotBridgeTree>,
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 };
}
Loading