@yoqa/runner 0.3.21 → 0.3.23
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.
- package/dist/index.js +597 -231
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -16801,7 +16801,7 @@ function getCatalogDb() {
|
|
|
16801
16801
|
// package.json
|
|
16802
16802
|
var package_default = {
|
|
16803
16803
|
name: "@yoqa/runner",
|
|
16804
|
-
version: "0.3.
|
|
16804
|
+
version: "0.3.23",
|
|
16805
16805
|
description: "Yoqa local runner \u2014 Appium + catalog HTTP API (Bun). Start with yoqa serve or the yoqa-runner bin.",
|
|
16806
16806
|
license: "UNLICENSED",
|
|
16807
16807
|
type: "module",
|
|
@@ -16899,6 +16899,229 @@ import { homedir as homedir6 } from "os";
|
|
|
16899
16899
|
import { join as join7 } from "path";
|
|
16900
16900
|
import { remote } from "webdriverio";
|
|
16901
16901
|
|
|
16902
|
+
// src/domains/devices/android-gestures.ts
|
|
16903
|
+
function isAndroidDriver(browser) {
|
|
16904
|
+
return String(browser.capabilities?.platformName ?? "").toLowerCase() === "android";
|
|
16905
|
+
}
|
|
16906
|
+
function toPx(norm, size) {
|
|
16907
|
+
return Math.round(Math.min(1000, Math.max(0, norm)) / 1000 * size);
|
|
16908
|
+
}
|
|
16909
|
+
function pngSizeFromBase64(base64) {
|
|
16910
|
+
if (!base64)
|
|
16911
|
+
return null;
|
|
16912
|
+
try {
|
|
16913
|
+
const buf = Buffer.from(base64.slice(0, 48), "base64");
|
|
16914
|
+
if (buf.length < 24)
|
|
16915
|
+
return null;
|
|
16916
|
+
if (buf[0] !== 137 || buf[1] !== 80 || buf[2] !== 78 || buf[3] !== 71)
|
|
16917
|
+
return null;
|
|
16918
|
+
const width = buf.readUInt32BE(16);
|
|
16919
|
+
const height = buf.readUInt32BE(20);
|
|
16920
|
+
if (width < 1 || height < 1 || width > 16384 || height > 16384)
|
|
16921
|
+
return null;
|
|
16922
|
+
return { width, height };
|
|
16923
|
+
} catch {
|
|
16924
|
+
return null;
|
|
16925
|
+
}
|
|
16926
|
+
}
|
|
16927
|
+
function preferPointerSize(window2, screenshot) {
|
|
16928
|
+
if (!screenshot || screenshot.width < 1 || screenshot.height < 1)
|
|
16929
|
+
return window2;
|
|
16930
|
+
if (window2.width < 1 || window2.height < 1)
|
|
16931
|
+
return screenshot;
|
|
16932
|
+
const dw = Math.abs(screenshot.width - window2.width) / window2.width;
|
|
16933
|
+
const dh = Math.abs(screenshot.height - window2.height) / window2.height;
|
|
16934
|
+
if (dw < 0.02 && dh < 0.02)
|
|
16935
|
+
return screenshot;
|
|
16936
|
+
return window2;
|
|
16937
|
+
}
|
|
16938
|
+
function w3cTapActions(x, y, holdMs) {
|
|
16939
|
+
return [
|
|
16940
|
+
{
|
|
16941
|
+
type: "pointer",
|
|
16942
|
+
id: "finger1",
|
|
16943
|
+
parameters: { pointerType: "touch" },
|
|
16944
|
+
actions: [
|
|
16945
|
+
{ type: "pointerMove", duration: 0, x, y },
|
|
16946
|
+
{ type: "pointerDown", button: 0 },
|
|
16947
|
+
{ type: "pause", duration: holdMs },
|
|
16948
|
+
{ type: "pointerUp", button: 0 }
|
|
16949
|
+
]
|
|
16950
|
+
}
|
|
16951
|
+
];
|
|
16952
|
+
}
|
|
16953
|
+
function w3cSwipeActions(x1, y1, x2, y2, durationMs) {
|
|
16954
|
+
return [
|
|
16955
|
+
{
|
|
16956
|
+
type: "pointer",
|
|
16957
|
+
id: "finger1",
|
|
16958
|
+
parameters: { pointerType: "touch" },
|
|
16959
|
+
actions: [
|
|
16960
|
+
{ type: "pointerMove", duration: 0, x: x1, y: y1 },
|
|
16961
|
+
{ type: "pointerDown", button: 0 },
|
|
16962
|
+
{ type: "pointerMove", duration: durationMs, x: x2, y: y2 },
|
|
16963
|
+
{ type: "pointerUp", button: 0 }
|
|
16964
|
+
]
|
|
16965
|
+
}
|
|
16966
|
+
];
|
|
16967
|
+
}
|
|
16968
|
+
async function w3cPerform(browser, actions) {
|
|
16969
|
+
await browser.performActions(actions);
|
|
16970
|
+
try {
|
|
16971
|
+
await browser.releaseActions();
|
|
16972
|
+
} catch {}
|
|
16973
|
+
}
|
|
16974
|
+
async function injectTap(browser, x, y, holdMs) {
|
|
16975
|
+
const duration = Math.max(50, holdMs);
|
|
16976
|
+
if (isAndroidDriver(browser)) {
|
|
16977
|
+
try {
|
|
16978
|
+
await browser.execute("mobile: clickGesture", { x, y, duration });
|
|
16979
|
+
return "clickGesture";
|
|
16980
|
+
} catch {}
|
|
16981
|
+
}
|
|
16982
|
+
await w3cPerform(browser, w3cTapActions(x, y, duration));
|
|
16983
|
+
return "w3c";
|
|
16984
|
+
}
|
|
16985
|
+
async function injectSwipe(browser, x1, y1, x2, y2, durationMs) {
|
|
16986
|
+
const duration = Math.max(50, durationMs);
|
|
16987
|
+
if (isAndroidDriver(browser)) {
|
|
16988
|
+
try {
|
|
16989
|
+
const distance = Math.hypot(x2 - x1, y2 - y1);
|
|
16990
|
+
const speed = Math.max(200, Math.round(distance / (duration / 1000)));
|
|
16991
|
+
await browser.execute("mobile: dragGesture", {
|
|
16992
|
+
startX: x1,
|
|
16993
|
+
startY: y1,
|
|
16994
|
+
endX: x2,
|
|
16995
|
+
endY: y2,
|
|
16996
|
+
speed
|
|
16997
|
+
});
|
|
16998
|
+
return "dragGesture";
|
|
16999
|
+
} catch {}
|
|
17000
|
+
}
|
|
17001
|
+
await w3cPerform(browser, w3cSwipeActions(x1, y1, x2, y2, duration));
|
|
17002
|
+
return "w3c";
|
|
17003
|
+
}
|
|
17004
|
+
|
|
17005
|
+
// src/domains/devices/android-alerts.ts
|
|
17006
|
+
var ANDROID_ACCEPT_BUTTON_LABELS = [
|
|
17007
|
+
"Allow",
|
|
17008
|
+
"ALLOW",
|
|
17009
|
+
"While using the app",
|
|
17010
|
+
"While using this app",
|
|
17011
|
+
"Allow all the time",
|
|
17012
|
+
"Allow only while using the app",
|
|
17013
|
+
"OK"
|
|
17014
|
+
];
|
|
17015
|
+
var ANDROID_DISMISS_BUTTON_LABELS = [
|
|
17016
|
+
"Don't allow",
|
|
17017
|
+
"Don\u2019t allow",
|
|
17018
|
+
"Don't Allow",
|
|
17019
|
+
"Deny",
|
|
17020
|
+
"DENY",
|
|
17021
|
+
"No thanks"
|
|
17022
|
+
];
|
|
17023
|
+
var ANDROID_ACCEPT_RESOURCE_IDS = [
|
|
17024
|
+
"com.android.permissioncontroller:id/permission_allow_button",
|
|
17025
|
+
"com.android.permissioncontroller:id/permission_allow_foreground_only_button",
|
|
17026
|
+
"com.android.permissioncontroller:id/permission_allow_one_time_button",
|
|
17027
|
+
"com.android.packageinstaller:id/permission_allow_button",
|
|
17028
|
+
"com.samsung.android.permissioncontroller:id/permission_allow_button"
|
|
17029
|
+
];
|
|
17030
|
+
var ANDROID_DISMISS_RESOURCE_IDS = [
|
|
17031
|
+
"com.android.permissioncontroller:id/permission_deny_button",
|
|
17032
|
+
"com.android.permissioncontroller:id/permission_deny_and_dont_ask_again_button",
|
|
17033
|
+
"com.android.packageinstaller:id/permission_deny_button",
|
|
17034
|
+
"com.samsung.android.permissioncontroller:id/permission_deny_button"
|
|
17035
|
+
];
|
|
17036
|
+
function androidAlertAttempts(action) {
|
|
17037
|
+
const labels = action === "accept" ? ANDROID_ACCEPT_BUTTON_LABELS : ANDROID_DISMISS_BUTTON_LABELS;
|
|
17038
|
+
const ids = action === "accept" ? ANDROID_ACCEPT_RESOURCE_IDS : ANDROID_DISMISS_RESOURCE_IDS;
|
|
17039
|
+
const mobile = action === "accept" ? "mobile-acceptAlert" : "mobile-dismissAlert";
|
|
17040
|
+
const attempts = [];
|
|
17041
|
+
for (const buttonLabel of labels) {
|
|
17042
|
+
attempts.push({ method: mobile, buttonLabel });
|
|
17043
|
+
}
|
|
17044
|
+
attempts.push({ method: mobile });
|
|
17045
|
+
for (const id of ids) {
|
|
17046
|
+
attempts.push({ method: "resource-id", id });
|
|
17047
|
+
}
|
|
17048
|
+
for (const text2 of labels) {
|
|
17049
|
+
attempts.push({ method: "text", text: text2 });
|
|
17050
|
+
}
|
|
17051
|
+
return attempts;
|
|
17052
|
+
}
|
|
17053
|
+
function iosAlertAttempts(action) {
|
|
17054
|
+
return [
|
|
17055
|
+
action === "accept" ? { method: "w3c-acceptAlert" } : { method: "w3c-dismissAlert" },
|
|
17056
|
+
{ method: "mobile-alert", action }
|
|
17057
|
+
];
|
|
17058
|
+
}
|
|
17059
|
+
function alertAttemptsFor(browser, action) {
|
|
17060
|
+
return isAndroidDriver(browser) ? androidAlertAttempts(action) : iosAlertAttempts(action);
|
|
17061
|
+
}
|
|
17062
|
+
async function tryClickSelector(browser, selector) {
|
|
17063
|
+
try {
|
|
17064
|
+
const el = browser.$(selector);
|
|
17065
|
+
if (await el.isExisting()) {
|
|
17066
|
+
await el.click();
|
|
17067
|
+
return true;
|
|
17068
|
+
}
|
|
17069
|
+
} catch {
|
|
17070
|
+
return false;
|
|
17071
|
+
}
|
|
17072
|
+
return false;
|
|
17073
|
+
}
|
|
17074
|
+
function xpathExactText(text2) {
|
|
17075
|
+
if (text2.includes("'") && text2.includes('"')) {
|
|
17076
|
+
const parts = text2.split("'").map((part) => `'${part}'`);
|
|
17077
|
+
return `//*[@text=concat(${parts.join(`, "'", `)})]`;
|
|
17078
|
+
}
|
|
17079
|
+
if (text2.includes("'")) {
|
|
17080
|
+
return `//*[@text="${text2}"]`;
|
|
17081
|
+
}
|
|
17082
|
+
return `//*[@text='${text2}']`;
|
|
17083
|
+
}
|
|
17084
|
+
async function runAttempt(browser, attempt) {
|
|
17085
|
+
try {
|
|
17086
|
+
switch (attempt.method) {
|
|
17087
|
+
case "mobile-acceptAlert": {
|
|
17088
|
+
const args = attempt.buttonLabel ? { buttonLabel: attempt.buttonLabel } : {};
|
|
17089
|
+
await browser.execute("mobile: acceptAlert", args);
|
|
17090
|
+
return true;
|
|
17091
|
+
}
|
|
17092
|
+
case "mobile-dismissAlert": {
|
|
17093
|
+
const args = attempt.buttonLabel ? { buttonLabel: attempt.buttonLabel } : {};
|
|
17094
|
+
await browser.execute("mobile: dismissAlert", args);
|
|
17095
|
+
return true;
|
|
17096
|
+
}
|
|
17097
|
+
case "resource-id":
|
|
17098
|
+
return await tryClickSelector(browser, `android=new UiSelector().resourceId("${attempt.id}")`);
|
|
17099
|
+
case "text":
|
|
17100
|
+
return await tryClickSelector(browser, xpathExactText(attempt.text));
|
|
17101
|
+
case "w3c-acceptAlert":
|
|
17102
|
+
await browser.acceptAlert();
|
|
17103
|
+
return true;
|
|
17104
|
+
case "w3c-dismissAlert":
|
|
17105
|
+
await browser.dismissAlert();
|
|
17106
|
+
return true;
|
|
17107
|
+
case "mobile-alert":
|
|
17108
|
+
await browser.execute("mobile: alert", { action: attempt.action });
|
|
17109
|
+
return true;
|
|
17110
|
+
}
|
|
17111
|
+
} catch {
|
|
17112
|
+
return false;
|
|
17113
|
+
}
|
|
17114
|
+
}
|
|
17115
|
+
async function resolveNativeAlert(browser, action) {
|
|
17116
|
+
const attempts = alertAttemptsFor(browser, action);
|
|
17117
|
+
for (const attempt of attempts) {
|
|
17118
|
+
if (await runAttempt(browser, attempt)) {
|
|
17119
|
+
return attempt;
|
|
17120
|
+
}
|
|
17121
|
+
}
|
|
17122
|
+
throw new Error(action === "accept" ? "Could not accept the system alert (no Allow / permission button found)" : "Could not dismiss the system alert (no Don't allow / Deny button found)");
|
|
17123
|
+
}
|
|
17124
|
+
|
|
16902
17125
|
// src/domains/devices/application.ts
|
|
16903
17126
|
import { mkdtemp as mkdtemp2, readFile, rm as rm2 } from "fs/promises";
|
|
16904
17127
|
import { tmpdir as tmpdir2 } from "os";
|
|
@@ -17557,7 +17780,7 @@ class ActionGate {
|
|
|
17557
17780
|
this.locked = false;
|
|
17558
17781
|
}
|
|
17559
17782
|
}
|
|
17560
|
-
async handlePointer(browser,
|
|
17783
|
+
async handlePointer(browser, toPx2, getWindowSize, phase, xNorm, yNorm, seq) {
|
|
17561
17784
|
if (this.locked && !this.gesture && !this.deferredTap) {
|
|
17562
17785
|
throw new Error("Device is busy with another action");
|
|
17563
17786
|
}
|
|
@@ -17587,7 +17810,7 @@ class ActionGate {
|
|
|
17587
17810
|
await this.flushGesture(pending.gesture, pending.browser, pending.toPx, pending.getWindowSize);
|
|
17588
17811
|
}
|
|
17589
17812
|
if (this.gesture) {
|
|
17590
|
-
await this.flushGesture(this.gesture, browser,
|
|
17813
|
+
await this.flushGesture(this.gesture, browser, toPx2, getWindowSize);
|
|
17591
17814
|
this.gesture = null;
|
|
17592
17815
|
}
|
|
17593
17816
|
this.locked = true;
|
|
@@ -17616,16 +17839,16 @@ class ActionGate {
|
|
|
17616
17839
|
this.gesture.endXNorm = x;
|
|
17617
17840
|
this.gesture.endYNorm = y;
|
|
17618
17841
|
const size = await getWindowSize();
|
|
17619
|
-
const startX =
|
|
17620
|
-
const startY =
|
|
17621
|
-
const endX =
|
|
17622
|
-
const endY =
|
|
17842
|
+
const startX = toPx2(this.gesture.startXNorm, size.width);
|
|
17843
|
+
const startY = toPx2(this.gesture.startYNorm, size.height);
|
|
17844
|
+
const endX = toPx2(this.gesture.endXNorm, size.width);
|
|
17845
|
+
const endY = toPx2(this.gesture.endYNorm, size.height);
|
|
17623
17846
|
const distance = Math.hypot(endX - startX, endY - startY);
|
|
17624
17847
|
const tapSlopPx = 12;
|
|
17625
17848
|
if (distance >= tapSlopPx || this.gesture.double) {
|
|
17626
17849
|
const gesture2 = this.gesture;
|
|
17627
17850
|
this.gesture = null;
|
|
17628
|
-
await this.flushGesture(gesture2, browser,
|
|
17851
|
+
await this.flushGesture(gesture2, browser, toPx2, getWindowSize);
|
|
17629
17852
|
return;
|
|
17630
17853
|
}
|
|
17631
17854
|
const gesture = this.gesture;
|
|
@@ -17640,74 +17863,41 @@ class ActionGate {
|
|
|
17640
17863
|
this.locked = false;
|
|
17641
17864
|
});
|
|
17642
17865
|
}, doubleTapMs);
|
|
17643
|
-
this.deferredTap = { gesture, browser, toPx, getWindowSize, timer };
|
|
17866
|
+
this.deferredTap = { gesture, browser, toPx: toPx2, getWindowSize, timer };
|
|
17644
17867
|
}
|
|
17645
|
-
async flushGesture(gesture, browser,
|
|
17868
|
+
async flushGesture(gesture, browser, toPx2, getWindowSize) {
|
|
17646
17869
|
const size = await getWindowSize();
|
|
17647
|
-
const startX =
|
|
17648
|
-
const startY =
|
|
17649
|
-
const endX =
|
|
17650
|
-
const endY =
|
|
17870
|
+
const startX = toPx2(gesture.startXNorm, size.width);
|
|
17871
|
+
const startY = toPx2(gesture.startYNorm, size.height);
|
|
17872
|
+
const endX = toPx2(gesture.endXNorm, size.width);
|
|
17873
|
+
const endY = toPx2(gesture.endYNorm, size.height);
|
|
17651
17874
|
const distance = Math.hypot(endX - startX, endY - startY);
|
|
17652
17875
|
const elapsedMs = Math.max(50, Date.now() - gesture.startedAt);
|
|
17653
17876
|
const tapSlopPx = 12;
|
|
17654
17877
|
try {
|
|
17655
17878
|
if (distance < tapSlopPx || gesture.double) {
|
|
17656
17879
|
const holdMs = Math.min(200, elapsedMs);
|
|
17657
|
-
|
|
17658
|
-
await browser.performActions([
|
|
17659
|
-
{
|
|
17660
|
-
type: "pointer",
|
|
17661
|
-
id: "finger1",
|
|
17662
|
-
parameters: { pointerType: "touch" },
|
|
17663
|
-
actions: [
|
|
17664
|
-
{ type: "pointerMove", duration: 0, x: startX, y: startY },
|
|
17665
|
-
{ type: "pointerDown", button: 0 },
|
|
17666
|
-
{ type: "pause", duration: holdMs },
|
|
17667
|
-
{ type: "pointerUp", button: 0 }
|
|
17668
|
-
]
|
|
17669
|
-
}
|
|
17670
|
-
]);
|
|
17671
|
-
try {
|
|
17672
|
-
await browser.releaseActions();
|
|
17673
|
-
} catch {}
|
|
17674
|
-
};
|
|
17675
|
-
await tapOnce();
|
|
17880
|
+
await injectTap(browser, startX, startY, holdMs);
|
|
17676
17881
|
if (gesture.double) {
|
|
17677
17882
|
await Bun.sleep(50);
|
|
17678
|
-
await
|
|
17883
|
+
await injectTap(browser, startX, startY, holdMs);
|
|
17679
17884
|
}
|
|
17680
17885
|
} else {
|
|
17681
17886
|
const duration = Math.min(2000, Math.max(80, elapsedMs));
|
|
17682
|
-
await browser
|
|
17683
|
-
{
|
|
17684
|
-
type: "pointer",
|
|
17685
|
-
id: "finger1",
|
|
17686
|
-
parameters: { pointerType: "touch" },
|
|
17687
|
-
actions: [
|
|
17688
|
-
{ type: "pointerMove", duration: 0, x: startX, y: startY },
|
|
17689
|
-
{ type: "pointerDown", button: 0 },
|
|
17690
|
-
{ type: "pointerMove", duration, x: endX, y: endY },
|
|
17691
|
-
{ type: "pointerUp", button: 0 }
|
|
17692
|
-
]
|
|
17693
|
-
}
|
|
17694
|
-
]);
|
|
17695
|
-
try {
|
|
17696
|
-
await browser.releaseActions();
|
|
17697
|
-
} catch {}
|
|
17887
|
+
await injectSwipe(browser, startX, startY, endX, endY, duration);
|
|
17698
17888
|
}
|
|
17699
17889
|
} finally {
|
|
17700
17890
|
this.locked = false;
|
|
17701
17891
|
}
|
|
17702
17892
|
}
|
|
17703
|
-
async forceEnd(browser,
|
|
17893
|
+
async forceEnd(browser, toPx2, getWindowSize) {
|
|
17704
17894
|
if (this.deferredTap) {
|
|
17705
17895
|
clearTimeout(this.deferredTap.timer);
|
|
17706
17896
|
const pending = this.deferredTap;
|
|
17707
17897
|
this.deferredTap = null;
|
|
17708
|
-
if (
|
|
17898
|
+
if (toPx2 && getWindowSize) {
|
|
17709
17899
|
try {
|
|
17710
|
-
await this.flushGesture(pending.gesture, pending.browser,
|
|
17900
|
+
await this.flushGesture(pending.gesture, pending.browser, toPx2, getWindowSize);
|
|
17711
17901
|
} catch {
|
|
17712
17902
|
this.locked = false;
|
|
17713
17903
|
}
|
|
@@ -17720,7 +17910,7 @@ class ActionGate {
|
|
|
17720
17910
|
this.locked = false;
|
|
17721
17911
|
return;
|
|
17722
17912
|
}
|
|
17723
|
-
if (!
|
|
17913
|
+
if (!toPx2 || !getWindowSize) {
|
|
17724
17914
|
this.gesture = null;
|
|
17725
17915
|
this.locked = false;
|
|
17726
17916
|
return;
|
|
@@ -17728,7 +17918,7 @@ class ActionGate {
|
|
|
17728
17918
|
const gesture = this.gesture;
|
|
17729
17919
|
this.gesture = null;
|
|
17730
17920
|
try {
|
|
17731
|
-
await this.flushGesture(gesture, browser,
|
|
17921
|
+
await this.flushGesture(gesture, browser, toPx2, getWindowSize);
|
|
17732
17922
|
} catch {
|
|
17733
17923
|
this.locked = false;
|
|
17734
17924
|
}
|
|
@@ -17791,7 +17981,11 @@ async function createDeviceSession(options) {
|
|
|
17791
17981
|
}
|
|
17792
17982
|
};
|
|
17793
17983
|
const getWindowSize = async () => guard(() => browser.getWindowSize());
|
|
17794
|
-
|
|
17984
|
+
let lastShotSize = null;
|
|
17985
|
+
const getPointerSize = async () => {
|
|
17986
|
+
const window2 = await getWindowSize();
|
|
17987
|
+
return preferPointerSize(window2, lastShotSize);
|
|
17988
|
+
};
|
|
17795
17989
|
const owned = { current: null };
|
|
17796
17990
|
const quit = async () => {
|
|
17797
17991
|
if (openByDeviceId.get(options.deviceId) === owned.current) {
|
|
@@ -17799,7 +17993,7 @@ async function createDeviceSession(options) {
|
|
|
17799
17993
|
}
|
|
17800
17994
|
if (gate.isPointerActive() && !sessionDeadNotified) {
|
|
17801
17995
|
try {
|
|
17802
|
-
await withTimeout(gate.forceEnd(browser, toPx,
|
|
17996
|
+
await withTimeout(gate.forceEnd(browser, toPx, getPointerSize), 3000, "live gesture flush");
|
|
17803
17997
|
} catch (error) {
|
|
17804
17998
|
console.warn("[yoqa-runner] gesture flush on quit:", error instanceof Error ? error.message : error);
|
|
17805
17999
|
}
|
|
@@ -17818,6 +18012,7 @@ async function createDeviceSession(options) {
|
|
|
17818
18012
|
};
|
|
17819
18013
|
const captureFrame = async () => guard(async () => {
|
|
17820
18014
|
const base64 = await browser.takeScreenshot();
|
|
18015
|
+
lastShotSize = pngSizeFromBase64(base64) ?? lastShotSize;
|
|
17821
18016
|
return { base64, mime: "image/png" };
|
|
17822
18017
|
});
|
|
17823
18018
|
const screenshot = async () => {
|
|
@@ -17831,55 +18026,19 @@ async function createDeviceSession(options) {
|
|
|
17831
18026
|
const tap = async (xNorm, yNorm, tapOptions) => {
|
|
17832
18027
|
await gate.withLock(async () => {
|
|
17833
18028
|
await guard(async () => {
|
|
17834
|
-
const size = await
|
|
18029
|
+
const size = await getPointerSize();
|
|
17835
18030
|
const x = toPx(xNorm, size.width);
|
|
17836
18031
|
const y = toPx(yNorm, size.height);
|
|
17837
18032
|
const holdMs = Math.max(50, tapOptions?.durationMs ?? 50);
|
|
17838
|
-
await browser
|
|
17839
|
-
{
|
|
17840
|
-
type: "pointer",
|
|
17841
|
-
id: "finger1",
|
|
17842
|
-
parameters: { pointerType: "touch" },
|
|
17843
|
-
actions: [
|
|
17844
|
-
{ type: "pointerMove", duration: 0, x, y },
|
|
17845
|
-
{ type: "pointerDown", button: 0 },
|
|
17846
|
-
{ type: "pause", duration: holdMs },
|
|
17847
|
-
{ type: "pointerUp", button: 0 }
|
|
17848
|
-
]
|
|
17849
|
-
}
|
|
17850
|
-
]);
|
|
17851
|
-
await browser.releaseActions();
|
|
18033
|
+
await injectTap(browser, x, y, holdMs);
|
|
17852
18034
|
});
|
|
17853
18035
|
});
|
|
17854
18036
|
};
|
|
17855
18037
|
const swipe = async (x1, y1, x2, y2, durationMs = 400) => {
|
|
17856
18038
|
await gate.withLock(async () => {
|
|
17857
18039
|
await guard(async () => {
|
|
17858
|
-
const size = await
|
|
17859
|
-
await browser.
|
|
17860
|
-
{
|
|
17861
|
-
type: "pointer",
|
|
17862
|
-
id: "finger1",
|
|
17863
|
-
parameters: { pointerType: "touch" },
|
|
17864
|
-
actions: [
|
|
17865
|
-
{
|
|
17866
|
-
type: "pointerMove",
|
|
17867
|
-
duration: 0,
|
|
17868
|
-
x: toPx(x1, size.width),
|
|
17869
|
-
y: toPx(y1, size.height)
|
|
17870
|
-
},
|
|
17871
|
-
{ type: "pointerDown", button: 0 },
|
|
17872
|
-
{
|
|
17873
|
-
type: "pointerMove",
|
|
17874
|
-
duration: durationMs,
|
|
17875
|
-
x: toPx(x2, size.width),
|
|
17876
|
-
y: toPx(y2, size.height)
|
|
17877
|
-
},
|
|
17878
|
-
{ type: "pointerUp", button: 0 }
|
|
17879
|
-
]
|
|
17880
|
-
}
|
|
17881
|
-
]);
|
|
17882
|
-
await browser.releaseActions();
|
|
18040
|
+
const size = await getPointerSize();
|
|
18041
|
+
await injectSwipe(browser, toPx(x1, size.width), toPx(y1, size.height), toPx(x2, size.width), toPx(y2, size.height), durationMs);
|
|
17883
18042
|
});
|
|
17884
18043
|
});
|
|
17885
18044
|
};
|
|
@@ -17924,20 +18083,20 @@ async function createDeviceSession(options) {
|
|
|
17924
18083
|
const acceptAlert = async () => {
|
|
17925
18084
|
await gate.withLock(async () => {
|
|
17926
18085
|
await guard(async () => {
|
|
17927
|
-
await browser
|
|
18086
|
+
await resolveNativeAlert(browser, "accept");
|
|
17928
18087
|
});
|
|
17929
18088
|
});
|
|
17930
18089
|
};
|
|
17931
18090
|
const dismissAlert = async () => {
|
|
17932
18091
|
await gate.withLock(async () => {
|
|
17933
18092
|
await guard(async () => {
|
|
17934
|
-
await browser
|
|
18093
|
+
await resolveNativeAlert(browser, "dismiss");
|
|
17935
18094
|
});
|
|
17936
18095
|
});
|
|
17937
18096
|
};
|
|
17938
18097
|
const pointerEvent = async (phase, xNorm, yNorm, seq) => {
|
|
17939
18098
|
await guard(async () => {
|
|
17940
|
-
await gate.handlePointer(browser, toPx,
|
|
18099
|
+
await gate.handlePointer(browser, toPx, getPointerSize, phase, xNorm, yNorm, seq);
|
|
17941
18100
|
});
|
|
17942
18101
|
};
|
|
17943
18102
|
const session = {
|
|
@@ -24118,6 +24277,11 @@ var caseScriptActionSchema = exports_external.union([
|
|
|
24118
24277
|
text: exports_external.string().min(1),
|
|
24119
24278
|
timeoutMs: exports_external.number().min(0).max(120000).optional(),
|
|
24120
24279
|
reason: exports_external.string().optional()
|
|
24280
|
+
}),
|
|
24281
|
+
exports_external.object({
|
|
24282
|
+
type: exports_external.literal("alert"),
|
|
24283
|
+
alertAction: exports_external.union([exports_external.literal("accept"), exports_external.literal("dismiss")]).default("accept"),
|
|
24284
|
+
reason: exports_external.string().optional()
|
|
24121
24285
|
})
|
|
24122
24286
|
]);
|
|
24123
24287
|
var caseScriptSchema = exports_external.object({
|
|
@@ -24789,12 +24953,26 @@ function buildScriptFromDecisions(decisions, sourceRunId) {
|
|
|
24789
24953
|
const actions = [];
|
|
24790
24954
|
for (const decision of decisions) {
|
|
24791
24955
|
if (decision.type === "tap") {
|
|
24792
|
-
|
|
24793
|
-
|
|
24794
|
-
|
|
24795
|
-
|
|
24796
|
-
|
|
24797
|
-
|
|
24956
|
+
if (decision.label) {
|
|
24957
|
+
actions.push({
|
|
24958
|
+
type: "tap",
|
|
24959
|
+
label: decision.label,
|
|
24960
|
+
reason: decision.reason
|
|
24961
|
+
});
|
|
24962
|
+
} else if (decision.id) {
|
|
24963
|
+
actions.push({
|
|
24964
|
+
type: "tap",
|
|
24965
|
+
id: decision.id,
|
|
24966
|
+
reason: decision.reason
|
|
24967
|
+
});
|
|
24968
|
+
} else {
|
|
24969
|
+
actions.push({
|
|
24970
|
+
type: "tap",
|
|
24971
|
+
x: decision.x ?? 500,
|
|
24972
|
+
y: decision.y ?? 500,
|
|
24973
|
+
reason: decision.reason
|
|
24974
|
+
});
|
|
24975
|
+
}
|
|
24798
24976
|
} else if (decision.type === "type") {
|
|
24799
24977
|
actions.push({
|
|
24800
24978
|
type: "type",
|
|
@@ -24807,6 +24985,12 @@ function buildScriptFromDecisions(decisions, sourceRunId) {
|
|
|
24807
24985
|
ms: Math.min(3000, Math.max(500, decision.ms ?? 1500)),
|
|
24808
24986
|
reason: decision.reason
|
|
24809
24987
|
});
|
|
24988
|
+
} else if (decision.type === "alert") {
|
|
24989
|
+
actions.push({
|
|
24990
|
+
type: "alert",
|
|
24991
|
+
alertAction: decision.alertAction === "dismiss" ? "dismiss" : "accept",
|
|
24992
|
+
reason: decision.reason
|
|
24993
|
+
});
|
|
24810
24994
|
}
|
|
24811
24995
|
}
|
|
24812
24996
|
if (actions.length === 0)
|
|
@@ -57546,6 +57730,195 @@ var originalGenerateCallId6 = createIdGenerator3({
|
|
|
57546
57730
|
});
|
|
57547
57731
|
var defaultDownload2 = createDownload();
|
|
57548
57732
|
|
|
57733
|
+
// src/domains/providers/json-salvage.ts
|
|
57734
|
+
function charAt(raw2, index) {
|
|
57735
|
+
return raw2[index];
|
|
57736
|
+
}
|
|
57737
|
+
function coerceLooseJson(raw2) {
|
|
57738
|
+
let out = "";
|
|
57739
|
+
let i = 0;
|
|
57740
|
+
while (i < raw2.length) {
|
|
57741
|
+
const c = charAt(raw2, i);
|
|
57742
|
+
if (c === undefined)
|
|
57743
|
+
break;
|
|
57744
|
+
if (c === '"') {
|
|
57745
|
+
out += c;
|
|
57746
|
+
i += 1;
|
|
57747
|
+
while (i < raw2.length) {
|
|
57748
|
+
const d = charAt(raw2, i);
|
|
57749
|
+
if (d === undefined)
|
|
57750
|
+
break;
|
|
57751
|
+
out += d;
|
|
57752
|
+
i += 1;
|
|
57753
|
+
if (d === "\\") {
|
|
57754
|
+
const escaped = charAt(raw2, i);
|
|
57755
|
+
if (escaped !== undefined) {
|
|
57756
|
+
out += escaped;
|
|
57757
|
+
i += 1;
|
|
57758
|
+
}
|
|
57759
|
+
continue;
|
|
57760
|
+
}
|
|
57761
|
+
if (d === '"')
|
|
57762
|
+
break;
|
|
57763
|
+
}
|
|
57764
|
+
continue;
|
|
57765
|
+
}
|
|
57766
|
+
if (c === "'") {
|
|
57767
|
+
i += 1;
|
|
57768
|
+
let content = "";
|
|
57769
|
+
while (i < raw2.length) {
|
|
57770
|
+
const d = charAt(raw2, i);
|
|
57771
|
+
if (d === undefined)
|
|
57772
|
+
break;
|
|
57773
|
+
if (d === "\\" && i + 1 < raw2.length) {
|
|
57774
|
+
const next = charAt(raw2, i + 1);
|
|
57775
|
+
if (next === undefined)
|
|
57776
|
+
break;
|
|
57777
|
+
if (next === "n")
|
|
57778
|
+
content += `
|
|
57779
|
+
`;
|
|
57780
|
+
else if (next === "t")
|
|
57781
|
+
content += "\t";
|
|
57782
|
+
else if (next === "r")
|
|
57783
|
+
content += "\r";
|
|
57784
|
+
else
|
|
57785
|
+
content += next;
|
|
57786
|
+
i += 2;
|
|
57787
|
+
continue;
|
|
57788
|
+
}
|
|
57789
|
+
if (d === "'") {
|
|
57790
|
+
i += 1;
|
|
57791
|
+
break;
|
|
57792
|
+
}
|
|
57793
|
+
content += d;
|
|
57794
|
+
i += 1;
|
|
57795
|
+
}
|
|
57796
|
+
out += JSON.stringify(content);
|
|
57797
|
+
continue;
|
|
57798
|
+
}
|
|
57799
|
+
if (c === ",") {
|
|
57800
|
+
let j = i + 1;
|
|
57801
|
+
while (j < raw2.length) {
|
|
57802
|
+
const ws = charAt(raw2, j);
|
|
57803
|
+
if (ws === undefined || !/\s/.test(ws))
|
|
57804
|
+
break;
|
|
57805
|
+
j += 1;
|
|
57806
|
+
}
|
|
57807
|
+
const closer = charAt(raw2, j);
|
|
57808
|
+
if (closer === "}" || closer === "]") {
|
|
57809
|
+
i += 1;
|
|
57810
|
+
continue;
|
|
57811
|
+
}
|
|
57812
|
+
}
|
|
57813
|
+
out += c;
|
|
57814
|
+
i += 1;
|
|
57815
|
+
}
|
|
57816
|
+
return out;
|
|
57817
|
+
}
|
|
57818
|
+
function tryParseJson(text3) {
|
|
57819
|
+
try {
|
|
57820
|
+
return JSON.parse(text3);
|
|
57821
|
+
} catch {
|
|
57822
|
+
return;
|
|
57823
|
+
}
|
|
57824
|
+
}
|
|
57825
|
+
function closeTruncatedJson(raw2) {
|
|
57826
|
+
let inString = false;
|
|
57827
|
+
let escaped = false;
|
|
57828
|
+
const stack = [];
|
|
57829
|
+
for (let i = 0;i < raw2.length; i += 1) {
|
|
57830
|
+
const c = charAt(raw2, i);
|
|
57831
|
+
if (c === undefined)
|
|
57832
|
+
break;
|
|
57833
|
+
if (inString) {
|
|
57834
|
+
if (escaped) {
|
|
57835
|
+
escaped = false;
|
|
57836
|
+
continue;
|
|
57837
|
+
}
|
|
57838
|
+
if (c === "\\") {
|
|
57839
|
+
escaped = true;
|
|
57840
|
+
continue;
|
|
57841
|
+
}
|
|
57842
|
+
if (c === '"')
|
|
57843
|
+
inString = false;
|
|
57844
|
+
continue;
|
|
57845
|
+
}
|
|
57846
|
+
if (c === '"') {
|
|
57847
|
+
inString = true;
|
|
57848
|
+
continue;
|
|
57849
|
+
}
|
|
57850
|
+
if (c === "{")
|
|
57851
|
+
stack.push("{");
|
|
57852
|
+
else if (c === "[")
|
|
57853
|
+
stack.push("[");
|
|
57854
|
+
else if (c === "}" || c === "]")
|
|
57855
|
+
stack.pop();
|
|
57856
|
+
}
|
|
57857
|
+
let out = raw2;
|
|
57858
|
+
if (inString) {
|
|
57859
|
+
if (escaped)
|
|
57860
|
+
out = out.slice(0, -1);
|
|
57861
|
+
out += '"';
|
|
57862
|
+
}
|
|
57863
|
+
while (stack.length > 0) {
|
|
57864
|
+
const open = stack.pop();
|
|
57865
|
+
out += open === "{" ? "}" : "]";
|
|
57866
|
+
}
|
|
57867
|
+
return out;
|
|
57868
|
+
}
|
|
57869
|
+
function salvageAgentJsonText(text3) {
|
|
57870
|
+
const trimmed = text3.trim();
|
|
57871
|
+
const fenced = trimmed.match(/```(?:json)?\s*([\s\S]*?)```/);
|
|
57872
|
+
const inner = fenced?.[1]?.trim() ?? trimmed;
|
|
57873
|
+
const start = inner.indexOf("{");
|
|
57874
|
+
if (start < 0)
|
|
57875
|
+
return trimmed;
|
|
57876
|
+
const fromBrace = inner.slice(start);
|
|
57877
|
+
const lastBrace = fromBrace.lastIndexOf("}");
|
|
57878
|
+
const candidates = lastBrace > 0 ? [fromBrace.slice(0, lastBrace + 1), fromBrace] : [fromBrace];
|
|
57879
|
+
for (const candidate of candidates) {
|
|
57880
|
+
const variants = [
|
|
57881
|
+
candidate,
|
|
57882
|
+
coerceLooseJson(candidate),
|
|
57883
|
+
closeTruncatedJson(candidate),
|
|
57884
|
+
closeTruncatedJson(coerceLooseJson(candidate))
|
|
57885
|
+
];
|
|
57886
|
+
for (const variant of variants) {
|
|
57887
|
+
if (tryParseJson(variant) !== undefined)
|
|
57888
|
+
return variant;
|
|
57889
|
+
}
|
|
57890
|
+
}
|
|
57891
|
+
return closeTruncatedJson(coerceLooseJson(fromBrace));
|
|
57892
|
+
}
|
|
57893
|
+
function clampNormCoord(n) {
|
|
57894
|
+
if (!Number.isFinite(n))
|
|
57895
|
+
return n;
|
|
57896
|
+
return Math.min(1000, Math.max(0, n));
|
|
57897
|
+
}
|
|
57898
|
+
function normalizeVisionJson(value) {
|
|
57899
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
57900
|
+
return value;
|
|
57901
|
+
const out = { ...value };
|
|
57902
|
+
if (typeof out.x === "number")
|
|
57903
|
+
out.x = clampNormCoord(out.x);
|
|
57904
|
+
if (typeof out.y === "number")
|
|
57905
|
+
out.y = clampNormCoord(out.y);
|
|
57906
|
+
if (typeof out.ms === "number" && Number.isFinite(out.ms)) {
|
|
57907
|
+
out.ms = Math.min(1e4, Math.max(0, out.ms));
|
|
57908
|
+
}
|
|
57909
|
+
const reason = typeof out.reason === "string" ? out.reason.trim() : "";
|
|
57910
|
+
const thoughts = typeof out.thoughts === "string" ? out.thoughts.trim() : "";
|
|
57911
|
+
if (reason)
|
|
57912
|
+
out.reason = reason;
|
|
57913
|
+
if (thoughts)
|
|
57914
|
+
out.thoughts = thoughts;
|
|
57915
|
+
if (reason && !thoughts)
|
|
57916
|
+
out.thoughts = reason;
|
|
57917
|
+
if (thoughts && !reason)
|
|
57918
|
+
out.reason = thoughts;
|
|
57919
|
+
return out;
|
|
57920
|
+
}
|
|
57921
|
+
|
|
57549
57922
|
// src/domains/providers/vision-model.ts
|
|
57550
57923
|
class AgentProviderError extends Error {
|
|
57551
57924
|
constructor(message) {
|
|
@@ -57646,20 +58019,27 @@ function mapProviderError(label, error40) {
|
|
|
57646
58019
|
}
|
|
57647
58020
|
throw new AgentProviderError(`Model did not return JSON (got: ${snippet}${text3.length > 160 ? "\u2026" : ""})`);
|
|
57648
58021
|
}
|
|
58022
|
+
if (error40 instanceof exports_external.ZodError) {
|
|
58023
|
+
const first = error40.issues[0];
|
|
58024
|
+
const detail = first ? `${first.path.length > 0 ? `${first.path.join(".")}: ` : ""}${first.message}` : error40.message;
|
|
58025
|
+
throw new AgentProviderError(`${label} JSON was not a valid action: ${detail}`);
|
|
58026
|
+
}
|
|
57649
58027
|
if (error40 instanceof Error) {
|
|
57650
58028
|
throw new AgentProviderError(error40.message);
|
|
57651
58029
|
}
|
|
57652
58030
|
throw new AgentProviderError(String(error40));
|
|
57653
58031
|
}
|
|
57654
|
-
function
|
|
57655
|
-
|
|
57656
|
-
|
|
57657
|
-
|
|
57658
|
-
|
|
57659
|
-
|
|
57660
|
-
|
|
57661
|
-
|
|
57662
|
-
|
|
58032
|
+
function parseVisionSchema(schema, raw2, label) {
|
|
58033
|
+
try {
|
|
58034
|
+
return schema.parse(normalizeVisionJson(raw2));
|
|
58035
|
+
} catch (error40) {
|
|
58036
|
+
if (error40 instanceof exports_external.ZodError) {
|
|
58037
|
+
const first = error40.issues[0];
|
|
58038
|
+
const detail = first ? `${first.path.length > 0 ? `${first.path.join(".")}: ` : ""}${first.message}` : error40.message;
|
|
58039
|
+
throw new AgentProviderError(`${label} JSON was not a valid action: ${detail}`);
|
|
58040
|
+
}
|
|
58041
|
+
throw error40;
|
|
58042
|
+
}
|
|
57663
58043
|
}
|
|
57664
58044
|
async function completeWithAiSdk(input) {
|
|
57665
58045
|
const run = async (prompt) => {
|
|
@@ -57669,7 +58049,7 @@ async function completeWithAiSdk(input) {
|
|
|
57669
58049
|
schema: input.schema,
|
|
57670
58050
|
system: input.system,
|
|
57671
58051
|
maxOutputTokens: VISION_MAX_TOKENS,
|
|
57672
|
-
experimental_repairText: async ({ text: text3 }) =>
|
|
58052
|
+
experimental_repairText: async ({ text: text3 }) => salvageAgentJsonText(text3),
|
|
57673
58053
|
messages: [
|
|
57674
58054
|
{
|
|
57675
58055
|
role: "user",
|
|
@@ -57684,12 +58064,18 @@ async function completeWithAiSdk(input) {
|
|
|
57684
58064
|
}
|
|
57685
58065
|
]
|
|
57686
58066
|
});
|
|
57687
|
-
return object3;
|
|
58067
|
+
return parseVisionSchema(input.schema, object3, input.label);
|
|
57688
58068
|
} catch (error40) {
|
|
58069
|
+
if (error40 instanceof AgentProviderError)
|
|
58070
|
+
throw error40;
|
|
57689
58071
|
if (NoObjectGeneratedError.isInstance(error40) && error40.text?.trim()) {
|
|
57690
58072
|
try {
|
|
57691
|
-
|
|
57692
|
-
|
|
58073
|
+
const salvaged = salvageAgentJsonText(error40.text);
|
|
58074
|
+
return parseVisionSchema(input.schema, JSON.parse(salvaged), input.label);
|
|
58075
|
+
} catch (salvageError) {
|
|
58076
|
+
if (salvageError instanceof AgentProviderError)
|
|
58077
|
+
throw salvageError;
|
|
58078
|
+
}
|
|
57693
58079
|
}
|
|
57694
58080
|
mapProviderError(input.label, error40);
|
|
57695
58081
|
}
|
|
@@ -67601,108 +67987,37 @@ var google = createGoogle();
|
|
|
67601
67987
|
|
|
67602
67988
|
// src/domains/providers/agent-json.ts
|
|
67603
67989
|
function extractAgentJsonObject(text3, providerLabel) {
|
|
67604
|
-
const
|
|
67605
|
-
const
|
|
67606
|
-
|
|
67607
|
-
|
|
67608
|
-
const
|
|
67609
|
-
|
|
67610
|
-
throw new AgentProviderError(`${providerLabel} did not return JSON (got: ${trimmed.replace(/\s+/g, " ").slice(0, 160)})`);
|
|
67611
|
-
}
|
|
67612
|
-
const slice = candidate.slice(start, end + 1);
|
|
67990
|
+
const snippet = text3.trim().replace(/\s+/g, " ").slice(0, 160);
|
|
67991
|
+
const fail = () => {
|
|
67992
|
+
throw new AgentProviderError(`${providerLabel} did not return JSON (got: ${snippet})`);
|
|
67993
|
+
};
|
|
67994
|
+
const salvaged = salvageAgentJsonText(text3);
|
|
67995
|
+
let parsed;
|
|
67613
67996
|
try {
|
|
67614
|
-
|
|
67615
|
-
} catch
|
|
67997
|
+
parsed = JSON.parse(salvaged);
|
|
67998
|
+
} catch {
|
|
67616
67999
|
try {
|
|
67617
|
-
|
|
68000
|
+
parsed = JSON.parse(coerceLooseJson(salvaged));
|
|
67618
68001
|
} catch {
|
|
67619
|
-
|
|
68002
|
+
fail();
|
|
67620
68003
|
}
|
|
67621
68004
|
}
|
|
68005
|
+
if (parsed === undefined || parsed === null || typeof parsed !== "object") {
|
|
68006
|
+
fail();
|
|
68007
|
+
}
|
|
68008
|
+
return parsed;
|
|
67622
68009
|
}
|
|
67623
|
-
function
|
|
67624
|
-
|
|
67625
|
-
|
|
67626
|
-
|
|
67627
|
-
|
|
67628
|
-
|
|
67629
|
-
|
|
67630
|
-
|
|
67631
|
-
if (c === undefined)
|
|
67632
|
-
break;
|
|
67633
|
-
if (c === '"') {
|
|
67634
|
-
out += c;
|
|
67635
|
-
i += 1;
|
|
67636
|
-
while (i < raw2.length) {
|
|
67637
|
-
const d = charAt(raw2, i);
|
|
67638
|
-
if (d === undefined)
|
|
67639
|
-
break;
|
|
67640
|
-
out += d;
|
|
67641
|
-
i += 1;
|
|
67642
|
-
if (d === "\\") {
|
|
67643
|
-
const escaped = charAt(raw2, i);
|
|
67644
|
-
if (escaped !== undefined) {
|
|
67645
|
-
out += escaped;
|
|
67646
|
-
i += 1;
|
|
67647
|
-
}
|
|
67648
|
-
continue;
|
|
67649
|
-
}
|
|
67650
|
-
if (d === '"')
|
|
67651
|
-
break;
|
|
67652
|
-
}
|
|
67653
|
-
continue;
|
|
67654
|
-
}
|
|
67655
|
-
if (c === "'") {
|
|
67656
|
-
i += 1;
|
|
67657
|
-
let content = "";
|
|
67658
|
-
while (i < raw2.length) {
|
|
67659
|
-
const d = charAt(raw2, i);
|
|
67660
|
-
if (d === undefined)
|
|
67661
|
-
break;
|
|
67662
|
-
if (d === "\\" && i + 1 < raw2.length) {
|
|
67663
|
-
const next = charAt(raw2, i + 1);
|
|
67664
|
-
if (next === undefined)
|
|
67665
|
-
break;
|
|
67666
|
-
if (next === "n")
|
|
67667
|
-
content += `
|
|
67668
|
-
`;
|
|
67669
|
-
else if (next === "t")
|
|
67670
|
-
content += "\t";
|
|
67671
|
-
else if (next === "r")
|
|
67672
|
-
content += "\r";
|
|
67673
|
-
else
|
|
67674
|
-
content += next;
|
|
67675
|
-
i += 2;
|
|
67676
|
-
continue;
|
|
67677
|
-
}
|
|
67678
|
-
if (d === "'") {
|
|
67679
|
-
i += 1;
|
|
67680
|
-
break;
|
|
67681
|
-
}
|
|
67682
|
-
content += d;
|
|
67683
|
-
i += 1;
|
|
67684
|
-
}
|
|
67685
|
-
out += JSON.stringify(content);
|
|
67686
|
-
continue;
|
|
67687
|
-
}
|
|
67688
|
-
if (c === ",") {
|
|
67689
|
-
let j = i + 1;
|
|
67690
|
-
while (j < raw2.length) {
|
|
67691
|
-
const ws = charAt(raw2, j);
|
|
67692
|
-
if (ws === undefined || !/\s/.test(ws))
|
|
67693
|
-
break;
|
|
67694
|
-
j += 1;
|
|
67695
|
-
}
|
|
67696
|
-
const closer = charAt(raw2, j);
|
|
67697
|
-
if (closer === "}" || closer === "]") {
|
|
67698
|
-
i += 1;
|
|
67699
|
-
continue;
|
|
67700
|
-
}
|
|
68010
|
+
function parseVisionObject(schema, raw2, providerLabel) {
|
|
68011
|
+
try {
|
|
68012
|
+
return schema.parse(normalizeVisionJson(raw2));
|
|
68013
|
+
} catch (error40) {
|
|
68014
|
+
if (error40 instanceof exports_external.ZodError) {
|
|
68015
|
+
const first = error40.issues[0];
|
|
68016
|
+
const detail = first ? `${first.path.length > 0 ? `${first.path.join(".")}: ` : ""}${first.message}` : error40.message;
|
|
68017
|
+
throw new AgentProviderError(`${providerLabel} JSON was not a valid action: ${detail}`);
|
|
67701
68018
|
}
|
|
67702
|
-
|
|
67703
|
-
i += 1;
|
|
68019
|
+
throw error40;
|
|
67704
68020
|
}
|
|
67705
|
-
return out;
|
|
67706
68021
|
}
|
|
67707
68022
|
|
|
67708
68023
|
// src/domains/providers/drivers/antigravity-vision.ts
|
|
@@ -67748,13 +68063,10 @@ ${result.stderr}`.trim();
|
|
|
67748
68063
|
if (result.exitCode !== 0 && !result.stdout.trim()) {
|
|
67749
68064
|
throw new AgentProviderError(combined ? `Antigravity CLI failed: ${combined.slice(0, 400)}` : `Antigravity CLI exited ${result.exitCode}`);
|
|
67750
68065
|
}
|
|
67751
|
-
return input.schema
|
|
68066
|
+
return parseVisionObject(input.schema, extractAgentJsonObject(result.stdout || combined, "Antigravity CLI"), "Antigravity CLI");
|
|
67752
68067
|
} catch (error40) {
|
|
67753
68068
|
if (error40 instanceof AgentProviderError)
|
|
67754
68069
|
throw error40;
|
|
67755
|
-
if (error40 instanceof exports_external.ZodError) {
|
|
67756
|
-
throw new AgentProviderError(`Antigravity JSON was not a valid action: ${error40.message}`);
|
|
67757
|
-
}
|
|
67758
68070
|
if (error40 instanceof SyntaxError) {
|
|
67759
68071
|
throw new AgentProviderError(`Antigravity CLI returned invalid JSON: ${error40.message}`);
|
|
67760
68072
|
}
|
|
@@ -76312,13 +76624,10 @@ Screenshot file (open and look at it): ${shotName}`,
|
|
|
76312
76624
|
imageBase64: image.base64,
|
|
76313
76625
|
mediaType: image.mediaType
|
|
76314
76626
|
});
|
|
76315
|
-
return input.schema
|
|
76627
|
+
return parseVisionObject(input.schema, extractAgentJsonObject(stdout, "Cursor Agent CLI"), "Cursor Agent CLI");
|
|
76316
76628
|
} catch (error40) {
|
|
76317
76629
|
if (error40 instanceof AgentProviderError)
|
|
76318
76630
|
throw error40;
|
|
76319
|
-
if (error40 instanceof exports_external.ZodError) {
|
|
76320
|
-
throw new AgentProviderError(`Cursor JSON was not a valid action: ${error40.message}`);
|
|
76321
|
-
}
|
|
76322
76631
|
if (error40 instanceof SyntaxError) {
|
|
76323
76632
|
throw new AgentProviderError(`Cursor Agent CLI returned invalid JSON: ${error40.message}`);
|
|
76324
76633
|
}
|
|
@@ -97455,14 +97764,18 @@ var agentDecisionSchema = exports_external.object({
|
|
|
97455
97764
|
exports_external.literal("tap"),
|
|
97456
97765
|
exports_external.literal("type"),
|
|
97457
97766
|
exports_external.literal("wait"),
|
|
97767
|
+
exports_external.literal("alert"),
|
|
97458
97768
|
exports_external.literal("verify"),
|
|
97459
97769
|
exports_external.literal("done"),
|
|
97460
97770
|
exports_external.literal("fail")
|
|
97461
97771
|
]),
|
|
97462
|
-
x: exports_external.number().
|
|
97463
|
-
y: exports_external.number().
|
|
97772
|
+
x: exports_external.number().optional(),
|
|
97773
|
+
y: exports_external.number().optional(),
|
|
97774
|
+
label: exports_external.string().min(1).optional(),
|
|
97775
|
+
id: exports_external.string().min(1).optional(),
|
|
97464
97776
|
text: exports_external.string().optional(),
|
|
97465
|
-
ms: exports_external.number().
|
|
97777
|
+
ms: exports_external.number().optional(),
|
|
97778
|
+
alertAction: exports_external.union([exports_external.literal("accept"), exports_external.literal("dismiss")]).optional(),
|
|
97466
97779
|
reason: exports_external.string().min(1),
|
|
97467
97780
|
thoughts: exports_external.string().min(1)
|
|
97468
97781
|
});
|
|
@@ -97475,17 +97788,21 @@ Every action MUST include:
|
|
|
97475
97788
|
- "thoughts": 2\u20134 sentences describing what you see on screen and how that led to this action
|
|
97476
97789
|
|
|
97477
97790
|
Valid shapes:
|
|
97791
|
+
{"type":"tap","label":"Allow","reason":"...","thoughts":"..."}
|
|
97792
|
+
{"type":"tap","id":"permission_allow_button","reason":"...","thoughts":"..."}
|
|
97478
97793
|
{"type":"tap","x":0-1000,"y":0-1000,"reason":"...","thoughts":"..."}
|
|
97794
|
+
{"type":"alert","alertAction":"accept","reason":"...","thoughts":"..."}
|
|
97479
97795
|
{"type":"type","text":"...","reason":"...","thoughts":"..."}
|
|
97480
97796
|
{"type":"wait","ms":500-3000,"reason":"...","thoughts":"..."}
|
|
97481
97797
|
{"type":"verify","reason":"...","thoughts":"..."}
|
|
97482
97798
|
{"type":"done","reason":"...","thoughts":"..."}
|
|
97483
97799
|
{"type":"fail","reason":"...","thoughts":"..."}
|
|
97484
97800
|
|
|
97485
|
-
Coordinates use a 0\u20131000 normalized grid (0,0 top-left).
|
|
97801
|
+
Coordinates use a 0\u20131000 normalized grid (0,0 top-left). Prefer label or id over coordinates whenever the control has visible text or a resource-id.
|
|
97486
97802
|
|
|
97487
97803
|
Rules:
|
|
97488
97804
|
- Look at the attached screenshot before deciding. Base taps on what is visible now.
|
|
97805
|
+
- System permission / notification dialogs (Allow, Don't allow, While using the app): use {"type":"tap","label":"Allow"} or {"type":"alert","alertAction":"accept"}. Never guess coordinates for these buttons \u2014 coordinate taps often miss.
|
|
97489
97806
|
- If the expected result (or the goal of the instructions) is already visible, return verify or done \u2014 do not keep tapping the same control.
|
|
97490
97807
|
- Splash / loading / blank screens: return wait (do not tap imaginary tabs).
|
|
97491
97808
|
- Do not repeat the same tap target from Recent actions unless the screenshot still shows you are on the wrong screen.
|
|
@@ -97496,8 +97813,17 @@ function formatRecentActions(actions) {
|
|
|
97496
97813
|
return actions.slice(-6).map((action, i) => {
|
|
97497
97814
|
const n = actions.length - Math.min(actions.length, 6) + i + 1;
|
|
97498
97815
|
if (action.type === "tap") {
|
|
97816
|
+
if (action.label) {
|
|
97817
|
+
return `${n}. tap label "${action.label}" \u2014 ${action.reason ?? ""}`;
|
|
97818
|
+
}
|
|
97819
|
+
if (action.id) {
|
|
97820
|
+
return `${n}. tap id "${action.id}" \u2014 ${action.reason ?? ""}`;
|
|
97821
|
+
}
|
|
97499
97822
|
return `${n}. tap (${action.x ?? "?"},${action.y ?? "?"}) \u2014 ${action.reason ?? ""}`;
|
|
97500
97823
|
}
|
|
97824
|
+
if (action.type === "alert") {
|
|
97825
|
+
return `${n}. alert ${action.alertAction ?? "accept"} \u2014 ${action.reason ?? ""}`;
|
|
97826
|
+
}
|
|
97501
97827
|
if (action.type === "type") {
|
|
97502
97828
|
return `${n}. type "${action.text ?? ""}" \u2014 ${action.reason ?? ""}`;
|
|
97503
97829
|
}
|
|
@@ -97658,8 +97984,8 @@ function cleanPageSource(xml, window2) {
|
|
|
97658
97984
|
|
|
97659
97985
|
// src/domains/devices/grounding.ts
|
|
97660
97986
|
var groundResultSchema = exports_external.object({
|
|
97661
|
-
x: exports_external.number()
|
|
97662
|
-
y: exports_external.number()
|
|
97987
|
+
x: exports_external.number(),
|
|
97988
|
+
y: exports_external.number()
|
|
97663
97989
|
});
|
|
97664
97990
|
var GROUND_SYSTEM = `You locate UI elements on a mobile screen.
|
|
97665
97991
|
Respond with ONLY JSON: {"x":0-1000,"y":0-1000} for the center of the best match.
|
|
@@ -97730,7 +98056,7 @@ class ActionNotFoundError extends Error {
|
|
|
97730
98056
|
async function performAction(session, body) {
|
|
97731
98057
|
let x = body.x;
|
|
97732
98058
|
let y = body.y;
|
|
97733
|
-
if ((body.id || body.label) && (body.kind === "tap" || body.kind === "input")
|
|
98059
|
+
if ((body.id || body.label) && (body.kind === "tap" || body.kind === "input")) {
|
|
97734
98060
|
const screen = await getScreen(session, { full: false });
|
|
97735
98061
|
const elements = screen.elements ?? [];
|
|
97736
98062
|
const match2 = body.id ? findElementById(elements, body.id) : findElementByLabel(elements, body.label ?? "");
|
|
@@ -97930,6 +98256,25 @@ async function executeScriptCase(deps) {
|
|
|
97930
98256
|
latencyMs,
|
|
97931
98257
|
detail: action.reason ?? `${assertion}: ${action.text}`
|
|
97932
98258
|
});
|
|
98259
|
+
} else if (action.type === "alert") {
|
|
98260
|
+
await perform(deps.session, {
|
|
98261
|
+
kind: "alert",
|
|
98262
|
+
alertAction: action.alertAction === "dismiss" ? "dismiss" : "accept"
|
|
98263
|
+
});
|
|
98264
|
+
await clock.sleep(settleMs);
|
|
98265
|
+
await deps.appendStep({
|
|
98266
|
+
idx: stepIdx,
|
|
98267
|
+
action: {
|
|
98268
|
+
type: "alert",
|
|
98269
|
+
alertAction: action.alertAction === "dismiss" ? "dismiss" : "accept",
|
|
98270
|
+
reason: action.reason ?? "Replayed saved script alert",
|
|
98271
|
+
thoughts: "Replaying the saved script without calling the AI agent."
|
|
98272
|
+
},
|
|
98273
|
+
screenshotUri: shot.path,
|
|
98274
|
+
ok: true,
|
|
98275
|
+
latencyMs,
|
|
98276
|
+
detail: action.reason ?? action.alertAction ?? "accept"
|
|
98277
|
+
});
|
|
97933
98278
|
} else {
|
|
97934
98279
|
const waitMs = Math.min(3000, Math.max(500, action.ms));
|
|
97935
98280
|
await clock.sleep(waitMs);
|
|
@@ -98054,9 +98399,30 @@ async function executeAgentCase(deps) {
|
|
|
98054
98399
|
recentActions.push(decision);
|
|
98055
98400
|
recordedDecisions.push(decision);
|
|
98056
98401
|
if (decision.type === "tap") {
|
|
98057
|
-
const
|
|
98058
|
-
|
|
98059
|
-
|
|
98402
|
+
const tapBody = { kind: "tap" };
|
|
98403
|
+
if (decision.label)
|
|
98404
|
+
tapBody.label = decision.label;
|
|
98405
|
+
if (decision.id)
|
|
98406
|
+
tapBody.id = decision.id;
|
|
98407
|
+
if (!tapBody.label && !tapBody.id) {
|
|
98408
|
+
tapBody.x = decision.x ?? 500;
|
|
98409
|
+
tapBody.y = decision.y ?? 500;
|
|
98410
|
+
}
|
|
98411
|
+
await perform(deps.session, tapBody);
|
|
98412
|
+
await clock.sleep(settleMs);
|
|
98413
|
+
await deps.appendStep({
|
|
98414
|
+
idx: stepIdx,
|
|
98415
|
+
action: decision,
|
|
98416
|
+
screenshotUri: shot.path,
|
|
98417
|
+
ok: true,
|
|
98418
|
+
latencyMs,
|
|
98419
|
+
detail: decision.reason ?? null
|
|
98420
|
+
});
|
|
98421
|
+
} else if (decision.type === "alert") {
|
|
98422
|
+
await perform(deps.session, {
|
|
98423
|
+
kind: "alert",
|
|
98424
|
+
alertAction: decision.alertAction === "dismiss" ? "dismiss" : "accept"
|
|
98425
|
+
});
|
|
98060
98426
|
await clock.sleep(settleMs);
|
|
98061
98427
|
await deps.appendStep({
|
|
98062
98428
|
idx: stepIdx,
|
package/package.json
CHANGED