@swmansion/argent 0.29.1-next.14 → 0.29.1-next.15
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 +6 -1
- package/dist/client.js +5 -0
- package/dist/installer.mjs +6 -1
- package/dist/mcp-server.mjs +5 -0
- package/dist/tool-args.generated.d.ts +2 -2
- package/dist/tool-server.cjs +419 -110
- package/package.json +1 -1
- package/skills/argent-create-flow/references/flow-yaml.md +3 -3
- package/skills/argent-device-interact/SKILL.md +8 -6
package/dist/cli-cmds.mjs
CHANGED
|
@@ -9608,6 +9608,11 @@ var FAILURE_CODES = {
|
|
|
9608
9608
|
// a device that is not foldable, no hinge route, a remote simulator.
|
|
9609
9609
|
IOS_FOLD_FAILED: "IOS_FOLD_FAILED",
|
|
9610
9610
|
IOS_FOLD_UNSUPPORTED: "IOS_FOLD_UNSUPPORTED",
|
|
9611
|
+
// The hinge of a foldable Android emulator: the console refused the move or
|
|
9612
|
+
// the guest never committed the state vs. nothing to fold — a phone profile,
|
|
9613
|
+
// a physical device.
|
|
9614
|
+
ANDROID_FOLD_FAILED: "ANDROID_FOLD_FAILED",
|
|
9615
|
+
ANDROID_FOLD_UNSUPPORTED: "ANDROID_FOLD_UNSUPPORTED",
|
|
9611
9616
|
PASTE_CLIPBOARD_SET_FAILED: "PASTE_CLIPBOARD_SET_FAILED",
|
|
9612
9617
|
PASTE_CLIPBOARD_UNSUPPORTED: "PASTE_CLIPBOARD_UNSUPPORTED",
|
|
9613
9618
|
SETTINGS_PERMISSION_UNSUPPORTED: "SETTINGS_PERMISSION_UNSUPPORTED",
|
|
@@ -35301,7 +35306,7 @@ var _CI_VENDOR_COUNT_FOR_TEST = vendors_default.length;
|
|
|
35301
35306
|
var SESSION_ID2 = randomUUID5();
|
|
35302
35307
|
function readCliVersion() {
|
|
35303
35308
|
if (true) {
|
|
35304
|
-
return "0.29.1-next.
|
|
35309
|
+
return "0.29.1-next.15";
|
|
35305
35310
|
}
|
|
35306
35311
|
return "0.0.0";
|
|
35307
35312
|
}
|
package/dist/client.js
CHANGED
|
@@ -8811,6 +8811,11 @@ var FAILURE_CODES = {
|
|
|
8811
8811
|
// a device that is not foldable, no hinge route, a remote simulator.
|
|
8812
8812
|
IOS_FOLD_FAILED: "IOS_FOLD_FAILED",
|
|
8813
8813
|
IOS_FOLD_UNSUPPORTED: "IOS_FOLD_UNSUPPORTED",
|
|
8814
|
+
// The hinge of a foldable Android emulator: the console refused the move or
|
|
8815
|
+
// the guest never committed the state vs. nothing to fold — a phone profile,
|
|
8816
|
+
// a physical device.
|
|
8817
|
+
ANDROID_FOLD_FAILED: "ANDROID_FOLD_FAILED",
|
|
8818
|
+
ANDROID_FOLD_UNSUPPORTED: "ANDROID_FOLD_UNSUPPORTED",
|
|
8814
8819
|
PASTE_CLIPBOARD_SET_FAILED: "PASTE_CLIPBOARD_SET_FAILED",
|
|
8815
8820
|
PASTE_CLIPBOARD_UNSUPPORTED: "PASTE_CLIPBOARD_UNSUPPORTED",
|
|
8816
8821
|
SETTINGS_PERMISSION_UNSUPPORTED: "SETTINGS_PERMISSION_UNSUPPORTED",
|
package/dist/installer.mjs
CHANGED
|
@@ -15773,6 +15773,11 @@ var FAILURE_CODES = {
|
|
|
15773
15773
|
// a device that is not foldable, no hinge route, a remote simulator.
|
|
15774
15774
|
IOS_FOLD_FAILED: "IOS_FOLD_FAILED",
|
|
15775
15775
|
IOS_FOLD_UNSUPPORTED: "IOS_FOLD_UNSUPPORTED",
|
|
15776
|
+
// The hinge of a foldable Android emulator: the console refused the move or
|
|
15777
|
+
// the guest never committed the state vs. nothing to fold — a phone profile,
|
|
15778
|
+
// a physical device.
|
|
15779
|
+
ANDROID_FOLD_FAILED: "ANDROID_FOLD_FAILED",
|
|
15780
|
+
ANDROID_FOLD_UNSUPPORTED: "ANDROID_FOLD_UNSUPPORTED",
|
|
15776
15781
|
PASTE_CLIPBOARD_SET_FAILED: "PASTE_CLIPBOARD_SET_FAILED",
|
|
15777
15782
|
PASTE_CLIPBOARD_UNSUPPORTED: "PASTE_CLIPBOARD_UNSUPPORTED",
|
|
15778
15783
|
SETTINGS_PERMISSION_UNSUPPORTED: "SETTINGS_PERMISSION_UNSUPPORTED",
|
|
@@ -16774,7 +16779,7 @@ var _CI_VENDOR_COUNT_FOR_TEST = vendors_default.length;
|
|
|
16774
16779
|
var SESSION_ID = randomUUID4();
|
|
16775
16780
|
function readCliVersion() {
|
|
16776
16781
|
if (true) {
|
|
16777
|
-
return "0.29.1-next.
|
|
16782
|
+
return "0.29.1-next.15";
|
|
16778
16783
|
}
|
|
16779
16784
|
return "0.0.0";
|
|
16780
16785
|
}
|
package/dist/mcp-server.mjs
CHANGED
|
@@ -25070,6 +25070,11 @@ var FAILURE_CODES = {
|
|
|
25070
25070
|
// a device that is not foldable, no hinge route, a remote simulator.
|
|
25071
25071
|
IOS_FOLD_FAILED: "IOS_FOLD_FAILED",
|
|
25072
25072
|
IOS_FOLD_UNSUPPORTED: "IOS_FOLD_UNSUPPORTED",
|
|
25073
|
+
// The hinge of a foldable Android emulator: the console refused the move or
|
|
25074
|
+
// the guest never committed the state vs. nothing to fold — a phone profile,
|
|
25075
|
+
// a physical device.
|
|
25076
|
+
ANDROID_FOLD_FAILED: "ANDROID_FOLD_FAILED",
|
|
25077
|
+
ANDROID_FOLD_UNSUPPORTED: "ANDROID_FOLD_UNSUPPORTED",
|
|
25073
25078
|
PASTE_CLIPBOARD_SET_FAILED: "PASTE_CLIPBOARD_SET_FAILED",
|
|
25074
25079
|
PASTE_CLIPBOARD_UNSUPPORTED: "PASTE_CLIPBOARD_UNSUPPORTED",
|
|
25075
25080
|
SETTINGS_PERMISSION_UNSUPPORTED: "SETTINGS_PERMISSION_UNSUPPORTED",
|
|
@@ -267,9 +267,9 @@ export interface ArgentToolArgs {
|
|
|
267
267
|
executionPrerequisite?: string;
|
|
268
268
|
};
|
|
269
269
|
fold: {
|
|
270
|
-
/** Target device id from `list-devices` (a foldable iOS simulator UDID). */
|
|
270
|
+
/** Target device id from `list-devices` (a foldable iOS simulator UDID or Android emulator serial). */
|
|
271
271
|
udid: string;
|
|
272
|
-
/** Where to move the hinge: `closed` (
|
|
272
|
+
/** Where to move the hinge: `closed` (the cover panel), `half-open` or `open` (the inner panel). Exactly one of `posture` and `angle`. */
|
|
273
273
|
posture?: "closed" | "half-open" | "open";
|
|
274
274
|
/** The hinge angle in degrees, 0 (closed) to 180 (open). Exactly one of `posture` and `angle`. */
|
|
275
275
|
angle?: number;
|
package/dist/tool-server.cjs
CHANGED
|
@@ -1325,6 +1325,11 @@ var init_failure_codes = __esm({
|
|
|
1325
1325
|
// a device that is not foldable, no hinge route, a remote simulator.
|
|
1326
1326
|
IOS_FOLD_FAILED: "IOS_FOLD_FAILED",
|
|
1327
1327
|
IOS_FOLD_UNSUPPORTED: "IOS_FOLD_UNSUPPORTED",
|
|
1328
|
+
// The hinge of a foldable Android emulator: the console refused the move or
|
|
1329
|
+
// the guest never committed the state vs. nothing to fold — a phone profile,
|
|
1330
|
+
// a physical device.
|
|
1331
|
+
ANDROID_FOLD_FAILED: "ANDROID_FOLD_FAILED",
|
|
1332
|
+
ANDROID_FOLD_UNSUPPORTED: "ANDROID_FOLD_UNSUPPORTED",
|
|
1328
1333
|
PASTE_CLIPBOARD_SET_FAILED: "PASTE_CLIPBOARD_SET_FAILED",
|
|
1329
1334
|
PASTE_CLIPBOARD_UNSUPPORTED: "PASTE_CLIPBOARD_UNSUPPORTED",
|
|
1330
1335
|
SETTINGS_PERMISSION_UNSUPPORTED: "SETTINGS_PERMISSION_UNSUPPORTED",
|
|
@@ -82959,6 +82964,7 @@ __export(adb_exports, {
|
|
|
82959
82964
|
getCachedAndroidRuntimeKind: () => getCachedAndroidRuntimeKind,
|
|
82960
82965
|
hasDefaultBootSnapshot: () => hasDefaultBootSnapshot,
|
|
82961
82966
|
isAndroidTv: () => isAndroidTv,
|
|
82967
|
+
isFoldableAvd: () => isFoldableAvd,
|
|
82962
82968
|
isTerminalAdbError: () => isTerminalAdbError,
|
|
82963
82969
|
listAndroidDevices: () => listAndroidDevices,
|
|
82964
82970
|
listAvds: () => listAvds,
|
|
@@ -83310,6 +83316,17 @@ async function resolveAvdPath(avdName) {
|
|
|
83310
83316
|
}
|
|
83311
83317
|
return null;
|
|
83312
83318
|
}
|
|
83319
|
+
async function isFoldableAvd(avdName) {
|
|
83320
|
+
const { readFile: readFile19 } = await import("node:fs/promises");
|
|
83321
|
+
const avdPath = await resolveAvdPath(avdName);
|
|
83322
|
+
if (!avdPath) return false;
|
|
83323
|
+
try {
|
|
83324
|
+
const hinge = (0, import_ini.parse)(await readFile19(`${avdPath}/config.ini`, "utf-8"))["hw.sensor.hinge"];
|
|
83325
|
+
return hinge === true || hinge === "yes" || hinge === "true";
|
|
83326
|
+
} catch {
|
|
83327
|
+
return false;
|
|
83328
|
+
}
|
|
83329
|
+
}
|
|
83313
83330
|
async function hasDefaultBootSnapshot(avdName) {
|
|
83314
83331
|
const { stat: stat7 } = await import("node:fs/promises");
|
|
83315
83332
|
const avdPath = await resolveAvdPath(avdName);
|
|
@@ -90386,16 +90403,16 @@ var require_foldFlowLines = __commonJS({
|
|
|
90386
90403
|
onFold();
|
|
90387
90404
|
let res = text.slice(0, folds[0]);
|
|
90388
90405
|
for (let i2 = 0; i2 < folds.length; ++i2) {
|
|
90389
|
-
const
|
|
90406
|
+
const fold2 = folds[i2];
|
|
90390
90407
|
const end2 = folds[i2 + 1] || text.length;
|
|
90391
|
-
if (
|
|
90408
|
+
if (fold2 === 0)
|
|
90392
90409
|
res = `
|
|
90393
90410
|
${indent}${text.slice(0, end2)}`;
|
|
90394
90411
|
else {
|
|
90395
|
-
if (mode === FOLD_QUOTED && escapedFolds[
|
|
90396
|
-
res += `${text[
|
|
90412
|
+
if (mode === FOLD_QUOTED && escapedFolds[fold2])
|
|
90413
|
+
res += `${text[fold2]}\\`;
|
|
90397
90414
|
res += `
|
|
90398
|
-
${indent}${text.slice(
|
|
90415
|
+
${indent}${text.slice(fold2 + 1, end2)}`;
|
|
90399
90416
|
}
|
|
90400
90417
|
}
|
|
90401
90418
|
return res;
|
|
@@ -94034,8 +94051,8 @@ var require_resolve_flow_scalar = __commonJS({
|
|
|
94034
94051
|
if (ch === "\r" && source[i + 1] === "\n")
|
|
94035
94052
|
continue;
|
|
94036
94053
|
if (ch === "\n") {
|
|
94037
|
-
const { fold, offset } = foldNewline(source, i);
|
|
94038
|
-
res +=
|
|
94054
|
+
const { fold: fold2, offset } = foldNewline(source, i);
|
|
94055
|
+
res += fold2;
|
|
94039
94056
|
i = offset;
|
|
94040
94057
|
} else if (ch === "\\") {
|
|
94041
94058
|
let next = source[++i];
|
|
@@ -94075,19 +94092,19 @@ var require_resolve_flow_scalar = __commonJS({
|
|
|
94075
94092
|
return res;
|
|
94076
94093
|
}
|
|
94077
94094
|
function foldNewline(source, offset) {
|
|
94078
|
-
let
|
|
94095
|
+
let fold2 = "";
|
|
94079
94096
|
let ch = source[offset + 1];
|
|
94080
94097
|
while (ch === " " || ch === " " || ch === "\n" || ch === "\r") {
|
|
94081
94098
|
if (ch === "\r" && source[offset + 2] !== "\n")
|
|
94082
94099
|
break;
|
|
94083
94100
|
if (ch === "\n")
|
|
94084
|
-
|
|
94101
|
+
fold2 += "\n";
|
|
94085
94102
|
offset += 1;
|
|
94086
94103
|
ch = source[offset + 1];
|
|
94087
94104
|
}
|
|
94088
|
-
if (!
|
|
94089
|
-
|
|
94090
|
-
return { fold, offset };
|
|
94105
|
+
if (!fold2)
|
|
94106
|
+
fold2 = " ";
|
|
94107
|
+
return { fold: fold2, offset };
|
|
94091
94108
|
}
|
|
94092
94109
|
var escapeCodes = {
|
|
94093
94110
|
"0": "\0",
|
|
@@ -100213,7 +100230,7 @@ var _CI_VENDOR_COUNT_FOR_TEST = vendors_default.length;
|
|
|
100213
100230
|
var SESSION_ID = (0, import_node_crypto3.randomUUID)();
|
|
100214
100231
|
function readCliVersion() {
|
|
100215
100232
|
if (true) {
|
|
100216
|
-
return "0.29.1-next.
|
|
100233
|
+
return "0.29.1-next.15";
|
|
100217
100234
|
}
|
|
100218
100235
|
return "0.0.0";
|
|
100219
100236
|
}
|
|
@@ -124990,7 +125007,7 @@ Android entries also carry a 'kind' ('emulator' for a local AVD, 'device' for a
|
|
|
124990
125007
|
Physical iPhones appear as iOS entries with kind 'device' (no iPads); no boot-device. State 'connected' = cabled and usable; 'paired' = not reachable over USB, never auto-bound.
|
|
124991
125008
|
TV targets are tagged with runtimeKind 'tv' (Apple TV simulators on iOS, Android TV / leanback devices on Android). These are focus-driven, not touch-driven: use \`describe\` to read focus, \`tv-remote\` for remote presses (up/down/left/right/select/back/menu/home), and \`keyboard\` to type, rather than the coordinate/gesture tools.
|
|
124992
125009
|
iOS simulators from an additional CoreSimulator device set (the 'ios.additionalDeviceSets' configuration, e.g. devices created by Radon IDE) are listed alongside default-set ones, tagged with their owning 'deviceSet' path; they are driven through the same tools by udid, but run headless (no Simulator.app window attaches to them).
|
|
124993
|
-
A foldable iOS simulator (the iPhone Duo) has 'foldable: true'. Use the \`fold\` tool to fold or unfold it.
|
|
125010
|
+
A foldable iOS simulator (the iPhone Duo) or Android emulator (and its AVD) has 'foldable: true'. Use the \`fold\` tool to fold or unfold it.
|
|
124994
125011
|
Chromium apps are discovered by probing CDP debugging ports (default 9222; extend via the ARGENT_CHROMIUM_PORTS=<comma-separated-ports> env var). They must already be running with --remote-debugging-port=<port>; use boot-device with electronAppPath to launch one.
|
|
124995
125012
|
Booted/ready devices are listed first. Platforms whose CLI is unavailable are silently omitted; an empty result usually means xcode-select, Android platform-tools, or the Vega SDK is not installed.`,
|
|
124996
125013
|
alwaysLoad: true,
|
|
@@ -125054,6 +125071,11 @@ Booted/ready devices are listed first. Platforms whose CLI is unavailable are si
|
|
|
125054
125071
|
tunnelState: d.tunnelState
|
|
125055
125072
|
}));
|
|
125056
125073
|
iosRemote.sort(sortIosRemote);
|
|
125074
|
+
const foldableAvds = new Set(
|
|
125075
|
+
(await Promise.all(
|
|
125076
|
+
[.../* @__PURE__ */ new Set([...avds.map((a) => a.name), ...android.map((d) => d.avdName)])].filter((name) => name !== null).map(async (name) => await isFoldableAvd(name) ? name : null)
|
|
125077
|
+
)).filter((name) => name !== null)
|
|
125078
|
+
);
|
|
125057
125079
|
const androidTagged = android.map((d) => ({
|
|
125058
125080
|
platform: "android",
|
|
125059
125081
|
serial: d.serial,
|
|
@@ -125063,7 +125085,8 @@ Booted/ready devices are listed first. Platforms whose CLI is unavailable are si
|
|
|
125063
125085
|
model: d.model,
|
|
125064
125086
|
avdName: d.avdName,
|
|
125065
125087
|
sdkLevel: d.sdkLevel,
|
|
125066
|
-
runtimeKind: d.runtimeKind
|
|
125088
|
+
runtimeKind: d.runtimeKind,
|
|
125089
|
+
...d.avdName !== null && foldableAvds.has(d.avdName) ? { foldable: true } : {}
|
|
125067
125090
|
}));
|
|
125068
125091
|
const vvdShadowSerials = await resolveVvdShadowAdbSerials(androidTagged, vega);
|
|
125069
125092
|
const shadowSerials = /* @__PURE__ */ new Set([...vvdShadowSerials, ...externalShadows]);
|
|
@@ -125079,7 +125102,14 @@ Booted/ready devices are listed first. Platforms whose CLI is unavailable are si
|
|
|
125079
125102
|
...external.map(toListedExternal)
|
|
125080
125103
|
];
|
|
125081
125104
|
devices.sort((a, b) => readinessRank(a) - readinessRank(b));
|
|
125082
|
-
|
|
125105
|
+
const avdsTagged = avds.map(
|
|
125106
|
+
(a) => foldableAvds.has(a.name) ? { ...a, foldable: true } : a
|
|
125107
|
+
);
|
|
125108
|
+
return {
|
|
125109
|
+
devices,
|
|
125110
|
+
avds: avdsTagged,
|
|
125111
|
+
...external.length > 0 ? { hint: EXTERNAL_DEVICES_HINT } : {}
|
|
125112
|
+
};
|
|
125083
125113
|
}
|
|
125084
125114
|
};
|
|
125085
125115
|
|
|
@@ -130408,11 +130438,11 @@ function scheduleGroupSigkill(pid, graceMs, opts) {
|
|
|
130408
130438
|
}
|
|
130409
130439
|
async function pollPidsUntilGone(pids, opts) {
|
|
130410
130440
|
const isAlive2 = opts.isAlive ?? pidIsAlive;
|
|
130411
|
-
const
|
|
130441
|
+
const sleep16 = opts.sleep ?? ((ms) => new Promise((resolve15) => setTimeout(resolve15, ms)));
|
|
130412
130442
|
const maxPolls = Math.max(1, Math.ceil(opts.timeoutMs / opts.pollIntervalMs));
|
|
130413
130443
|
let remaining = pids.filter((pid) => isAlive2(pid));
|
|
130414
130444
|
for (let poll = 0; poll < maxPolls && remaining.length > 0; poll += 1) {
|
|
130415
|
-
await
|
|
130445
|
+
await sleep16(opts.pollIntervalMs);
|
|
130416
130446
|
remaining = remaining.filter((pid) => isAlive2(pid));
|
|
130417
130447
|
}
|
|
130418
130448
|
return remaining;
|
|
@@ -131038,7 +131068,7 @@ function transportFailureSignal(kind) {
|
|
|
131038
131068
|
failure_stage: "ios_device_runner_transport",
|
|
131039
131069
|
failure_area: "tool_server"
|
|
131040
131070
|
};
|
|
131041
|
-
const
|
|
131071
|
+
const failed3 = {
|
|
131042
131072
|
...notReady,
|
|
131043
131073
|
error_code: FAILURE_CODES.IOS_DEVICE_RUNNER_TRANSPORT_FAILED
|
|
131044
131074
|
};
|
|
@@ -131048,11 +131078,11 @@ function transportFailureSignal(kind) {
|
|
|
131048
131078
|
case "runner-not-listening":
|
|
131049
131079
|
return { ...notReady, error_kind: "network", network_failure: "connection_refused" };
|
|
131050
131080
|
case "timeout":
|
|
131051
|
-
return { ...
|
|
131081
|
+
return { ...failed3, error_kind: "timeout" };
|
|
131052
131082
|
case "protocol":
|
|
131053
|
-
return { ...
|
|
131083
|
+
return { ...failed3, error_kind: "network", network_failure: "invalid_response" };
|
|
131054
131084
|
case "http":
|
|
131055
|
-
return { ...
|
|
131085
|
+
return { ...failed3, error_kind: "network", network_failure: "other" };
|
|
131056
131086
|
}
|
|
131057
131087
|
}
|
|
131058
131088
|
function createRunnerClient(options) {
|
|
@@ -131808,7 +131838,7 @@ function createConsoleLogServer(consoleEvents, logWriter) {
|
|
|
131808
131838
|
});
|
|
131809
131839
|
}
|
|
131810
131840
|
function makeStubSourceResolver() {
|
|
131811
|
-
const
|
|
131841
|
+
const unsupported2 = () => {
|
|
131812
131842
|
throw new Error(
|
|
131813
131843
|
"SourceResolver is not implemented on Chromium debugger sessions \u2014 Metro symbolicate is the only backing implementation."
|
|
131814
131844
|
);
|
|
@@ -131816,7 +131846,7 @@ function makeStubSourceResolver() {
|
|
|
131816
131846
|
return {
|
|
131817
131847
|
resolveDebugStack: async () => null,
|
|
131818
131848
|
symbolicate: async () => null,
|
|
131819
|
-
readSourceFragment:
|
|
131849
|
+
readSourceFragment: unsupported2
|
|
131820
131850
|
};
|
|
131821
131851
|
}
|
|
131822
131852
|
var chromiumJsRuntimeDebuggerBlueprint = {
|
|
@@ -137095,8 +137125,8 @@ var vegaImpl3 = {
|
|
|
137095
137125
|
const output2 = `${stdout}
|
|
137096
137126
|
${stderr}`;
|
|
137097
137127
|
const succeeded = /\.\.\.\s*success\b/i.test(output2);
|
|
137098
|
-
const
|
|
137099
|
-
if (!succeeded ||
|
|
137128
|
+
const failed3 = /\.\.\.\s*failed\b/i.test(output2);
|
|
137129
|
+
if (!succeeded || failed3) {
|
|
137100
137130
|
throw new FailureError(`vega install-app failed: ${output2.trim()}`, {
|
|
137101
137131
|
error_code: FAILURE_CODES.VEGA_CLI_COMMAND_FAILED,
|
|
137102
137132
|
failure_stage: "vega_reinstall_install_app",
|
|
@@ -137780,10 +137810,10 @@ async function readAndroidSurfaceRotation(serial, peek) {
|
|
|
137780
137810
|
} catch {
|
|
137781
137811
|
}
|
|
137782
137812
|
}
|
|
137783
|
-
for (const
|
|
137813
|
+
for (const shell2 of [adbServerShell, adbShell]) {
|
|
137784
137814
|
for (const { cmd, pattern } of ROTATION_PROBES) {
|
|
137785
137815
|
try {
|
|
137786
|
-
const out = await
|
|
137816
|
+
const out = await shell2(serial, cmd, { timeoutMs: 5e3 });
|
|
137787
137817
|
const value = Number(pattern.exec(out)?.[1]);
|
|
137788
137818
|
if (Number.isInteger(value) && isSurfaceRotation(value)) return value;
|
|
137789
137819
|
} catch {
|
|
@@ -140662,6 +140692,20 @@ var iosRemoteImpl6 = {
|
|
|
140662
140692
|
// ../tool-server/src/tools/shake/platforms/android.ts
|
|
140663
140693
|
init_src2();
|
|
140664
140694
|
init_adb();
|
|
140695
|
+
|
|
140696
|
+
// ../tool-server/src/utils/emulator-console.ts
|
|
140697
|
+
init_adb();
|
|
140698
|
+
async function runEmuConsole(serial, args) {
|
|
140699
|
+
const { stdout } = await runAdb(["-s", serial, "emu", ...args], { timeoutMs: 1e4 });
|
|
140700
|
+
const lines = stdout.split("\n").map((line) => line.trim()).filter(Boolean);
|
|
140701
|
+
const verdict = lines[lines.length - 1];
|
|
140702
|
+
if (verdict?.startsWith("KO:")) {
|
|
140703
|
+
throw new Error(`emulator console rejected \`${args.join(" ")}\` \u2014 ${verdict}`);
|
|
140704
|
+
}
|
|
140705
|
+
return stdout;
|
|
140706
|
+
}
|
|
140707
|
+
|
|
140708
|
+
// ../tool-server/src/tools/shake/platforms/android.ts
|
|
140665
140709
|
var SHAKE_AMPLITUDE = 30;
|
|
140666
140710
|
var SWINGS_PER_SHAKE = 8;
|
|
140667
140711
|
var SWING_INTERVAL_MS = 50;
|
|
@@ -140688,15 +140732,6 @@ function restingVectorFrom(stdout) {
|
|
|
140688
140732
|
}
|
|
140689
140733
|
var fmt = (v) => `${v.x.toFixed(4)}:${v.y.toFixed(4)}:${v.z.toFixed(4)}`;
|
|
140690
140734
|
var sleep12 = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
140691
|
-
async function emuConsole(serial, args) {
|
|
140692
|
-
const { stdout } = await runAdb(["-s", serial, "emu", ...args], { timeoutMs: 1e4 });
|
|
140693
|
-
const lines = stdout.split("\n").map((line) => line.trim()).filter(Boolean);
|
|
140694
|
-
const verdict = lines[lines.length - 1];
|
|
140695
|
-
if (verdict?.startsWith("KO:")) {
|
|
140696
|
-
throw new Error(`emulator console rejected \`${args.join(" ")}\` \u2014 ${verdict}`);
|
|
140697
|
-
}
|
|
140698
|
-
return stdout;
|
|
140699
|
-
}
|
|
140700
140735
|
var inFlightByDevice = /* @__PURE__ */ new Map();
|
|
140701
140736
|
function withDeviceLock(serial, fn) {
|
|
140702
140737
|
const previous = inFlightByDevice.get(serial) ?? Promise.resolve();
|
|
@@ -140733,12 +140768,12 @@ var androidImpl6 = {
|
|
|
140733
140768
|
);
|
|
140734
140769
|
}
|
|
140735
140770
|
const setAcceleration = async (v) => {
|
|
140736
|
-
await
|
|
140771
|
+
await runEmuConsole(serial, ["sensor", "set", "acceleration", fmt(v)]);
|
|
140737
140772
|
};
|
|
140738
140773
|
return withDeviceLock(serial, async () => {
|
|
140739
140774
|
let rest;
|
|
140740
140775
|
try {
|
|
140741
|
-
rest = restingVectorFrom(await
|
|
140776
|
+
rest = restingVectorFrom(await runEmuConsole(serial, ["sensor", "get", "acceleration"]));
|
|
140742
140777
|
} catch (err) {
|
|
140743
140778
|
const detail = err instanceof Error ? err.message : String(err);
|
|
140744
140779
|
throw new FailureError(
|
|
@@ -140837,13 +140872,270 @@ Only phone/tablet simulators and emulators are supported.`,
|
|
|
140837
140872
|
init_zod();
|
|
140838
140873
|
init_src2();
|
|
140839
140874
|
init_device_info();
|
|
140875
|
+
|
|
140876
|
+
// ../tool-server/src/tools/fold/platforms/android.ts
|
|
140877
|
+
init_src2();
|
|
140878
|
+
init_adb();
|
|
140840
140879
|
var POSTURES = ["closed", "half-open", "open"];
|
|
140880
|
+
var EMULATOR_POSTURES = {
|
|
140881
|
+
"closed": { id: "1", state: "CLOSED" },
|
|
140882
|
+
"half-open": { id: "2", state: "HALF_OPENED" },
|
|
140883
|
+
"open": { id: "3", state: "OPENED" }
|
|
140884
|
+
};
|
|
140885
|
+
var POLL_MS = 250;
|
|
140886
|
+
var SETTLE_TIMEOUT_MS2 = 15e3;
|
|
140887
|
+
var STABLE_READS = 3;
|
|
140888
|
+
var MAX_DISMISS_ATTEMPTS = 3;
|
|
140889
|
+
var DISMISS_SPACING_MS = 1e3;
|
|
140890
|
+
var DISMISS_TIMEOUT_MS = 5e3;
|
|
140891
|
+
var WAKE_GRACE_READS = 2;
|
|
140892
|
+
var abortReason2 = (signal) => signal.reason instanceof Error ? signal.reason : new Error("The fold was cancelled.");
|
|
140893
|
+
var sleep13 = (ms, signal) => new Promise((resolve15, reject) => {
|
|
140894
|
+
if (signal?.aborted) return reject(abortReason2(signal));
|
|
140895
|
+
const timer = setTimeout(resolve15, ms);
|
|
140896
|
+
signal?.addEventListener(
|
|
140897
|
+
"abort",
|
|
140898
|
+
() => {
|
|
140899
|
+
clearTimeout(timer);
|
|
140900
|
+
reject(abortReason2(signal));
|
|
140901
|
+
},
|
|
140902
|
+
{ once: true }
|
|
140903
|
+
);
|
|
140904
|
+
});
|
|
140905
|
+
function unsupported(message, stage) {
|
|
140906
|
+
return new FailureError(message, {
|
|
140907
|
+
error_code: FAILURE_CODES.ANDROID_FOLD_UNSUPPORTED,
|
|
140908
|
+
failure_stage: stage,
|
|
140909
|
+
failure_area: "tool_server",
|
|
140910
|
+
error_kind: "unsupported"
|
|
140911
|
+
});
|
|
140912
|
+
}
|
|
140913
|
+
function failed(message, stage, cause) {
|
|
140914
|
+
return new FailureError(
|
|
140915
|
+
message,
|
|
140916
|
+
{
|
|
140917
|
+
error_code: FAILURE_CODES.ANDROID_FOLD_FAILED,
|
|
140918
|
+
failure_stage: stage,
|
|
140919
|
+
failure_area: "tool_server",
|
|
140920
|
+
error_kind: "subprocess"
|
|
140921
|
+
},
|
|
140922
|
+
cause instanceof Error ? { cause } : void 0
|
|
140923
|
+
);
|
|
140924
|
+
}
|
|
140925
|
+
async function shell(serial, command) {
|
|
140926
|
+
const { stdout } = await runAdb(["-s", serial, "shell", command], { timeoutMs: 1e4 });
|
|
140927
|
+
return stdout;
|
|
140928
|
+
}
|
|
140929
|
+
function parseDeviceStates(stdout) {
|
|
140930
|
+
const states = /* @__PURE__ */ new Map();
|
|
140931
|
+
for (const match of stdout.matchAll(/identifier=(\d+), name='([A-Z_]+)'/g)) {
|
|
140932
|
+
states.set(match[2], match[1]);
|
|
140933
|
+
}
|
|
140934
|
+
return states;
|
|
140935
|
+
}
|
|
140936
|
+
function parseSnapshot(stdout) {
|
|
140937
|
+
const screenOn = !/mScreenOnFully=false/.test(stdout);
|
|
140938
|
+
const wakefulness = /mWakefulness=([A-Za-z]+)/.exec(stdout)?.[1];
|
|
140939
|
+
return {
|
|
140940
|
+
state: /^\s*(\d+)\s*$/m.exec(stdout)?.[1],
|
|
140941
|
+
screenOn,
|
|
140942
|
+
awake: wakefulness ? !/^(Asleep|Dozing)$/.test(wakefulness) : screenOn,
|
|
140943
|
+
keyguard: /isKeyguardShowing=true/.test(stdout)
|
|
140944
|
+
};
|
|
140945
|
+
}
|
|
140946
|
+
async function readSnapshot(serial) {
|
|
140947
|
+
return parseSnapshot(
|
|
140948
|
+
await shell(
|
|
140949
|
+
serial,
|
|
140950
|
+
"cmd device_state print-state; dumpsys window displays | grep -o 'mScreenOnFully=[a-z]*'; dumpsys power | grep -o 'mWakefulness=[A-Za-z]*'; dumpsys window | grep -o 'isKeyguardShowing=[a-z]*'"
|
|
140951
|
+
)
|
|
140952
|
+
);
|
|
140953
|
+
}
|
|
140954
|
+
function parseHingeAngle(stdout) {
|
|
140955
|
+
const match = /hinge-angle0\s*=\s*(-?\d+(?:\.\d+)?)/.exec(stdout);
|
|
140956
|
+
return match ? Number(match[1]) : void 0;
|
|
140957
|
+
}
|
|
140958
|
+
function isReady(read, expected) {
|
|
140959
|
+
return read.state !== void 0 && (read.screenOn || !read.awake) && (expected === void 0 || read.state === expected);
|
|
140960
|
+
}
|
|
140961
|
+
async function awaitSettled(serial, expected, wake, signal) {
|
|
140962
|
+
let last;
|
|
140963
|
+
let agreeing = 0;
|
|
140964
|
+
const deadline = Date.now() + SETTLE_TIMEOUT_MS2;
|
|
140965
|
+
while (Date.now() < deadline) {
|
|
140966
|
+
await sleep13(POLL_MS, signal);
|
|
140967
|
+
const read = await readSnapshot(serial);
|
|
140968
|
+
const same = read.state === last?.state && read.awake === last?.awake;
|
|
140969
|
+
if (!isReady(read, expected)) agreeing = 0;
|
|
140970
|
+
else agreeing = agreeing > 0 && same ? agreeing + 1 : 1;
|
|
140971
|
+
last = read;
|
|
140972
|
+
const mayWake = wake.wasAsleep && !read.awake && read.state !== wake.closedId;
|
|
140973
|
+
if (agreeing >= STABLE_READS + (mayWake ? WAKE_GRACE_READS : 0)) {
|
|
140974
|
+
return { last, settled: true, steady: true };
|
|
140975
|
+
}
|
|
140976
|
+
}
|
|
140977
|
+
const settled = last !== void 0 && isReady(last, expected);
|
|
140978
|
+
return { last, settled, steady: false };
|
|
140979
|
+
}
|
|
140980
|
+
async function dismissFoldLockScreen(serial, signal) {
|
|
140981
|
+
let clear = 0;
|
|
140982
|
+
let attempts = 0;
|
|
140983
|
+
let lastAttempt = -Infinity;
|
|
140984
|
+
const deadline = Date.now() + DISMISS_TIMEOUT_MS;
|
|
140985
|
+
for (; ; ) {
|
|
140986
|
+
if (!(await readSnapshot(serial)).keyguard) {
|
|
140987
|
+
if (++clear >= STABLE_READS) return void 0;
|
|
140988
|
+
} else {
|
|
140989
|
+
clear = 0;
|
|
140990
|
+
if (Date.now() >= deadline) break;
|
|
140991
|
+
if (attempts < MAX_DISMISS_ATTEMPTS && Date.now() - lastAttempt >= DISMISS_SPACING_MS) {
|
|
140992
|
+
attempts++;
|
|
140993
|
+
lastAttempt = Date.now();
|
|
140994
|
+
await shell(serial, "wm dismiss-keyguard");
|
|
140995
|
+
}
|
|
140996
|
+
}
|
|
140997
|
+
await sleep13(POLL_MS, signal);
|
|
140998
|
+
}
|
|
140999
|
+
return "The fold raised the lock screen and argent could not dismiss it (a PIN or password keeps it up). Unlock the device before the next action.";
|
|
141000
|
+
}
|
|
141001
|
+
function targetLabel(params) {
|
|
141002
|
+
return params.posture ?? `${params.angle}\xB0`;
|
|
141003
|
+
}
|
|
141004
|
+
var inFlightByDevice2 = /* @__PURE__ */ new Map();
|
|
141005
|
+
function withDeviceLock2(serial, fn) {
|
|
141006
|
+
const previous = inFlightByDevice2.get(serial) ?? Promise.resolve();
|
|
141007
|
+
const run2 = previous.then(fn, fn);
|
|
141008
|
+
const settled = run2.catch(() => {
|
|
141009
|
+
});
|
|
141010
|
+
inFlightByDevice2.set(serial, settled);
|
|
141011
|
+
void settled.then(() => {
|
|
141012
|
+
if (inFlightByDevice2.get(serial) === settled) inFlightByDevice2.delete(serial);
|
|
141013
|
+
});
|
|
141014
|
+
return run2;
|
|
141015
|
+
}
|
|
141016
|
+
var NO_DEVICE_STATE_SERVICE = /Can't find service: device_state|cmd: not found|No shell command implementation/i;
|
|
141017
|
+
async function readDeviceStates(serial) {
|
|
141018
|
+
try {
|
|
141019
|
+
return parseDeviceStates(await shell(serial, "cmd device_state print-states"));
|
|
141020
|
+
} catch (err) {
|
|
141021
|
+
if (!NO_DEVICE_STATE_SERVICE.test(err instanceof Error ? err.message : String(err))) throw err;
|
|
141022
|
+
return /* @__PURE__ */ new Map();
|
|
141023
|
+
}
|
|
141024
|
+
}
|
|
141025
|
+
async function fold(serial, params, device, signal) {
|
|
141026
|
+
signal?.throwIfAborted();
|
|
141027
|
+
const states = await readDeviceStates(serial);
|
|
141028
|
+
const byId = new Map([...states].map(([name, id]) => [id, name]));
|
|
141029
|
+
const supported2 = POSTURES.filter((p) => states.has(EMULATOR_POSTURES[p].state));
|
|
141030
|
+
if (supported2.length === 0) {
|
|
141031
|
+
throw unsupported(
|
|
141032
|
+
`${device.id} is not a foldable emulator: it lists no CLOSED, HALF_OPENED or OPENED device state. Boot a foldable AVD (a Pixel Fold or a generic foldable profile); \`list-devices\` marks them with \`foldable: true\`.`,
|
|
141033
|
+
"android_fold_not_foldable"
|
|
141034
|
+
);
|
|
141035
|
+
}
|
|
141036
|
+
const expected = params.posture ? states.get(EMULATOR_POSTURES[params.posture].state) : void 0;
|
|
141037
|
+
if (params.posture && expected === void 0) {
|
|
141038
|
+
throw unsupported(
|
|
141039
|
+
`${device.id} has no ${EMULATOR_POSTURES[params.posture].state} device state for the ${params.posture} posture; it supports ${supported2.join(", ")}.`,
|
|
141040
|
+
"android_fold_posture_unsupported"
|
|
141041
|
+
);
|
|
141042
|
+
}
|
|
141043
|
+
const before = await readSnapshot(serial);
|
|
141044
|
+
signal?.throwIfAborted();
|
|
141045
|
+
try {
|
|
141046
|
+
if (params.posture) {
|
|
141047
|
+
await runEmuConsole(serial, ["posture", EMULATOR_POSTURES[params.posture].id]);
|
|
141048
|
+
} else {
|
|
141049
|
+
await runEmuConsole(serial, ["sensor", "set", "hinge-angle0", String(params.angle)]);
|
|
141050
|
+
}
|
|
141051
|
+
} catch (err) {
|
|
141052
|
+
throw failed(
|
|
141053
|
+
`Failed to fold ${serial} to ${targetLabel(params)}: ${err instanceof Error ? err.message : String(err)}`,
|
|
141054
|
+
"android_fold_console",
|
|
141055
|
+
err
|
|
141056
|
+
);
|
|
141057
|
+
}
|
|
141058
|
+
const { last, settled, steady } = await awaitSettled(
|
|
141059
|
+
serial,
|
|
141060
|
+
expected,
|
|
141061
|
+
{ wasAsleep: !before.awake, closedId: states.get("CLOSED") },
|
|
141062
|
+
signal
|
|
141063
|
+
);
|
|
141064
|
+
const stateName = last?.state !== void 0 ? byId.get(last.state) ?? last.state : "unknown";
|
|
141065
|
+
const darkAwake = last?.state !== void 0 && last.awake && !last.screenOn;
|
|
141066
|
+
const budget = `${SETTLE_TIMEOUT_MS2 / 1e3} s`;
|
|
141067
|
+
if (!settled && expected !== void 0) {
|
|
141068
|
+
throw failed(
|
|
141069
|
+
last?.state === expected ? `${serial} reached device state ${stateName} for the ${params.posture} posture, but the device is awake and the screen did not stay on within ${budget}. Take a screenshot before the next action.` : `${serial} did not reach the ${params.posture} posture: the guest still reports device state ${stateName}. Fold again; a screenshot shows what the device renders.`,
|
|
141070
|
+
"android_fold_unverified"
|
|
141071
|
+
);
|
|
141072
|
+
}
|
|
141073
|
+
const warnings = [];
|
|
141074
|
+
if (!settled) {
|
|
141075
|
+
warnings.push(
|
|
141076
|
+
darkAwake ? `The device state is ${stateName}, but the screen did not stay on within ${budget}. Take a screenshot before the next action.` : `The device state did not settle within ${budget}; it last read ${stateName}. Take a screenshot before the next action.`
|
|
141077
|
+
);
|
|
141078
|
+
} else if (!steady) {
|
|
141079
|
+
warnings.push(
|
|
141080
|
+
`The screen did not hold steady within ${budget}. Take a screenshot before the next action.`
|
|
141081
|
+
);
|
|
141082
|
+
}
|
|
141083
|
+
let lockScreen = last?.keyguard ?? false;
|
|
141084
|
+
if (stateName === "CLOSED" && !before.keyguard && last?.awake) {
|
|
141085
|
+
lockScreen = false;
|
|
141086
|
+
const warning = await dismissFoldLockScreen(serial, signal);
|
|
141087
|
+
if (warning) warnings.push(warning);
|
|
141088
|
+
}
|
|
141089
|
+
const screenOff = settled && last !== void 0 && !last.screenOn;
|
|
141090
|
+
if (screenOff && lockScreen) {
|
|
141091
|
+
warnings.push(
|
|
141092
|
+
"The screen is off and the lock screen is showing after the fold. Wake and unlock the device before the next action."
|
|
141093
|
+
);
|
|
141094
|
+
} else if (screenOff) {
|
|
141095
|
+
warnings.push("The screen is off after the fold. Wake the device before the next action.");
|
|
141096
|
+
} else if (lockScreen) {
|
|
141097
|
+
warnings.push(
|
|
141098
|
+
"The lock screen is showing after the fold. Unlock or dismiss it before the next action."
|
|
141099
|
+
);
|
|
141100
|
+
}
|
|
141101
|
+
let hingeAngle;
|
|
141102
|
+
try {
|
|
141103
|
+
hingeAngle = parseHingeAngle(await runEmuConsole(serial, ["sensor", "get", "hinge-angle0"]));
|
|
141104
|
+
} catch {
|
|
141105
|
+
}
|
|
141106
|
+
const posture = POSTURES.find((p) => EMULATOR_POSTURES[p].state === stateName);
|
|
141107
|
+
return {
|
|
141108
|
+
...posture ? { posture } : {},
|
|
141109
|
+
deviceState: stateName,
|
|
141110
|
+
...hingeAngle !== void 0 ? { hingeAngle } : {},
|
|
141111
|
+
...warnings.length > 0 ? { warning: warnings.join(" ") } : {}
|
|
141112
|
+
};
|
|
141113
|
+
}
|
|
141114
|
+
var androidImpl7 = {
|
|
141115
|
+
requires: ["adb"],
|
|
141116
|
+
async handler(_services, params, device, options) {
|
|
141117
|
+
const serial = params.udid;
|
|
141118
|
+
const signal = options?.signal;
|
|
141119
|
+
if (!/^emulator-\d+$/.test(serial)) {
|
|
141120
|
+
throw unsupported(
|
|
141121
|
+
`Cannot fold ${serial}: only an Android emulator's hinge can be moved; fold a physical device by hand.`,
|
|
141122
|
+
"android_fold_not_emulator"
|
|
141123
|
+
);
|
|
141124
|
+
}
|
|
141125
|
+
return withDeviceLock2(serial, () => fold(serial, params, device, signal));
|
|
141126
|
+
}
|
|
141127
|
+
};
|
|
141128
|
+
|
|
141129
|
+
// ../tool-server/src/tools/fold/index.ts
|
|
141130
|
+
var POSTURES2 = ["closed", "half-open", "open"];
|
|
140841
141131
|
var POSTURE_ANGLE = { "closed": 0, "half-open": 120, "open": 180 };
|
|
140842
141132
|
var HINGE_REQUEST_TIMEOUT_MS = 3e4;
|
|
140843
141133
|
var zodSchema27 = external_exports.object({
|
|
140844
|
-
udid: external_exports.string().describe(
|
|
140845
|
-
|
|
140846
|
-
|
|
141134
|
+
udid: external_exports.string().describe(
|
|
141135
|
+
"Target device id from `list-devices` (a foldable iOS simulator UDID or Android emulator serial)."
|
|
141136
|
+
),
|
|
141137
|
+
posture: external_exports.enum(POSTURES2).optional().describe(
|
|
141138
|
+
"Where to move the hinge: `closed` (the cover panel), `half-open` or `open` (the inner panel). Exactly one of `posture` and `angle`."
|
|
140847
141139
|
),
|
|
140848
141140
|
angle: external_exports.number().min(0).max(180).optional().describe(
|
|
140849
141141
|
"The hinge angle in degrees, 0 (closed) to 180 (open). Exactly one of `posture` and `angle`."
|
|
@@ -140855,10 +141147,14 @@ var capability21 = {
|
|
|
140855
141147
|
// Local simulators only. A remote (MoQ) simulator is driven on its main
|
|
140856
141148
|
// screen and carries no hinge message; the server itself rejects a device
|
|
140857
141149
|
// that is not foldable, with its own reason.
|
|
140858
|
-
apple: { simulator: true }
|
|
141150
|
+
apple: { simulator: true },
|
|
141151
|
+
// Emulators only: the hinge is moved through the emulator console. `unknown`
|
|
141152
|
+
// is let through and the handler rejects a serial with no console; it also
|
|
141153
|
+
// rejects a profile with no hinge, which the capability cannot see.
|
|
141154
|
+
android: { emulator: true, unknown: true }
|
|
140859
141155
|
};
|
|
140860
141156
|
function presetAtAngle(angle) {
|
|
140861
|
-
return
|
|
141157
|
+
return POSTURES2.find((p) => POSTURE_ANGLE[p] === angle);
|
|
140862
141158
|
}
|
|
140863
141159
|
function classifyHingeRefusal(err, known) {
|
|
140864
141160
|
if (known?.foldable !== false) return err;
|
|
@@ -140880,7 +141176,7 @@ function hingeRequest(params) {
|
|
|
140880
141176
|
if (params.angle !== void 0) request3.angle = params.angle;
|
|
140881
141177
|
return request3;
|
|
140882
141178
|
}
|
|
140883
|
-
function
|
|
141179
|
+
function targetLabel2(params) {
|
|
140884
141180
|
return params.posture ?? `${params.angle}\xB0`;
|
|
140885
141181
|
}
|
|
140886
141182
|
function targetAngle(params) {
|
|
@@ -140906,26 +141202,8 @@ function screenResult(activeScreen, panels) {
|
|
|
140906
141202
|
...panel ? { width: panel.width, height: panel.height } : {}
|
|
140907
141203
|
};
|
|
140908
141204
|
}
|
|
140909
|
-
var
|
|
140910
|
-
|
|
140911
|
-
interaction: {
|
|
140912
|
-
startedMsg: ({ params }) => `Folding device to ${targetLabel(params)}`,
|
|
140913
|
-
completedMsg: ({ params, result }) => `Folded device to ${targetLabel(params)}; it renders to screen ${result.activeScreen} (${result.screen.panel})`,
|
|
140914
|
-
failedMsg: ({ params, failureSignal: failureSignal2 }) => `Failed to fold device to ${targetLabel(params)}: ${failureSignal2.error_code}`
|
|
140915
|
-
},
|
|
140916
|
-
description: `Move the hinge of a foldable iOS simulator (the iPhone Duo) to a \`posture\` (closed, half-open, open) or to an \`angle\` (0-180\xB0). The tool returns when the device accepts input again, so the next tap lands.
|
|
140917
|
-
Use when you must see or test an app in a different posture: closed shows the cover panel, half-open and open show the inner panel. \`list-devices\` marks foldable simulators with \`foldable: true\`. To turn a device without a fold, use \`rotate\`.
|
|
140918
|
-
All tools use the active panel (the panel that shows the UI), also after a fold made outside argent. The coordinates and the screenshot size change with the panel. Before you tap, read the element tree in this result or run \`describe\` again. Unfolded, the UI is landscape, as on a rotated iPhone.
|
|
140919
|
-
A fold between two angles that are not closed or open can keep the current panel. To change panels, fold to closed or open.
|
|
140920
|
-
Returns { activeScreen, screen: { id, panel, width, height }, posture?, hingeAngle, warning? }. screen names the active panel and gives its size in pixels. posture is set only at 0\xB0, 120\xB0 and 180\xB0. warning is set when argent cannot find the active panel, or when the device did not change to the expected panel.
|
|
140921
|
-
Fails if the device is not foldable or is a remote simulator.`,
|
|
140922
|
-
searchHint: "fold unfold hinge foldable duo posture open closed half-open panel screen",
|
|
140923
|
-
zodSchema: zodSchema27,
|
|
140924
|
-
capability: capability21,
|
|
140925
|
-
services: (params) => ({
|
|
140926
|
-
simulatorServer: simulatorServerRef(resolveDevice(params.udid))
|
|
140927
|
-
}),
|
|
140928
|
-
async execute(services, params, ctx) {
|
|
141205
|
+
var iosImpl5 = {
|
|
141206
|
+
async handler(services, params, _device, ctx) {
|
|
140929
141207
|
const api = services.simulatorServer;
|
|
140930
141208
|
const udid = params.udid;
|
|
140931
141209
|
const signal = ctx?.signal ? AbortSignal.any([ctx.signal, AbortSignal.timeout(HINGE_REQUEST_TIMEOUT_MS)]) : AbortSignal.timeout(HINGE_REQUEST_TIMEOUT_MS);
|
|
@@ -140972,7 +141250,7 @@ Fails if the device is not foldable or is a remote simulator.`,
|
|
|
140972
141250
|
warning = `${unresolvedPanelNote(udid, reason2, "commands target", panels)} Take a screenshot to see the screen.`;
|
|
140973
141251
|
} else if (expected !== void 0 && settled.screen !== expected) {
|
|
140974
141252
|
const advice = atStop(angle) ? "Fold again if the device did not switch; a screenshot shows what it renders." : "To switch panels, fold to closed or open first, then to the angle wanted.";
|
|
140975
|
-
warning = `The hinge was swept to ${
|
|
141253
|
+
warning = `The hinge was swept to ${targetLabel2(params)}, but the device kept rendering to ${screenLabel(activeScreen, panels)} rather than switching to ${screenLabel(expected, panels)}. Commands target the panel the device renders to. ${advice}`;
|
|
140976
141254
|
}
|
|
140977
141255
|
return {
|
|
140978
141256
|
activeScreen,
|
|
@@ -140983,6 +141261,36 @@ Fails if the device is not foldable or is a remote simulator.`,
|
|
|
140983
141261
|
};
|
|
140984
141262
|
}
|
|
140985
141263
|
};
|
|
141264
|
+
var foldTool = {
|
|
141265
|
+
id: "fold",
|
|
141266
|
+
interaction: {
|
|
141267
|
+
startedMsg: ({ params }) => `Folding device to ${targetLabel2(params)}`,
|
|
141268
|
+
completedMsg: ({ params, result }) => "activeScreen" in result ? `Folded device to ${targetLabel2(params)}; it renders to screen ${result.activeScreen} (${result.screen.panel})` : `Folded device to ${targetLabel2(params)}; it is in device state ${result.deviceState}`,
|
|
141269
|
+
failedMsg: ({ params, failureSignal: failureSignal2 }) => `Failed to fold device to ${targetLabel2(params)}: ${failureSignal2.error_code}`
|
|
141270
|
+
},
|
|
141271
|
+
description: `Move the hinge of a foldable iOS simulator (the iPhone Duo) or Android emulator (a Pixel Fold or generic foldable AVD) to a \`posture\` (closed, half-open, open) or to an \`angle\` (0-180\xB0). The tool returns when the device accepts input again, so the next tap lands.
|
|
141272
|
+
Use when you must see or test an app in a different posture: closed shows the cover panel, half-open and open show the inner panel. \`list-devices\` marks foldable devices with \`foldable: true\`. To turn a device without a fold, use \`rotate\`.
|
|
141273
|
+
All tools use the active panel (the panel that shows the UI), also after a fold made outside argent. The coordinates and the screenshot size change with the panel. Before you tap, read the element tree in this result or run \`describe\` again.
|
|
141274
|
+
iOS: unfolded, the UI is landscape, as on a rotated iPhone. A fold between two angles that are not closed or open can keep the current panel. To change panels, fold to closed or open.
|
|
141275
|
+
iOS returns { activeScreen, screen: { id, panel, width, height }, posture?, hingeAngle, warning? }. screen names the active panel and gives its size in pixels. posture is set only at 0\xB0, 120\xB0 and 180\xB0. warning is set when argent cannot find the active panel, or when the device did not change to the expected panel.
|
|
141276
|
+
Android returns { posture?, deviceState, hingeAngle?, warning? }. deviceState is the state Android committed (CLOSED, HALF_OPENED, OPENED), and posture is the matching preset. A lock screen the fold raises is dismissed; warning is set when it cannot be, or when a lock screen is showing or the screen is off after the fold. On a generic foldable AVD, closed shows only the left part of the display and describe frames do not match tap coordinates; use a Pixel Fold AVD to test the cover panel.
|
|
141277
|
+
Fails if the device is not foldable, is a remote simulator or a physical Android device.`,
|
|
141278
|
+
searchHint: "fold unfold hinge foldable duo posture open closed half-open panel screen",
|
|
141279
|
+
zodSchema: zodSchema27,
|
|
141280
|
+
capability: capability21,
|
|
141281
|
+
// The Android hinge is moved through `adb`, so no simulator-server is
|
|
141282
|
+
// spawned for it; the server follows the new panel on its own.
|
|
141283
|
+
services: (params) => {
|
|
141284
|
+
const device = resolveDevice(params.udid);
|
|
141285
|
+
return device.platform === "android" ? {} : { simulatorServer: simulatorServerRef(device) };
|
|
141286
|
+
},
|
|
141287
|
+
execute: dispatchByPlatform({
|
|
141288
|
+
toolId: "fold",
|
|
141289
|
+
capability: capability21,
|
|
141290
|
+
ios: iosImpl5,
|
|
141291
|
+
android: androidImpl7
|
|
141292
|
+
})
|
|
141293
|
+
};
|
|
140986
141294
|
|
|
140987
141295
|
// ../tool-server/src/tools/tv-remote/index.ts
|
|
140988
141296
|
init_zod();
|
|
@@ -140995,11 +141303,11 @@ function expandButtons(button, repeat) {
|
|
|
140995
141303
|
}
|
|
140996
141304
|
|
|
140997
141305
|
// ../tool-server/src/tools/tv-remote/platforms/focus-remote.ts
|
|
140998
|
-
async function pressFocusRemote(registry2, device, params,
|
|
141306
|
+
async function pressFocusRemote(registry2, device, params, unsupported2, options) {
|
|
140999
141307
|
const buttons = expandButtons(params.button, params.repeat);
|
|
141000
141308
|
const api = await resolveTvApi(registry2, device.id);
|
|
141001
|
-
if (
|
|
141002
|
-
const bad = [...new Set(buttons.filter((b) =>
|
|
141309
|
+
if (unsupported2?.size) {
|
|
141310
|
+
const bad = [...new Set(buttons.filter((b) => unsupported2.has(b)))];
|
|
141003
141311
|
if (bad.length) {
|
|
141004
141312
|
throw new UnsupportedOperationError(
|
|
141005
141313
|
"tv-remote",
|
|
@@ -146885,7 +147193,7 @@ Allowed tools and their args (udid is auto-injected, do NOT include it in args):
|
|
|
146885
147193
|
paste: { text: string } (device clipboard + paste shortcut; only where a user would paste, e.g. an OTP; keyboard otherwise) [ios sim/android emu]
|
|
146886
147194
|
rotate: { orientation: "Portrait"|"LandscapeLeft"|"LandscapeRight"|"PortraitUpsideDown" } [ios/android]
|
|
146887
147195
|
shake: { count?: number } [ios sim/android emu]
|
|
146888
|
-
fold: { posture?: "closed"|"half-open"|"open", angle?: 0-180 } (one of posture/angle; the next step lands on the new panel, whose coordinates are different) [foldable ios sim]
|
|
147196
|
+
fold: { posture?: "closed"|"half-open"|"open", angle?: 0-180 } (one of posture/angle; the next step lands on the new panel, whose coordinates are different) [foldable ios sim/android emu]
|
|
146889
147197
|
tv-remote: { button: <remote button | array of them>, repeat?: number } [apple tv/android tv/vega]
|
|
146890
147198
|
buttons: up/down/left/right/select/back/home/menu/playPause (+ rewind/fastForward/next/previous/volumeUp/volumeDown/mute work on Android TV and Vega; rejected on the Apple TV simulator)
|
|
146891
147199
|
await-ui-element: { condition: "exists"|"visible"|"hidden"|"text", selector: {text?,identifier?,role?}, expectedText?, timeoutMs?, pollIntervalMs? } [ios/android/chromium]
|
|
@@ -149356,7 +149664,7 @@ function formatDescribeTree(root, opts) {
|
|
|
149356
149664
|
}
|
|
149357
149665
|
|
|
149358
149666
|
// ../tool-server/src/tools/describe/platforms/tv.ts
|
|
149359
|
-
var
|
|
149667
|
+
var sleep14 = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
149360
149668
|
var EMPTY_RETRY_ATTEMPTS = 3;
|
|
149361
149669
|
var EMPTY_RETRY_DELAY_MS = 600;
|
|
149362
149670
|
var EMPTY_HINT = "No focusable elements after retrying and recycling the read path. The app is most likely still launching (splash / loading screen) or mid-transition \u2014 this is normal right after launch-app / restart-app. Wait ~2-3s and call describe again; a React Native app only exposes focus once its JS bundle has loaded. If it stays empty, take a screenshot to confirm what's actually on screen.";
|
|
@@ -149397,7 +149705,7 @@ async function describeTv(registry2, device) {
|
|
|
149397
149705
|
let res = await api.describe();
|
|
149398
149706
|
if (device.platform !== "android") {
|
|
149399
149707
|
for (let attempt = 1; attempt < EMPTY_RETRY_ATTEMPTS && isEmpty(res); attempt++) {
|
|
149400
|
-
await
|
|
149708
|
+
await sleep14(EMPTY_RETRY_DELAY_MS);
|
|
149401
149709
|
res = await api.describe();
|
|
149402
149710
|
}
|
|
149403
149711
|
}
|
|
@@ -149981,12 +150289,12 @@ function adaptAndroidTree(capture, screen) {
|
|
|
149981
150289
|
unsupportedFields: LEGACY_UNSUPPORTED_FIELDS2
|
|
149982
150290
|
};
|
|
149983
150291
|
if (!v2) return tree;
|
|
149984
|
-
const
|
|
150292
|
+
const unsupported2 = [];
|
|
149985
150293
|
const sdk = capture.sdkInt ?? 0;
|
|
149986
|
-
if (sdk < 26)
|
|
149987
|
-
if (sdk < 28)
|
|
150294
|
+
if (sdk < 26) unsupported2.push("placeholder", "hintShowing");
|
|
150295
|
+
if (sdk < 28) unsupported2.push("heading");
|
|
149988
150296
|
if (capture.captureMode === "active-window") {
|
|
149989
|
-
|
|
150297
|
+
unsupported2.push("keyboardVisible", "foregroundApp", "alertVisible");
|
|
149990
150298
|
} else {
|
|
149991
150299
|
let keyboard = false;
|
|
149992
150300
|
let foreground;
|
|
@@ -150012,7 +150320,7 @@ function adaptAndroidTree(capture, screen) {
|
|
|
150012
150320
|
}
|
|
150013
150321
|
}
|
|
150014
150322
|
}
|
|
150015
|
-
tree.unsupportedFields =
|
|
150323
|
+
tree.unsupportedFields = unsupported2;
|
|
150016
150324
|
return tree;
|
|
150017
150325
|
}
|
|
150018
150326
|
async function readAndroidUiTree(registry2, device) {
|
|
@@ -150209,7 +150517,7 @@ init_src2();
|
|
|
150209
150517
|
var MAX_CLEAR_KEYS = 100;
|
|
150210
150518
|
var XCTEST_DELETE = "\b";
|
|
150211
150519
|
var KEY_TIMING = { holdMs: 20, settleMs: 300, typeDelayMs: 30 };
|
|
150212
|
-
var
|
|
150520
|
+
var sleep15 = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
150213
150521
|
function typeableOnSimulator(text) {
|
|
150214
150522
|
for (const char of text) if (!charToKeyPress(char)) return false;
|
|
150215
150523
|
return true;
|
|
@@ -150234,9 +150542,9 @@ async function pressSimulatorBackspace(registry2, device) {
|
|
|
150234
150542
|
const ref = simulatorServerRef(device);
|
|
150235
150543
|
const api = await registry2.resolveService(ref.urn, ref.options);
|
|
150236
150544
|
await api.pressKey("Down", NAMED_KEYS.backspace);
|
|
150237
|
-
await
|
|
150545
|
+
await sleep15(KEY_TIMING.holdMs);
|
|
150238
150546
|
await api.pressKey("Up", NAMED_KEYS.backspace);
|
|
150239
|
-
await
|
|
150547
|
+
await sleep15(KEY_TIMING.settleMs);
|
|
150240
150548
|
}
|
|
150241
150549
|
async function typeOnSimulator(registry2, device, text) {
|
|
150242
150550
|
if (!text) return;
|
|
@@ -164206,7 +164514,7 @@ var FOCUS_REPORTING_SOURCES = /* @__PURE__ */ new Set([
|
|
|
164206
164514
|
"cdp-dom"
|
|
164207
164515
|
]);
|
|
164208
164516
|
var SETTLE_POLL_MS2 = 250;
|
|
164209
|
-
var
|
|
164517
|
+
var SETTLE_TIMEOUT_MS3 = 3e3;
|
|
164210
164518
|
var SETTLE_MIN_READS = 2;
|
|
164211
164519
|
var MAX_SCROLL_ITERATIONS = 25;
|
|
164212
164520
|
var SCROLL_INCREMENT = 0.5;
|
|
@@ -164273,7 +164581,7 @@ function readFlowTree(env) {
|
|
|
164273
164581
|
});
|
|
164274
164582
|
}
|
|
164275
164583
|
async function settleTree(env, opts = {}) {
|
|
164276
|
-
const deadline = Date.now() +
|
|
164584
|
+
const deadline = Date.now() + SETTLE_TIMEOUT_MS3;
|
|
164277
164585
|
let prevFp;
|
|
164278
164586
|
let prevTree;
|
|
164279
164587
|
let lastError;
|
|
@@ -165522,12 +165830,12 @@ function flowExecuteOutcome(result) {
|
|
|
165522
165830
|
function runSequenceOutcome(result) {
|
|
165523
165831
|
const steps = result.steps;
|
|
165524
165832
|
if (!Array.isArray(steps)) return void 0;
|
|
165525
|
-
const
|
|
165526
|
-
if (
|
|
165527
|
-
const tool = typeof
|
|
165833
|
+
const failed3 = steps.find((s) => isRecord(s) && typeof s.error === "string");
|
|
165834
|
+
if (failed3 && isRecord(failed3)) {
|
|
165835
|
+
const tool = typeof failed3.tool === "string" ? failed3.tool : "step";
|
|
165528
165836
|
return {
|
|
165529
165837
|
status: "fail",
|
|
165530
|
-
reason: `run-sequence stopped at ${tool} after ${count(result.completed)} of ${count(result.total)} steps: ${String(
|
|
165838
|
+
reason: `run-sequence stopped at ${tool} after ${count(result.completed)} of ${count(result.total)} steps: ${String(failed3.error)}`
|
|
165531
165839
|
};
|
|
165532
165840
|
}
|
|
165533
165841
|
const total = count(result.total);
|
|
@@ -166532,7 +166840,7 @@ function isTerminalResponse(response) {
|
|
|
166532
166840
|
var DEFAULT_SCRIPT_TIMEOUT_MS = 3e4;
|
|
166533
166841
|
var SCRIPT_STEP_LOG_LIMIT_BYTES = 64 * 1024;
|
|
166534
166842
|
var SCRIPT_RUN_LOG_LIMIT_BYTES = 256 * 1024;
|
|
166535
|
-
var
|
|
166843
|
+
var SETTLE_TIMEOUT_MS4 = 500;
|
|
166536
166844
|
var STOP_GRACE_MS2 = 1500;
|
|
166537
166845
|
var CHILD_DEADLINE_MARGIN_MS = 2e3;
|
|
166538
166846
|
var GROUP_POLL_MS = 50;
|
|
@@ -166943,7 +167251,7 @@ var FlowScriptExecutor = class {
|
|
|
166943
167251
|
const deadlinePassed = Date.now() >= deadlineAt;
|
|
166944
167252
|
clearTimeout(timer);
|
|
166945
167253
|
request3.signal?.removeEventListener("abort", onAbort);
|
|
166946
|
-
await Promise.race([closed, sleep(
|
|
167254
|
+
await Promise.race([closed, sleep(SETTLE_TIMEOUT_MS4)]);
|
|
166947
167255
|
await stop();
|
|
166948
167256
|
capture.end();
|
|
166949
167257
|
child.stdout?.destroy();
|
|
@@ -166983,11 +167291,11 @@ function flowScriptExecutor() {
|
|
|
166983
167291
|
}
|
|
166984
167292
|
function classifyOutcome(input2) {
|
|
166985
167293
|
const { exit } = input2;
|
|
166986
|
-
if (input2.spawnProblem) return
|
|
166987
|
-
if (input2.protocolProblem) return
|
|
167294
|
+
if (input2.spawnProblem) return failed2("spawn", input2.spawnProblem);
|
|
167295
|
+
if (input2.protocolProblem) return failed2("protocol", input2.protocolProblem);
|
|
166988
167296
|
if (input2.terminal) {
|
|
166989
167297
|
if (input2.terminal.type === "failure") {
|
|
166990
|
-
return
|
|
167298
|
+
return failed2(
|
|
166991
167299
|
input2.terminal.failureType,
|
|
166992
167300
|
clampText(input2.terminal.message, SCRIPT_MAX_FAILURE_MESSAGE_CHARS),
|
|
166993
167301
|
clampText(input2.terminal.stack, SCRIPT_MAX_FAILURE_STACK_CHARS)
|
|
@@ -166996,26 +167304,26 @@ function classifyOutcome(input2) {
|
|
|
166996
167304
|
return commitOutput(input2.terminal.outputJson);
|
|
166997
167305
|
}
|
|
166998
167306
|
if (input2.interrupted === "cancelled") {
|
|
166999
|
-
return
|
|
167307
|
+
return failed2("cancelled", "The run was cancelled and the script process was stopped.");
|
|
167000
167308
|
}
|
|
167001
167309
|
if (input2.interrupted === "timeout") return timedOut(input2.timeoutMs);
|
|
167002
167310
|
if (isHeapAbort(exit, input2.heapFatalSeen)) {
|
|
167003
|
-
return
|
|
167311
|
+
return failed2("heap", `The script exceeded its ${input2.heapLimitMb} MiB heap limit.`);
|
|
167004
167312
|
}
|
|
167005
167313
|
if (!input2.startedSeen) {
|
|
167006
|
-
return
|
|
167314
|
+
return failed2(
|
|
167007
167315
|
"protocol",
|
|
167008
167316
|
`The script runner exited before it started the script (${describeExit(exit)}).`
|
|
167009
167317
|
);
|
|
167010
167318
|
}
|
|
167011
167319
|
if (exit.signal) {
|
|
167012
167320
|
if (input2.deadlinePassed) return timedOut(input2.timeoutMs);
|
|
167013
|
-
return
|
|
167321
|
+
return failed2(
|
|
167014
167322
|
"signal",
|
|
167015
167323
|
`The script process was killed by ${exit.signal} before it returned output. It did not stop itself.`
|
|
167016
167324
|
);
|
|
167017
167325
|
}
|
|
167018
|
-
return
|
|
167326
|
+
return failed2(
|
|
167019
167327
|
"exit",
|
|
167020
167328
|
`The script stopped its own process with exit code ${exit.code ?? 0} instead of returning; no output was captured.`
|
|
167021
167329
|
);
|
|
@@ -167025,7 +167333,7 @@ function redactSecrets(verdict, secrets) {
|
|
|
167025
167333
|
if (verdict.output) {
|
|
167026
167334
|
const collision = scrubDocument(verdict.output, secrets);
|
|
167027
167335
|
if (collision) {
|
|
167028
|
-
return
|
|
167336
|
+
return failed2(
|
|
167029
167337
|
"output",
|
|
167030
167338
|
`Two keys in the script's output become "${collision}" once the secret in them is redacted, so one would silently replace the other. Rename one of them.`
|
|
167031
167339
|
);
|
|
@@ -167072,7 +167380,7 @@ function scrubDocument(root, secrets) {
|
|
|
167072
167380
|
function commitOutput(outputJson) {
|
|
167073
167381
|
const bytes = Buffer.byteLength(outputJson, "utf8");
|
|
167074
167382
|
if (bytes > SCRIPT_MAX_OUTPUT_BYTES) {
|
|
167075
|
-
return
|
|
167383
|
+
return failed2(
|
|
167076
167384
|
"output",
|
|
167077
167385
|
`The script returned ${describeBytes(bytes)} of encoded output; the limit is ${describeBytes(SCRIPT_MAX_OUTPUT_BYTES)}.`
|
|
167078
167386
|
);
|
|
@@ -167081,14 +167389,14 @@ function commitOutput(outputJson) {
|
|
|
167081
167389
|
try {
|
|
167082
167390
|
parsed = JSON.parse(outputJson);
|
|
167083
167391
|
} catch (err) {
|
|
167084
|
-
return
|
|
167392
|
+
return failed2("output", `The script's output did not parse: ${errorMessage(err)}`);
|
|
167085
167393
|
}
|
|
167086
167394
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
167087
|
-
return
|
|
167395
|
+
return failed2("output", "The script's output was not an object.");
|
|
167088
167396
|
}
|
|
167089
167397
|
const polluted = findOwnProtoKey(parsed);
|
|
167090
167398
|
if (polluted !== void 0) {
|
|
167091
|
-
return
|
|
167399
|
+
return failed2(
|
|
167092
167400
|
"output",
|
|
167093
167401
|
`${polluted} has an own "__proto__" key; output must be JSON-compatible data.`
|
|
167094
167402
|
);
|
|
@@ -167149,12 +167457,12 @@ function clampText(text, max) {
|
|
|
167149
167457
|
return marked;
|
|
167150
167458
|
}
|
|
167151
167459
|
function timedOut(timeoutMs) {
|
|
167152
|
-
return
|
|
167460
|
+
return failed2(
|
|
167153
167461
|
"timeout",
|
|
167154
167462
|
`The script did not finish within its ${describeDuration(timeoutMs)} time limit and its process tree was stopped.`
|
|
167155
167463
|
);
|
|
167156
167464
|
}
|
|
167157
|
-
function
|
|
167465
|
+
function failed2(kind, message, stack) {
|
|
167158
167466
|
return { ok: false, failure: { kind, message, ...stack ? { stack } : {} } };
|
|
167159
167467
|
}
|
|
167160
167468
|
function isHeapAbort(exit, heapFatalSeen) {
|
|
@@ -171255,13 +171563,13 @@ async function frontChromiumPage(registry2, device) {
|
|
|
171255
171563
|
}
|
|
171256
171564
|
function summarize(flowName, deviceId, executionPrerequisite, steps, aborted2, timing) {
|
|
171257
171565
|
let passed = 0;
|
|
171258
|
-
let
|
|
171566
|
+
let failed3 = 0;
|
|
171259
171567
|
let skipped = 0;
|
|
171260
171568
|
let errored = 0;
|
|
171261
171569
|
for (const s of steps) {
|
|
171262
171570
|
if (s.kind === "echo") continue;
|
|
171263
171571
|
if (s.status === "pass") passed++;
|
|
171264
|
-
else if (s.status === "fail")
|
|
171572
|
+
else if (s.status === "fail") failed3++;
|
|
171265
171573
|
else if (s.status === "skip") skipped++;
|
|
171266
171574
|
else errored++;
|
|
171267
171575
|
}
|
|
@@ -171273,10 +171581,10 @@ function summarize(flowName, deviceId, executionPrerequisite, steps, aborted2, t
|
|
|
171273
171581
|
// (no fail/error report), so the verdict folds the abort in directly. A
|
|
171274
171582
|
// skip by itself is NOT a failure: an unmet `when:` guard skips its block
|
|
171275
171583
|
// as a successful omission.
|
|
171276
|
-
ok:
|
|
171584
|
+
ok: failed3 === 0 && errored === 0 && !aborted2,
|
|
171277
171585
|
...aborted2 ? { aborted: true } : {},
|
|
171278
171586
|
passed,
|
|
171279
|
-
failed:
|
|
171587
|
+
failed: failed3,
|
|
171280
171588
|
skipped,
|
|
171281
171589
|
errored,
|
|
171282
171590
|
steps,
|
|
@@ -171579,9 +171887,10 @@ async function runFold(state3, step) {
|
|
|
171579
171887
|
const result = await invokeSubTool(registry2, ctx, "fold", args);
|
|
171580
171888
|
const size = result.screen?.width !== void 0 && result.screen?.height !== void 0 ? ` ${result.screen.width}x${result.screen.height}` : "";
|
|
171581
171889
|
const target = step.posture ?? `${step.angle}\xB0`;
|
|
171890
|
+
const landed = result.deviceState !== void 0 ? `device state ${result.deviceState}` : `screen ${result.activeScreen ?? "?"} (${result.screen?.panel ?? "?"}${size})`;
|
|
171582
171891
|
return {
|
|
171583
171892
|
ok: true,
|
|
171584
|
-
reason: `${target}:
|
|
171893
|
+
reason: `${target}: ${landed}`,
|
|
171585
171894
|
...result.warning !== void 0 ? { warning: result.warning } : {}
|
|
171586
171895
|
};
|
|
171587
171896
|
} catch (err) {
|
|
@@ -172485,7 +172794,7 @@ var updateArgentTool = {
|
|
|
172485
172794
|
});
|
|
172486
172795
|
child.unref();
|
|
172487
172796
|
}, 2e3);
|
|
172488
|
-
const
|
|
172797
|
+
const targetLabel3 = effectiveTarget === "both" ? "global and project-local installs" : `${effectiveTarget} install`;
|
|
172489
172798
|
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.` : "";
|
|
172490
172799
|
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.` : "";
|
|
172491
172800
|
const versionInfo = targetsOnlyRunningInstall ? `(v${currentVersion} -> v${installableVersion}) ` : "";
|
|
@@ -172493,7 +172802,7 @@ var updateArgentTool = {
|
|
|
172493
172802
|
const coversRunningInstall = targetsOnlyRunningInstall || effectiveTarget === "both";
|
|
172494
172803
|
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.`;
|
|
172495
172804
|
return {
|
|
172496
|
-
message: `Argent update initiated ${versionInfo}for the ${
|
|
172805
|
+
message: `Argent update initiated ${versionInfo}for the ${targetLabel3}.` + crossTargetNote + restartNote + `${otherHint}${bothDegradedNote}`
|
|
172497
172806
|
};
|
|
172498
172807
|
}
|
|
172499
172808
|
};
|
package/package.json
CHANGED
|
@@ -111,12 +111,12 @@ Scopes can combine and nest, with at most six scope keys. Use strict selectors f
|
|
|
111
111
|
|
|
112
112
|
Directives stop the flow on failure and skip later steps. The available directives are `launch`, `tap`, `long-press`, `swipe`, `type`, `scroll-to`, `pinch`, `rotate`, `fold`, `await`, `assert`, `wait`, `snapshot`, `run`, `script`, `when`, `echo`, and `tool`.
|
|
113
113
|
|
|
114
|
-
`fold` folds or unfolds a foldable iOS simulator. Write a posture (`fold: closed`, `fold: half-open`, `fold: open`) or an angle from 0 to 180 (`fold: 120`). The step waits until the device accepts input again. A recorded `fold` tool call becomes a `fold:` step. After a `fold` step:
|
|
114
|
+
`fold` folds or unfolds a foldable iOS simulator or Android emulator. Write a posture (`fold: closed`, `fold: half-open`, `fold: open`) or an angle from 0 to 180 (`fold: 120`). The step waits until the device accepts input again. A recorded `fold` tool call becomes a `fold:` step. After a `fold` step:
|
|
115
115
|
|
|
116
116
|
- The coordinates change with the panel. Selectors resolve against a new tree.
|
|
117
117
|
- A `snapshot` baseline is valid only for the posture that made it.
|
|
118
|
-
-
|
|
119
|
-
-
|
|
118
|
+
- iOS: unfolded, the UI is landscape. `swipe` and `scroll-to` directions and the reading order stay as the user sees the UI. Coordinates stay in the space of the `describe` frames.
|
|
119
|
+
- iOS: a fold between two angles that are not `closed` or `open` can keep the current panel. The step passes, and the report names the panel. To change panels, fold to `closed` or `open`.
|
|
120
120
|
|
|
121
121
|
Use the launch map for cross-platform flows. A bare launch applies everywhere and becomes an app path on Chromium. The map takes `native:`, `ios:`, `android:`, `vega:`, and `chromium:`. `native:` is one id shared by iOS, Android, and Vega, and a per-platform key overrides it for that platform. `chromium:` accepts a relative or absolute app path. `ios:` also accepts `{ app, args }`; a simulator passes `args` to the app at launch. A launch that declares no id for the run's platform is an error, not a cue to switch platforms. A run on a remote simulator uses the `ios:` id, or the `native:` id when the map has no `ios:` key, so no flow needs a key for a remote run. On iOS, a successful launch also pins later tree reads to that app until the next raw `tool:` step, so read [The runner tree is not the discovery tree](#the-runner-tree-is-not-the-discovery-tree) when a read describes the wrong screen.
|
|
122
122
|
|
|
@@ -67,7 +67,7 @@ Common schemes: `messages://`, `settings://`, `maps://?q=<query>`, `tel://<numbe
|
|
|
67
67
|
| Type text | `keyboard` | Every platform. Text or one named key per call, never both |
|
|
68
68
|
| Paste text | `paste` | Only where a user would paste (OTP code, long link). Sim/emu only |
|
|
69
69
|
| Rotate device | `rotate` | Orientation changes |
|
|
70
|
-
| Fold device | `fold` | Foldable
|
|
70
|
+
| Fold device | `fold` | Foldable simulator/emulator: closed / half-open / open, or angle |
|
|
71
71
|
| Shake device | `shake` | Shake handlers (sim/emu only), Undo-typing prompt, RN dev menu |
|
|
72
72
|
| Wait for UI | `await-ui-element` | Block until an element is visible/hidden/exists/contains text |
|
|
73
73
|
| Wait for idle | `await-screen-idle` | Block until a non-empty screen tree stops changing |
|
|
@@ -201,7 +201,7 @@ Values: `Portrait`, `LandscapeLeft`, `LandscapeRight`, `PortraitUpsideDown`
|
|
|
201
201
|
|
|
202
202
|
On an unfolded foldable simulator, the value sets the orientation of the device, not of the UI. `Portrait` gives a landscape UI. `LandscapeLeft` gives a portrait UI.
|
|
203
203
|
|
|
204
|
-
### fold: Fold or unfold a foldable
|
|
204
|
+
### fold: Fold or unfold a foldable device
|
|
205
205
|
|
|
206
206
|
```json
|
|
207
207
|
{ "udid": "<UDID>", "posture": "open" }
|
|
@@ -209,7 +209,7 @@ On an unfolded foldable simulator, the value sets the orientation of the device,
|
|
|
209
209
|
|
|
210
210
|
Give `posture` (`closed`, `half-open` or `open`) or `angle` (0–180). Do not give both.
|
|
211
211
|
|
|
212
|
-
Use `fold` only on a foldable iOS simulator. `list-devices` marks
|
|
212
|
+
Use `fold` only on a foldable iOS simulator (the iPhone Duo) or Android emulator (a Pixel Fold or generic foldable AVD). `list-devices` marks them with `foldable: true`. Other devices reject the call.
|
|
213
213
|
|
|
214
214
|
Closed, the cover panel shows the UI. Half-open and open, the inner panel shows the UI. All tools use the active panel (the panel that shows the UI), also after a fold made outside argent. `fold` returns when the device accepts input again, so the next tap lands. This is also true for the next step in `run-sequence`.
|
|
215
215
|
|
|
@@ -217,10 +217,12 @@ Rules:
|
|
|
217
217
|
|
|
218
218
|
- The coordinates change with the panel. Before you tap, read the element tree in the `fold` result, or run `describe` again. Do not use frames from before the fold.
|
|
219
219
|
- The screenshot size changes with the panel. Keep one screenshot-diff baseline for each posture.
|
|
220
|
-
-
|
|
221
|
-
-
|
|
220
|
+
- iOS: unfolded, the UI is landscape. The screenshot shows the UI turned 90 degrees, and the `describe` frames use the same axes, as on a rotated iPhone.
|
|
221
|
+
- iOS: a fold between two angles that are not 0 or 180 can keep the current panel. The result names the active panel. To change panels, fold to `closed` or `open`.
|
|
222
222
|
- Fold between gestures, not during a gesture. A gesture stays on the panel where it started.
|
|
223
|
-
-
|
|
223
|
+
- Android: the result gives `deviceState` (`CLOSED`, `HALF_OPENED`, `OPENED`). A lock screen that the fold to closed shows is dismissed; a `warning` says when it cannot be, or when a lock screen is up after the fold. Unlock the device before the next action.
|
|
224
|
+
- Android: only a Pixel Fold AVD has a cover panel. When a generic foldable AVD is closed, the emulator shows only the left part of the display. `describe` frames do not match tap coordinates. Argent can reliably tap only an element that covers the visible part, for example a full-width row. Use a Pixel Fold AVD to test the cover panel.
|
|
225
|
+
- iOS: if argent cannot find the active panel, it uses the cover panel. The tool result then has a `warning`, and a screenshot-diff summary has a `panel:` line. Take a screenshot to see what the device shows, then do the check that the warning gives.
|
|
224
226
|
|
|
225
227
|
### await-ui-element: Block until a UI element reaches a state
|
|
226
228
|
|