@swmansion/argent 0.22.2-next.0 → 0.22.2-next.10
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/cli-cmds.mjs +64 -21
- package/dist/installer.mjs +197 -7
- package/dist/mcp-server.mjs +92 -23
- package/dist/tool-server.cjs +1017 -236
- package/package.json +1 -1
- package/rules/argent.md +7 -7
- package/skills/argent-create-flow/references/flow-yaml.md +30 -3
- package/skills/argent-create-flow/references/live-authoring.md +14 -12
- package/skills/argent-create-flow/references/reliability-and-recovery.md +2 -2
- package/skills/argent-device-interact/SKILL.md +4 -2
- package/skills/argent-qa-flows/SKILL.md +1 -1
- package/bin/linux/resources/android/LICENSE.txt +0 -186
- package/bin/linux/resources/android/README.md +0 -11
- package/bin/linux/resources/android/arm64-v8a/libscreen-sharing-agent.so +0 -0
- package/bin/linux/resources/android/armeabi-v7a/libscreen-sharing-agent.so +0 -0
- package/bin/linux/resources/android/screen-sharing-agent.jar +0 -0
- package/bin/linux/resources/android/x86_64/libscreen-sharing-agent.so +0 -0
- package/bin/linux-arm64/resources/android/LICENSE.txt +0 -186
- package/bin/linux-arm64/resources/android/README.md +0 -11
- package/bin/linux-arm64/resources/android/arm64-v8a/libscreen-sharing-agent.so +0 -0
- package/bin/linux-arm64/resources/android/armeabi-v7a/libscreen-sharing-agent.so +0 -0
- package/bin/linux-arm64/resources/android/screen-sharing-agent.jar +0 -0
- package/bin/linux-arm64/resources/android/x86_64/libscreen-sharing-agent.so +0 -0
- package/bin/win32/resources/android/LICENSE.txt +0 -186
- package/bin/win32/resources/android/README.md +0 -11
- package/bin/win32/resources/android/arm64-v8a/libscreen-sharing-agent.so +0 -0
- package/bin/win32/resources/android/armeabi-v7a/libscreen-sharing-agent.so +0 -0
- package/bin/win32/resources/android/screen-sharing-agent.jar +0 -0
- package/bin/win32/resources/android/x86_64/libscreen-sharing-agent.so +0 -0
- /package/bin/{darwin/resources → resources}/android/LICENSE.txt +0 -0
- /package/bin/{darwin/resources → resources}/android/README.md +0 -0
- /package/bin/{darwin/resources → resources}/android/arm64-v8a/libscreen-sharing-agent.so +0 -0
- /package/bin/{darwin/resources → resources}/android/armeabi-v7a/libscreen-sharing-agent.so +0 -0
- /package/bin/{darwin/resources → resources}/android/screen-sharing-agent.jar +0 -0
- /package/bin/{darwin/resources → resources}/android/x86_64/libscreen-sharing-agent.so +0 -0
package/dist/tool-server.cjs
CHANGED
|
@@ -628,6 +628,9 @@ var init_failure_codes = __esm({
|
|
|
628
628
|
SIMULATOR_SERVER_READY_TIMEOUT: "SIMULATOR_SERVER_READY_TIMEOUT",
|
|
629
629
|
SIMULATOR_SERVER_PROCESS_ERROR: "SIMULATOR_SERVER_PROCESS_ERROR",
|
|
630
630
|
SIMULATOR_SERVER_TERMINATED: "SIMULATOR_SERVER_TERMINATED",
|
|
631
|
+
SIMULATOR_COMMAND_REJECTED: "SIMULATOR_COMMAND_REJECTED",
|
|
632
|
+
SIMULATOR_COMMAND_ACK_TIMEOUT: "SIMULATOR_COMMAND_ACK_TIMEOUT",
|
|
633
|
+
SIMULATOR_COMMAND_TRANSPORT_FAILED: "SIMULATOR_COMMAND_TRANSPORT_FAILED",
|
|
631
634
|
AX_QUERY_TIMEOUT: "AX_QUERY_TIMEOUT",
|
|
632
635
|
AX_DAEMON_READY_TIMEOUT: "AX_DAEMON_READY_TIMEOUT",
|
|
633
636
|
AX_DAEMON_EXITED_BEFORE_READY: "AX_DAEMON_EXITED_BEFORE_READY",
|
|
@@ -76339,15 +76342,28 @@ async function emulatorSupportsFlag(flag, options = {}) {
|
|
|
76339
76342
|
const cached3 = emulatorFlagSupportCache.get(cacheKey3);
|
|
76340
76343
|
if (cached3 !== void 0) return cached3;
|
|
76341
76344
|
let output;
|
|
76345
|
+
let failure = null;
|
|
76342
76346
|
try {
|
|
76343
76347
|
const { stdout, stderr } = await execFileAsync9(emulatorPath, ["-help"], {
|
|
76344
76348
|
timeout: options.timeoutMs ?? 1e4,
|
|
76349
|
+
// Same hazard runAdb guards against: a SIGTERM-immune child (emulator
|
|
76350
|
+
// wedged in Qt/graphics init) leaves execFile unsettled forever, and
|
|
76351
|
+
// this await has no deadline of its own.
|
|
76352
|
+
killSignal: "SIGKILL",
|
|
76345
76353
|
maxBuffer: 8 * 1024 * 1024
|
|
76346
76354
|
});
|
|
76347
76355
|
output = stdout + stderr;
|
|
76348
76356
|
} catch (err) {
|
|
76349
76357
|
const e = err;
|
|
76350
|
-
output = (e.stdout ?? "") + (e.stderr ?? "");
|
|
76358
|
+
output = e.killed ? "" : (e.stdout ?? "") + (e.stderr ?? "");
|
|
76359
|
+
failure = err instanceof Error ? err.message : String(err);
|
|
76360
|
+
}
|
|
76361
|
+
if (output === "") {
|
|
76362
|
+
process.stderr.write(
|
|
76363
|
+
`[argent] \`${emulatorPath} -help\` produced no complete listing ` + (failure ? `(${failure})` : "but exited successfully") + `; assuming "${flag}" unsupported for this boot and retrying on the next one
|
|
76364
|
+
`
|
|
76365
|
+
);
|
|
76366
|
+
return false;
|
|
76351
76367
|
}
|
|
76352
76368
|
const supported = output.includes(flag);
|
|
76353
76369
|
emulatorFlagSupportCache.set(cacheKey3, supported);
|
|
@@ -76557,7 +76573,10 @@ async function listAvds() {
|
|
|
76557
76573
|
const emulatorPath = await resolveAndroidBinary("emulator");
|
|
76558
76574
|
if (!emulatorPath) return [];
|
|
76559
76575
|
try {
|
|
76560
|
-
const { stdout } = await execFileAsync9(emulatorPath, ["-list-avds"], {
|
|
76576
|
+
const { stdout } = await execFileAsync9(emulatorPath, ["-list-avds"], {
|
|
76577
|
+
timeout: 5e3,
|
|
76578
|
+
killSignal: "SIGKILL"
|
|
76579
|
+
});
|
|
76561
76580
|
return stdout.split("\n").map((l) => l.trim()).filter((l) => l && AVD_NAME_PATTERN.test(l)).map((name) => ({ name }));
|
|
76562
76581
|
} catch {
|
|
76563
76582
|
return [];
|
|
@@ -76575,6 +76594,7 @@ async function checkSnapshotLoadable(avdName, snapshotName = "default_boot", opt
|
|
|
76575
76594
|
];
|
|
76576
76595
|
const { stdout } = await execFileAsync9(emulatorPath, args, {
|
|
76577
76596
|
timeout: options.timeoutMs ?? 1e4,
|
|
76597
|
+
killSignal: "SIGKILL",
|
|
76578
76598
|
maxBuffer: 4 * 1024 * 1024
|
|
76579
76599
|
});
|
|
76580
76600
|
const tail = stdout.split("\n").slice(-6).join("\n");
|
|
@@ -89978,6 +89998,10 @@ var FLAG_REGISTRY = [
|
|
|
89978
89998
|
name: "disable-auto-screenshot",
|
|
89979
89999
|
description: "Disable the automatic screenshot captured after interaction tools."
|
|
89980
90000
|
},
|
|
90001
|
+
{
|
|
90002
|
+
name: "disable-auto-describe",
|
|
90003
|
+
description: "Disable the accessibility element tree appended after interaction tools."
|
|
90004
|
+
},
|
|
89981
90005
|
{
|
|
89982
90006
|
name: "argent-lens",
|
|
89983
90007
|
description: "Argent Lens \u2014 the propose_variant / await_user_selection tools and the Electron preview window for staging UI design variants and letting a human pick among them. Off by default while the feature is in development."
|
|
@@ -90278,6 +90302,14 @@ var CONFIG_SCHEMA = [
|
|
|
90278
90302
|
// drained/reset, not just the file rewritten.
|
|
90279
90303
|
manageCommand: "argent telemetry"
|
|
90280
90304
|
},
|
|
90305
|
+
{
|
|
90306
|
+
key: "allowlist.enabled",
|
|
90307
|
+
description: "Whether `argent update` re-applies editor auto-approve allowlist rules. Unset (the default) keeps the current behavior: update refreshes the rules for editors that already have argent configured. Set to `false` to keep update from touching editor allowlists. `false` in either scope wins, so a committed project opt-out holds for every teammate.",
|
|
90308
|
+
scopes: ["project", "global"],
|
|
90309
|
+
parse: asBoolean,
|
|
90310
|
+
merge: "prioritize-restrictive",
|
|
90311
|
+
example: "false"
|
|
90312
|
+
},
|
|
90281
90313
|
{
|
|
90282
90314
|
key: "lens.agent",
|
|
90283
90315
|
description: "Coding-agent id remembered by `argent lens` to skip the picker.",
|
|
@@ -93869,7 +93901,7 @@ var _CI_VENDOR_COUNT_FOR_TEST = vendors_default.length;
|
|
|
93869
93901
|
var SESSION_ID = (0, import_node_crypto3.randomUUID)();
|
|
93870
93902
|
function readCliVersion() {
|
|
93871
93903
|
if (true) {
|
|
93872
|
-
return "0.22.2-next.
|
|
93904
|
+
return "0.22.2-next.10";
|
|
93873
93905
|
}
|
|
93874
93906
|
return "0.0.0";
|
|
93875
93907
|
}
|
|
@@ -94237,7 +94269,10 @@ function simulatorServerBinaryPath() {
|
|
|
94237
94269
|
}
|
|
94238
94270
|
return p;
|
|
94239
94271
|
}
|
|
94240
|
-
function
|
|
94272
|
+
function simulatorServerRunDir() {
|
|
94273
|
+
if (fs7.existsSync(path8.join(BIN_DIR, "resources", "android"))) {
|
|
94274
|
+
return BIN_DIR;
|
|
94275
|
+
}
|
|
94241
94276
|
return platformBinDir();
|
|
94242
94277
|
}
|
|
94243
94278
|
function requireBinIn(dir, name) {
|
|
@@ -95381,13 +95416,56 @@ var import_express = __toESM(require_express2());
|
|
|
95381
95416
|
|
|
95382
95417
|
// ../tool-server/src/blueprints/simulator-server.ts
|
|
95383
95418
|
var import_node_child_process10 = require("node:child_process");
|
|
95384
|
-
var
|
|
95419
|
+
var readline = __toESM(require("node:readline"));
|
|
95385
95420
|
init_src();
|
|
95386
95421
|
|
|
95387
95422
|
// ../tool-server/src/blueprints/ax-service.ts
|
|
95388
95423
|
var net = __toESM(require("node:net"));
|
|
95389
95424
|
var fs13 = __toESM(require("node:fs"));
|
|
95390
|
-
|
|
95425
|
+
|
|
95426
|
+
// ../tool-server/src/utils/ndjson-socket.ts
|
|
95427
|
+
var PREVIEW_CHARS = 80;
|
|
95428
|
+
function preview(raw) {
|
|
95429
|
+
const printable = raw.replace(/[\x00-\x1f\x7f\u2028\u2029]/g, "\xB7");
|
|
95430
|
+
return printable.length > PREVIEW_CHARS ? `${printable.slice(0, PREVIEW_CHARS)}\u2026` : printable;
|
|
95431
|
+
}
|
|
95432
|
+
function reportDroppedFrameToStderr(tag3) {
|
|
95433
|
+
return ({ bytes, preview: preview2 }) => {
|
|
95434
|
+
process.stderr.write(`[${tag3}] dropped unparseable frame (${bytes} bytes): ${preview2}
|
|
95435
|
+
`);
|
|
95436
|
+
};
|
|
95437
|
+
}
|
|
95438
|
+
function attachNdjsonReader(socket, handlers) {
|
|
95439
|
+
socket.setEncoding("utf8");
|
|
95440
|
+
let buf = "";
|
|
95441
|
+
const deliver = (raw) => {
|
|
95442
|
+
if (raw.length === 0 || raw === "\r") return;
|
|
95443
|
+
let msg;
|
|
95444
|
+
try {
|
|
95445
|
+
msg = JSON.parse(raw);
|
|
95446
|
+
} catch {
|
|
95447
|
+
handlers.onDropped({ bytes: Buffer.byteLength(raw, "utf8"), preview: preview(raw) });
|
|
95448
|
+
return;
|
|
95449
|
+
}
|
|
95450
|
+
handlers.onMessage(msg);
|
|
95451
|
+
};
|
|
95452
|
+
socket.on("data", (chunk) => {
|
|
95453
|
+
buf += typeof chunk === "string" ? chunk : chunk.toString("utf8");
|
|
95454
|
+
let nl;
|
|
95455
|
+
while ((nl = buf.indexOf("\n")) !== -1) {
|
|
95456
|
+
const raw = buf.slice(0, nl);
|
|
95457
|
+
buf = buf.slice(nl + 1);
|
|
95458
|
+
deliver(raw);
|
|
95459
|
+
}
|
|
95460
|
+
});
|
|
95461
|
+
socket.on("end", () => {
|
|
95462
|
+
const rest = buf;
|
|
95463
|
+
buf = "";
|
|
95464
|
+
if (rest.length > 0) deliver(rest);
|
|
95465
|
+
});
|
|
95466
|
+
}
|
|
95467
|
+
|
|
95468
|
+
// ../tool-server/src/blueprints/ax-service.ts
|
|
95391
95469
|
init_src();
|
|
95392
95470
|
|
|
95393
95471
|
// ../tool-server/src/utils/ios-host.ts
|
|
@@ -95686,9 +95764,8 @@ async function simctlShutdown(udid) {
|
|
|
95686
95764
|
await run(["simctl", "shutdown", stripRemotePrefix(udid)]);
|
|
95687
95765
|
}
|
|
95688
95766
|
async function simctlBootstatus(udid, opts) {
|
|
95689
|
-
const args = ["simctl", "bootstatus"];
|
|
95767
|
+
const args = ["simctl", "bootstatus", stripRemotePrefix(udid)];
|
|
95690
95768
|
if (opts?.boot) args.push("-b");
|
|
95691
|
-
args.push(stripRemotePrefix(udid));
|
|
95692
95769
|
await run(args, { timeoutMs: 5 * 6e4 });
|
|
95693
95770
|
}
|
|
95694
95771
|
async function simctlLaunch(udid, bundleId, args = []) {
|
|
@@ -96223,37 +96300,33 @@ var axServiceBlueprint = {
|
|
|
96223
96300
|
daemonSocket.destroy();
|
|
96224
96301
|
}
|
|
96225
96302
|
daemonSocket = socket;
|
|
96226
|
-
|
|
96227
|
-
|
|
96228
|
-
|
|
96229
|
-
|
|
96230
|
-
msg
|
|
96231
|
-
|
|
96232
|
-
return;
|
|
96233
|
-
|
|
96234
|
-
|
|
96235
|
-
|
|
96236
|
-
|
|
96237
|
-
|
|
96238
|
-
|
|
96239
|
-
|
|
96240
|
-
|
|
96241
|
-
|
|
96242
|
-
|
|
96243
|
-
|
|
96244
|
-
|
|
96245
|
-
|
|
96246
|
-
|
|
96247
|
-
|
|
96248
|
-
|
|
96249
|
-
|
|
96250
|
-
);
|
|
96251
|
-
} else {
|
|
96252
|
-
pending.resolve(msg.result);
|
|
96303
|
+
attachNdjsonReader(socket, {
|
|
96304
|
+
onDropped: reportDroppedFrameToStderr(`ax-service ${udid.slice(0, 8)}`),
|
|
96305
|
+
onMessage: (parsed) => {
|
|
96306
|
+
const msg = parsed;
|
|
96307
|
+
if (typeof msg.id !== "number") return;
|
|
96308
|
+
const pending = pendingRpc.get(msg.id);
|
|
96309
|
+
if (!pending) return;
|
|
96310
|
+
pendingRpc.delete(msg.id);
|
|
96311
|
+
clearTimeout(pending.timer);
|
|
96312
|
+
if (msg.error !== void 0 && msg.error !== null) {
|
|
96313
|
+
pending.reject(
|
|
96314
|
+
new FailureError(
|
|
96315
|
+
typeof msg.error === "string" ? msg.error : JSON.stringify(msg.error),
|
|
96316
|
+
{
|
|
96317
|
+
error_code: FAILURE_CODES.AX_QUERY_FAILED,
|
|
96318
|
+
failure_stage: "ax_service_query_rpc",
|
|
96319
|
+
failure_area: "tool_server",
|
|
96320
|
+
error_kind: "unknown"
|
|
96321
|
+
}
|
|
96322
|
+
)
|
|
96323
|
+
);
|
|
96324
|
+
} else {
|
|
96325
|
+
pending.resolve(msg.result);
|
|
96326
|
+
}
|
|
96253
96327
|
}
|
|
96254
96328
|
});
|
|
96255
96329
|
socket.on("close", () => {
|
|
96256
|
-
rl.close();
|
|
96257
96330
|
if (daemonSocket === socket) {
|
|
96258
96331
|
daemonSocket = null;
|
|
96259
96332
|
if (!disposed) {
|
|
@@ -106263,33 +106336,127 @@ var DEFAULT_SCREENSHOT_SCALE = 0.25;
|
|
|
106263
106336
|
var NO_IMAGE_ERROR = /no image to export/i;
|
|
106264
106337
|
var FIRST_FRAME_WAIT_MS = 6e3;
|
|
106265
106338
|
var FIRST_FRAME_POLL_MS = 250;
|
|
106339
|
+
var COMMAND_ACK_TIMEOUT_MS = 5e3;
|
|
106266
106340
|
var connections = /* @__PURE__ */ new Map();
|
|
106267
106341
|
var cmdId = 0;
|
|
106268
|
-
function
|
|
106342
|
+
function failAllPending(conn, makeError) {
|
|
106343
|
+
const entries = [...conn.pending.values()];
|
|
106344
|
+
conn.pending.clear();
|
|
106345
|
+
for (const entry of entries) entry.settle(makeError(entry.cmd));
|
|
106346
|
+
}
|
|
106347
|
+
function transportError(cmd, apiUrl, detail) {
|
|
106348
|
+
return new FailureError(
|
|
106349
|
+
`simulator-server did not accept the '${cmd}' command: ${detail}. The command was NOT delivered to the device. Check that the simulator is still booted and the simulator-server for ${apiUrl} is running.`,
|
|
106350
|
+
{
|
|
106351
|
+
error_code: FAILURE_CODES.SIMULATOR_COMMAND_TRANSPORT_FAILED,
|
|
106352
|
+
failure_stage: "simulator_command_transport",
|
|
106353
|
+
failure_area: "tool_server",
|
|
106354
|
+
error_kind: "network",
|
|
106355
|
+
network_failure: "connection_reset",
|
|
106356
|
+
failure_command: "simulator_server"
|
|
106357
|
+
}
|
|
106358
|
+
);
|
|
106359
|
+
}
|
|
106360
|
+
function getOrCreateConnection(api) {
|
|
106269
106361
|
const key2 = api.apiUrl;
|
|
106270
106362
|
const existing = connections.get(key2);
|
|
106271
|
-
if (existing && (existing.readyState === wrapper_default.OPEN || existing.readyState === wrapper_default.CONNECTING)) {
|
|
106363
|
+
if (existing && (existing.ws.readyState === wrapper_default.OPEN || existing.ws.readyState === wrapper_default.CONNECTING)) {
|
|
106272
106364
|
return existing;
|
|
106273
106365
|
}
|
|
106274
106366
|
const { host } = new URL(api.apiUrl);
|
|
106275
106367
|
const ws = new wrapper_default(`ws://${host}/ws`);
|
|
106276
|
-
ws
|
|
106277
|
-
ws.on("
|
|
106278
|
-
|
|
106279
|
-
|
|
106368
|
+
const conn = { ws, pending: /* @__PURE__ */ new Map() };
|
|
106369
|
+
ws.on("message", (data) => {
|
|
106370
|
+
const text = Buffer.isBuffer(data) ? data.toString() : Array.isArray(data) ? Buffer.concat(data).toString() : Buffer.from(data).toString();
|
|
106371
|
+
let ack;
|
|
106372
|
+
try {
|
|
106373
|
+
ack = JSON.parse(text);
|
|
106374
|
+
} catch {
|
|
106375
|
+
return;
|
|
106376
|
+
}
|
|
106377
|
+
if (ack.status !== "ok" && ack.status !== "error") return;
|
|
106378
|
+
if (ack.id != null && !conn.pending.has(ack.id)) return;
|
|
106379
|
+
const id = ack.id ?? conn.pending.keys().next().value;
|
|
106380
|
+
if (id == null) return;
|
|
106381
|
+
const entry = conn.pending.get(id);
|
|
106382
|
+
if (entry == null) return;
|
|
106383
|
+
conn.pending.delete(id);
|
|
106384
|
+
if (ack.status === "ok") {
|
|
106385
|
+
entry.settle();
|
|
106386
|
+
return;
|
|
106387
|
+
}
|
|
106388
|
+
entry.settle(
|
|
106389
|
+
new FailureError(
|
|
106390
|
+
`simulator-server rejected the '${entry.cmd}' command: ${ack.message ?? "unknown error"}. The command was NOT delivered to the device.`,
|
|
106391
|
+
{
|
|
106392
|
+
error_code: FAILURE_CODES.SIMULATOR_COMMAND_REJECTED,
|
|
106393
|
+
failure_stage: "simulator_command_rejected",
|
|
106394
|
+
failure_area: "tool_server",
|
|
106395
|
+
error_kind: "unknown",
|
|
106396
|
+
failure_command: "simulator_server"
|
|
106397
|
+
}
|
|
106398
|
+
)
|
|
106399
|
+
);
|
|
106400
|
+
});
|
|
106401
|
+
ws.on("error", (err) => {
|
|
106402
|
+
connections.delete(key2);
|
|
106403
|
+
failAllPending(conn, (cmd) => transportError(cmd, key2, err.message));
|
|
106404
|
+
});
|
|
106405
|
+
ws.on("close", () => {
|
|
106406
|
+
connections.delete(key2);
|
|
106407
|
+
failAllPending(conn, (cmd) => transportError(cmd, key2, "the connection closed"));
|
|
106408
|
+
});
|
|
106409
|
+
connections.set(key2, conn);
|
|
106410
|
+
return conn;
|
|
106280
106411
|
}
|
|
106281
106412
|
function sendCommand(api, cmd) {
|
|
106282
106413
|
if (api.transport) {
|
|
106283
106414
|
routeViaTransport(api.transport, cmd);
|
|
106284
|
-
return;
|
|
106285
|
-
}
|
|
106286
|
-
const ws = getOrCreateWs(api);
|
|
106287
|
-
const payload = JSON.stringify({ id: String(++cmdId), ...cmd });
|
|
106288
|
-
if (ws.readyState === wrapper_default.OPEN) {
|
|
106289
|
-
ws.send(payload);
|
|
106290
|
-
} else {
|
|
106291
|
-
ws.once("open", () => ws.send(payload));
|
|
106415
|
+
return Promise.resolve();
|
|
106292
106416
|
}
|
|
106417
|
+
const conn = getOrCreateConnection(api);
|
|
106418
|
+
const id = String(++cmdId);
|
|
106419
|
+
const cmdName = typeof cmd.cmd === "string" ? cmd.cmd : "unknown";
|
|
106420
|
+
const payload = JSON.stringify({ id, ...cmd });
|
|
106421
|
+
return new Promise((resolve12, reject) => {
|
|
106422
|
+
let done = false;
|
|
106423
|
+
const settle = (err) => {
|
|
106424
|
+
if (done) return;
|
|
106425
|
+
done = true;
|
|
106426
|
+
clearTimeout(timer);
|
|
106427
|
+
conn.pending.delete(id);
|
|
106428
|
+
if (err) reject(err);
|
|
106429
|
+
else resolve12();
|
|
106430
|
+
};
|
|
106431
|
+
const timer = setTimeout(
|
|
106432
|
+
() => settleAndDropConnection(
|
|
106433
|
+
new FailureError(
|
|
106434
|
+
`simulator-server did not acknowledge the '${cmdName}' command within ${COMMAND_ACK_TIMEOUT_MS}ms. The command may not have reached the device \u2014 the simulator may be wedged or the simulator-server unresponsive.`,
|
|
106435
|
+
{
|
|
106436
|
+
error_code: FAILURE_CODES.SIMULATOR_COMMAND_ACK_TIMEOUT,
|
|
106437
|
+
failure_stage: "simulator_command_ack",
|
|
106438
|
+
failure_area: "tool_server",
|
|
106439
|
+
error_kind: "timeout",
|
|
106440
|
+
network_failure: "timeout",
|
|
106441
|
+
failure_command: "simulator_server"
|
|
106442
|
+
}
|
|
106443
|
+
)
|
|
106444
|
+
),
|
|
106445
|
+
COMMAND_ACK_TIMEOUT_MS
|
|
106446
|
+
);
|
|
106447
|
+
timer.unref?.();
|
|
106448
|
+
const settleAndDropConnection = (err) => {
|
|
106449
|
+
settle(err);
|
|
106450
|
+
connections.delete(api.apiUrl);
|
|
106451
|
+
conn.ws.close();
|
|
106452
|
+
};
|
|
106453
|
+
conn.pending.set(id, { settle, cmd: cmdName });
|
|
106454
|
+
const write = () => conn.ws.send(payload, (err) => {
|
|
106455
|
+
if (err) settle(transportError(cmdName, api.apiUrl, err.message));
|
|
106456
|
+
});
|
|
106457
|
+
if (conn.ws.readyState === wrapper_default.OPEN) write();
|
|
106458
|
+
else conn.ws.once("open", write);
|
|
106459
|
+
});
|
|
106293
106460
|
}
|
|
106294
106461
|
function setPointerVisible(api, show, signal) {
|
|
106295
106462
|
return pointerPost(api, { show }, signal);
|
|
@@ -106517,8 +106684,8 @@ function simulatorServerRef(device) {
|
|
|
106517
106684
|
}
|
|
106518
106685
|
var getPaths = () => {
|
|
106519
106686
|
const BINARY_PATH = simulatorServerBinaryPath();
|
|
106520
|
-
const
|
|
106521
|
-
return { BINARY_PATH,
|
|
106687
|
+
const RUN_DIR = simulatorServerRunDir();
|
|
106688
|
+
return { BINARY_PATH, RUN_DIR };
|
|
106522
106689
|
};
|
|
106523
106690
|
var READY_TIMEOUT_MS = 3e4;
|
|
106524
106691
|
async function buildRemoteInstance(device) {
|
|
@@ -106562,12 +106729,12 @@ async function spawnSimulatorServerProcess(udid, subcommand) {
|
|
|
106562
106729
|
throw new Error(`Refusing to start simulator-server for unsafe device id "${udid}".`);
|
|
106563
106730
|
}
|
|
106564
106731
|
const deviceSet = subcommand === "ios" ? await deviceSetForUdid(udid) : null;
|
|
106565
|
-
const { BINARY_PATH,
|
|
106732
|
+
const { BINARY_PATH, RUN_DIR } = getPaths();
|
|
106566
106733
|
return new Promise((resolve12, reject) => {
|
|
106567
106734
|
const args = [subcommand, "--id", udid];
|
|
106568
106735
|
if (deviceSet) args.push("--device-set", deviceSet);
|
|
106569
106736
|
const proc = (0, import_node_child_process10.spawn)(BINARY_PATH, args, {
|
|
106570
|
-
cwd:
|
|
106737
|
+
cwd: RUN_DIR,
|
|
106571
106738
|
stdio: ["pipe", "pipe", "pipe"]
|
|
106572
106739
|
});
|
|
106573
106740
|
let apiUrl = null;
|
|
@@ -106575,7 +106742,7 @@ async function spawnSimulatorServerProcess(udid, subcommand) {
|
|
|
106575
106742
|
let settled = false;
|
|
106576
106743
|
const STREAM_GRACE_MS = 500;
|
|
106577
106744
|
let apiReadyTimer = null;
|
|
106578
|
-
const rl =
|
|
106745
|
+
const rl = readline.createInterface({ input: proc.stdout });
|
|
106579
106746
|
const settle = (fn, cleanup) => {
|
|
106580
106747
|
if (settled) return;
|
|
106581
106748
|
settled = true;
|
|
@@ -109223,7 +109390,6 @@ var variantProposalStore = new VariantProposalStore();
|
|
|
109223
109390
|
// ../tool-server/src/blueprints/native-devtools.ts
|
|
109224
109391
|
var net2 = __toESM(require("node:net"));
|
|
109225
109392
|
var fs18 = __toESM(require("node:fs"));
|
|
109226
|
-
var readline3 = __toESM(require("node:readline"));
|
|
109227
109393
|
init_src();
|
|
109228
109394
|
var NATIVE_DEVTOOLS_NAMESPACE = "NativeDevtools";
|
|
109229
109395
|
function isInjectableBundleId(bundleId) {
|
|
@@ -109490,67 +109656,63 @@ var nativeDevtoolsBlueprint = {
|
|
|
109490
109656
|
}
|
|
109491
109657
|
const server = net2.createServer((socket) => {
|
|
109492
109658
|
let bundleId = null;
|
|
109493
|
-
|
|
109494
|
-
|
|
109495
|
-
|
|
109496
|
-
|
|
109497
|
-
|
|
109498
|
-
|
|
109499
|
-
|
|
109500
|
-
|
|
109501
|
-
|
|
109502
|
-
|
|
109503
|
-
|
|
109504
|
-
|
|
109505
|
-
|
|
109506
|
-
|
|
109507
|
-
|
|
109508
|
-
|
|
109509
|
-
|
|
109510
|
-
|
|
109511
|
-
|
|
109512
|
-
|
|
109513
|
-
|
|
109514
|
-
}) + "\n"
|
|
109515
|
-
);
|
|
109659
|
+
attachNdjsonReader(socket, {
|
|
109660
|
+
onDropped: reportDroppedFrameToStderr(`native-devtools ${udid.slice(0, 8)}`),
|
|
109661
|
+
onMessage: (parsed) => {
|
|
109662
|
+
const msg = parsed;
|
|
109663
|
+
if (bundleId === null) {
|
|
109664
|
+
if (msg.type !== "Control") return;
|
|
109665
|
+
bundleId = msg.payload.bundleId;
|
|
109666
|
+
const existing = connections2.get(bundleId);
|
|
109667
|
+
if (existing) {
|
|
109668
|
+
existing.socket.destroy();
|
|
109669
|
+
}
|
|
109670
|
+
connections2.set(bundleId, { socket, networkLog: [] });
|
|
109671
|
+
if (activatedBundleIds.has(bundleId)) {
|
|
109672
|
+
socket.write(
|
|
109673
|
+
JSON.stringify({
|
|
109674
|
+
type: "Control",
|
|
109675
|
+
payload: { command: "activateNetworkInspection" }
|
|
109676
|
+
}) + "\n"
|
|
109677
|
+
);
|
|
109678
|
+
}
|
|
109679
|
+
return;
|
|
109516
109680
|
}
|
|
109517
|
-
|
|
109518
|
-
|
|
109519
|
-
|
|
109520
|
-
|
|
109521
|
-
|
|
109522
|
-
|
|
109523
|
-
|
|
109524
|
-
|
|
109525
|
-
conn.networkLog.
|
|
109681
|
+
if (msg.type === "CDP") {
|
|
109682
|
+
const p = msg.payload;
|
|
109683
|
+
if (p.method && p.id === void 0) {
|
|
109684
|
+
const conn = connections2.get(bundleId);
|
|
109685
|
+
if (conn) {
|
|
109686
|
+
if (conn.networkLog.length >= MAX_LOG_ENTRIES) {
|
|
109687
|
+
conn.networkLog.shift();
|
|
109688
|
+
}
|
|
109689
|
+
conn.networkLog.push({
|
|
109690
|
+
method: p.method,
|
|
109691
|
+
params: p.params,
|
|
109692
|
+
timestamp: Date.now()
|
|
109693
|
+
});
|
|
109526
109694
|
}
|
|
109527
|
-
conn.networkLog.push({
|
|
109528
|
-
method: p.method,
|
|
109529
|
-
params: p.params,
|
|
109530
|
-
timestamp: Date.now()
|
|
109531
|
-
});
|
|
109532
109695
|
}
|
|
109533
109696
|
}
|
|
109534
|
-
|
|
109535
|
-
|
|
109536
|
-
|
|
109537
|
-
|
|
109538
|
-
|
|
109539
|
-
|
|
109540
|
-
|
|
109541
|
-
|
|
109542
|
-
|
|
109543
|
-
|
|
109544
|
-
|
|
109545
|
-
|
|
109546
|
-
|
|
109547
|
-
|
|
109548
|
-
);
|
|
109549
|
-
}
|
|
109697
|
+
if (msg.type === "ViewInspector") {
|
|
109698
|
+
const p = msg.payload;
|
|
109699
|
+
const pending = pendingRpc.get(p.id);
|
|
109700
|
+
if (!pending) return;
|
|
109701
|
+
pendingRpc.delete(p.id);
|
|
109702
|
+
if (p.error) {
|
|
109703
|
+
pending.reject(
|
|
109704
|
+
new FailureError(p.error.message, {
|
|
109705
|
+
error_code: FAILURE_CODES.NATIVE_DEVTOOLS_RPC_ERROR,
|
|
109706
|
+
failure_stage: "native_devtools_rpc_response",
|
|
109707
|
+
failure_area: "tool_server",
|
|
109708
|
+
error_kind: "subprocess"
|
|
109709
|
+
})
|
|
109710
|
+
);
|
|
109711
|
+
} else pending.resolve(p.result);
|
|
109712
|
+
}
|
|
109550
109713
|
}
|
|
109551
109714
|
});
|
|
109552
109715
|
socket.on("close", () => {
|
|
109553
|
-
rl.close();
|
|
109554
109716
|
if (bundleId !== null) {
|
|
109555
109717
|
if (connections2.get(bundleId)?.socket === socket) {
|
|
109556
109718
|
connections2.delete(bundleId);
|
|
@@ -109994,22 +110156,28 @@ async function describeIos(registry2, device, params, options = {}) {
|
|
|
109994
110156
|
if (isTvOs) {
|
|
109995
110157
|
return { tree: emptyTree(), source: "ax-service", hint: TVOS_HINT };
|
|
109996
110158
|
}
|
|
109997
|
-
let tree;
|
|
110159
|
+
let tree = emptyTree();
|
|
109998
110160
|
let degraded;
|
|
109999
110161
|
let resolverHint;
|
|
110162
|
+
let readFailureHint;
|
|
110163
|
+
let axApi;
|
|
110000
110164
|
try {
|
|
110001
110165
|
const axRef = axServiceRef(device);
|
|
110002
|
-
|
|
110003
|
-
const response = await axApi.describe();
|
|
110004
|
-
tree = adaptAXDescribeToDescribeResult(response);
|
|
110166
|
+
axApi = await registry2.resolveService(axRef.urn, axRef.options);
|
|
110005
110167
|
degraded = axApi.degraded;
|
|
110006
110168
|
} catch (err) {
|
|
110007
|
-
tree = emptyTree();
|
|
110008
110169
|
resolverHint = tcpArtifactHint(err);
|
|
110009
110170
|
degraded = resolverHint === void 0;
|
|
110010
110171
|
}
|
|
110172
|
+
if (axApi) {
|
|
110173
|
+
try {
|
|
110174
|
+
tree = adaptAXDescribeToDescribeResult(await axApi.describe());
|
|
110175
|
+
} catch (err) {
|
|
110176
|
+
readFailureHint = `The accessibility read failed (${errMsg(err)}).`;
|
|
110177
|
+
}
|
|
110178
|
+
}
|
|
110011
110179
|
const degradedHint = !degraded ? void 0 : tree.children.length === 0 ? DEGRADED_BLIND_HINT : DEGRADED_STANDING_HINT;
|
|
110012
|
-
const hint = resolverHint ?? degradedHint;
|
|
110180
|
+
const hint = resolverHint ?? (readFailureHint ? degradedHint ? `${degradedHint}. ${readFailureHint}` : readFailureHint : degradedHint);
|
|
110013
110181
|
if (tree.children.length > 0) {
|
|
110014
110182
|
return { tree, source: "ax-service", hint };
|
|
110015
110183
|
}
|
|
@@ -110501,7 +110669,7 @@ function parseUiAutomatorDump(rawOutput, screenW, screenH, options = {}) {
|
|
|
110501
110669
|
|
|
110502
110670
|
// ../tool-server/src/blueprints/android-devtools.ts
|
|
110503
110671
|
var import_node_child_process13 = require("node:child_process");
|
|
110504
|
-
var
|
|
110672
|
+
var readline2 = __toESM(require("node:readline"));
|
|
110505
110673
|
init_src();
|
|
110506
110674
|
|
|
110507
110675
|
// ../native-devtools-android/src/index.ts
|
|
@@ -110898,7 +111066,6 @@ async function ensureAndroidDevtoolsInstalled(serial) {
|
|
|
110898
111066
|
|
|
110899
111067
|
// ../tool-server/src/utils/android-devtools-client.ts
|
|
110900
111068
|
var net3 = __toESM(require("node:net"));
|
|
110901
|
-
var readline4 = __toESM(require("node:readline"));
|
|
110902
111069
|
init_src();
|
|
110903
111070
|
var DEFAULT_RPC_TIMEOUT_MS = 5e3;
|
|
110904
111071
|
var LONG_RPC_TIMEOUT_MS = 15e3;
|
|
@@ -110932,36 +111099,32 @@ function connectAndroidDevtoolsClient(localPort, onTerminated) {
|
|
|
110932
111099
|
onTerminated(error52);
|
|
110933
111100
|
};
|
|
110934
111101
|
socket.once("connect", () => {
|
|
110935
|
-
|
|
110936
|
-
|
|
110937
|
-
|
|
110938
|
-
|
|
110939
|
-
|
|
110940
|
-
|
|
110941
|
-
|
|
110942
|
-
|
|
110943
|
-
|
|
110944
|
-
|
|
110945
|
-
|
|
110946
|
-
|
|
110947
|
-
|
|
110948
|
-
|
|
110949
|
-
|
|
110950
|
-
|
|
110951
|
-
|
|
110952
|
-
|
|
110953
|
-
|
|
110954
|
-
|
|
110955
|
-
|
|
110956
|
-
|
|
110957
|
-
|
|
110958
|
-
})
|
|
110959
|
-
);
|
|
110960
|
-
} else {
|
|
110961
|
-
req.resolve(parsed.result);
|
|
111102
|
+
attachNdjsonReader(socket, {
|
|
111103
|
+
onDropped: reportDroppedFrameToStderr("android-devtools"),
|
|
111104
|
+
onMessage: (raw) => {
|
|
111105
|
+
const parsed = raw;
|
|
111106
|
+
if (typeof parsed.id !== "number") return;
|
|
111107
|
+
const req = pending.get(parsed.id);
|
|
111108
|
+
if (!req) return;
|
|
111109
|
+
pending.delete(parsed.id);
|
|
111110
|
+
clearTimeout(req.timer);
|
|
111111
|
+
if (parsed.error) {
|
|
111112
|
+
const message = parsed.error.message ?? "Unknown helper error";
|
|
111113
|
+
const type = parsed.error.type ?? "HelperError";
|
|
111114
|
+
req.reject(
|
|
111115
|
+
new FailureError(`${type}: ${message}`, {
|
|
111116
|
+
error_code: FAILURE_CODES.ANDROID_DEVTOOLS_RPC_ERROR,
|
|
111117
|
+
failure_stage: "android_devtools_rpc_response",
|
|
111118
|
+
failure_area: "tool_server",
|
|
111119
|
+
error_kind: "subprocess"
|
|
111120
|
+
})
|
|
111121
|
+
);
|
|
111122
|
+
} else {
|
|
111123
|
+
req.resolve(parsed.result);
|
|
111124
|
+
}
|
|
110962
111125
|
}
|
|
110963
111126
|
});
|
|
110964
|
-
|
|
111127
|
+
socket.on("close", () => cleanup());
|
|
110965
111128
|
resolve12({
|
|
110966
111129
|
request(method, params = {}) {
|
|
110967
111130
|
const send = () => {
|
|
@@ -111076,7 +111239,7 @@ async function spawnHelper(serial) {
|
|
|
111076
111239
|
cleanup?.();
|
|
111077
111240
|
fn();
|
|
111078
111241
|
};
|
|
111079
|
-
const rl =
|
|
111242
|
+
const rl = readline2.createInterface({ input: proc.stdout });
|
|
111080
111243
|
rl.on("line", async (rawLine) => {
|
|
111081
111244
|
const line = rawLine.trim();
|
|
111082
111245
|
const portMatch = HELPER_PORT_MARKER.exec(line);
|
|
@@ -116837,13 +117000,7 @@ var zodSchema9 = external_exports.object({
|
|
|
116837
117000
|
function bootTarget(params) {
|
|
116838
117001
|
return params.udid ?? params.avdName ?? params.vvdImage ?? params.electronAppPath ?? "device";
|
|
116839
117002
|
}
|
|
116840
|
-
var LAUNCH_HARDENING_ARGS = [
|
|
116841
|
-
"-no-boot-anim",
|
|
116842
|
-
"-netfast",
|
|
116843
|
-
"-crash-report-mode",
|
|
116844
|
-
"never",
|
|
116845
|
-
"-no-metrics"
|
|
116846
|
-
];
|
|
117003
|
+
var LAUNCH_HARDENING_ARGS = ["-no-boot-anim", "-netfast", "-no-metrics"];
|
|
116847
117004
|
function launchHardeningArgs(sound) {
|
|
116848
117005
|
return sound ? [...LAUNCH_HARDENING_ARGS] : ["-noaudio", ...LAUNCH_HARDENING_ARGS];
|
|
116849
117006
|
}
|
|
@@ -117357,7 +117514,7 @@ async function bootAndroidImpl(params) {
|
|
|
117357
117514
|
} else {
|
|
117358
117515
|
const RENDERER_ARGS = ["-gpu", gpuMode, ...extraEmulatorArgs];
|
|
117359
117516
|
const probe3 = await checkSnapshotLoadable(params.avdName, "default_boot", {
|
|
117360
|
-
extraArgs: [...RENDERER_ARGS, ...hardeningArgs]
|
|
117517
|
+
extraArgs: [...RENDERER_ARGS, ...hardeningArgs, ...crashReportArgs]
|
|
117361
117518
|
});
|
|
117362
117519
|
if (!probe3.loadable) {
|
|
117363
117520
|
hotBootFailureReason = `-check-snapshot-loadable: ${probe3.reason ?? "unknown"}`;
|
|
@@ -119392,7 +119549,7 @@ Before tapping, determine the correct coordinates by using discovery tools \u201
|
|
|
119392
119549
|
const api = services.simulatorServer;
|
|
119393
119550
|
for (let i = 1; i <= clickCount; i++) {
|
|
119394
119551
|
if (i > 1) await sleep2(MULTI_TAP_GAP_MS);
|
|
119395
|
-
sendCommand(api, {
|
|
119552
|
+
await sendCommand(api, {
|
|
119396
119553
|
cmd: "touch",
|
|
119397
119554
|
type: "Down",
|
|
119398
119555
|
x: params.x,
|
|
@@ -119401,7 +119558,7 @@ Before tapping, determine the correct coordinates by using discovery tools \u201
|
|
|
119401
119558
|
second_y: null
|
|
119402
119559
|
});
|
|
119403
119560
|
await sleep2(TAP_HOLD_MS);
|
|
119404
|
-
sendCommand(api, {
|
|
119561
|
+
await sendCommand(api, {
|
|
119405
119562
|
cmd: "touch",
|
|
119406
119563
|
type: "Up",
|
|
119407
119564
|
x: params.x,
|
|
@@ -119417,18 +119574,39 @@ Before tapping, determine the correct coordinates by using discovery tools \u201
|
|
|
119417
119574
|
// ../tool-server/src/tools/gesture-swipe/index.ts
|
|
119418
119575
|
init_zod();
|
|
119419
119576
|
var sleep3 = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
119420
|
-
var
|
|
119577
|
+
var MOMENTUM_FREE_EASE_EXPONENT = 3;
|
|
119578
|
+
var DEFAULT_DURATION_MS = 300;
|
|
119579
|
+
var MOMENTUM_FREE_MIN_DURATION_MS = 150;
|
|
119580
|
+
var MAX_DURATION_MS = 1e4;
|
|
119421
119581
|
var zodSchema17 = external_exports.object({
|
|
119422
119582
|
udid: external_exports.string().describe("Target device id from `list-devices` (iOS UDID or Android serial)."),
|
|
119423
119583
|
fromX: external_exports.number().describe("Start x: normalized 0.0\u20131.0 (not pixels; same as tap)"),
|
|
119424
119584
|
fromY: external_exports.number().describe("Start y: normalized 0.0\u20131.0 (not pixels; same as tap)"),
|
|
119425
119585
|
toX: external_exports.number().describe("End x: normalized 0.0\u20131.0 (not pixels; same as tap)"),
|
|
119426
119586
|
toY: external_exports.number().describe("End y: normalized 0.0\u20131.0 (not pixels; same as tap)"),
|
|
119427
|
-
durationMs: external_exports.number().
|
|
119428
|
-
|
|
119429
|
-
|
|
119587
|
+
durationMs: external_exports.number().max(MAX_DURATION_MS, {
|
|
119588
|
+
message: `durationMs must be at most ${MAX_DURATION_MS} (10s): every frame is a real 16ms sleep with the finger held down, so a larger value is that many milliseconds of wall clock spent holding a touch the device cannot shake off.`
|
|
119589
|
+
}).optional().describe(
|
|
119590
|
+
`Total gesture duration in milliseconds (default 300, at most ${MAX_DURATION_MS} - the gesture holds a finger down for exactly this long)`
|
|
119591
|
+
),
|
|
119592
|
+
momentum: external_exports.boolean().optional().describe(
|
|
119593
|
+
`Whether the swipe releases with momentum; default true (a natural flinging swipe). Pass false for a momentum-free swipe at the default durationMs: the finger decelerates into the end point (ease-out) so the OS reads ~0 release velocity and applies little to no fling. Use false for scroll-to-element loops. momentum: false needs durationMs >= ${MOMENTUM_FREE_MIN_DURATION_MS} and is rejected below it: a shorter ease-out gives the OS velocity fit too little wall clock to read the deceleration as a stop, and it flings harder than a plain swipe instead (on Android, backwards). At ${MOMENTUM_FREE_MIN_DURATION_MS} itself the swipe lands short of where the finger stopped, and 2 of 47 runs still flung backwards.`
|
|
119594
|
+
),
|
|
119595
|
+
// `momentum`'s shipped spelling, with the opposite polarity. Declared so this
|
|
119596
|
+
// non-strict object refuses it instead of stripping it and flinging - the exact
|
|
119597
|
+
// inverse of the gesture the caller asked for.
|
|
119598
|
+
settle: external_exports.never({
|
|
119599
|
+
error: "gesture-swipe's `settle` was renamed to `momentum`, with the opposite sense \u2014 write `momentum: false` for the momentum-free swipe that `settle: true` used to mean (plain `settle: false` was the default, so just drop it)"
|
|
119600
|
+
}).optional().describe(
|
|
119601
|
+
"Retired: renamed to `momentum` with the opposite sense. Pass `momentum: false` for what `settle: true` meant; `settle: false` was the default, so drop the key."
|
|
119430
119602
|
)
|
|
119431
|
-
})
|
|
119603
|
+
}).refine(
|
|
119604
|
+
(p) => p.momentum !== false || (p.durationMs ?? DEFAULT_DURATION_MS) >= MOMENTUM_FREE_MIN_DURATION_MS,
|
|
119605
|
+
{
|
|
119606
|
+
message: `momentum: false needs durationMs of at least ${MOMENTUM_FREE_MIN_DURATION_MS}: below that the ease-out has too little wall clock for the OS velocity fit to read it as a stop rather than a flick, so it flings harder than a plain swipe and, on Android, backwards. Raise durationMs, or drop momentum: false for a plain flinging swipe at the duration you asked for.`,
|
|
119607
|
+
path: ["durationMs"]
|
|
119608
|
+
}
|
|
119609
|
+
);
|
|
119432
119610
|
var capability9 = {
|
|
119433
119611
|
apple: { simulator: true, device: true },
|
|
119434
119612
|
appleRemote: { simulator: true },
|
|
@@ -119441,11 +119619,13 @@ var gestureSwipeTool = {
|
|
|
119441
119619
|
completedMsg: ({ params }) => `Swiped from (${Math.round(params.fromX * 100)}%, ${Math.round(params.fromY * 100)}%) to (${Math.round(params.toX * 100)}%, ${Math.round(params.toY * 100)}%)`,
|
|
119442
119620
|
failedMsg: ({ failureSignal: failureSignal2 }) => `Failed to swipe: ${failureSignal2.error_code}`
|
|
119443
119621
|
},
|
|
119622
|
+
// The bounds are spelled out rather than interpolated: extract-tools scans this
|
|
119623
|
+
// description statically, so a `${}` in it drops the tool out of the scan.
|
|
119444
119624
|
description: `Execute a smooth swipe / drag touch gesture between two points on the device (iOS simulator or Android emulator). All from/to positions are normalized 0.0\u20131.0 (fractions of screen width/height, not pixels), same as gesture-tap.
|
|
119445
119625
|
Generates interpolated Move events for a natural feel (~60fps).
|
|
119446
119626
|
Swipe up (fromY > toY) to scroll content down.
|
|
119447
119627
|
Use when you need to scroll a list, dismiss a modal, drag an element, or navigate between pages. Not supported on Chromium \u2014 use gesture-scroll there instead.
|
|
119448
|
-
Pass
|
|
119628
|
+
Pass momentum:false for a momentum-free swipe that lands where the finger lifts (little to no fling at the 300 default), when you need a deterministic scroll distance; it needs durationMs >= 150 and is rejected below that, a shorter ease-out leaving the OS too little wall clock to read the deceleration as a stop. At 150 it lands short of the lift point instead, and 2 of 47 runs still flung backwards. A plain swipe takes any duration up to 10000ms and is delivered as close to the speed it was authored as a 16ms frame allows: below ~32ms the whole travel lands in one or two frames, which the OS flings as hard as it flings anything. Returns { swiped: true, timestampMs }. Fails if the simulator-server / emulator backend is not reachable for the given device.`,
|
|
119449
119629
|
alwaysLoad: true,
|
|
119450
119630
|
searchHint: "swipe scroll drag pan gesture device simulator emulator touch move",
|
|
119451
119631
|
zodSchema: zodSchema17,
|
|
@@ -119453,19 +119633,52 @@ Pass settle:true for a momentum-free swipe that lands exactly where the finger l
|
|
|
119453
119633
|
services: (params) => ({
|
|
119454
119634
|
simulatorServer: simulatorServerRef(resolveDevice(params.udid))
|
|
119455
119635
|
}),
|
|
119456
|
-
async execute(services, params) {
|
|
119457
|
-
const duration3 = params.durationMs ??
|
|
119458
|
-
const
|
|
119636
|
+
async execute(services, params, ctx) {
|
|
119637
|
+
const duration3 = params.durationMs ?? DEFAULT_DURATION_MS;
|
|
119638
|
+
const momentumFree = params.momentum === false;
|
|
119459
119639
|
const timestampMs = Date.now();
|
|
119460
119640
|
const api = services.simulatorServer;
|
|
119461
119641
|
const steps = Math.max(1, Math.round(duration3 / 16));
|
|
119642
|
+
let lastX = 0;
|
|
119643
|
+
let lastY = 0;
|
|
119462
119644
|
for (let i = 0; i <= steps; i++) {
|
|
119645
|
+
if (ctx?.signal?.aborted) {
|
|
119646
|
+
const err = new Error(
|
|
119647
|
+
`gesture-swipe aborted - cancelled mid-gesture after ${i} of ${steps + 1} frames`
|
|
119648
|
+
);
|
|
119649
|
+
err.name = "AbortError";
|
|
119650
|
+
if (i > 0) {
|
|
119651
|
+
try {
|
|
119652
|
+
await sendCommand(api, {
|
|
119653
|
+
cmd: "touch",
|
|
119654
|
+
type: "Up",
|
|
119655
|
+
x: lastX,
|
|
119656
|
+
y: lastY,
|
|
119657
|
+
second_x: null,
|
|
119658
|
+
second_y: null
|
|
119659
|
+
});
|
|
119660
|
+
} catch (liftErr) {
|
|
119661
|
+
err.cause = liftErr;
|
|
119662
|
+
}
|
|
119663
|
+
}
|
|
119664
|
+
throw err;
|
|
119665
|
+
}
|
|
119463
119666
|
const t = i / steps;
|
|
119464
|
-
const progress =
|
|
119667
|
+
const progress = momentumFree ? 1 - Math.pow(1 - t, MOMENTUM_FREE_EASE_EXPONENT) : t;
|
|
119465
119668
|
const x = params.fromX + (params.toX - params.fromX) * progress;
|
|
119466
119669
|
const y = params.fromY + (params.toY - params.fromY) * progress;
|
|
119467
119670
|
const type = i === 0 ? "Down" : i === steps ? "Up" : "Move";
|
|
119468
|
-
|
|
119671
|
+
if (type === "Up") {
|
|
119672
|
+
await sendCommand(api, {
|
|
119673
|
+
cmd: "touch",
|
|
119674
|
+
type: "Move",
|
|
119675
|
+
x,
|
|
119676
|
+
y,
|
|
119677
|
+
second_x: null,
|
|
119678
|
+
second_y: null
|
|
119679
|
+
});
|
|
119680
|
+
}
|
|
119681
|
+
await sendCommand(api, {
|
|
119469
119682
|
cmd: "touch",
|
|
119470
119683
|
type,
|
|
119471
119684
|
x,
|
|
@@ -119473,6 +119686,8 @@ Pass settle:true for a momentum-free swipe that lands exactly where the finger l
|
|
|
119473
119686
|
second_x: null,
|
|
119474
119687
|
second_y: null
|
|
119475
119688
|
});
|
|
119689
|
+
lastX = x;
|
|
119690
|
+
lastY = y;
|
|
119476
119691
|
if (i < steps) await sleep3(16);
|
|
119477
119692
|
}
|
|
119478
119693
|
return { swiped: true, timestampMs };
|
|
@@ -119548,13 +119763,30 @@ Returns { scrolled: true, timestampMs }. Fails if the Chromium CDP session is no
|
|
|
119548
119763
|
// ../tool-server/src/tools/gesture-drag/index.ts
|
|
119549
119764
|
init_zod();
|
|
119550
119765
|
var sleep5 = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
119766
|
+
var MOMENTUM_FREE_EASE_EXPONENT2 = 3;
|
|
119767
|
+
var MOMENTUM_FREE_MIN_STEPS = 8;
|
|
119768
|
+
var MAX_DURATION_MS2 = 1e4;
|
|
119551
119769
|
var zodSchema19 = external_exports.object({
|
|
119552
119770
|
udid: external_exports.string().describe("Target Chromium device id from `list-devices` (chromium-cdp-<port>)."),
|
|
119553
119771
|
fromX: external_exports.number().describe("Press x: normalized 0.0\u20131.0 (fraction of window width, not pixels)."),
|
|
119554
119772
|
fromY: external_exports.number().describe("Press y: normalized 0.0\u20131.0 (fraction of window height, not pixels)."),
|
|
119555
119773
|
toX: external_exports.number().describe("Release x: normalized 0.0\u20131.0 (not pixels; same space as tap)."),
|
|
119556
119774
|
toY: external_exports.number().describe("Release y: normalized 0.0\u20131.0 (not pixels; same space as tap)."),
|
|
119557
|
-
durationMs: external_exports.number().
|
|
119775
|
+
durationMs: external_exports.number().max(MAX_DURATION_MS2, {
|
|
119776
|
+
message: `durationMs must be at most ${MAX_DURATION_MS2} (10s): the drag holds the left button down for exactly this long, one frame per ~16ms, so a larger value is that much wall clock spent mid-press.`
|
|
119777
|
+
}).optional().describe(
|
|
119778
|
+
`Total drag duration in milliseconds (default 300, at most ${MAX_DURATION_MS2} - the button stays down for exactly this long), interpolated at ~60fps.`
|
|
119779
|
+
),
|
|
119780
|
+
momentum: external_exports.boolean().optional().describe(
|
|
119781
|
+
"Whether the drag releases with momentum; default true (a constant-speed drag). Pass false to decelerate into the release point (ease-out) so an app deriving fling from pointer release velocity (carousels, drag libraries) reads ~0 and applies little to no momentum \u2014 use it when the drag must stop where it was aimed rather than fling past. Deceleration needs wall clock: under ~100ms the whole drag fits inside the velocity window a page averages over (tens of ms), so some fling survives, and under ~70ms its extra frames cannot dispatch fast enough to fit durationMs. Keep durationMs at its default when the fling must be fully suppressed."
|
|
119782
|
+
),
|
|
119783
|
+
// `momentum`'s earlier spelling, with the opposite polarity. Declared so this
|
|
119784
|
+
// non-strict object refuses it instead of stripping it and running the default.
|
|
119785
|
+
settle: external_exports.never({
|
|
119786
|
+
error: "gesture-drag's `settle` was renamed to `momentum`, with the opposite sense - write `momentum: false` for the momentum-free drag that `settle: true` used to mean (plain `settle: false` was the default, so just drop it)"
|
|
119787
|
+
}).optional().describe(
|
|
119788
|
+
"Retired: renamed to `momentum` with the opposite sense. Pass `momentum: false` for what `settle: true` meant; `settle: false` was the default, so drop the key."
|
|
119789
|
+
)
|
|
119558
119790
|
});
|
|
119559
119791
|
var capability11 = {
|
|
119560
119792
|
chromium: { app: true }
|
|
@@ -119566,9 +119798,9 @@ var gestureDragTool = {
|
|
|
119566
119798
|
completedMsg: ({ params }) => `Dragged from (${Math.round(params.fromX * 100)}%, ${Math.round(params.fromY * 100)}%) to (${Math.round(params.toX * 100)}%, ${Math.round(params.toY * 100)}%)`,
|
|
119567
119799
|
failedMsg: ({ failureSignal: failureSignal2 }) => `Failed to drag: ${failureSignal2.error_code}`
|
|
119568
119800
|
},
|
|
119569
|
-
description: `Press the left mouse button at a start point, move to an end point, and release \u2014 a desktop mouse drag in a Chromium app. All positions are normalized 0.0\u20131.0 (fractions of the window, not pixels), same coordinate space as gesture-tap and describe. Interpolates mouse-move events at ~60fps over durationMs for a natural drag.
|
|
119801
|
+
description: `Press the left mouse button at a start point, move to an end point, and release \u2014 a desktop mouse drag in a Chromium app. All positions are normalized 0.0\u20131.0 (fractions of the window, not pixels), same coordinate space as gesture-tap and describe, except that a coordinate of exactly 1.0 lands one pixel inside the window edge (gesture-tap maps it to the edge itself). Interpolates mouse-move events at ~60fps over durationMs for a natural drag (a momentum-free drag samples more finely when durationMs is short, so its ease-out has a curve).
|
|
119570
119802
|
Use for slider thumbs, drag-and-drop, text selection, or draggable UI elements. Dragging never scrolls content on desktop \u2014 use gesture-scroll for lists/pages. Chromium only \u2014 on iOS/Android use gesture-swipe.
|
|
119571
|
-
Returns { dragged: true, timestampMs }. Fails if the Chromium CDP session is not reachable for the given device.`,
|
|
119803
|
+
Pass momentum:false for a momentum-free drag that decelerates into the release, so apps that compute a fling from the pointer stream read ~0 velocity and the drag ends where it was aimed instead of flinging past it (a durationMs under ~100ms is too short for the deceleration to suppress the fling entirely). Returns { dragged: true, timestampMs }. Fails if the Chromium CDP session is not reachable for the given device.`,
|
|
119572
119804
|
alwaysLoad: true,
|
|
119573
119805
|
searchHint: "drag drop slider mouse press move release chromium select",
|
|
119574
119806
|
zodSchema: zodSchema19,
|
|
@@ -119576,15 +119808,49 @@ Returns { dragged: true, timestampMs }. Fails if the Chromium CDP session is not
|
|
|
119576
119808
|
services: (params) => ({
|
|
119577
119809
|
chromium: chromiumCdpRef(resolveDevice(params.udid))
|
|
119578
119810
|
}),
|
|
119579
|
-
async execute(services, params) {
|
|
119811
|
+
async execute(services, params, ctx) {
|
|
119580
119812
|
const timestampMs = Date.now();
|
|
119581
119813
|
const chromium = services.chromium;
|
|
119582
119814
|
await assertChromiumWindowVisible(chromium, "drag", "chromium_drag_window_hidden");
|
|
119583
119815
|
const vp = chromium.getViewport();
|
|
119584
|
-
const
|
|
119585
|
-
const
|
|
119816
|
+
const clampPx2 = (px, size) => Math.min(Math.max(px, 0), size - 1);
|
|
119817
|
+
const startPx = {
|
|
119818
|
+
x: clampPx2(params.fromX * vp.width, vp.width),
|
|
119819
|
+
y: clampPx2(params.fromY * vp.height, vp.height)
|
|
119820
|
+
};
|
|
119821
|
+
const endPx = {
|
|
119822
|
+
x: clampPx2(params.toX * vp.width, vp.width),
|
|
119823
|
+
y: clampPx2(params.toY * vp.height, vp.height)
|
|
119824
|
+
};
|
|
119586
119825
|
const durationMs = params.durationMs ?? 300;
|
|
119587
|
-
const
|
|
119826
|
+
const momentumFree = params.momentum === false;
|
|
119827
|
+
const steps = Math.max(momentumFree ? MOMENTUM_FREE_MIN_STEPS : 2, Math.round(durationMs / 16));
|
|
119828
|
+
const frameMs = durationMs / steps;
|
|
119829
|
+
const t0 = Date.now();
|
|
119830
|
+
let lastX = startPx.x;
|
|
119831
|
+
let lastY = startPx.y;
|
|
119832
|
+
const abortError = (frame) => {
|
|
119833
|
+
const err = new Error(
|
|
119834
|
+
`gesture-drag aborted - cancelled mid-drag after ${frame} of ${steps + 1} frames`
|
|
119835
|
+
);
|
|
119836
|
+
err.name = "AbortError";
|
|
119837
|
+
return err;
|
|
119838
|
+
};
|
|
119839
|
+
const releaseAndAbort = async (frame) => {
|
|
119840
|
+
const err = abortError(frame);
|
|
119841
|
+
try {
|
|
119842
|
+
await chromium.dispatchMouseEvent({
|
|
119843
|
+
type: "mouseReleased",
|
|
119844
|
+
x: lastX,
|
|
119845
|
+
y: lastY,
|
|
119846
|
+
clickCount: 1
|
|
119847
|
+
});
|
|
119848
|
+
} catch (releaseErr) {
|
|
119849
|
+
err.cause = releaseErr;
|
|
119850
|
+
}
|
|
119851
|
+
throw err;
|
|
119852
|
+
};
|
|
119853
|
+
if (ctx?.signal?.aborted) throw abortError(0);
|
|
119588
119854
|
await chromium.dispatchMouseEvent({
|
|
119589
119855
|
type: "mousePressed",
|
|
119590
119856
|
x: startPx.x,
|
|
@@ -119592,15 +119858,19 @@ Returns { dragged: true, timestampMs }. Fails if the Chromium CDP session is not
|
|
|
119592
119858
|
clickCount: 1
|
|
119593
119859
|
});
|
|
119594
119860
|
for (let i = 1; i < steps; i++) {
|
|
119861
|
+
if (ctx?.signal?.aborted) await releaseAndAbort(i);
|
|
119862
|
+
await sleep5(Math.max(0, t0 + i * frameMs - Date.now()));
|
|
119595
119863
|
const t = i / steps;
|
|
119596
|
-
|
|
119597
|
-
|
|
119598
|
-
|
|
119599
|
-
|
|
119600
|
-
|
|
119601
|
-
|
|
119602
|
-
|
|
119603
|
-
|
|
119864
|
+
const progress = momentumFree ? 1 - Math.pow(1 - t, MOMENTUM_FREE_EASE_EXPONENT2) : t;
|
|
119865
|
+
const x = startPx.x + (endPx.x - startPx.x) * progress;
|
|
119866
|
+
const y = startPx.y + (endPx.y - startPx.y) * progress;
|
|
119867
|
+
await chromium.dispatchMouseEvent({ type: "mouseMoved", x, y, button: "left" });
|
|
119868
|
+
lastX = x;
|
|
119869
|
+
lastY = y;
|
|
119870
|
+
}
|
|
119871
|
+
if (ctx?.signal?.aborted) await releaseAndAbort(steps);
|
|
119872
|
+
await sleep5(Math.max(0, t0 + durationMs - Date.now()));
|
|
119873
|
+
if (ctx?.signal?.aborted) await releaseAndAbort(steps);
|
|
119604
119874
|
await chromium.dispatchMouseEvent({
|
|
119605
119875
|
type: "mouseReleased",
|
|
119606
119876
|
x: endPx.x,
|
|
@@ -119651,7 +119921,7 @@ function interpolateEvents(events, steps) {
|
|
|
119651
119921
|
return result;
|
|
119652
119922
|
}
|
|
119653
119923
|
function sendTouchEvent(api, type, x, y, x2, y2) {
|
|
119654
|
-
sendCommand(api, {
|
|
119924
|
+
return sendCommand(api, {
|
|
119655
119925
|
cmd: "touch",
|
|
119656
119926
|
type,
|
|
119657
119927
|
x,
|
|
@@ -119722,7 +119992,7 @@ Example pinch-to-zoom (with interpolate:10 for smoothness):
|
|
|
119722
119992
|
const events = params.interpolate && params.interpolate > 0 ? interpolateEvents(params.events, params.interpolate) : params.events;
|
|
119723
119993
|
for (const event2 of events) {
|
|
119724
119994
|
await sleep6(event2.delayMs ?? 16);
|
|
119725
|
-
sendCommand(api, {
|
|
119995
|
+
await sendCommand(api, {
|
|
119726
119996
|
cmd: "touch",
|
|
119727
119997
|
type: event2.type,
|
|
119728
119998
|
x: event2.x,
|
|
@@ -119805,7 +120075,7 @@ Use when you need to zoom in or out on a map, image, or zoomable view. Returns {
|
|
|
119805
120075
|
const y2 = cy + halfDist * sinA;
|
|
119806
120076
|
const type = i === 0 ? "Down" : i === steps ? "Up" : "Move";
|
|
119807
120077
|
if (i === 0) timestampMs = Date.now();
|
|
119808
|
-
sendTouchEvent(api, type, x1, y1, x2, y2);
|
|
120078
|
+
await sendTouchEvent(api, type, x1, y1, x2, y2);
|
|
119809
120079
|
if (i < steps) await sleep(16);
|
|
119810
120080
|
}
|
|
119811
120081
|
return { pinched: true, timestampMs };
|
|
@@ -119883,11 +120153,17 @@ Size the orbit with radius, or with radiusX and radiusY together (the pair overr
|
|
|
119883
120153
|
let lastY2 = 0;
|
|
119884
120154
|
for (let i = 0; i <= steps; i++) {
|
|
119885
120155
|
if (ctx?.signal?.aborted) {
|
|
119886
|
-
if (i > 0) sendTouchEvent(api, "Up", lastX1, lastY1, lastX2, lastY2);
|
|
119887
120156
|
const err = new Error(
|
|
119888
120157
|
`gesture-rotate aborted \u2014 cancelled mid-gesture after ${i} of ${steps + 1} frames`
|
|
119889
120158
|
);
|
|
119890
120159
|
err.name = "AbortError";
|
|
120160
|
+
if (i > 0) {
|
|
120161
|
+
try {
|
|
120162
|
+
await sendTouchEvent(api, "Up", lastX1, lastY1, lastX2, lastY2);
|
|
120163
|
+
} catch (liftErr) {
|
|
120164
|
+
err.cause = liftErr;
|
|
120165
|
+
}
|
|
120166
|
+
}
|
|
119891
120167
|
throw err;
|
|
119892
120168
|
}
|
|
119893
120169
|
const t = i / steps;
|
|
@@ -119899,7 +120175,7 @@ Size the orbit with radius, or with radiusX and radiusY together (the pair overr
|
|
|
119899
120175
|
const y2 = params.centerY - radiusY * Math.sin(angleRad);
|
|
119900
120176
|
const type = i === 0 ? "Down" : i === steps ? "Up" : "Move";
|
|
119901
120177
|
if (i === 0) timestampMs = Date.now();
|
|
119902
|
-
sendTouchEvent(api, type, x1, y1, x2, y2);
|
|
120178
|
+
await sendTouchEvent(api, type, x1, y1, x2, y2);
|
|
119903
120179
|
lastX1 = x1;
|
|
119904
120180
|
lastY1 = y1;
|
|
119905
120181
|
lastX2 = x2;
|
|
@@ -119968,13 +120244,13 @@ Fails if the device backend is not reachable \u2014 the simulator-server for iOS
|
|
|
119968
120244
|
return { pressed: params.button };
|
|
119969
120245
|
}
|
|
119970
120246
|
const api = services.simulatorServer;
|
|
119971
|
-
sendCommand(api, {
|
|
120247
|
+
await sendCommand(api, {
|
|
119972
120248
|
cmd: "button",
|
|
119973
120249
|
direction: "Down",
|
|
119974
120250
|
button: params.button
|
|
119975
120251
|
});
|
|
119976
120252
|
await sleep7(50);
|
|
119977
|
-
sendCommand(api, { cmd: "button", direction: "Up", button: params.button });
|
|
120253
|
+
await sendCommand(api, { cmd: "button", direction: "Up", button: params.button });
|
|
119978
120254
|
return { pressed: params.button };
|
|
119979
120255
|
}
|
|
119980
120256
|
};
|
|
@@ -120785,7 +121061,7 @@ Returns { orientation }. Fails if the target device is not booted.`,
|
|
|
120785
121061
|
}),
|
|
120786
121062
|
async execute(services, params) {
|
|
120787
121063
|
const api = services.simulatorServer;
|
|
120788
|
-
sendCommand(api, { cmd: "rotate", direction: params.orientation });
|
|
121064
|
+
await sendCommand(api, { cmd: "rotate", direction: params.orientation });
|
|
120789
121065
|
return { orientation: params.orientation };
|
|
120790
121066
|
}
|
|
120791
121067
|
};
|
|
@@ -127820,9 +128096,9 @@ a prior tap), use individual tool calls instead.
|
|
|
127820
128096
|
Allowed tools and their args (udid is auto-injected, do NOT include it in args):
|
|
127821
128097
|
|
|
127822
128098
|
gesture-tap: { x: number, y: number, clickCount?: number } [ios/android/chromium]
|
|
127823
|
-
gesture-swipe: { fromX: number, fromY: number, toX: number, toY: number, durationMs?: number }
|
|
128099
|
+
gesture-swipe: { fromX: number, fromY: number, toX: number, toY: number, durationMs?: number, momentum?: boolean } [ios/android]
|
|
127824
128100
|
gesture-scroll: { x: number, y: number, deltaX?: number, deltaY?: number, durationMs?: number } [chromium only]
|
|
127825
|
-
gesture-drag: { fromX: number, fromY: number, toX: number, toY: number, durationMs?: number }
|
|
128101
|
+
gesture-drag: { fromX: number, fromY: number, toX: number, toY: number, durationMs?: number, momentum?: boolean } [chromium only]
|
|
127826
128102
|
gesture-custom: { events: [{ type: "Down"|"Move"|"Up", x: number, y: number, x2?: number, y2?: number, delayMs?: number }], interpolate?: number } [ios/android]
|
|
127827
128103
|
gesture-pinch: { centerX: number, centerY: number, startDistance: number, endDistance: number, endCenterX?: number, endCenterY?: number, angle?: number, durationMs?: number } [ios/android]
|
|
127828
128104
|
gesture-rotate: { centerX: number, centerY: number, radius?: number, radiusX?: number, radiusY?: number, startAngle: number, endAngle: number, durationMs?: number } [ios/android]
|
|
@@ -140883,6 +141159,12 @@ function chromiumLaunchSpec(launch) {
|
|
|
140883
141159
|
return typeof c === "string" ? { path: c } : { path: c.path, args: c.args };
|
|
140884
141160
|
}
|
|
140885
141161
|
function selectorToYaml(sel) {
|
|
141162
|
+
const unknown2 = Object.keys(sel).filter((key2) => !WRITABLE_SELECTOR_KEYS.includes(key2));
|
|
141163
|
+
if (unknown2.length > 0) {
|
|
141164
|
+
throw new Error(
|
|
141165
|
+
`Cannot serialize flow selector: ${describeUnknownKeys(unknown2, WRITABLE_SELECTOR_KEYS)} - allowed keys: ${WRITABLE_SELECTOR_KEYS.join(", ")}.`
|
|
141166
|
+
);
|
|
141167
|
+
}
|
|
140886
141168
|
if (sel.text !== void 0 && sel.textMatches !== void 0) {
|
|
140887
141169
|
throw new Error(
|
|
140888
141170
|
'Cannot serialize flow selector without losing constraints: both `text` and `textMatches` are set, but flow YAML can represent only one `text` constraint (a literal string or `{ matches: "<regex>" }`). Use either literal or regex text matching.'
|
|
@@ -141005,6 +141287,79 @@ function targetToYaml(step) {
|
|
|
141005
141287
|
}
|
|
141006
141288
|
return { x: step.x, y: step.y };
|
|
141007
141289
|
}
|
|
141290
|
+
function swipeTargetToYaml(target, label) {
|
|
141291
|
+
const yaml = targetToYaml(target);
|
|
141292
|
+
if (typeof yaml !== "string" && "x" in yaml && !Object.keys(target).every((key2) => key2 === "x" || key2 === "y")) {
|
|
141293
|
+
throw new Error(`Cannot serialize flow ${label}: a coordinate target takes only { x, y }`);
|
|
141294
|
+
}
|
|
141295
|
+
return yaml;
|
|
141296
|
+
}
|
|
141297
|
+
var SWIPE_MIN_TRAVEL = 0.03;
|
|
141298
|
+
var SWIPE_MIN_DURATION_MS = 150;
|
|
141299
|
+
var SWIPE_MAX_DURATION_MS = 1e4;
|
|
141300
|
+
var LONG_PRESS_MAX_DURATION_MS = SWIPE_MAX_DURATION_MS;
|
|
141301
|
+
function swipeByToYaml(by) {
|
|
141302
|
+
const keys = Object.keys(by);
|
|
141303
|
+
if (keys.some((key2) => key2 !== "x" && key2 !== "y")) {
|
|
141304
|
+
throw new Error("Cannot serialize flow swipe.by: accepts only x and y");
|
|
141305
|
+
}
|
|
141306
|
+
const axes = ["x", "y"].filter((axis) => by[axis] !== void 0);
|
|
141307
|
+
if (axes.length === 0) {
|
|
141308
|
+
throw new Error("Cannot serialize flow swipe.by: needs at least one of x or y");
|
|
141309
|
+
}
|
|
141310
|
+
const result = {};
|
|
141311
|
+
for (const axis of axes) {
|
|
141312
|
+
const value = by[axis];
|
|
141313
|
+
if (!Number.isFinite(value) || value === 0 || value < -1 || value > 1) {
|
|
141314
|
+
throw new Error(
|
|
141315
|
+
`Cannot serialize flow swipe.by.${axis}: must be a non-zero fraction of the screen between -1 and 1`
|
|
141316
|
+
);
|
|
141317
|
+
}
|
|
141318
|
+
result[axis] = value;
|
|
141319
|
+
}
|
|
141320
|
+
const magnitude = Math.hypot(result.x ?? 0, result.y ?? 0);
|
|
141321
|
+
if (magnitude < SWIPE_MIN_TRAVEL) {
|
|
141322
|
+
throw new Error(
|
|
141323
|
+
`Cannot serialize flow swipe.by: travels only ${magnitude} \u2014 below the minimum swipe travel of ${SWIPE_MIN_TRAVEL} \u2014 a travel that small is a tap, not a swipe`
|
|
141324
|
+
);
|
|
141325
|
+
}
|
|
141326
|
+
return result;
|
|
141327
|
+
}
|
|
141328
|
+
function swipeByLabel(by) {
|
|
141329
|
+
return ["x", "y"].filter((axis) => by[axis] !== void 0).map((axis) => `${axis}=${by[axis]}`).join(", ");
|
|
141330
|
+
}
|
|
141331
|
+
function isPositiveMs(raw) {
|
|
141332
|
+
return typeof raw === "number" && Number.isFinite(raw) && raw > 0;
|
|
141333
|
+
}
|
|
141334
|
+
function positiveMsToYaml(value, label) {
|
|
141335
|
+
if (!isPositiveMs(value)) {
|
|
141336
|
+
throw new Error(`Cannot serialize flow ${label}: needs a positive number of milliseconds`);
|
|
141337
|
+
}
|
|
141338
|
+
return value;
|
|
141339
|
+
}
|
|
141340
|
+
function swipeDurationToYaml(value) {
|
|
141341
|
+
const duration3 = positiveMsToYaml(value, "swipe.duration");
|
|
141342
|
+
if (duration3 < SWIPE_MIN_DURATION_MS) {
|
|
141343
|
+
throw new Error(
|
|
141344
|
+
`Cannot serialize flow swipe.duration: only ${duration3}ms \u2014 below the minimum swipe duration of ${SWIPE_MIN_DURATION_MS}ms \u2014 that leaves too few 16ms frames for the content to track the travel it was given, so it overshoots instead of landing on it`
|
|
141345
|
+
);
|
|
141346
|
+
}
|
|
141347
|
+
if (duration3 > SWIPE_MAX_DURATION_MS) {
|
|
141348
|
+
throw new Error(
|
|
141349
|
+
`Cannot serialize flow swipe.duration: ${duration3}ms - above the maximum swipe duration of ${SWIPE_MAX_DURATION_MS}ms - the step would hold a finger on the screen for exactly that long, one dispatched frame per 16ms`
|
|
141350
|
+
);
|
|
141351
|
+
}
|
|
141352
|
+
return duration3;
|
|
141353
|
+
}
|
|
141354
|
+
function longPressDurationToYaml(value) {
|
|
141355
|
+
const duration3 = positiveMsToYaml(value, "long-press.duration");
|
|
141356
|
+
if (duration3 > LONG_PRESS_MAX_DURATION_MS) {
|
|
141357
|
+
throw new Error(
|
|
141358
|
+
`Cannot serialize flow long-press.duration: ${duration3}ms - above the maximum long-press duration of ${LONG_PRESS_MAX_DURATION_MS}ms - the step would hold a finger down for exactly that long`
|
|
141359
|
+
);
|
|
141360
|
+
}
|
|
141361
|
+
return duration3;
|
|
141362
|
+
}
|
|
141008
141363
|
function waitToYaml(condition, selector, expectedText, textMatch, timeoutMs) {
|
|
141009
141364
|
const sel = selectorToYaml(selector);
|
|
141010
141365
|
let body;
|
|
@@ -141022,7 +141377,7 @@ function waitToYaml(condition, selector, expectedText, textMatch, timeoutMs) {
|
|
|
141022
141377
|
body = textWaitToYaml(sel, expectedText, textMatch);
|
|
141023
141378
|
break;
|
|
141024
141379
|
}
|
|
141025
|
-
if (timeoutMs !== void 0) body.timeout = timeoutMs;
|
|
141380
|
+
if (timeoutMs !== void 0) body.timeout = positiveMsToYaml(timeoutMs, "await.timeout");
|
|
141026
141381
|
return body;
|
|
141027
141382
|
}
|
|
141028
141383
|
function idleToYaml(step) {
|
|
@@ -141058,9 +141413,29 @@ function toYamlStep(step) {
|
|
|
141058
141413
|
case "long-press": {
|
|
141059
141414
|
const target = targetToYaml(step);
|
|
141060
141415
|
return {
|
|
141061
|
-
"long-press": step.duration !== void 0 ? { on: target, duration: step.duration } : target
|
|
141416
|
+
"long-press": step.duration !== void 0 ? { on: target, duration: longPressDurationToYaml(step.duration) } : target
|
|
141062
141417
|
};
|
|
141063
141418
|
}
|
|
141419
|
+
case "swipe": {
|
|
141420
|
+
const travels = ["direction", "to", "by"].filter((key2) => step[key2] !== void 0);
|
|
141421
|
+
if (travels.length !== 1) {
|
|
141422
|
+
throw new Error("Cannot serialize flow swipe: needs exactly one of direction, to, or by");
|
|
141423
|
+
}
|
|
141424
|
+
if (step.momentum !== void 0 && typeof step.momentum !== "boolean") {
|
|
141425
|
+
throw new Error("Cannot serialize flow swipe.momentum: must be true or false");
|
|
141426
|
+
}
|
|
141427
|
+
if (step.direction !== void 0 && step.from === void 0 && step.to === void 0 && step.by === void 0 && step.momentum !== false && step.duration === void 0) {
|
|
141428
|
+
return { swipe: step.direction };
|
|
141429
|
+
}
|
|
141430
|
+
const body = {};
|
|
141431
|
+
if (step.from !== void 0) body.from = swipeTargetToYaml(step.from, "swipe.from");
|
|
141432
|
+
if (step.direction !== void 0) body.direction = step.direction;
|
|
141433
|
+
if (step.to !== void 0) body.to = swipeTargetToYaml(step.to, "swipe.to");
|
|
141434
|
+
if (step.by !== void 0) body.by = swipeByToYaml(step.by);
|
|
141435
|
+
if (step.momentum === false) body.momentum = false;
|
|
141436
|
+
if (step.duration !== void 0) body.duration = swipeDurationToYaml(step.duration);
|
|
141437
|
+
return { swipe: body };
|
|
141438
|
+
}
|
|
141064
141439
|
case "type": {
|
|
141065
141440
|
const body = {
|
|
141066
141441
|
into: selectorToYaml(step.into),
|
|
@@ -141153,6 +141528,12 @@ function badEntry(raw, detail) {
|
|
|
141153
141528
|
error_kind: "validation"
|
|
141154
141529
|
});
|
|
141155
141530
|
}
|
|
141531
|
+
function parsePositiveMs(raw, entry, label, example) {
|
|
141532
|
+
if (!isPositiveMs(raw)) {
|
|
141533
|
+
badEntry(entry, `${label} needs a positive number of milliseconds (e.g. \`${example}\`)`);
|
|
141534
|
+
}
|
|
141535
|
+
return raw;
|
|
141536
|
+
}
|
|
141156
141537
|
function validatePattern(raw, pattern, where) {
|
|
141157
141538
|
try {
|
|
141158
141539
|
new RegExp(pattern);
|
|
@@ -141216,6 +141597,17 @@ var SELECTOR_KEYS = [
|
|
|
141216
141597
|
"any",
|
|
141217
141598
|
...SELECTOR_RELATIONS
|
|
141218
141599
|
];
|
|
141600
|
+
var WRITABLE_SELECTOR_KEYS = Object.keys({
|
|
141601
|
+
text: true,
|
|
141602
|
+
textMatches: true,
|
|
141603
|
+
identifier: true,
|
|
141604
|
+
role: true,
|
|
141605
|
+
any: true,
|
|
141606
|
+
loose: true,
|
|
141607
|
+
within: true,
|
|
141608
|
+
after: true,
|
|
141609
|
+
next: true
|
|
141610
|
+
});
|
|
141219
141611
|
var MAX_SELECTOR_SCOPES = 6;
|
|
141220
141612
|
function parseSelector(raw, where, budget = { scopes: MAX_SELECTOR_SCOPES }) {
|
|
141221
141613
|
if (budget.scopes < 0) {
|
|
@@ -141346,7 +141738,7 @@ function parseWaitFields(raw, kind) {
|
|
|
141346
141738
|
"assert has no timeout \u2014 it is an immediate check; use `await` for a timed wait"
|
|
141347
141739
|
);
|
|
141348
141740
|
}
|
|
141349
|
-
timeout =
|
|
141741
|
+
timeout = parsePositiveMs(b.timeout, { [kind]: b }, "await.timeout", "timeout: 10000");
|
|
141350
141742
|
}
|
|
141351
141743
|
rejectUnknownKeys(
|
|
141352
141744
|
{ [kind]: b },
|
|
@@ -141515,6 +141907,7 @@ var STEP_DIRECTIVE_KEYS = [
|
|
|
141515
141907
|
"tool",
|
|
141516
141908
|
"tap",
|
|
141517
141909
|
"long-press",
|
|
141910
|
+
"swipe",
|
|
141518
141911
|
"type",
|
|
141519
141912
|
"await",
|
|
141520
141913
|
"assert",
|
|
@@ -141613,13 +142006,19 @@ function parseLongPress(body, entry) {
|
|
|
141613
142006
|
}
|
|
141614
142007
|
const step = { kind: "long-press", ...parseTarget(obj.on, "long-press.on") };
|
|
141615
142008
|
if (obj.duration !== void 0) {
|
|
141616
|
-
|
|
142009
|
+
const duration3 = parsePositiveMs(
|
|
142010
|
+
obj.duration,
|
|
142011
|
+
entry,
|
|
142012
|
+
"long-press.duration",
|
|
142013
|
+
"duration: 1200"
|
|
142014
|
+
);
|
|
142015
|
+
if (duration3 > LONG_PRESS_MAX_DURATION_MS) {
|
|
141617
142016
|
badEntry(
|
|
141618
142017
|
entry,
|
|
141619
|
-
|
|
142018
|
+
`long-press.duration is ${duration3}ms - above the maximum long-press duration of ${LONG_PRESS_MAX_DURATION_MS}ms; the step holds a finger down for exactly that long, and on Chromium it dispatches a gesture-drag that refuses more`
|
|
141620
142019
|
);
|
|
141621
142020
|
}
|
|
141622
|
-
step.duration =
|
|
142021
|
+
step.duration = duration3;
|
|
141623
142022
|
}
|
|
141624
142023
|
return step;
|
|
141625
142024
|
}
|
|
@@ -141789,6 +142188,116 @@ function completeRunExtension(value) {
|
|
|
141789
142188
|
const candidate = `${value}.yaml`;
|
|
141790
142189
|
return FLOW_FILE_NAME_PATTERN.test(path32.posix.basename(candidate)) ? candidate : value;
|
|
141791
142190
|
}
|
|
142191
|
+
var SWIPE_DIRECTIONS = ["up", "down", "left", "right"];
|
|
142192
|
+
var SWIPE_OPTION_KEYS = ["from", "direction", "to", "by", "momentum", "duration"];
|
|
142193
|
+
function parseSwipeBy(raw, entry) {
|
|
142194
|
+
if (raw === null || typeof raw !== "object") {
|
|
142195
|
+
badEntry(entry, "swipe.by needs { x } and/or { y } \u2014 signed 0\u20131 fractions of the screen");
|
|
142196
|
+
}
|
|
142197
|
+
const obj = raw;
|
|
142198
|
+
rejectUnknownKeys(entry, obj, ["x", "y"], "swipe.by");
|
|
142199
|
+
if (obj.x === void 0 && obj.y === void 0) {
|
|
142200
|
+
badEntry(entry, "swipe.by needs at least one of x, y");
|
|
142201
|
+
}
|
|
142202
|
+
const by = {};
|
|
142203
|
+
for (const axis of ["x", "y"]) {
|
|
142204
|
+
const v = obj[axis];
|
|
142205
|
+
if (v === void 0) continue;
|
|
142206
|
+
if (typeof v !== "number" || !Number.isFinite(v) || v === 0 || v < -1 || v > 1) {
|
|
142207
|
+
badEntry(
|
|
142208
|
+
entry,
|
|
142209
|
+
`swipe.by.${axis} must be a non-zero fraction of the screen between -1 and 1 (omit the axis instead of 0)`
|
|
142210
|
+
);
|
|
142211
|
+
}
|
|
142212
|
+
by[axis] = v;
|
|
142213
|
+
}
|
|
142214
|
+
const magnitude = Math.hypot(by.x ?? 0, by.y ?? 0);
|
|
142215
|
+
if (magnitude < SWIPE_MIN_TRAVEL) {
|
|
142216
|
+
badEntry(
|
|
142217
|
+
entry,
|
|
142218
|
+
`swipe.by travels only ${magnitude} \u2014 below the minimum swipe travel of ${SWIPE_MIN_TRAVEL}; a travel that small is a tap, not a swipe`
|
|
142219
|
+
);
|
|
142220
|
+
}
|
|
142221
|
+
return by;
|
|
142222
|
+
}
|
|
142223
|
+
function parseSwipe(body, entry) {
|
|
142224
|
+
if (typeof body === "string") {
|
|
142225
|
+
if (!SWIPE_DIRECTIONS.includes(body)) {
|
|
142226
|
+
badEntry(
|
|
142227
|
+
entry,
|
|
142228
|
+
`swipe takes a direction (${SWIPE_DIRECTIONS.join(", ")}) \u2014 to anchor on an element use swipe: { from: <target>, direction: \u2026 }`
|
|
142229
|
+
);
|
|
142230
|
+
}
|
|
142231
|
+
return { kind: "swipe", direction: body };
|
|
142232
|
+
}
|
|
142233
|
+
if (body === null || typeof body !== "object") {
|
|
142234
|
+
badEntry(entry, `swipe needs a direction (${SWIPE_DIRECTIONS.join(", ")}) or an options map`);
|
|
142235
|
+
}
|
|
142236
|
+
const obj = body;
|
|
142237
|
+
if (hasSelectorField(obj)) {
|
|
142238
|
+
badEntry(
|
|
142239
|
+
entry,
|
|
142240
|
+
'the swipe options form takes a nested target \u2014 e.g. swipe: { from: { text: "Card" }, direction: left }'
|
|
142241
|
+
);
|
|
142242
|
+
}
|
|
142243
|
+
if (obj.x !== void 0 || obj.y !== void 0) {
|
|
142244
|
+
badEntry(
|
|
142245
|
+
entry,
|
|
142246
|
+
"the swipe options form takes a nested point \u2014 e.g. swipe: { from: { x: 0.5, y: 0.5 }, direction: left }"
|
|
142247
|
+
);
|
|
142248
|
+
}
|
|
142249
|
+
if (obj.settle !== void 0) {
|
|
142250
|
+
badEntry(
|
|
142251
|
+
entry,
|
|
142252
|
+
"swipe.settle was renamed to swipe.momentum, with the opposite sense \u2014 write `momentum: false` for the momentum-free swipe that `settle: true` used to mean (plain `settle: false` was the default, so just drop it)"
|
|
142253
|
+
);
|
|
142254
|
+
}
|
|
142255
|
+
rejectUnknownKeys(entry, obj, SWIPE_OPTION_KEYS, "swipe");
|
|
142256
|
+
const travels = ["direction", "to", "by"].filter((k) => obj[k] !== void 0);
|
|
142257
|
+
if (travels.length !== 1) {
|
|
142258
|
+
badEntry(entry, "swipe needs exactly one of `direction`, `to`, or `by`");
|
|
142259
|
+
}
|
|
142260
|
+
const step = { kind: "swipe" };
|
|
142261
|
+
if (obj.from !== void 0) step.from = parseTarget(obj.from, "swipe.from");
|
|
142262
|
+
switch (travels[0]) {
|
|
142263
|
+
case "direction": {
|
|
142264
|
+
if (typeof obj.direction !== "string" || !SWIPE_DIRECTIONS.includes(obj.direction)) {
|
|
142265
|
+
badEntry(entry, `swipe.direction must be one of ${SWIPE_DIRECTIONS.join(", ")}`);
|
|
142266
|
+
}
|
|
142267
|
+
step.direction = obj.direction;
|
|
142268
|
+
break;
|
|
142269
|
+
}
|
|
142270
|
+
case "to":
|
|
142271
|
+
step.to = parseTarget(obj.to, "swipe.to");
|
|
142272
|
+
break;
|
|
142273
|
+
case "by":
|
|
142274
|
+
step.by = parseSwipeBy(obj.by, entry);
|
|
142275
|
+
break;
|
|
142276
|
+
}
|
|
142277
|
+
if (obj.momentum !== void 0) {
|
|
142278
|
+
if (typeof obj.momentum !== "boolean") {
|
|
142279
|
+
badEntry(entry, "swipe.momentum must be true or false");
|
|
142280
|
+
}
|
|
142281
|
+
if (!obj.momentum) step.momentum = false;
|
|
142282
|
+
}
|
|
142283
|
+
if (obj.duration !== void 0) {
|
|
142284
|
+
const duration3 = parsePositiveMs(obj.duration, entry, "swipe.duration", "duration: 800");
|
|
142285
|
+
if (duration3 < SWIPE_MIN_DURATION_MS) {
|
|
142286
|
+
badEntry(
|
|
142287
|
+
entry,
|
|
142288
|
+
`swipe.duration is only ${duration3}ms \u2014 below the minimum swipe duration of ${SWIPE_MIN_DURATION_MS}ms; that leaves too few 16ms frames for the content to track the travel it was given, so it overshoots instead of landing on it`
|
|
142289
|
+
);
|
|
142290
|
+
}
|
|
142291
|
+
if (duration3 > SWIPE_MAX_DURATION_MS) {
|
|
142292
|
+
badEntry(
|
|
142293
|
+
entry,
|
|
142294
|
+
`swipe.duration is ${duration3}ms - above the maximum swipe duration of ${SWIPE_MAX_DURATION_MS}ms; the step holds a finger on the screen for exactly that long, one dispatched frame per 16ms, and nothing outside the run can cut it short`
|
|
142295
|
+
);
|
|
142296
|
+
}
|
|
142297
|
+
step.duration = duration3;
|
|
142298
|
+
}
|
|
142299
|
+
return step;
|
|
142300
|
+
}
|
|
141792
142301
|
function fromYamlStep(raw, blockDepth = 0) {
|
|
141793
142302
|
const entry = raw;
|
|
141794
142303
|
if ("optional" in raw) {
|
|
@@ -141830,6 +142339,7 @@ function fromYamlStep(raw, blockDepth = 0) {
|
|
|
141830
142339
|
if ("long-press" in raw) {
|
|
141831
142340
|
return parseLongPress(raw["long-press"], raw);
|
|
141832
142341
|
}
|
|
142342
|
+
if ("swipe" in raw) return parseSwipe(raw.swipe, raw);
|
|
141833
142343
|
if ("type" in raw) {
|
|
141834
142344
|
const body = raw.type;
|
|
141835
142345
|
if (!body || typeof body !== "object") badEntry(raw, "type needs { into, text }");
|
|
@@ -142446,6 +142956,7 @@ function stepRequiresDevice(registry2, step) {
|
|
|
142446
142956
|
case "launch":
|
|
142447
142957
|
case "tap":
|
|
142448
142958
|
case "long-press":
|
|
142959
|
+
case "swipe":
|
|
142449
142960
|
case "type":
|
|
142450
142961
|
case "await":
|
|
142451
142962
|
case "assert":
|
|
@@ -143287,22 +143798,32 @@ async function settleTree(env, opts = {}) {
|
|
|
143287
143798
|
if (!await sleepOrAbort(SETTLE_POLL_MS, env.signal)) return void 0;
|
|
143288
143799
|
}
|
|
143289
143800
|
}
|
|
143290
|
-
async function
|
|
143801
|
+
async function waitForFrames(env, selectors) {
|
|
143802
|
+
const pending = selectors.flatMap((selector, i) => selector ? [{ i, selector }] : []);
|
|
143803
|
+
if (pending.length === 0) return selectors.map(() => void 0);
|
|
143291
143804
|
const deadline = Date.now() + DEFAULT_ACTION_TIMEOUT_MS;
|
|
143805
|
+
let unresolved = pending[0].selector;
|
|
143292
143806
|
for (; ; ) {
|
|
143293
143807
|
if (env.signal?.aborted) return "aborted";
|
|
143294
143808
|
const tree = await settleTree(env);
|
|
143295
143809
|
if (tree) {
|
|
143296
|
-
const
|
|
143297
|
-
|
|
143810
|
+
const frames = selectors.map((s) => s ? flowSelectorToFrame(tree, s) : void 0);
|
|
143811
|
+
const missing = pending.find(({ i }) => frames[i] === void 0);
|
|
143812
|
+
if (!missing) return frames;
|
|
143813
|
+
unresolved = missing.selector;
|
|
143298
143814
|
} else if (env.signal?.aborted) {
|
|
143299
143815
|
return "aborted";
|
|
143300
143816
|
}
|
|
143301
|
-
if (Date.now() >= deadline) return
|
|
143817
|
+
if (Date.now() >= deadline) return { unresolved };
|
|
143302
143818
|
const sleepMs = Math.min(POLL_INTERVAL_MS, Math.max(0, deadline - Date.now()));
|
|
143303
143819
|
if (!await sleepOrAbort(sleepMs, env.signal)) return "aborted";
|
|
143304
143820
|
}
|
|
143305
143821
|
}
|
|
143822
|
+
async function waitForFrame(env, selector) {
|
|
143823
|
+
const frames = await waitForFrames(env, [selector]);
|
|
143824
|
+
if (frames === "aborted") return "aborted";
|
|
143825
|
+
return Array.isArray(frames) ? frames[0] : void 0;
|
|
143826
|
+
}
|
|
143306
143827
|
function framesOverlap(a, b) {
|
|
143307
143828
|
return a.x < b.x + b.width && b.x < a.x + a.width && a.y < b.y + b.height && b.y < a.y + a.height;
|
|
143308
143829
|
}
|
|
@@ -143366,14 +143887,19 @@ async function scrollIncrement(env, direction, region) {
|
|
|
143366
143887
|
to = { x: clamp014(cx + dist), y: cy };
|
|
143367
143888
|
break;
|
|
143368
143889
|
}
|
|
143369
|
-
|
|
143370
|
-
|
|
143371
|
-
|
|
143372
|
-
|
|
143373
|
-
|
|
143374
|
-
|
|
143375
|
-
|
|
143376
|
-
|
|
143890
|
+
try {
|
|
143891
|
+
await invokeOnDevice(env, "gesture-swipe", {
|
|
143892
|
+
fromX: cx,
|
|
143893
|
+
fromY: cy,
|
|
143894
|
+
toX: to.x,
|
|
143895
|
+
toY: to.y,
|
|
143896
|
+
momentum: false,
|
|
143897
|
+
durationMs: 600
|
|
143898
|
+
});
|
|
143899
|
+
} catch (err) {
|
|
143900
|
+
if (env.signal?.aborted) return;
|
|
143901
|
+
throw err;
|
|
143902
|
+
}
|
|
143377
143903
|
}
|
|
143378
143904
|
async function scrollToVisible(env, target, direction, within) {
|
|
143379
143905
|
let prevFp;
|
|
@@ -143407,7 +143933,7 @@ function offscreenHint(sel) {
|
|
|
143407
143933
|
return `no visible element matched selector ${describeSelector(sel)} \u2014 if it is off-screen, add a scroll-to step before this one`;
|
|
143408
143934
|
}
|
|
143409
143935
|
async function runDirective(env, step) {
|
|
143410
|
-
if (env.device.platform === "vega" && (step.kind === "tap" || step.kind === "long-press" || step.kind === "type" || step.kind === "scroll-to" || step.kind === "pinch" || step.kind === "rotate")) {
|
|
143936
|
+
if (env.device.platform === "vega" && (step.kind === "tap" || step.kind === "long-press" || step.kind === "swipe" || step.kind === "type" || step.kind === "scroll-to" || step.kind === "pinch" || step.kind === "rotate")) {
|
|
143411
143937
|
return {
|
|
143412
143938
|
ok: false,
|
|
143413
143939
|
reason: `${step.kind} is a touch directive and Vega is remote-driven \u2014 move focus with \`tool: tv-remote\` steps (and type via \`tool: keyboard\`) instead`
|
|
@@ -143424,6 +143950,8 @@ async function runDirective(env, step) {
|
|
|
143424
143950
|
return runTap(env, step);
|
|
143425
143951
|
case "long-press":
|
|
143426
143952
|
return runLongPress(env, step);
|
|
143953
|
+
case "swipe":
|
|
143954
|
+
return runSwipe(env, step);
|
|
143427
143955
|
case "type":
|
|
143428
143956
|
return runType(env, step);
|
|
143429
143957
|
case "await":
|
|
@@ -143471,10 +143999,16 @@ async function resolveTargetPoint(env, target) {
|
|
|
143471
143999
|
}
|
|
143472
144000
|
return { point: getDescribeTapPoint(frame) };
|
|
143473
144001
|
}
|
|
144002
|
+
const point = targetPointFromFrame(target, void 0);
|
|
144003
|
+
if ("fail" in point) return point;
|
|
144004
|
+
const settle = await settleForGesture(env);
|
|
144005
|
+
if (settle.aborted) return { fail: ABORTED_OUTCOME };
|
|
144006
|
+
return { point, ...warned(settle) };
|
|
144007
|
+
}
|
|
144008
|
+
function targetPointFromFrame(target, frame) {
|
|
144009
|
+
if (frame) return getDescribeTapPoint(frame);
|
|
143474
144010
|
if (typeof target.x === "number" && typeof target.y === "number") {
|
|
143475
|
-
|
|
143476
|
-
if (settle.aborted) return { fail: ABORTED_OUTCOME };
|
|
143477
|
-
return { point: { x: target.x, y: target.y }, ...warned(settle) };
|
|
144011
|
+
return { x: target.x, y: target.y };
|
|
143478
144012
|
}
|
|
143479
144013
|
return { fail: { ok: false, reason: "gesture needs a selector or x/y coordinates" } };
|
|
143480
144014
|
}
|
|
@@ -143494,13 +144028,18 @@ async function runLongPress(env, step) {
|
|
|
143494
144028
|
const point = resolved.point;
|
|
143495
144029
|
const duration3 = step.duration ?? DEFAULT_LONG_PRESS_MS;
|
|
143496
144030
|
if (env.device.platform === "chromium") {
|
|
143497
|
-
|
|
143498
|
-
|
|
143499
|
-
|
|
143500
|
-
|
|
143501
|
-
|
|
143502
|
-
|
|
143503
|
-
|
|
144031
|
+
try {
|
|
144032
|
+
await invokeOnDevice(env, "gesture-drag", {
|
|
144033
|
+
fromX: point.x,
|
|
144034
|
+
fromY: point.y,
|
|
144035
|
+
toX: point.x,
|
|
144036
|
+
toY: point.y,
|
|
144037
|
+
durationMs: duration3
|
|
144038
|
+
});
|
|
144039
|
+
} catch (err) {
|
|
144040
|
+
if (env.signal?.aborted) return ABORTED_OUTCOME;
|
|
144041
|
+
throw err;
|
|
144042
|
+
}
|
|
143504
144043
|
} else {
|
|
143505
144044
|
await invokeOnDevice(env, "gesture-custom", {
|
|
143506
144045
|
events: [
|
|
@@ -143620,6 +144159,132 @@ async function runRotate(env, step) {
|
|
|
143620
144159
|
}
|
|
143621
144160
|
return { ok: true, ...warned(settle) };
|
|
143622
144161
|
}
|
|
144162
|
+
var SWIPE_GEOMETRY = {
|
|
144163
|
+
left: { start: { x: 0.9, y: 0.5 }, axis: "x", end: 0.1 },
|
|
144164
|
+
right: { start: { x: 0.1, y: 0.5 }, axis: "x", end: 0.9 },
|
|
144165
|
+
down: { start: { x: 0.5, y: 0.2 }, axis: "y", end: 0.9 },
|
|
144166
|
+
up: { start: { x: 0.5, y: 0.5 }, axis: "y", end: 0.1 }
|
|
144167
|
+
};
|
|
144168
|
+
async function runSwipe(env, step) {
|
|
144169
|
+
const ends = [step.from, step.to];
|
|
144170
|
+
const selectors = ends.map((end2) => end2 && "selector" in end2 ? end2.selector : void 0);
|
|
144171
|
+
const frames = await waitForFrames(env, selectors);
|
|
144172
|
+
if (frames === "aborted") return ABORTED_OUTCOME;
|
|
144173
|
+
if (!Array.isArray(frames)) return { ok: false, reason: offscreenHint(frames.unresolved) };
|
|
144174
|
+
const [fromFrame, toFrame] = frames;
|
|
144175
|
+
let settle = {};
|
|
144176
|
+
if (selectors.every((selector) => selector === void 0)) {
|
|
144177
|
+
settle = await settleForGesture(env);
|
|
144178
|
+
if (settle.aborted) return ABORTED_OUTCOME;
|
|
144179
|
+
}
|
|
144180
|
+
let toPoint;
|
|
144181
|
+
if (step.to) {
|
|
144182
|
+
const p = targetPointFromFrame(step.to, toFrame);
|
|
144183
|
+
if ("fail" in p) return p.fail;
|
|
144184
|
+
toPoint = p;
|
|
144185
|
+
}
|
|
144186
|
+
let start2;
|
|
144187
|
+
if (step.from) {
|
|
144188
|
+
const p = targetPointFromFrame(step.from, fromFrame);
|
|
144189
|
+
if ("fail" in p) return p.fail;
|
|
144190
|
+
start2 = p;
|
|
144191
|
+
} else if (step.direction) {
|
|
144192
|
+
start2 = { ...SWIPE_GEOMETRY[step.direction].start };
|
|
144193
|
+
} else {
|
|
144194
|
+
start2 = { x: 0.5, y: 0.5 };
|
|
144195
|
+
}
|
|
144196
|
+
if (!Number.isFinite(start2.x) || start2.x < 0 || start2.x > 1 || !Number.isFinite(start2.y) || start2.y < 0 || start2.y > 1) {
|
|
144197
|
+
return {
|
|
144198
|
+
ok: false,
|
|
144199
|
+
reason: `swipe.from resolved outside the normalized screen: (${start2.x}, ${start2.y}); both coordinates must be between 0 and 1`
|
|
144200
|
+
};
|
|
144201
|
+
}
|
|
144202
|
+
let end;
|
|
144203
|
+
if (step.direction) {
|
|
144204
|
+
const g = SWIPE_GEOMETRY[step.direction];
|
|
144205
|
+
const startOnTravelAxis = start2[g.axis];
|
|
144206
|
+
const endOnTravelAxis = step.from ? clamp014(startOnTravelAxis + (g.end - g.start[g.axis])) : g.end;
|
|
144207
|
+
end = g.axis === "x" ? { x: endOnTravelAxis, y: start2.y } : { x: start2.x, y: endOnTravelAxis };
|
|
144208
|
+
const travel2 = Math.abs(endOnTravelAxis - startOnTravelAxis);
|
|
144209
|
+
if (travel2 < SWIPE_MIN_TRAVEL) {
|
|
144210
|
+
return {
|
|
144211
|
+
ok: false,
|
|
144212
|
+
reason: `cannot swipe ${step.direction} from ${g.axis}=${startOnTravelAxis}: only ${travel2} of travel to the screen edge, less than the minimum swipe travel of ${SWIPE_MIN_TRAVEL} \u2014 a tap, not a swipe`
|
|
144213
|
+
};
|
|
144214
|
+
}
|
|
144215
|
+
} else if (step.by) {
|
|
144216
|
+
const overflowAxis = ["x", "y"].find((axis) => {
|
|
144217
|
+
const d = step.by[axis];
|
|
144218
|
+
return d !== void 0 && (start2[axis] + d < 0 || start2[axis] + d > 1);
|
|
144219
|
+
});
|
|
144220
|
+
if (overflowAxis === void 0) {
|
|
144221
|
+
end = {
|
|
144222
|
+
x: step.by.x !== void 0 ? start2.x + step.by.x : start2.x,
|
|
144223
|
+
y: step.by.y !== void 0 ? start2.y + step.by.y : start2.y
|
|
144224
|
+
};
|
|
144225
|
+
} else if (step.from) {
|
|
144226
|
+
const requested = step.by[overflowAxis];
|
|
144227
|
+
const raw = start2[overflowAxis] + requested;
|
|
144228
|
+
return {
|
|
144229
|
+
ok: false,
|
|
144230
|
+
reason: `swipe.by.${overflowAxis} of ${requested} from ${overflowAxis}=${start2[overflowAxis]} lands at ${raw}, off the normalized screen; reduce the delta so from + by stays within [0, 1]`
|
|
144231
|
+
};
|
|
144232
|
+
} else {
|
|
144233
|
+
end = { x: start2.x, y: start2.y };
|
|
144234
|
+
for (const axis of ["x", "y"]) {
|
|
144235
|
+
const d = step.by[axis];
|
|
144236
|
+
if (d === void 0) {
|
|
144237
|
+
end[axis] = start2[axis];
|
|
144238
|
+
continue;
|
|
144239
|
+
}
|
|
144240
|
+
const s = start2[axis];
|
|
144241
|
+
const lo = Math.min(s, s + d);
|
|
144242
|
+
const hi = Math.max(s, s + d);
|
|
144243
|
+
const shift = lo < 0 ? -lo : hi > 1 ? 1 - hi : 0;
|
|
144244
|
+
start2[axis] = s + shift;
|
|
144245
|
+
end[axis] = s + d + shift;
|
|
144246
|
+
}
|
|
144247
|
+
}
|
|
144248
|
+
} else {
|
|
144249
|
+
end = toPoint;
|
|
144250
|
+
if (!Number.isFinite(end.x) || end.x < 0 || end.x > 1 || !Number.isFinite(end.y) || end.y < 0 || end.y > 1) {
|
|
144251
|
+
return {
|
|
144252
|
+
ok: false,
|
|
144253
|
+
reason: `swipe.to resolved outside the normalized screen: (${end.x}, ${end.y}); both coordinates must be between 0 and 1`
|
|
144254
|
+
};
|
|
144255
|
+
}
|
|
144256
|
+
if (Math.hypot(end.x - start2.x, end.y - start2.y) < SWIPE_MIN_TRAVEL) {
|
|
144257
|
+
return {
|
|
144258
|
+
ok: false,
|
|
144259
|
+
reason: `swipe.to (${end.x}, ${end.y}) resolved within the minimum swipe travel of the start point (${start2.x}, ${start2.y}); aim it at a point or element farther from the start`
|
|
144260
|
+
};
|
|
144261
|
+
}
|
|
144262
|
+
}
|
|
144263
|
+
const travel = {
|
|
144264
|
+
fromX: start2.x,
|
|
144265
|
+
fromY: start2.y,
|
|
144266
|
+
toX: end.x,
|
|
144267
|
+
toY: end.y,
|
|
144268
|
+
...step.duration !== void 0 ? { durationMs: step.duration } : {},
|
|
144269
|
+
...step.momentum === false ? { momentum: false } : {}
|
|
144270
|
+
};
|
|
144271
|
+
try {
|
|
144272
|
+
await invokeOnDevice(
|
|
144273
|
+
env,
|
|
144274
|
+
env.device.platform === "chromium" ? "gesture-drag" : "gesture-swipe",
|
|
144275
|
+
travel
|
|
144276
|
+
);
|
|
144277
|
+
} catch (err) {
|
|
144278
|
+
if (env.signal?.aborted) return ABORTED_OUTCOME;
|
|
144279
|
+
throw err;
|
|
144280
|
+
}
|
|
144281
|
+
try {
|
|
144282
|
+
await settleTree(env);
|
|
144283
|
+
} catch {
|
|
144284
|
+
}
|
|
144285
|
+
if (env.signal?.aborted) return ABORTED_OUTCOME;
|
|
144286
|
+
return { ok: true, ...warned(settle) };
|
|
144287
|
+
}
|
|
143623
144288
|
async function runType(env, step) {
|
|
143624
144289
|
const frame = await waitForFrame(env, step.into);
|
|
143625
144290
|
if (frame === "aborted") return ABORTED_OUTCOME;
|
|
@@ -143951,6 +144616,9 @@ function textConditionLabel(sel, expectedText, textMatch) {
|
|
|
143951
144616
|
const expected = expectedText ?? "";
|
|
143952
144617
|
return textMatch === "matches" ? `text ${selector} matches /${expected}/` : textMatch === "equals" ? `text ${selector} == ${JSON.stringify(expected)}` : `text ${selector} contains ${JSON.stringify(expected)}`;
|
|
143953
144618
|
}
|
|
144619
|
+
function targetLabel(target) {
|
|
144620
|
+
return "selector" in target ? selectorLabel(target.selector) : `(${target.x}, ${target.y})`;
|
|
144621
|
+
}
|
|
143954
144622
|
var zodSchema62 = external_exports.object({
|
|
143955
144623
|
name: external_exports.string().describe("Name of the flow being recorded \u2014 the one passed to flow-start-recording."),
|
|
143956
144624
|
project_root: external_exports.string().describe(
|
|
@@ -144078,11 +144746,23 @@ function summarizeStep(step, n) {
|
|
|
144078
144746
|
return `${n}. run: ${step.flow}`;
|
|
144079
144747
|
case "tap":
|
|
144080
144748
|
case "long-press": {
|
|
144081
|
-
const target =
|
|
144749
|
+
const target = targetLabel(
|
|
144750
|
+
step.selector ? { selector: step.selector } : { x: step.x, y: step.y }
|
|
144751
|
+
);
|
|
144082
144752
|
const times = step.kind === "tap" && step.times !== void 0 && step.times > 1 ? ` \xD7${step.times}` : "";
|
|
144083
144753
|
const held = step.kind === "long-press" && step.duration !== void 0 ? ` for ${step.duration}ms` : "";
|
|
144084
144754
|
return `${n}. ${step.kind}: ${target}${times}${held}`;
|
|
144085
144755
|
}
|
|
144756
|
+
case "swipe": {
|
|
144757
|
+
const travel = step.direction ?? (step.by ? `by ${swipeByLabel(step.by)}` : `to ${targetLabel(step.to)}`);
|
|
144758
|
+
const from2 = step.from ? ` from ${targetLabel(step.from)}` : "";
|
|
144759
|
+
const options = [
|
|
144760
|
+
...step.momentum === false ? ["momentum-free"] : [],
|
|
144761
|
+
...step.duration !== void 0 ? [`${step.duration}ms`] : []
|
|
144762
|
+
];
|
|
144763
|
+
const tail = options.length > 0 ? ` (${options.join(", ")})` : "";
|
|
144764
|
+
return `${n}. swipe: ${travel}${from2}${tail}`;
|
|
144765
|
+
}
|
|
144086
144766
|
case "type":
|
|
144087
144767
|
return `${n}. type: ${selectorLabel(step.into)} \u2190 "${step.text}"`;
|
|
144088
144768
|
case "await":
|
|
@@ -144124,7 +144804,7 @@ var zodSchema63 = external_exports.object({
|
|
|
144124
144804
|
"Absolute path to the project root of the flow being recorded \u2014 the same value passed to flow-start-recording. Together with `name` it identifies which recording this step belongs to."
|
|
144125
144805
|
),
|
|
144126
144806
|
command: external_exports.string().describe(
|
|
144127
|
-
'MCP tool name (e.g. "gesture-tap", "screenshot", "launch-app") \u2014 a TOOL, not a flow directive. A flow-file directive name ("tap", "launch", "run", "type", "await", "assert", "pinch", "echo", "wait", "long-press", "scroll-to", "snapshot", "when") is answered with guidance, and nothing runs or is recorded: most name the tool that records the directive, while "wait", "long-press", "scroll-to", "snapshot" and "when" have no recording tool at all and are answered with what to do instead. A recording tool (flow-add-step, flow-add-echo, flow-start-recording, flow-finish-recording) is refused the same way, each for its own reason \u2014 nesting one would erase this flow at replay, end the take, or write the step twice.'
|
|
144807
|
+
'MCP tool name (e.g. "gesture-tap", "screenshot", "launch-app") \u2014 a TOOL, not a flow directive. A flow-file directive name ("tap", "launch", "run", "type", "await", "assert", "pinch", "swipe", "echo", "wait", "long-press", "scroll-to", "snapshot", "when") is answered with guidance, and nothing runs or is recorded: most name the tool that records the directive, while "wait", "long-press", "scroll-to", "snapshot" and "when" have no recording tool at all and are answered with what to do instead. A recording tool (flow-add-step, flow-add-echo, flow-start-recording, flow-finish-recording) is refused the same way, each for its own reason \u2014 nesting one would erase this flow at replay, end the take, or write the step twice.'
|
|
144128
144808
|
),
|
|
144129
144809
|
args: external_exports.string().optional().describe(
|
|
144130
144810
|
`Tool arguments as a JSON string, e.g. '{"udid": "ABC", "x": 0.5, "y": 0.3}'. Omit for tools with no arguments.`
|
|
@@ -144378,6 +145058,9 @@ function isToolNotFound(err, command) {
|
|
|
144378
145058
|
return err instanceof ToolNotFoundError && err.toolId === command;
|
|
144379
145059
|
}
|
|
144380
145060
|
function directiveCommandHint(command) {
|
|
145061
|
+
if (command === "swipe") {
|
|
145062
|
+
return `"swipe" is a flow directive, not a tool. Record the movement by calling \`gesture-swipe\` (\`gesture-drag\` on chromium, where gesture-swipe is not supported) through flow-add-step. It is stored as the raw \`tool:\` step for whichever one you called; converting it to \`swipe:\` is part of the polish pass.`;
|
|
145063
|
+
}
|
|
144381
145064
|
if (command === "echo") {
|
|
144382
145065
|
return `"echo" is a flow directive, not a tool. Call \`flow-add-echo\` DIRECTLY \u2014 not through flow-add-step, which would run it as a nested tool AND record a \`tool: flow-add-echo\` step that fails on every replay.`;
|
|
144383
145066
|
}
|
|
@@ -147402,15 +148085,71 @@ function displayFlowName(params) {
|
|
|
147402
148085
|
const stem = params.flow_path === void 0 ? void 0 : path36.basename(params.flow_path, ".yaml");
|
|
147403
148086
|
return params.name || stem || params.flow_path || "(unspecified)";
|
|
147404
148087
|
}
|
|
147405
|
-
function* walkSteps(steps) {
|
|
147406
|
-
for (const step of steps) {
|
|
147407
|
-
|
|
148088
|
+
function* walkSteps(steps, within = "") {
|
|
148089
|
+
for (const [i, step] of steps.entries()) {
|
|
148090
|
+
const where = `step ${i + 1}${within}`;
|
|
148091
|
+
yield { step, where };
|
|
147408
148092
|
const inner = blockSteps(step);
|
|
147409
|
-
if (inner) yield* walkSteps(inner);
|
|
148093
|
+
if (inner) yield* walkSteps(inner, ` of the ${step.kind}: block at ${where}`);
|
|
148094
|
+
}
|
|
148095
|
+
}
|
|
148096
|
+
function retiredKeyGuidance(prop) {
|
|
148097
|
+
const schema = prop;
|
|
148098
|
+
if (!schema?.not || Object.keys(schema.not).length > 0) return void 0;
|
|
148099
|
+
return (schema.description ?? "").replace(/^Retired:\s*/, "");
|
|
148100
|
+
}
|
|
148101
|
+
function toolArgProps(registry2, tool) {
|
|
148102
|
+
return registry2.getTool(tool)?.inputSchema?.properties;
|
|
148103
|
+
}
|
|
148104
|
+
function retiredArgIn(props, tool, args, where) {
|
|
148105
|
+
for (const key2 of Object.keys(args)) {
|
|
148106
|
+
const guidance = retiredKeyGuidance(props[key2]);
|
|
148107
|
+
if (guidance !== void 0) return { where, tool, key: key2, guidance };
|
|
147410
148108
|
}
|
|
148109
|
+
return void 0;
|
|
148110
|
+
}
|
|
148111
|
+
function* nestedInvocations(props, args) {
|
|
148112
|
+
for (const [key2, value] of Object.entries(args)) {
|
|
148113
|
+
if (!Object.hasOwn(props, key2)) continue;
|
|
148114
|
+
const entries = Array.isArray(value) ? value : [value];
|
|
148115
|
+
for (const [i, entry] of entries.entries()) {
|
|
148116
|
+
const call = entry;
|
|
148117
|
+
if (typeof call?.tool !== "string") continue;
|
|
148118
|
+
if (typeof call.args !== "object" || call.args === null || Array.isArray(call.args)) continue;
|
|
148119
|
+
yield {
|
|
148120
|
+
tool: call.tool,
|
|
148121
|
+
args: call.args,
|
|
148122
|
+
at: Array.isArray(value) ? `step ${i + 1}` : `\`${key2}\``
|
|
148123
|
+
};
|
|
148124
|
+
}
|
|
148125
|
+
}
|
|
148126
|
+
}
|
|
148127
|
+
function findRetiredToolArg(registry2, steps) {
|
|
148128
|
+
for (const { step, where } of walkSteps(steps)) {
|
|
148129
|
+
if (step.kind !== "tool") continue;
|
|
148130
|
+
const props = toolArgProps(registry2, step.name);
|
|
148131
|
+
if (!props) continue;
|
|
148132
|
+
const direct = retiredArgIn(props, step.name, step.args, where);
|
|
148133
|
+
if (direct) return direct;
|
|
148134
|
+
for (const call of nestedInvocations(props, step.args)) {
|
|
148135
|
+
const nestedProps = toolArgProps(registry2, call.tool);
|
|
148136
|
+
if (!nestedProps) continue;
|
|
148137
|
+
const hit = retiredArgIn(
|
|
148138
|
+
nestedProps,
|
|
148139
|
+
call.tool,
|
|
148140
|
+
call.args,
|
|
148141
|
+
`${call.at} of the ${step.name} step at ${where}`
|
|
148142
|
+
);
|
|
148143
|
+
if (hit) return hit;
|
|
148144
|
+
}
|
|
148145
|
+
}
|
|
148146
|
+
return void 0;
|
|
148147
|
+
}
|
|
148148
|
+
function retiredArgReason(use) {
|
|
148149
|
+
return `${use.where} as written (echo included) passes ${use.tool}'s retired \`${use.key}\` key${use.guidance ? `: ${use.guidance}` : ""}`;
|
|
147411
148150
|
}
|
|
147412
148151
|
function assertUploadSelfContained(flow) {
|
|
147413
|
-
for (const step of walkSteps(flow.steps)) {
|
|
148152
|
+
for (const { step } of walkSteps(flow.steps)) {
|
|
147414
148153
|
if (step.kind === "run") {
|
|
147415
148154
|
throw new FailureError(
|
|
147416
148155
|
`This flow uses run: composition ("run: ${step.flow}"), which requires a co-located client and tool server \u2014 an uploaded flow's referenced files are not available on this host.`,
|
|
@@ -147444,6 +148183,9 @@ function createRunFlowTool(registry2) {
|
|
|
147444
148183
|
failedMsg: ({ params, failureSignal: failureSignal2 }) => `Failed to run flow ${displayFlowName(params)}: ${failureSignal2.error_code}`
|
|
147445
148184
|
},
|
|
147446
148185
|
description: `Run a saved flow from the .argent/flows/ directory, or an explicit boundary-managed flow_path.
|
|
148186
|
+
Use when a scenario is already authored as YAML and the whole of it should replay in one call with a
|
|
148187
|
+
per-step verdict; reach for the individual gesture tools when nothing is authored yet, and for
|
|
148188
|
+
run-sequence when the steps are an ad-hoc list rather than a stored flow.
|
|
147447
148189
|
Steps run in order: \`launch\` starts an app from scratch (terminate + relaunch) and waits until it is
|
|
147448
148190
|
ready (on iOS it also pins later element lookups to that app rather than auto-detecting the frontmost
|
|
147449
148191
|
one); \`tool\` calls dispatch through the registry (a raw \`tool\` step ends that iOS pin, so lookups
|
|
@@ -147458,6 +148200,11 @@ order), \`next: <selector>\` (CSS \`+\` \u2014 the nearest such follower, which
|
|
|
147458
148200
|
non-matching neighbour rather than failing), plus \`any: true\` (CSS \`*\` \u2014 legal only WITH a scope and
|
|
147459
148201
|
never beside text/id/role). Scopes nest to disambiguate \u2014 \`within: { id: card, within: { id: list } }\`
|
|
147460
148202
|
reads "inside card inside list", each container's frame inside the next);
|
|
148203
|
+
\`swipe\` performs one finger flick (\`swipe: left\`, or \`swipe: { from?, direction|to|by, momentum?, duration? }\` \u2014
|
|
148204
|
+
direction is the FINGER's travel, the opposite sense of scroll-to's content direction; \`by: { x?, y? }\` \u2014 signed
|
|
148205
|
+
0\u20131 screen fractions, combined length at least 0.03 (a diagonal clears it where neither axis does); duration in ms,
|
|
148206
|
+
default 300, minimum 150, maximum 10000; each bound is a parse error that rejects the file before any step runs;
|
|
148207
|
+
\`momentum: false\` lands exactly where the finger lifts instead of flinging);
|
|
147461
148208
|
\`scroll-to\` scrolls (momentum-free) until a target is visible; \`pinch\` zooms
|
|
147462
148209
|
(\`pinch: { on?, scale }\` \u2014 scale > 1 in, < 1 out; screen center when \`on\` is omitted); \`rotate\` is the
|
|
147463
148210
|
two-finger rotation gesture (\`rotate: { on?, by }\` \u2014 degrees, + clockwise, within \xB13000\xB0; screen center
|
|
@@ -147474,7 +148221,7 @@ baseline (a missing baseline fails the step \u2014 set updateBaselines to adopt
|
|
|
147474
148221
|
cropped element whose size drifted fails on dimensions); \`echo\` annotates; \`run\` executes another flow
|
|
147475
148222
|
inline \u2014 a YAML path resolved against the directory of the flow file that references it (co-located
|
|
147476
148223
|
runs only).
|
|
147477
|
-
A selector-less gesture \u2014 a coordinate \`tap\`/\`long-press\`, or a \`pinch\`/\`rotate\` with no \`on\` \u2014 resolves
|
|
148224
|
+
A selector-less gesture \u2014 a coordinate \`tap\`/\`long-press\`/\`swipe\`, or a \`pinch\`/\`rotate\` with no \`on\` \u2014 resolves
|
|
147478
148225
|
no frame out of the tree, so an unreadable tree source does NOT stop it the way it stops \`idle\`: it
|
|
147479
148226
|
settles best-effort, dispatches anyway, and the step PASSES carrying a \`warning\` that quotes the source's
|
|
147480
148227
|
own error. That green says the gesture was SENT, not that it landed. Restore the tree source (usually
|
|
@@ -147522,6 +148269,15 @@ Pass exactly one flow source: name for a saved flow under project_root, or flow_
|
|
|
147522
148269
|
const flowsDir = path36.dirname(canonicalPath);
|
|
147523
148270
|
const flow = parseFlow(await fs51.readFile(canonicalPath, "utf8"));
|
|
147524
148271
|
if (viaUpload) assertUploadSelfContained(flow);
|
|
148272
|
+
const retiredArg = findRetiredToolArg(registry2, flow.steps);
|
|
148273
|
+
if (retiredArg) {
|
|
148274
|
+
throw new FailureError(`Flow "${flowName}" ${retiredArgReason(retiredArg)}`, {
|
|
148275
|
+
error_code: FAILURE_CODES.FLOW_FILE_INVALID,
|
|
148276
|
+
failure_stage: "flow_run_validate",
|
|
148277
|
+
failure_area: "tool_server",
|
|
148278
|
+
error_kind: "validation"
|
|
148279
|
+
});
|
|
148280
|
+
}
|
|
147525
148281
|
const rootEntry = { canonical: canonicalPath, display: flowName };
|
|
147526
148282
|
if (flow.executionPrerequisite && !pinnedToChromium(params.device)) {
|
|
147527
148283
|
const leading = await leadingLaunch(flow, [rootEntry]);
|
|
@@ -147792,6 +148548,9 @@ function conditionLabel(cond, renderSelector) {
|
|
|
147792
148548
|
}
|
|
147793
148549
|
return `${cond.condition} ${sel}`;
|
|
147794
148550
|
}
|
|
148551
|
+
function gestureTargetLabel(target) {
|
|
148552
|
+
return "selector" in target ? selectorLabel2(target.selector) : `(${target.x}, ${target.y})`;
|
|
148553
|
+
}
|
|
147795
148554
|
function stepTarget(step) {
|
|
147796
148555
|
switch (step.kind) {
|
|
147797
148556
|
case "tap":
|
|
@@ -147799,6 +148558,19 @@ function stepTarget(step) {
|
|
|
147799
148558
|
if (step.selector) return selectorLabel2(step.selector);
|
|
147800
148559
|
if (step.x !== void 0 && step.y !== void 0) return `(${step.x}, ${step.y})`;
|
|
147801
148560
|
return void 0;
|
|
148561
|
+
case "swipe": {
|
|
148562
|
+
let travel;
|
|
148563
|
+
if (step.direction !== void 0) {
|
|
148564
|
+
travel = step.direction;
|
|
148565
|
+
} else if (step.by !== void 0) {
|
|
148566
|
+
travel = `by ${swipeByLabel(step.by)}`;
|
|
148567
|
+
} else if (step.to !== void 0) {
|
|
148568
|
+
travel = `to ${gestureTargetLabel(step.to)}`;
|
|
148569
|
+
} else {
|
|
148570
|
+
return void 0;
|
|
148571
|
+
}
|
|
148572
|
+
return `${travel}${step.from ? ` from ${gestureTargetLabel(step.from)}` : ""}`;
|
|
148573
|
+
}
|
|
147802
148574
|
case "type":
|
|
147803
148575
|
return `into ${selectorLabel2(step.into)}`;
|
|
147804
148576
|
case "await":
|
|
@@ -148061,6 +148833,8 @@ async function execRunStep(state3, step, scope) {
|
|
|
148061
148833
|
} catch (err) {
|
|
148062
148834
|
return fail(`could not load fragment "${target}": ${errMsg3(err)}`);
|
|
148063
148835
|
}
|
|
148836
|
+
const retiredArg = findRetiredToolArg(state3.registry, fragment.steps);
|
|
148837
|
+
if (retiredArg) return fail(`fragment "${target}" ${retiredArgReason(retiredArg)}`);
|
|
148064
148838
|
pushReport(state3, {
|
|
148065
148839
|
index,
|
|
148066
148840
|
kind: "run",
|
|
@@ -148094,6 +148868,7 @@ async function execLeafStep(state3, step, index, scope) {
|
|
|
148094
148868
|
}
|
|
148095
148869
|
case "tap":
|
|
148096
148870
|
case "long-press":
|
|
148871
|
+
case "swipe":
|
|
148097
148872
|
case "type":
|
|
148098
148873
|
case "await":
|
|
148099
148874
|
case "assert":
|
|
@@ -148221,6 +148996,9 @@ async function execLeafStep(state3, step, index, scope) {
|
|
|
148221
148996
|
}
|
|
148222
148997
|
return { ...base, status: "pass", tool: step.name, result, outputHint, args };
|
|
148223
148998
|
} catch (err) {
|
|
148999
|
+
if (signal?.aborted) {
|
|
149000
|
+
return { ...base, status: "skip", tool: step.name, reason: ABORTED_OUTCOME.reason };
|
|
149001
|
+
}
|
|
148224
149002
|
const reframed = describeNestedParamError(registry2, err, step.name, args, step.args ?? {});
|
|
148225
149003
|
return { ...base, status: "error", tool: step.name, reason: reframed ?? errMsg3(err) };
|
|
148226
149004
|
}
|
|
@@ -148400,10 +149178,13 @@ var flowReadPrerequisiteTool = {
|
|
|
148400
149178
|
failedMsg: ({ failureSignal: failureSignal2 }) => `Failed to read flow prerequisite: ${failureSignal2.error_code}`
|
|
148401
149179
|
},
|
|
148402
149180
|
description: `Read the execution prerequisite of a flow without running it \u2014 a saved flow from the .argent/flows/ directory, or an explicit boundary-managed flow_path.
|
|
148403
|
-
Returns
|
|
148404
|
-
|
|
148405
|
-
|
|
148406
|
-
the
|
|
149181
|
+
Returns { flow, executionPrerequisite }: the logical name, plus the precondition its author recorded
|
|
149182
|
+
verbatim. Empty when none was declared, which is always so for a self-contained scenario: one opening
|
|
149183
|
+
on a launch may declare no prerequisite, because it builds its own start state.
|
|
149184
|
+
Use when deciding whether the device already sits where a fragment expects it (correct app foregrounded,
|
|
149185
|
+
correct account, correct screen) before committing to a run, or when relaying that requirement to a human.
|
|
149186
|
+
Touches no device: nothing is launched, tapped, dispatched or torn down, and no simulator or emulator
|
|
149187
|
+
needs booting, so calling this costs nothing but a file read.
|
|
148407
149188
|
Fails if the flow file does not exist.
|
|
148408
149189
|
Address the flow exactly as you will address it in flow-execute: name or flow_path, one and only one; supplying both or neither is rejected. The name goes in \`name\`, which resolves <project_root>/.argent/flows/<name>.yaml.`,
|
|
148409
149190
|
zodSchema: zodSchema66,
|
|
@@ -148911,7 +149692,7 @@ var updateArgentTool = {
|
|
|
148911
149692
|
});
|
|
148912
149693
|
child.unref();
|
|
148913
149694
|
}, 2e3);
|
|
148914
|
-
const
|
|
149695
|
+
const targetLabel2 = effectiveTarget === "both" ? "global and project-local installs" : `${effectiveTarget} install`;
|
|
148915
149696
|
const otherHint = requested === "auto" && resolved !== "both" ? resolved === "local" ? ` If you also have a global install, call this tool again with target "global" to update it too.` : ` If you also have a project-local install, run \`argent update --local\` in that project to update it too.` : "";
|
|
148916
149697
|
const bothDegradedNote = resolved === "both" && effectiveTarget === "global" ? ` The project-local install was skipped: no project declaring ${PACKAGE_NAME2} could be located from this server \u2014 run \`argent update --local\` in the project directory for it.` : "";
|
|
148917
149698
|
const versionInfo = targetsOnlyRunningInstall ? `(v${currentVersion} -> v${installableVersion}) ` : "";
|
|
@@ -148919,7 +149700,7 @@ var updateArgentTool = {
|
|
|
148919
149700
|
const coversRunningInstall = targetsOnlyRunningInstall || effectiveTarget === "both";
|
|
148920
149701
|
const restartNote = coversRunningInstall ? ` The tool server will stop and restart automatically once the update is installed. Subsequent tool calls will reconnect to the updated server.` : ` This session's tool server is not affected and keeps running; the update applies only to the targeted install.`;
|
|
148921
149702
|
return {
|
|
148922
|
-
message: `Argent update initiated ${versionInfo}for the ${
|
|
149703
|
+
message: `Argent update initiated ${versionInfo}for the ${targetLabel2}.` + crossTargetNote + restartNote + `${otherHint}${bothDegradedNote}`
|
|
148923
149704
|
};
|
|
148924
149705
|
}
|
|
148925
149706
|
};
|