Skip to content

Commit 5faa43d

Browse files
authored
Fix shared snapshot_ui warning state (#185)
1 parent 9ddf2d5 commit 5faa43d

File tree

7 files changed

+33
-106
lines changed

7 files changed

+33
-106
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
- Update UI automation guard guidance to point at `debug_continue` when paused.
1818
- Fix tool loading bugs in static tool registration.
1919
- Fix xcodemake command argument corruption when project directory path appears as substring in non-path arguments.
20+
- Fix snapshot_ui warning state being isolated per UI automation tool, causing false warnings.
2021

2122
## [1.16.0] - 2025-12-30
2223
- Remove dynamic tool discovery (`discover_tools`) and `XCODEBUILDMCP_DYNAMIC_TOOLS`. Use `XCODEBUILDMCP_ENABLED_WORKFLOWS` to limit startup tool registration.

src/mcp/tools/ui-automation/long_press.ts

Lines changed: 2 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import {
2929
createSessionAwareTool,
3030
getSessionAwareToolSchemaShape,
3131
} from '../../../utils/typed-tool-factory.ts';
32+
import { getSnapshotUiWarning } from './shared/snapshot-ui-state.ts';
3233

3334
// Define schema as ZodObject
3435
const longPressSchema = z.object({
@@ -99,7 +100,7 @@ export async function long_pressLogic(
99100
await executeAxeCommand(commandArgs, simulatorId, 'touch', executor, axeHelpers);
100101
log('info', `${LOG_PREFIX}/${toolName}: Success for ${simulatorId}`);
101102

102-
const coordinateWarning = getCoordinateWarning(simulatorId);
103+
const coordinateWarning = getSnapshotUiWarning(simulatorId);
103104
const message = `Long press at (${x}, ${y}) for ${duration}ms simulated successfully.`;
104105
const warnings = [guard.warningText, coordinateWarning].filter(Boolean).join('\n\n');
105106

@@ -153,30 +154,6 @@ export default {
153154
}),
154155
};
155156

156-
// Session tracking for snapshot_ui warnings
157-
interface DescribeUISession {
158-
timestamp: number;
159-
simulatorId: string;
160-
}
161-
162-
const snapshotUiTimestamps = new Map<string, DescribeUISession>();
163-
const SNAPSHOT_UI_WARNING_TIMEOUT = 60000; // 60 seconds
164-
165-
function getCoordinateWarning(simulatorId: string): string | null {
166-
const session = snapshotUiTimestamps.get(simulatorId);
167-
if (!session) {
168-
return 'Warning: snapshot_ui has not been called yet. Consider using snapshot_ui for precise coordinates instead of guessing from screenshots.';
169-
}
170-
171-
const timeSinceDescribe = Date.now() - session.timestamp;
172-
if (timeSinceDescribe > SNAPSHOT_UI_WARNING_TIMEOUT) {
173-
const secondsAgo = Math.round(timeSinceDescribe / 1000);
174-
return `Warning: snapshot_ui was last called ${secondsAgo} seconds ago. Consider refreshing UI coordinates with snapshot_ui instead of using potentially stale coordinates.`;
175-
}
176-
177-
return null;
178-
}
179-
180157
// Helper function for executing axe commands (inlined from src/tools/axe/index.ts)
181158
async function executeAxeCommand(
182159
commandArgs: string[],
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
const SNAPSHOT_UI_WARNING_TIMEOUT_MS = 60000; // 60 seconds
2+
3+
const snapshotUiTimestamps = new Map<string, number>();
4+
5+
export function recordSnapshotUiCall(simulatorId: string): void {
6+
snapshotUiTimestamps.set(simulatorId, Date.now());
7+
}
8+
9+
export function getSnapshotUiWarning(simulatorId: string): string | null {
10+
const timestamp = snapshotUiTimestamps.get(simulatorId);
11+
if (!timestamp) {
12+
return 'Warning: snapshot_ui has not been called yet. Consider using snapshot_ui for precise coordinates instead of guessing from screenshots.';
13+
}
14+
15+
const timeSinceDescribe = Date.now() - timestamp;
16+
if (timeSinceDescribe > SNAPSHOT_UI_WARNING_TIMEOUT_MS) {
17+
const secondsAgo = Math.round(timeSinceDescribe / 1000);
18+
return `Warning: snapshot_ui was last called ${secondsAgo} seconds ago. Consider refreshing UI coordinates with snapshot_ui instead of using potentially stale coordinates.`;
19+
}
20+
21+
return null;
22+
}

src/mcp/tools/ui-automation/snapshot_ui.ts

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
createSessionAwareTool,
1818
getSessionAwareToolSchemaShape,
1919
} from '../../../utils/typed-tool-factory.ts';
20+
import { recordSnapshotUiCall } from './shared/snapshot-ui-state.ts';
2021

2122
// Define schema as ZodObject
2223
const snapshotUiSchema = z.object({
@@ -34,16 +35,6 @@ export interface AxeHelpers {
3435

3536
const LOG_PREFIX = '[AXe]';
3637

37-
// Session tracking for snapshot_ui warnings (shared across UI tools)
38-
const snapshotUiTimestamps = new Map<string, { timestamp: number; simulatorId: string }>();
39-
40-
function recordSnapshotUICall(simulatorId: string): void {
41-
snapshotUiTimestamps.set(simulatorId, {
42-
timestamp: Date.now(),
43-
simulatorId,
44-
});
45-
}
46-
4738
/**
4839
* Core business logic for snapshot_ui functionality
4940
*/
@@ -80,7 +71,7 @@ export async function snapshot_uiLogic(
8071
);
8172

8273
// Record the snapshot_ui call for warning system
83-
recordSnapshotUICall(simulatorId);
74+
recordSnapshotUiCall(simulatorId);
8475

8576
log('info', `${LOG_PREFIX}/${toolName}: Success for ${simulatorId}`);
8677
const response: ToolResponse = {

src/mcp/tools/ui-automation/swipe.ts

Lines changed: 2 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {
2323
createSessionAwareTool,
2424
getSessionAwareToolSchemaShape,
2525
} from '../../../utils/typed-tool-factory.ts';
26+
import { getSnapshotUiWarning } from './shared/snapshot-ui-state.ts';
2627

2728
// Define schema as ZodObject
2829
const swipeSchema = z.object({
@@ -119,7 +120,7 @@ export async function swipeLogic(
119120
await executeAxeCommand(commandArgs, simulatorId, 'swipe', executor, axeHelpers);
120121
log('info', `${LOG_PREFIX}/${toolName}: Success for ${simulatorId}`);
121122

122-
const coordinateWarning = getCoordinateWarning(simulatorId);
123+
const coordinateWarning = getSnapshotUiWarning(simulatorId);
123124
const message = `Swipe from (${x1}, ${y1}) to (${x2}, ${y2})${optionsText} simulated successfully.`;
124125
const warnings = [guard.warningText, coordinateWarning].filter(Boolean).join('\n\n');
125126

@@ -170,30 +171,6 @@ export default {
170171
}),
171172
};
172173

173-
// Session tracking for snapshot_ui warnings
174-
interface DescribeUISession {
175-
timestamp: number;
176-
simulatorId: string;
177-
}
178-
179-
const snapshotUiTimestamps = new Map<string, DescribeUISession>();
180-
const SNAPSHOT_UI_WARNING_TIMEOUT = 60000; // 60 seconds
181-
182-
function getCoordinateWarning(simulatorId: string): string | null {
183-
const session = snapshotUiTimestamps.get(simulatorId);
184-
if (!session) {
185-
return 'Warning: snapshot_ui has not been called yet. Consider using snapshot_ui for precise coordinates instead of guessing from screenshots.';
186-
}
187-
188-
const timeSinceDescribe = Date.now() - session.timestamp;
189-
if (timeSinceDescribe > SNAPSHOT_UI_WARNING_TIMEOUT) {
190-
const secondsAgo = Math.round(timeSinceDescribe / 1000);
191-
return `Warning: snapshot_ui was last called ${secondsAgo} seconds ago. Consider refreshing UI coordinates with snapshot_ui instead of using potentially stale coordinates.`;
192-
}
193-
194-
return null;
195-
}
196-
197174
// Helper function for executing axe commands (inlined from src/tools/axe/index.ts)
198175
async function executeAxeCommand(
199176
commandArgs: string[],

src/mcp/tools/ui-automation/tap.ts

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
createSessionAwareTool,
1818
getSessionAwareToolSchemaShape,
1919
} from '../../../utils/typed-tool-factory.ts';
20+
import { getSnapshotUiWarning } from './shared/snapshot-ui-state.ts';
2021

2122
export interface AxeHelpers {
2223
getAxePath: () => string | null;
@@ -90,25 +91,6 @@ const publicSchemaObject = z.strictObject(baseTapSchema.omit({ simulatorId: true
9091

9192
const LOG_PREFIX = '[AXe]';
9293

93-
// Session tracking for snapshot_ui warnings (shared across UI tools)
94-
const snapshotUiTimestamps = new Map<string, { timestamp: number }>();
95-
const SNAPSHOT_UI_WARNING_TIMEOUT = 60000; // 60 seconds
96-
97-
function getCoordinateWarning(simulatorId: string): string | null {
98-
const session = snapshotUiTimestamps.get(simulatorId);
99-
if (!session) {
100-
return 'Warning: snapshot_ui has not been called yet. Consider using snapshot_ui for precise coordinates instead of guessing from screenshots.';
101-
}
102-
103-
const timeSinceDescribe = Date.now() - session.timestamp;
104-
if (timeSinceDescribe > SNAPSHOT_UI_WARNING_TIMEOUT) {
105-
const secondsAgo = Math.round(timeSinceDescribe / 1000);
106-
return `Warning: snapshot_ui was last called ${secondsAgo} seconds ago. Consider refreshing UI coordinates with snapshot_ui instead of using potentially stale coordinates.`;
107-
}
108-
109-
return null;
110-
}
111-
11294
export async function tapLogic(
11395
params: TapParams,
11496
executor: CommandExecutor,
@@ -167,7 +149,7 @@ export async function tapLogic(
167149
await executeAxeCommand(commandArgs, simulatorId, 'tap', executor, axeHelpers);
168150
log('info', `${LOG_PREFIX}/${toolName}: Success for ${simulatorId}`);
169151

170-
const coordinateWarning = usesCoordinates ? getCoordinateWarning(simulatorId) : null;
152+
const coordinateWarning = usesCoordinates ? getSnapshotUiWarning(simulatorId) : null;
171153
const message = `${actionDescription} simulated successfully.`;
172154
const warnings = [guard.warningText, coordinateWarning].filter(Boolean).join('\n\n');
173155

src/mcp/tools/ui-automation/touch.ts

Lines changed: 2 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
createSessionAwareTool,
2525
getSessionAwareToolSchemaShape,
2626
} from '../../../utils/typed-tool-factory.ts';
27+
import { getSnapshotUiWarning } from './shared/snapshot-ui-state.ts';
2728

2829
// Define schema as ZodObject
2930
const touchSchema = z.object({
@@ -95,7 +96,7 @@ export async function touchLogic(
9596
await executeAxeCommand(commandArgs, simulatorId, 'touch', executor, axeHelpers);
9697
log('info', `${LOG_PREFIX}/${toolName}: Success for ${simulatorId}`);
9798

98-
const coordinateWarning = getCoordinateWarning(simulatorId);
99+
const coordinateWarning = getSnapshotUiWarning(simulatorId);
99100
const message = `Touch event (${actionText}) at (${x}, ${y}) executed successfully.`;
100101
const warnings = [guard.warningText, coordinateWarning].filter(Boolean).join('\n\n');
101102

@@ -147,30 +148,6 @@ export default {
147148
}),
148149
};
149150

150-
// Session tracking for snapshot_ui warnings
151-
interface DescribeUISession {
152-
timestamp: number;
153-
simulatorId: string;
154-
}
155-
156-
const snapshotUiTimestamps = new Map<string, DescribeUISession>();
157-
const SNAPSHOT_UI_WARNING_TIMEOUT = 60000; // 60 seconds
158-
159-
function getCoordinateWarning(simulatorId: string): string | null {
160-
const session = snapshotUiTimestamps.get(simulatorId);
161-
if (!session) {
162-
return 'Warning: snapshot_ui has not been called yet. Consider using snapshot_ui for precise coordinates instead of guessing from screenshots.';
163-
}
164-
165-
const timeSinceDescribe = Date.now() - session.timestamp;
166-
if (timeSinceDescribe > SNAPSHOT_UI_WARNING_TIMEOUT) {
167-
const secondsAgo = Math.round(timeSinceDescribe / 1000);
168-
return `Warning: snapshot_ui was last called ${secondsAgo} seconds ago. Consider refreshing UI coordinates with snapshot_ui instead of using potentially stale coordinates.`;
169-
}
170-
171-
return null;
172-
}
173-
174151
// Helper function for executing axe commands (inlined from src/tools/axe/index.ts)
175152
async function executeAxeCommand(
176153
commandArgs: string[],

0 commit comments

Comments
 (0)