@loadmill/droid-cua 2.10.0 → 2.10.1-rc.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -45,17 +45,6 @@ function describeControlledTarget(deviceInfo = {}) {
45
45
  }
46
46
  return "a mobile device";
47
47
  }
48
- function buildAlwaysAimAssistGuidance(targetKind) {
49
- return [
50
- "- Before every click action, call `preview_click` with the exact coordinates you intend to click.",
51
- "- Review the returned annotated screenshot before clicking.",
52
- `- Click if the marker is clearly inside the intended ${targetKind === "browser" ? "clickable" : "tappable"} control.${targetKind === "browser" ? "" : " Text inside a button, field, row, or bottom bar is okay."}`,
53
- `- Adjust and preview again only if the marker is outside the target, on the ${targetKind === "browser" ? "edge" : "target border/edge"}, or closer to a neighboring target than the intended one.`,
54
- "- If you preview more than once, the next click should use the most recent accepted preview coordinates unless the screen changed.",
55
- "- If the click does not cause the expected UI change, treat it as a likely misclick and try a meaningfully different coordinate.",
56
- "- Click precisely based on your visual estimate.",
57
- ];
58
- }
59
48
  function buildAimAssistGuidance(targetKind, aimAssistMode = "always") {
60
49
  if (aimAssistMode === "off") {
61
50
  return [
@@ -65,19 +54,29 @@ function buildAimAssistGuidance(targetKind, aimAssistMode = "always") {
65
54
  ].join("\n ");
66
55
  }
67
56
  if (aimAssistMode === "smart") {
68
- const targetWord = targetKind === "browser" ? "clickable" : "tappable";
69
57
  return [
70
- ...buildAlwaysAimAssistGuidance(targetKind),
71
- "- In Smart mode, you may skip preview only when the target is broad, isolated, stable, and unambiguous, and you are confident the click point is clearly inside the intended target.",
58
+ "- Before every click action, call `preview_click` with the exact coordinates you intend to click.",
59
+ "- Review the returned annotated screenshot before clicking.",
60
+ `- Click if the marker is clearly inside the intended ${targetKind === "browser" ? "clickable target" : "tappable control"}.${targetKind === "browser" ? "" : " Text inside a button, field, row, or bottom bar is okay."}`,
61
+ `- Adjust and preview again only if the marker is outside the target, on the ${targetKind === "browser" ? "edge" : "target border/edge"}, or closer to a neighboring target than the intended one.`,
62
+ "- If you preview more than once, the next click should use the most recent accepted preview coordinates unless the screen changed.",
63
+ "- If the click does not cause the expected UI change, treat it as a likely misclick and try a meaningfully different coordinate.",
64
+ "- Click precisely based on your visual estimate.",
65
+ "- You may skip preview only when the target is broad, isolated, stable, and unambiguous, and you are confident the click point is clearly inside the intended target.",
72
66
  "- Good skip examples are large empty text fields, large search fields, large CTA buttons with generous padding, launcher icons, and wide rows with generous spacing and no nearby competing actions.",
73
- `- Do not skip preview when the target is small, dense, visually ambiguous, close to neighboring controls, or you are not confident the marker will land clearly inside the intended ${targetWord} target.`,
74
- "- If a dropdown, expanded option list, popover, or select menu is visible, always use preview before selecting an option.",
75
- "- Do not skip preview in search results, bottom sheets, bottom cards, modals, overlays, keyboard-driven flows, or after recent screen changes.",
76
- "- Do not skip preview for known tricky UI such as dense lists, date pickers, time pickers, calendar cells, wheels, segmented selectors, and small inline controls inside larger rows or cards.",
77
- "- If the target is one option among many visually similar options, use preview before clicking.",
67
+ "- Always use preview for dropdowns, expanded option lists, popovers, select menus, dense lists or search results, bottom sheets or cards, modals, overlays, date or time pickers, calendar cells, wheels, segmented selectors, and small inline controls.",
68
+ "- Do not skip preview when the target is one of many similar options, close to neighboring controls, in a keyboard-driven flow, after a recent screen change, or whenever you are not confident.",
78
69
  ].join("\n ");
79
70
  }
80
- return buildAlwaysAimAssistGuidance(targetKind).join("\n ");
71
+ return [
72
+ "- Before every click action, call `preview_click` with the exact coordinates you intend to click.",
73
+ "- Review the returned annotated screenshot before clicking.",
74
+ `- Click if the marker is clearly inside the intended ${targetKind === "browser" ? "clickable target" : "tappable control"}.${targetKind === "browser" ? "" : " Text inside a button, field, row, or bottom bar is okay."}`,
75
+ `- Adjust and preview again only if the marker is outside the target, on the ${targetKind === "browser" ? "edge" : "target border/edge"}, or closer to a neighboring target than the intended one.`,
76
+ "- If you preview more than once, the next click should use the most recent accepted preview coordinates unless the screen changed.",
77
+ "- If the click does not cause the expected UI change, treat it as a likely misclick and try a meaningfully different coordinate.",
78
+ "- Click precisely based on your visual estimate.",
79
+ ].join("\n ");
81
80
  }
82
81
  function buildBrowserBaseSystemPrompt(deviceInfo, options = {}) {
83
82
  const aimAssistMode = typeof options.aimAssistMode === "string" ? options.aimAssistMode : "always";
@@ -141,7 +140,7 @@ function buildBrowserBaseSystemPrompt(deviceInfo, options = {}) {
141
140
 
142
141
  Browser-Specific Notes:
143
142
  - You are operating a desktop browser, not a phone UI
144
- - Direct URL navigation is handled by runner-owned \`navigate:\` lines when available; do not try to reach the browser address bar or fake URL entry with keyboard shortcuts
143
+ - Use the \`navigate\` tool for direct URL changes when available; do not try to reach the browser address bar or fake URL entry with keyboard shortcuts
145
144
  - ESC dismisses many overlays and dialogs
146
145
  - Cookie or consent banners may block the main task and may need dismissal first
147
146
  - Vertical scrolling is the default way to discover content below the fold
@@ -13,15 +13,21 @@ Your task:
13
13
  1. Understand what the user wants to test from their initial instruction
14
14
  2. Explore the ${browserTarget ? "website" : "app"} autonomously to understand the flows
15
15
  3. Take screenshots and interact as needed to discover the UI and behavior
16
- 4. Once you've successfully completed the user's requested flow, immediately generate the test script
16
+ 4. Once you've successfully completed the user's requested flow, call \`test_design_complete\` with a structured summary of what was proven
17
17
 
18
18
  CRITICAL - After Completing the Task:
19
19
  - DO NOT navigate back or away from the final screen
20
20
  - The final screen state is what matters for verification
21
- - Generate the test script immediately showing the current state
21
+ - Call \`test_design_complete\` immediately once the requested flow has been proven
22
22
  - Use assertions to verify state, not navigation
23
23
  - "Check that it changed" means verify the current visual state, not navigate elsewhere
24
- - If the target validation state is visible (including expected error states), STOP actions and immediately output the final test script
24
+ - If the target validation state is visible (including expected error states), STOP actions and call \`test_design_complete\`
25
+ - If an assertion or validation PASS confirms the user goal, treat the task as complete
26
+ - After a passing assertion confirms the goal, your very next action should be calling \`test_design_complete\`
27
+ - Do NOT repeat the same assertion or validation on an unchanged screen after it has already passed
28
+ - Do NOT keep browsing, screenshotting, or re-checking once the goal has been confirmed
29
+ - Do NOT output the final test script before calling \`test_design_complete\`
30
+ - After calling \`test_design_complete\`, stop exploring and wait for the final script-generation turn
25
31
 
26
32
  CRITICAL - Recognizing When You Are Stuck:
27
33
  If you find yourself:
@@ -39,7 +45,7 @@ Example:
39
45
  "Chrome is open but I don't see a search bar or new tab button. Should I open a new tab, or is there a specific way you'd like me to navigate?"
40
46
 
41
47
  DO NOT continue brute-forcing the UI when stuck. The user prefers being asked over watching repeated failed attempts.
42
- DO NOT ask if the user wants a script after successfully completing the flow - just generate it automatically.
48
+ DO NOT ask if the user wants a script after successfully completing the flow - complete the handoff automatically.
43
49
 
44
50
  CRITICAL - Off-Screen Element Discovery:
45
51
  - If a required element is not visible, assume it may be off-screen before changing strategy
@@ -52,11 +58,11 @@ CRITICAL - Off-Screen Element Discovery:
52
58
  CRITICAL - Test Script Format Rules:
53
59
  - One simple instruction per line (NO numbers, NO bullets)
54
60
  - Use imperative commands: "Open X", "Click Y", "Type Z"
55
- - Include "assert: <condition>" lines to validate expected behavior
56
- - Normalize validation wording into assertions:
57
- - Convert "check", "verify", "ensure", "fetch", and "compare" intent into explicit "assert: ..." lines
58
- - Do not leave standalone "Check ..." or "Verify ..." lines in the final script
59
- - Merge duplicate or near-duplicate validation lines into one clear assertion
61
+ - Use plain-language verification lines such as "Verify ..." or "Validate ..."
62
+ - Normalize validation wording into plain-language verification:
63
+ - Convert "check", "assert", "ensure", "fetch", and "compare" intent into clear "Verify ..." or "Validate ..." lines
64
+ - Do not leave vague standalone "Check ..." lines in the final script
65
+ - Merge duplicate or near-duplicate verification lines into one clear validation step
60
66
  - End with "exit"
61
67
  - Keep it simple and executable
62
68
  - When you generate the final result, include a suggested test name before the script
@@ -74,7 +80,7 @@ CORRECT Example:
74
80
  Suggested test name: ${browserTarget ? "docs-search" : "calculator-addition"}
75
81
 
76
82
  \`\`\`
77
- ${browserTarget ? "navigate: https://docs.example.com\nType \"pricing\" in the search field\nPress Enter\nwait\nClick \"Pricing\"\nassert: Pricing page is visible" : "Open Calculator app\nassert: Calculator app is visible\nType \"2\"\nClick the plus button\nType \"3\"\nClick the equals button\nassert: result shows 5"}
83
+ ${browserTarget ? "Go to https://docs.example.com\nType \"pricing\" in the search field\nPress Enter\nwait\nClick \"Pricing\"\nVerify the Pricing page is visible" : "Open Calculator app\nVerify the Calculator app is visible\nType \"2\"\nClick the plus button\nType \"3\"\nClick the equals button\nVerify the result shows 5"}
78
84
  exit
79
85
  \`\`\`
80
86
 
@@ -105,5 +111,40 @@ ${transcript}
105
111
  Original objective:
106
112
  ${objective ?? "(not provided)"}
107
113
 
108
- If the objective is already completed, generate the final test script now.`;
114
+ If the objective is already completed, or a recent passing validation already confirmed the goal, call \`test_design_complete\` now.
115
+ Do not repeat the same assertion on an unchanged screen.`;
116
+ }
117
+ function formatStringList(items = []) {
118
+ const normalized = Array.isArray(items)
119
+ ? items
120
+ .map((item) => (typeof item === "string" ? item.trim() : ""))
121
+ .filter(Boolean)
122
+ : [];
123
+ if (normalized.length === 0) {
124
+ return "- (none provided)";
125
+ }
126
+ return normalized.map((item) => `- ${item}`).join("\n");
127
+ }
128
+ export function buildDesignScriptGenerationPrompt(summary = {}) {
129
+ const suggestedTestName = typeof summary.suggestedTestName === "string" ? summary.suggestedTestName.trim() : "";
130
+ const goalSummary = typeof summary.goalSummary === "string" ? summary.goalSummary.trim() : "";
131
+ const finalState = typeof summary.finalState === "string" ? summary.finalState.trim() : "";
132
+ const notesForScript = typeof summary.notesForScript === "string" ? summary.notesForScript.trim() : "";
133
+ return `AUTHORING MODE:
134
+ Test design exploration is complete. Use the validated completion summary below and the conversation so far to produce the final test script now.
135
+
136
+ Do not call tools.
137
+ Do not continue exploring.
138
+ Do not add explanation.
139
+ Output only the final result in the required design-mode format.
140
+
141
+ Completion summary:
142
+ Suggested test name: ${suggestedTestName || "(choose a short kebab-case name)"}
143
+ Goal summary: ${goalSummary || "(not provided)"}
144
+ Final state: ${finalState || "(not provided)"}
145
+ Important steps:
146
+ ${formatStringList(summary.importantSteps)}
147
+ Verification points:
148
+ ${formatStringList(summary.verificationPoints)}
149
+ ${notesForScript ? `Notes for script:\n${notesForScript}\n` : ""}`;
109
150
  }
@@ -12,7 +12,7 @@ Apply the user's changes and output the revised test script.
12
12
  FORMAT RULES:
13
13
  - One simple instruction per line (NO numbers, NO bullets)
14
14
  - Use imperative commands: "Open X", "Click Y", "Type Z"
15
- - Include "assert: <condition>" lines to validate expected behavior
15
+ - Use plain-language verification lines such as "Verify ..." or "Validate ..." for expected behavior
16
16
  - End with "exit"
17
17
 
18
18
  Output only the revised test script, nothing else.`;
@@ -39,11 +39,15 @@ CRITICAL RULES:
39
39
 
40
40
  Your process:
41
41
  1. Read the instruction
42
- 2. Execute the required actions
43
- 3. Before tapping a mutating action, dismiss the keyboard if it is open and not needed
44
- 4. After a mutating action, inspect the resulting screen for success cues such as cleared fields, reset buttons, changed status, refreshed content, or navigation
45
- 5. Stop as soon as success is visible
46
- 6. Stop immediately - no commentary, no questions
42
+ 2. Choose the right tool when the instruction is really a higher-level capability:
43
+ - Use \`assert\` for assertions and verification intent, including legacy \`assert:\` lines and natural language like verify, validate, ensure, or confirm
44
+ - Use \`navigate\` for direct browser navigation instead of trying to manipulate the address bar
45
+ - Use \`loadmill\` only when the instruction explicitly references the word "loadmill"
46
+ 3. Execute the required actions
47
+ 4. Before tapping a mutating action, dismiss the keyboard if it is open and not needed
48
+ 5. After a mutating action, inspect the resulting screen for success cues such as cleared fields, reset buttons, changed status, refreshed content, or navigation
49
+ 6. Stop as soon as success is visible
50
+ 7. Stop immediately - no commentary, no questions
47
51
 
48
52
  Each instruction is independent. Do not reference previous instructions or ask about next steps.
49
53
  ${appContextSection ? `\n\n${appContextSection}` : ""}`;
@@ -191,7 +191,9 @@ export class CuaDebugTracer {
191
191
  ...(Number.isInteger(debugContext?.instructionIndex) ? { instructionIndex: debugContext.instructionIndex } : {})
192
192
  };
193
193
  const chainId = scope === "design" ? ids.sessionId : ids.runId;
194
- const chainKey = `${scope}:${chainId || "unknown"}`;
194
+ const chainKey = typeof debugContext?.chainId === "string" && debugContext.chainId.trim()
195
+ ? debugContext.chainId.trim()
196
+ : `${scope}:${chainId || "unknown"}`;
195
197
  const previousPendingSafetyChecks = Array.isArray(this.lastPendingSafetyChecksByChain.get(chainKey))
196
198
  ? this.lastPendingSafetyChecksByChain.get(chainKey)
197
199
  : [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loadmill/droid-cua",
3
- "version": "2.10.0",
3
+ "version": "2.10.1-rc.8",
4
4
  "description": "AI-powered Android testing agent using OpenAI's computer-use model and ADB",
5
5
  "main": "build/index.js",
6
6
  "type": "module",
@@ -23,9 +23,8 @@
23
23
  "test": "node --test unit-tests/*.test.js",
24
24
  "build": "tsc",
25
25
  "clean": "rm -rf build",
26
- "prerelease": "npm run build",
27
- "release:patch": "npm version patch && npm publish",
28
- "postversion": "git push && git push --tags"
26
+ "typecheck": "tsc --noEmit",
27
+ "prepublishOnly": "npm run build"
29
28
  },
30
29
  "repository": {
31
30
  "type": "git",
@@ -1,33 +0,0 @@
1
- export const TARGET_SCALED_WIDTH = 400;
2
- export const SCREENSHOT_RESOLUTION_MODE_DOWNSCALED = "downscaled";
3
- export const SCREENSHOT_RESOLUTION_MODE_NATIVE = "native";
4
- export function normalizeScreenshotResolutionMode(value) {
5
- return value === SCREENSHOT_RESOLUTION_MODE_NATIVE
6
- ? SCREENSHOT_RESOLUTION_MODE_NATIVE
7
- : SCREENSHOT_RESOLUTION_MODE_DOWNSCALED;
8
- }
9
- export function validateScreenshotResolutionMode(value, label) {
10
- if (typeof value !== "string") {
11
- throw new Error(`${label} must be one of: downscaled, native.`);
12
- }
13
- const normalized = normalizeScreenshotResolutionMode(value);
14
- if (normalized !== value) {
15
- throw new Error(`${label} must be one of: downscaled, native.`);
16
- }
17
- return normalized;
18
- }
19
- export function buildResolutionAwareDeviceInfo({ width, height, screenshotResolutionMode, }) {
20
- const normalizedMode = normalizeScreenshotResolutionMode(screenshotResolutionMode);
21
- const scale = normalizedMode === SCREENSHOT_RESOLUTION_MODE_NATIVE || width <= TARGET_SCALED_WIDTH
22
- ? 1.0
23
- : TARGET_SCALED_WIDTH / width;
24
- return {
25
- scaled_width: Math.round(width * scale),
26
- scaled_height: Math.round(height * scale),
27
- scale,
28
- screenshot_resolution_mode: normalizedMode,
29
- };
30
- }
31
- export function readScreenshotResolutionModeFromEnv() {
32
- return normalizeScreenshotResolutionMode(process.env.DROID_CUA_SCREENSHOT_RESOLUTION_MODE);
33
- }