@swmansion/argent 0.29.1-next.13 → 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 +52 -47
- package/dist/mcp-server.mjs +5 -0
- package/dist/tool-args.generated.d.ts +2 -2
- package/dist/tool-server.cjs +422 -155
- 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
|
}
|
|
@@ -20093,52 +20098,6 @@ function resolveInstallModeFromFlags(opts) {
|
|
|
20093
20098
|
return null;
|
|
20094
20099
|
}
|
|
20095
20100
|
|
|
20096
|
-
// ../argent-installer/src/install-targets.ts
|
|
20097
|
-
function parseTargetFlags(args) {
|
|
20098
|
-
return { global: args.includes("--global"), local: args.includes("--local") };
|
|
20099
|
-
}
|
|
20100
|
-
function decideInstallTargets(ctx) {
|
|
20101
|
-
const { flags } = ctx;
|
|
20102
|
-
if (flags.global || flags.local) {
|
|
20103
|
-
const targets = [];
|
|
20104
|
-
if (flags.global) targets.push("global");
|
|
20105
|
-
if (flags.local) targets.push("local");
|
|
20106
|
-
return { kind: "targets", targets, reason: "flags" };
|
|
20107
|
-
}
|
|
20108
|
-
if (ctx.globalPresent && ctx.localPresent) {
|
|
20109
|
-
if (ctx.nonInteractive) {
|
|
20110
|
-
return {
|
|
20111
|
-
kind: "targets",
|
|
20112
|
-
targets: ctx.nonInteractiveBothDefault,
|
|
20113
|
-
reason: "noninteractive-both"
|
|
20114
|
-
};
|
|
20115
|
-
}
|
|
20116
|
-
return { kind: "prompt" };
|
|
20117
|
-
}
|
|
20118
|
-
return { kind: "targets", targets: [ctx.defaultTarget], reason: "single" };
|
|
20119
|
-
}
|
|
20120
|
-
async function promptInstallTargets(verb) {
|
|
20121
|
-
const selected = await multiselect({
|
|
20122
|
-
message: `argent is installed both globally and in this project. Which should ${verb === "update" ? "update" : "removal"} affect?`,
|
|
20123
|
-
options: [
|
|
20124
|
-
{
|
|
20125
|
-
value: "global",
|
|
20126
|
-
label: "Global install",
|
|
20127
|
-
hint: "the argent command on your PATH"
|
|
20128
|
-
},
|
|
20129
|
-
{
|
|
20130
|
-
value: "local",
|
|
20131
|
-
label: "This project's devDependency",
|
|
20132
|
-
hint: `${PACKAGE_NAME} in this project's node_modules`
|
|
20133
|
-
}
|
|
20134
|
-
],
|
|
20135
|
-
initialValues: verb === "update" ? ["global", "local"] : ["local"],
|
|
20136
|
-
required: true
|
|
20137
|
-
});
|
|
20138
|
-
if (isCancel(selected)) return "cancel";
|
|
20139
|
-
return selected;
|
|
20140
|
-
}
|
|
20141
|
-
|
|
20142
20101
|
// ../argent-installer/src/utils.ts
|
|
20143
20102
|
var PACKAGE_ROOT = resolvePackageRoot(import.meta.dirname);
|
|
20144
20103
|
function resolveBundledDir(dirName) {
|
|
@@ -23289,6 +23248,52 @@ var import_picocolors11 = __toESM(require_picocolors(), 1);
|
|
|
23289
23248
|
var import_semver5 = __toESM(require_semver2(), 1);
|
|
23290
23249
|
import * as path24 from "node:path";
|
|
23291
23250
|
|
|
23251
|
+
// ../argent-installer/src/install-targets.ts
|
|
23252
|
+
function parseTargetFlags(args) {
|
|
23253
|
+
return { global: args.includes("--global"), local: args.includes("--local") };
|
|
23254
|
+
}
|
|
23255
|
+
function decideInstallTargets(ctx) {
|
|
23256
|
+
const { flags } = ctx;
|
|
23257
|
+
if (flags.global || flags.local) {
|
|
23258
|
+
const targets = [];
|
|
23259
|
+
if (flags.global) targets.push("global");
|
|
23260
|
+
if (flags.local) targets.push("local");
|
|
23261
|
+
return { kind: "targets", targets, reason: "flags" };
|
|
23262
|
+
}
|
|
23263
|
+
if (ctx.globalPresent && ctx.localPresent) {
|
|
23264
|
+
if (ctx.nonInteractive) {
|
|
23265
|
+
return {
|
|
23266
|
+
kind: "targets",
|
|
23267
|
+
targets: ctx.nonInteractiveBothDefault,
|
|
23268
|
+
reason: "noninteractive-both"
|
|
23269
|
+
};
|
|
23270
|
+
}
|
|
23271
|
+
return { kind: "prompt" };
|
|
23272
|
+
}
|
|
23273
|
+
return { kind: "targets", targets: [ctx.defaultTarget], reason: "single" };
|
|
23274
|
+
}
|
|
23275
|
+
async function promptInstallTargets(verb) {
|
|
23276
|
+
const selected = await multiselect({
|
|
23277
|
+
message: `argent is installed both globally and in this project. Which should ${verb === "update" ? "update" : "removal"} affect?`,
|
|
23278
|
+
options: [
|
|
23279
|
+
{
|
|
23280
|
+
value: "global",
|
|
23281
|
+
label: "Global install",
|
|
23282
|
+
hint: "the argent command on your PATH"
|
|
23283
|
+
},
|
|
23284
|
+
{
|
|
23285
|
+
value: "local",
|
|
23286
|
+
label: "This project's devDependency",
|
|
23287
|
+
hint: `${PACKAGE_NAME} in this project's node_modules`
|
|
23288
|
+
}
|
|
23289
|
+
],
|
|
23290
|
+
initialValues: verb === "update" ? ["global", "local"] : ["local"],
|
|
23291
|
+
required: true
|
|
23292
|
+
});
|
|
23293
|
+
if (isCancel(selected)) return "cancel";
|
|
23294
|
+
return selected;
|
|
23295
|
+
}
|
|
23296
|
+
|
|
23292
23297
|
// ../argent-installer/src/update-target.ts
|
|
23293
23298
|
var import_update_core = __toESM(require_dist2(), 1);
|
|
23294
23299
|
async function resolveInstallableUpdateTarget(pm, current) {
|
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
|
@@ -957,18 +957,6 @@ var init_event_emitter = __esm({
|
|
|
957
957
|
this.listeners.get(event2).add(listener);
|
|
958
958
|
return this;
|
|
959
959
|
}
|
|
960
|
-
/**
|
|
961
|
-
* @public
|
|
962
|
-
* Every caller is in another workspace — tool-server (the variant-proposal
|
|
963
|
-
* store, the chromium server's frame/fps and network listeners, both
|
|
964
|
-
* js-runtime-debugger blueprints) and telemetry (`registry-listener`, which
|
|
965
|
-
* detaches from `Registry.events`) — and the dead-code gate runs against an
|
|
966
|
-
* unbuilt tree, where a cross-workspace edge cannot form. See `knip.jsonc`.
|
|
967
|
-
*
|
|
968
|
-
* To re-audit whether this tag is still earned, rename the member and read
|
|
969
|
-
* the `Property 'off' does not exist` errors `tsc` reports. Do not grep
|
|
970
|
-
* `.off(`: most hits in this repo are Node `EventEmitter`s.
|
|
971
|
-
*/
|
|
972
960
|
off(event2, listener) {
|
|
973
961
|
this.listeners.get(event2)?.delete(listener);
|
|
974
962
|
return this;
|
|
@@ -1034,19 +1022,6 @@ var init_artifacts = __esm({
|
|
|
1034
1022
|
};
|
|
1035
1023
|
ArtifactStore = class {
|
|
1036
1024
|
entries = /* @__PURE__ */ new Map();
|
|
1037
|
-
/**
|
|
1038
|
-
* @public
|
|
1039
|
-
* Every caller is in tool-server — `tools/flows/flow-visual`,
|
|
1040
|
-
* `tools/screenshot`, `tools/screenshot-diff`, `tools/screen-recording-stop`,
|
|
1041
|
-
* both `tools/profiler/native-profiler` stop/analyze handlers and
|
|
1042
|
-
* `tools/profiler/react/react-profiler-analyze` — and the dead-code gate runs
|
|
1043
|
-
* against an unbuilt tree, where a cross-workspace edge cannot form. See
|
|
1044
|
-
* `knip.jsonc`.
|
|
1045
|
-
*
|
|
1046
|
-
* To re-audit whether this tag is still earned, rename the member and read
|
|
1047
|
-
* the `Property 'register' does not exist` errors `tsc --build` reports. Do
|
|
1048
|
-
* not grep `.register(`: `registerTool` / `registerBlueprint` swamp the hits.
|
|
1049
|
-
*/
|
|
1050
1025
|
async register(opts) {
|
|
1051
1026
|
const { hostPath, kind } = opts;
|
|
1052
1027
|
const filename = opts.filename ?? (0, import_node_path2.basename)(hostPath);
|
|
@@ -1077,21 +1052,9 @@ var init_artifacts = __esm({
|
|
|
1077
1052
|
if (opts.saveDir) handle.saveDir = opts.saveDir;
|
|
1078
1053
|
return handle;
|
|
1079
1054
|
}
|
|
1080
|
-
/**
|
|
1081
|
-
* @public
|
|
1082
|
-
* The only caller is the tool-server `GET /artifacts/:id` route, and the
|
|
1083
|
-
* dead-code gate runs against an unbuilt tree, where a cross-workspace edge
|
|
1084
|
-
* cannot form. See `knip.jsonc`.
|
|
1085
|
-
*/
|
|
1086
1055
|
get(id) {
|
|
1087
1056
|
return this.entries.get(id);
|
|
1088
1057
|
}
|
|
1089
|
-
/**
|
|
1090
|
-
* @public
|
|
1091
|
-
* The only caller is the tool-server `/artifacts` route, and the dead-code
|
|
1092
|
-
* gate runs against an unbuilt tree, where a cross-workspace edge cannot
|
|
1093
|
-
* form. See `knip.jsonc`.
|
|
1094
|
-
*/
|
|
1095
1058
|
list() {
|
|
1096
1059
|
return [...this.entries.entries()].map(([id, entry]) => ({
|
|
1097
1060
|
id,
|
|
@@ -1362,6 +1325,11 @@ var init_failure_codes = __esm({
|
|
|
1362
1325
|
// a device that is not foldable, no hinge route, a remote simulator.
|
|
1363
1326
|
IOS_FOLD_FAILED: "IOS_FOLD_FAILED",
|
|
1364
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",
|
|
1365
1333
|
PASTE_CLIPBOARD_SET_FAILED: "PASTE_CLIPBOARD_SET_FAILED",
|
|
1366
1334
|
PASTE_CLIPBOARD_UNSUPPORTED: "PASTE_CLIPBOARD_UNSUPPORTED",
|
|
1367
1335
|
SETTINGS_PERMISSION_UNSUPPORTED: "SETTINGS_PERMISSION_UNSUPPORTED",
|
|
@@ -1690,13 +1658,6 @@ var init_errors = __esm({
|
|
|
1690
1658
|
}
|
|
1691
1659
|
};
|
|
1692
1660
|
ToolNotFoundError = class extends Error {
|
|
1693
|
-
/**
|
|
1694
|
-
* @public
|
|
1695
|
-
* Knip's `classMembers` pass is workspace-local, so it cannot see the caller:
|
|
1696
|
-
* `flow-add-step` keys `isToolNotFound` on this alongside the error identity,
|
|
1697
|
-
* so a tool that ran and reported its own "not found" is not read as the
|
|
1698
|
-
* command being absent.
|
|
1699
|
-
*/
|
|
1700
1661
|
toolId;
|
|
1701
1662
|
constructor(toolId) {
|
|
1702
1663
|
super(`Tool "${toolId}" not found`);
|
|
@@ -83003,6 +82964,7 @@ __export(adb_exports, {
|
|
|
83003
82964
|
getCachedAndroidRuntimeKind: () => getCachedAndroidRuntimeKind,
|
|
83004
82965
|
hasDefaultBootSnapshot: () => hasDefaultBootSnapshot,
|
|
83005
82966
|
isAndroidTv: () => isAndroidTv,
|
|
82967
|
+
isFoldableAvd: () => isFoldableAvd,
|
|
83006
82968
|
isTerminalAdbError: () => isTerminalAdbError,
|
|
83007
82969
|
listAndroidDevices: () => listAndroidDevices,
|
|
83008
82970
|
listAvds: () => listAvds,
|
|
@@ -83354,6 +83316,17 @@ async function resolveAvdPath(avdName) {
|
|
|
83354
83316
|
}
|
|
83355
83317
|
return null;
|
|
83356
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
|
+
}
|
|
83357
83330
|
async function hasDefaultBootSnapshot(avdName) {
|
|
83358
83331
|
const { stat: stat7 } = await import("node:fs/promises");
|
|
83359
83332
|
const avdPath = await resolveAvdPath(avdName);
|
|
@@ -90430,16 +90403,16 @@ var require_foldFlowLines = __commonJS({
|
|
|
90430
90403
|
onFold();
|
|
90431
90404
|
let res = text.slice(0, folds[0]);
|
|
90432
90405
|
for (let i2 = 0; i2 < folds.length; ++i2) {
|
|
90433
|
-
const
|
|
90406
|
+
const fold2 = folds[i2];
|
|
90434
90407
|
const end2 = folds[i2 + 1] || text.length;
|
|
90435
|
-
if (
|
|
90408
|
+
if (fold2 === 0)
|
|
90436
90409
|
res = `
|
|
90437
90410
|
${indent}${text.slice(0, end2)}`;
|
|
90438
90411
|
else {
|
|
90439
|
-
if (mode === FOLD_QUOTED && escapedFolds[
|
|
90440
|
-
res += `${text[
|
|
90412
|
+
if (mode === FOLD_QUOTED && escapedFolds[fold2])
|
|
90413
|
+
res += `${text[fold2]}\\`;
|
|
90441
90414
|
res += `
|
|
90442
|
-
${indent}${text.slice(
|
|
90415
|
+
${indent}${text.slice(fold2 + 1, end2)}`;
|
|
90443
90416
|
}
|
|
90444
90417
|
}
|
|
90445
90418
|
return res;
|
|
@@ -94078,8 +94051,8 @@ var require_resolve_flow_scalar = __commonJS({
|
|
|
94078
94051
|
if (ch === "\r" && source[i + 1] === "\n")
|
|
94079
94052
|
continue;
|
|
94080
94053
|
if (ch === "\n") {
|
|
94081
|
-
const { fold, offset } = foldNewline(source, i);
|
|
94082
|
-
res +=
|
|
94054
|
+
const { fold: fold2, offset } = foldNewline(source, i);
|
|
94055
|
+
res += fold2;
|
|
94083
94056
|
i = offset;
|
|
94084
94057
|
} else if (ch === "\\") {
|
|
94085
94058
|
let next = source[++i];
|
|
@@ -94119,19 +94092,19 @@ var require_resolve_flow_scalar = __commonJS({
|
|
|
94119
94092
|
return res;
|
|
94120
94093
|
}
|
|
94121
94094
|
function foldNewline(source, offset) {
|
|
94122
|
-
let
|
|
94095
|
+
let fold2 = "";
|
|
94123
94096
|
let ch = source[offset + 1];
|
|
94124
94097
|
while (ch === " " || ch === " " || ch === "\n" || ch === "\r") {
|
|
94125
94098
|
if (ch === "\r" && source[offset + 2] !== "\n")
|
|
94126
94099
|
break;
|
|
94127
94100
|
if (ch === "\n")
|
|
94128
|
-
|
|
94101
|
+
fold2 += "\n";
|
|
94129
94102
|
offset += 1;
|
|
94130
94103
|
ch = source[offset + 1];
|
|
94131
94104
|
}
|
|
94132
|
-
if (!
|
|
94133
|
-
|
|
94134
|
-
return { fold, offset };
|
|
94105
|
+
if (!fold2)
|
|
94106
|
+
fold2 = " ";
|
|
94107
|
+
return { fold: fold2, offset };
|
|
94135
94108
|
}
|
|
94136
94109
|
var escapeCodes = {
|
|
94137
94110
|
"0": "\0",
|
|
@@ -100257,7 +100230,7 @@ var _CI_VENDOR_COUNT_FOR_TEST = vendors_default.length;
|
|
|
100257
100230
|
var SESSION_ID = (0, import_node_crypto3.randomUUID)();
|
|
100258
100231
|
function readCliVersion() {
|
|
100259
100232
|
if (true) {
|
|
100260
|
-
return "0.29.1-next.
|
|
100233
|
+
return "0.29.1-next.15";
|
|
100261
100234
|
}
|
|
100262
100235
|
return "0.0.0";
|
|
100263
100236
|
}
|
|
@@ -125034,7 +125007,7 @@ Android entries also carry a 'kind' ('emulator' for a local AVD, 'device' for a
|
|
|
125034
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.
|
|
125035
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.
|
|
125036
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).
|
|
125037
|
-
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.
|
|
125038
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.
|
|
125039
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.`,
|
|
125040
125013
|
alwaysLoad: true,
|
|
@@ -125098,6 +125071,11 @@ Booted/ready devices are listed first. Platforms whose CLI is unavailable are si
|
|
|
125098
125071
|
tunnelState: d.tunnelState
|
|
125099
125072
|
}));
|
|
125100
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
|
+
);
|
|
125101
125079
|
const androidTagged = android.map((d) => ({
|
|
125102
125080
|
platform: "android",
|
|
125103
125081
|
serial: d.serial,
|
|
@@ -125107,7 +125085,8 @@ Booted/ready devices are listed first. Platforms whose CLI is unavailable are si
|
|
|
125107
125085
|
model: d.model,
|
|
125108
125086
|
avdName: d.avdName,
|
|
125109
125087
|
sdkLevel: d.sdkLevel,
|
|
125110
|
-
runtimeKind: d.runtimeKind
|
|
125088
|
+
runtimeKind: d.runtimeKind,
|
|
125089
|
+
...d.avdName !== null && foldableAvds.has(d.avdName) ? { foldable: true } : {}
|
|
125111
125090
|
}));
|
|
125112
125091
|
const vvdShadowSerials = await resolveVvdShadowAdbSerials(androidTagged, vega);
|
|
125113
125092
|
const shadowSerials = /* @__PURE__ */ new Set([...vvdShadowSerials, ...externalShadows]);
|
|
@@ -125123,7 +125102,14 @@ Booted/ready devices are listed first. Platforms whose CLI is unavailable are si
|
|
|
125123
125102
|
...external.map(toListedExternal)
|
|
125124
125103
|
];
|
|
125125
125104
|
devices.sort((a, b) => readinessRank(a) - readinessRank(b));
|
|
125126
|
-
|
|
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
|
+
};
|
|
125127
125113
|
}
|
|
125128
125114
|
};
|
|
125129
125115
|
|
|
@@ -130452,11 +130438,11 @@ function scheduleGroupSigkill(pid, graceMs, opts) {
|
|
|
130452
130438
|
}
|
|
130453
130439
|
async function pollPidsUntilGone(pids, opts) {
|
|
130454
130440
|
const isAlive2 = opts.isAlive ?? pidIsAlive;
|
|
130455
|
-
const
|
|
130441
|
+
const sleep16 = opts.sleep ?? ((ms) => new Promise((resolve15) => setTimeout(resolve15, ms)));
|
|
130456
130442
|
const maxPolls = Math.max(1, Math.ceil(opts.timeoutMs / opts.pollIntervalMs));
|
|
130457
130443
|
let remaining = pids.filter((pid) => isAlive2(pid));
|
|
130458
130444
|
for (let poll = 0; poll < maxPolls && remaining.length > 0; poll += 1) {
|
|
130459
|
-
await
|
|
130445
|
+
await sleep16(opts.pollIntervalMs);
|
|
130460
130446
|
remaining = remaining.filter((pid) => isAlive2(pid));
|
|
130461
130447
|
}
|
|
130462
130448
|
return remaining;
|
|
@@ -131082,7 +131068,7 @@ function transportFailureSignal(kind) {
|
|
|
131082
131068
|
failure_stage: "ios_device_runner_transport",
|
|
131083
131069
|
failure_area: "tool_server"
|
|
131084
131070
|
};
|
|
131085
|
-
const
|
|
131071
|
+
const failed3 = {
|
|
131086
131072
|
...notReady,
|
|
131087
131073
|
error_code: FAILURE_CODES.IOS_DEVICE_RUNNER_TRANSPORT_FAILED
|
|
131088
131074
|
};
|
|
@@ -131092,11 +131078,11 @@ function transportFailureSignal(kind) {
|
|
|
131092
131078
|
case "runner-not-listening":
|
|
131093
131079
|
return { ...notReady, error_kind: "network", network_failure: "connection_refused" };
|
|
131094
131080
|
case "timeout":
|
|
131095
|
-
return { ...
|
|
131081
|
+
return { ...failed3, error_kind: "timeout" };
|
|
131096
131082
|
case "protocol":
|
|
131097
|
-
return { ...
|
|
131083
|
+
return { ...failed3, error_kind: "network", network_failure: "invalid_response" };
|
|
131098
131084
|
case "http":
|
|
131099
|
-
return { ...
|
|
131085
|
+
return { ...failed3, error_kind: "network", network_failure: "other" };
|
|
131100
131086
|
}
|
|
131101
131087
|
}
|
|
131102
131088
|
function createRunnerClient(options) {
|
|
@@ -131852,7 +131838,7 @@ function createConsoleLogServer(consoleEvents, logWriter) {
|
|
|
131852
131838
|
});
|
|
131853
131839
|
}
|
|
131854
131840
|
function makeStubSourceResolver() {
|
|
131855
|
-
const
|
|
131841
|
+
const unsupported2 = () => {
|
|
131856
131842
|
throw new Error(
|
|
131857
131843
|
"SourceResolver is not implemented on Chromium debugger sessions \u2014 Metro symbolicate is the only backing implementation."
|
|
131858
131844
|
);
|
|
@@ -131860,7 +131846,7 @@ function makeStubSourceResolver() {
|
|
|
131860
131846
|
return {
|
|
131861
131847
|
resolveDebugStack: async () => null,
|
|
131862
131848
|
symbolicate: async () => null,
|
|
131863
|
-
readSourceFragment:
|
|
131849
|
+
readSourceFragment: unsupported2
|
|
131864
131850
|
};
|
|
131865
131851
|
}
|
|
131866
131852
|
var chromiumJsRuntimeDebuggerBlueprint = {
|
|
@@ -137139,8 +137125,8 @@ var vegaImpl3 = {
|
|
|
137139
137125
|
const output2 = `${stdout}
|
|
137140
137126
|
${stderr}`;
|
|
137141
137127
|
const succeeded = /\.\.\.\s*success\b/i.test(output2);
|
|
137142
|
-
const
|
|
137143
|
-
if (!succeeded ||
|
|
137128
|
+
const failed3 = /\.\.\.\s*failed\b/i.test(output2);
|
|
137129
|
+
if (!succeeded || failed3) {
|
|
137144
137130
|
throw new FailureError(`vega install-app failed: ${output2.trim()}`, {
|
|
137145
137131
|
error_code: FAILURE_CODES.VEGA_CLI_COMMAND_FAILED,
|
|
137146
137132
|
failure_stage: "vega_reinstall_install_app",
|
|
@@ -137824,10 +137810,10 @@ async function readAndroidSurfaceRotation(serial, peek) {
|
|
|
137824
137810
|
} catch {
|
|
137825
137811
|
}
|
|
137826
137812
|
}
|
|
137827
|
-
for (const
|
|
137813
|
+
for (const shell2 of [adbServerShell, adbShell]) {
|
|
137828
137814
|
for (const { cmd, pattern } of ROTATION_PROBES) {
|
|
137829
137815
|
try {
|
|
137830
|
-
const out = await
|
|
137816
|
+
const out = await shell2(serial, cmd, { timeoutMs: 5e3 });
|
|
137831
137817
|
const value = Number(pattern.exec(out)?.[1]);
|
|
137832
137818
|
if (Number.isInteger(value) && isSurfaceRotation(value)) return value;
|
|
137833
137819
|
} catch {
|
|
@@ -140706,6 +140692,20 @@ var iosRemoteImpl6 = {
|
|
|
140706
140692
|
// ../tool-server/src/tools/shake/platforms/android.ts
|
|
140707
140693
|
init_src2();
|
|
140708
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
|
|
140709
140709
|
var SHAKE_AMPLITUDE = 30;
|
|
140710
140710
|
var SWINGS_PER_SHAKE = 8;
|
|
140711
140711
|
var SWING_INTERVAL_MS = 50;
|
|
@@ -140732,15 +140732,6 @@ function restingVectorFrom(stdout) {
|
|
|
140732
140732
|
}
|
|
140733
140733
|
var fmt = (v) => `${v.x.toFixed(4)}:${v.y.toFixed(4)}:${v.z.toFixed(4)}`;
|
|
140734
140734
|
var sleep12 = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
140735
|
-
async function emuConsole(serial, args) {
|
|
140736
|
-
const { stdout } = await runAdb(["-s", serial, "emu", ...args], { timeoutMs: 1e4 });
|
|
140737
|
-
const lines = stdout.split("\n").map((line) => line.trim()).filter(Boolean);
|
|
140738
|
-
const verdict = lines[lines.length - 1];
|
|
140739
|
-
if (verdict?.startsWith("KO:")) {
|
|
140740
|
-
throw new Error(`emulator console rejected \`${args.join(" ")}\` \u2014 ${verdict}`);
|
|
140741
|
-
}
|
|
140742
|
-
return stdout;
|
|
140743
|
-
}
|
|
140744
140735
|
var inFlightByDevice = /* @__PURE__ */ new Map();
|
|
140745
140736
|
function withDeviceLock(serial, fn) {
|
|
140746
140737
|
const previous = inFlightByDevice.get(serial) ?? Promise.resolve();
|
|
@@ -140777,12 +140768,12 @@ var androidImpl6 = {
|
|
|
140777
140768
|
);
|
|
140778
140769
|
}
|
|
140779
140770
|
const setAcceleration = async (v) => {
|
|
140780
|
-
await
|
|
140771
|
+
await runEmuConsole(serial, ["sensor", "set", "acceleration", fmt(v)]);
|
|
140781
140772
|
};
|
|
140782
140773
|
return withDeviceLock(serial, async () => {
|
|
140783
140774
|
let rest;
|
|
140784
140775
|
try {
|
|
140785
|
-
rest = restingVectorFrom(await
|
|
140776
|
+
rest = restingVectorFrom(await runEmuConsole(serial, ["sensor", "get", "acceleration"]));
|
|
140786
140777
|
} catch (err) {
|
|
140787
140778
|
const detail = err instanceof Error ? err.message : String(err);
|
|
140788
140779
|
throw new FailureError(
|
|
@@ -140881,13 +140872,270 @@ Only phone/tablet simulators and emulators are supported.`,
|
|
|
140881
140872
|
init_zod();
|
|
140882
140873
|
init_src2();
|
|
140883
140874
|
init_device_info();
|
|
140875
|
+
|
|
140876
|
+
// ../tool-server/src/tools/fold/platforms/android.ts
|
|
140877
|
+
init_src2();
|
|
140878
|
+
init_adb();
|
|
140884
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"];
|
|
140885
141131
|
var POSTURE_ANGLE = { "closed": 0, "half-open": 120, "open": 180 };
|
|
140886
141132
|
var HINGE_REQUEST_TIMEOUT_MS = 3e4;
|
|
140887
141133
|
var zodSchema27 = external_exports.object({
|
|
140888
|
-
udid: external_exports.string().describe(
|
|
140889
|
-
|
|
140890
|
-
|
|
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`."
|
|
140891
141139
|
),
|
|
140892
141140
|
angle: external_exports.number().min(0).max(180).optional().describe(
|
|
140893
141141
|
"The hinge angle in degrees, 0 (closed) to 180 (open). Exactly one of `posture` and `angle`."
|
|
@@ -140899,10 +141147,14 @@ var capability21 = {
|
|
|
140899
141147
|
// Local simulators only. A remote (MoQ) simulator is driven on its main
|
|
140900
141148
|
// screen and carries no hinge message; the server itself rejects a device
|
|
140901
141149
|
// that is not foldable, with its own reason.
|
|
140902
|
-
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 }
|
|
140903
141155
|
};
|
|
140904
141156
|
function presetAtAngle(angle) {
|
|
140905
|
-
return
|
|
141157
|
+
return POSTURES2.find((p) => POSTURE_ANGLE[p] === angle);
|
|
140906
141158
|
}
|
|
140907
141159
|
function classifyHingeRefusal(err, known) {
|
|
140908
141160
|
if (known?.foldable !== false) return err;
|
|
@@ -140924,7 +141176,7 @@ function hingeRequest(params) {
|
|
|
140924
141176
|
if (params.angle !== void 0) request3.angle = params.angle;
|
|
140925
141177
|
return request3;
|
|
140926
141178
|
}
|
|
140927
|
-
function
|
|
141179
|
+
function targetLabel2(params) {
|
|
140928
141180
|
return params.posture ?? `${params.angle}\xB0`;
|
|
140929
141181
|
}
|
|
140930
141182
|
function targetAngle(params) {
|
|
@@ -140950,26 +141202,8 @@ function screenResult(activeScreen, panels) {
|
|
|
140950
141202
|
...panel ? { width: panel.width, height: panel.height } : {}
|
|
140951
141203
|
};
|
|
140952
141204
|
}
|
|
140953
|
-
var
|
|
140954
|
-
|
|
140955
|
-
interaction: {
|
|
140956
|
-
startedMsg: ({ params }) => `Folding device to ${targetLabel(params)}`,
|
|
140957
|
-
completedMsg: ({ params, result }) => `Folded device to ${targetLabel(params)}; it renders to screen ${result.activeScreen} (${result.screen.panel})`,
|
|
140958
|
-
failedMsg: ({ params, failureSignal: failureSignal2 }) => `Failed to fold device to ${targetLabel(params)}: ${failureSignal2.error_code}`
|
|
140959
|
-
},
|
|
140960
|
-
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.
|
|
140961
|
-
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\`.
|
|
140962
|
-
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.
|
|
140963
|
-
A fold between two angles that are not closed or open can keep the current panel. To change panels, fold to closed or open.
|
|
140964
|
-
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.
|
|
140965
|
-
Fails if the device is not foldable or is a remote simulator.`,
|
|
140966
|
-
searchHint: "fold unfold hinge foldable duo posture open closed half-open panel screen",
|
|
140967
|
-
zodSchema: zodSchema27,
|
|
140968
|
-
capability: capability21,
|
|
140969
|
-
services: (params) => ({
|
|
140970
|
-
simulatorServer: simulatorServerRef(resolveDevice(params.udid))
|
|
140971
|
-
}),
|
|
140972
|
-
async execute(services, params, ctx) {
|
|
141205
|
+
var iosImpl5 = {
|
|
141206
|
+
async handler(services, params, _device, ctx) {
|
|
140973
141207
|
const api = services.simulatorServer;
|
|
140974
141208
|
const udid = params.udid;
|
|
140975
141209
|
const signal = ctx?.signal ? AbortSignal.any([ctx.signal, AbortSignal.timeout(HINGE_REQUEST_TIMEOUT_MS)]) : AbortSignal.timeout(HINGE_REQUEST_TIMEOUT_MS);
|
|
@@ -141016,7 +141250,7 @@ Fails if the device is not foldable or is a remote simulator.`,
|
|
|
141016
141250
|
warning = `${unresolvedPanelNote(udid, reason2, "commands target", panels)} Take a screenshot to see the screen.`;
|
|
141017
141251
|
} else if (expected !== void 0 && settled.screen !== expected) {
|
|
141018
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.";
|
|
141019
|
-
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}`;
|
|
141020
141254
|
}
|
|
141021
141255
|
return {
|
|
141022
141256
|
activeScreen,
|
|
@@ -141027,6 +141261,36 @@ Fails if the device is not foldable or is a remote simulator.`,
|
|
|
141027
141261
|
};
|
|
141028
141262
|
}
|
|
141029
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
|
+
};
|
|
141030
141294
|
|
|
141031
141295
|
// ../tool-server/src/tools/tv-remote/index.ts
|
|
141032
141296
|
init_zod();
|
|
@@ -141039,11 +141303,11 @@ function expandButtons(button, repeat) {
|
|
|
141039
141303
|
}
|
|
141040
141304
|
|
|
141041
141305
|
// ../tool-server/src/tools/tv-remote/platforms/focus-remote.ts
|
|
141042
|
-
async function pressFocusRemote(registry2, device, params,
|
|
141306
|
+
async function pressFocusRemote(registry2, device, params, unsupported2, options) {
|
|
141043
141307
|
const buttons = expandButtons(params.button, params.repeat);
|
|
141044
141308
|
const api = await resolveTvApi(registry2, device.id);
|
|
141045
|
-
if (
|
|
141046
|
-
const bad = [...new Set(buttons.filter((b) =>
|
|
141309
|
+
if (unsupported2?.size) {
|
|
141310
|
+
const bad = [...new Set(buttons.filter((b) => unsupported2.has(b)))];
|
|
141047
141311
|
if (bad.length) {
|
|
141048
141312
|
throw new UnsupportedOperationError(
|
|
141049
141313
|
"tv-remote",
|
|
@@ -146929,7 +147193,7 @@ Allowed tools and their args (udid is auto-injected, do NOT include it in args):
|
|
|
146929
147193
|
paste: { text: string } (device clipboard + paste shortcut; only where a user would paste, e.g. an OTP; keyboard otherwise) [ios sim/android emu]
|
|
146930
147194
|
rotate: { orientation: "Portrait"|"LandscapeLeft"|"LandscapeRight"|"PortraitUpsideDown" } [ios/android]
|
|
146931
147195
|
shake: { count?: number } [ios sim/android emu]
|
|
146932
|
-
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]
|
|
146933
147197
|
tv-remote: { button: <remote button | array of them>, repeat?: number } [apple tv/android tv/vega]
|
|
146934
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)
|
|
146935
147199
|
await-ui-element: { condition: "exists"|"visible"|"hidden"|"text", selector: {text?,identifier?,role?}, expectedText?, timeoutMs?, pollIntervalMs? } [ios/android/chromium]
|
|
@@ -149400,7 +149664,7 @@ function formatDescribeTree(root, opts) {
|
|
|
149400
149664
|
}
|
|
149401
149665
|
|
|
149402
149666
|
// ../tool-server/src/tools/describe/platforms/tv.ts
|
|
149403
|
-
var
|
|
149667
|
+
var sleep14 = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
149404
149668
|
var EMPTY_RETRY_ATTEMPTS = 3;
|
|
149405
149669
|
var EMPTY_RETRY_DELAY_MS = 600;
|
|
149406
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.";
|
|
@@ -149441,7 +149705,7 @@ async function describeTv(registry2, device) {
|
|
|
149441
149705
|
let res = await api.describe();
|
|
149442
149706
|
if (device.platform !== "android") {
|
|
149443
149707
|
for (let attempt = 1; attempt < EMPTY_RETRY_ATTEMPTS && isEmpty(res); attempt++) {
|
|
149444
|
-
await
|
|
149708
|
+
await sleep14(EMPTY_RETRY_DELAY_MS);
|
|
149445
149709
|
res = await api.describe();
|
|
149446
149710
|
}
|
|
149447
149711
|
}
|
|
@@ -150025,12 +150289,12 @@ function adaptAndroidTree(capture, screen) {
|
|
|
150025
150289
|
unsupportedFields: LEGACY_UNSUPPORTED_FIELDS2
|
|
150026
150290
|
};
|
|
150027
150291
|
if (!v2) return tree;
|
|
150028
|
-
const
|
|
150292
|
+
const unsupported2 = [];
|
|
150029
150293
|
const sdk = capture.sdkInt ?? 0;
|
|
150030
|
-
if (sdk < 26)
|
|
150031
|
-
if (sdk < 28)
|
|
150294
|
+
if (sdk < 26) unsupported2.push("placeholder", "hintShowing");
|
|
150295
|
+
if (sdk < 28) unsupported2.push("heading");
|
|
150032
150296
|
if (capture.captureMode === "active-window") {
|
|
150033
|
-
|
|
150297
|
+
unsupported2.push("keyboardVisible", "foregroundApp", "alertVisible");
|
|
150034
150298
|
} else {
|
|
150035
150299
|
let keyboard = false;
|
|
150036
150300
|
let foreground;
|
|
@@ -150056,7 +150320,7 @@ function adaptAndroidTree(capture, screen) {
|
|
|
150056
150320
|
}
|
|
150057
150321
|
}
|
|
150058
150322
|
}
|
|
150059
|
-
tree.unsupportedFields =
|
|
150323
|
+
tree.unsupportedFields = unsupported2;
|
|
150060
150324
|
return tree;
|
|
150061
150325
|
}
|
|
150062
150326
|
async function readAndroidUiTree(registry2, device) {
|
|
@@ -150253,7 +150517,7 @@ init_src2();
|
|
|
150253
150517
|
var MAX_CLEAR_KEYS = 100;
|
|
150254
150518
|
var XCTEST_DELETE = "\b";
|
|
150255
150519
|
var KEY_TIMING = { holdMs: 20, settleMs: 300, typeDelayMs: 30 };
|
|
150256
|
-
var
|
|
150520
|
+
var sleep15 = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
150257
150521
|
function typeableOnSimulator(text) {
|
|
150258
150522
|
for (const char of text) if (!charToKeyPress(char)) return false;
|
|
150259
150523
|
return true;
|
|
@@ -150278,9 +150542,9 @@ async function pressSimulatorBackspace(registry2, device) {
|
|
|
150278
150542
|
const ref = simulatorServerRef(device);
|
|
150279
150543
|
const api = await registry2.resolveService(ref.urn, ref.options);
|
|
150280
150544
|
await api.pressKey("Down", NAMED_KEYS.backspace);
|
|
150281
|
-
await
|
|
150545
|
+
await sleep15(KEY_TIMING.holdMs);
|
|
150282
150546
|
await api.pressKey("Up", NAMED_KEYS.backspace);
|
|
150283
|
-
await
|
|
150547
|
+
await sleep15(KEY_TIMING.settleMs);
|
|
150284
150548
|
}
|
|
150285
150549
|
async function typeOnSimulator(registry2, device, text) {
|
|
150286
150550
|
if (!text) return;
|
|
@@ -154755,6 +155019,9 @@ async function runIosProfilerPipeline(files, options = {}) {
|
|
|
154755
155019
|
return { bottlenecks, cpuSamples, uiHangs, cpuHotspots, memoryLeaks };
|
|
154756
155020
|
}
|
|
154757
155021
|
|
|
155022
|
+
// ../tool-server/src/utils/ios-profiler/capture-strategy/select.ts
|
|
155023
|
+
var import_child_process3 = require("child_process");
|
|
155024
|
+
|
|
154758
155025
|
// ../tool-server/src/utils/ios-profiler/capture-strategy/device.ts
|
|
154759
155026
|
var deviceStrategy = {
|
|
154760
155027
|
name: "device",
|
|
@@ -154810,7 +155077,6 @@ var allProcessesStrategy = {
|
|
|
154810
155077
|
};
|
|
154811
155078
|
|
|
154812
155079
|
// ../tool-server/src/utils/ios-profiler/capture-strategy/select.ts
|
|
154813
|
-
var import_child_process3 = require("child_process");
|
|
154814
155080
|
var ENV_OVERRIDE = "ARGENT_IOS_CAPTURE";
|
|
154815
155081
|
function parseEnvOverride() {
|
|
154816
155082
|
const original = process.env[ENV_OVERRIDE]?.trim();
|
|
@@ -164248,7 +164514,7 @@ var FOCUS_REPORTING_SOURCES = /* @__PURE__ */ new Set([
|
|
|
164248
164514
|
"cdp-dom"
|
|
164249
164515
|
]);
|
|
164250
164516
|
var SETTLE_POLL_MS2 = 250;
|
|
164251
|
-
var
|
|
164517
|
+
var SETTLE_TIMEOUT_MS3 = 3e3;
|
|
164252
164518
|
var SETTLE_MIN_READS = 2;
|
|
164253
164519
|
var MAX_SCROLL_ITERATIONS = 25;
|
|
164254
164520
|
var SCROLL_INCREMENT = 0.5;
|
|
@@ -164315,7 +164581,7 @@ function readFlowTree(env) {
|
|
|
164315
164581
|
});
|
|
164316
164582
|
}
|
|
164317
164583
|
async function settleTree(env, opts = {}) {
|
|
164318
|
-
const deadline = Date.now() +
|
|
164584
|
+
const deadline = Date.now() + SETTLE_TIMEOUT_MS3;
|
|
164319
164585
|
let prevFp;
|
|
164320
164586
|
let prevTree;
|
|
164321
164587
|
let lastError;
|
|
@@ -165564,12 +165830,12 @@ function flowExecuteOutcome(result) {
|
|
|
165564
165830
|
function runSequenceOutcome(result) {
|
|
165565
165831
|
const steps = result.steps;
|
|
165566
165832
|
if (!Array.isArray(steps)) return void 0;
|
|
165567
|
-
const
|
|
165568
|
-
if (
|
|
165569
|
-
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";
|
|
165570
165836
|
return {
|
|
165571
165837
|
status: "fail",
|
|
165572
|
-
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)}`
|
|
165573
165839
|
};
|
|
165574
165840
|
}
|
|
165575
165841
|
const total = count(result.total);
|
|
@@ -166574,7 +166840,7 @@ function isTerminalResponse(response) {
|
|
|
166574
166840
|
var DEFAULT_SCRIPT_TIMEOUT_MS = 3e4;
|
|
166575
166841
|
var SCRIPT_STEP_LOG_LIMIT_BYTES = 64 * 1024;
|
|
166576
166842
|
var SCRIPT_RUN_LOG_LIMIT_BYTES = 256 * 1024;
|
|
166577
|
-
var
|
|
166843
|
+
var SETTLE_TIMEOUT_MS4 = 500;
|
|
166578
166844
|
var STOP_GRACE_MS2 = 1500;
|
|
166579
166845
|
var CHILD_DEADLINE_MARGIN_MS = 2e3;
|
|
166580
166846
|
var GROUP_POLL_MS = 50;
|
|
@@ -166985,7 +167251,7 @@ var FlowScriptExecutor = class {
|
|
|
166985
167251
|
const deadlinePassed = Date.now() >= deadlineAt;
|
|
166986
167252
|
clearTimeout(timer);
|
|
166987
167253
|
request3.signal?.removeEventListener("abort", onAbort);
|
|
166988
|
-
await Promise.race([closed, sleep(
|
|
167254
|
+
await Promise.race([closed, sleep(SETTLE_TIMEOUT_MS4)]);
|
|
166989
167255
|
await stop();
|
|
166990
167256
|
capture.end();
|
|
166991
167257
|
child.stdout?.destroy();
|
|
@@ -167025,11 +167291,11 @@ function flowScriptExecutor() {
|
|
|
167025
167291
|
}
|
|
167026
167292
|
function classifyOutcome(input2) {
|
|
167027
167293
|
const { exit } = input2;
|
|
167028
|
-
if (input2.spawnProblem) return
|
|
167029
|
-
if (input2.protocolProblem) return
|
|
167294
|
+
if (input2.spawnProblem) return failed2("spawn", input2.spawnProblem);
|
|
167295
|
+
if (input2.protocolProblem) return failed2("protocol", input2.protocolProblem);
|
|
167030
167296
|
if (input2.terminal) {
|
|
167031
167297
|
if (input2.terminal.type === "failure") {
|
|
167032
|
-
return
|
|
167298
|
+
return failed2(
|
|
167033
167299
|
input2.terminal.failureType,
|
|
167034
167300
|
clampText(input2.terminal.message, SCRIPT_MAX_FAILURE_MESSAGE_CHARS),
|
|
167035
167301
|
clampText(input2.terminal.stack, SCRIPT_MAX_FAILURE_STACK_CHARS)
|
|
@@ -167038,26 +167304,26 @@ function classifyOutcome(input2) {
|
|
|
167038
167304
|
return commitOutput(input2.terminal.outputJson);
|
|
167039
167305
|
}
|
|
167040
167306
|
if (input2.interrupted === "cancelled") {
|
|
167041
|
-
return
|
|
167307
|
+
return failed2("cancelled", "The run was cancelled and the script process was stopped.");
|
|
167042
167308
|
}
|
|
167043
167309
|
if (input2.interrupted === "timeout") return timedOut(input2.timeoutMs);
|
|
167044
167310
|
if (isHeapAbort(exit, input2.heapFatalSeen)) {
|
|
167045
|
-
return
|
|
167311
|
+
return failed2("heap", `The script exceeded its ${input2.heapLimitMb} MiB heap limit.`);
|
|
167046
167312
|
}
|
|
167047
167313
|
if (!input2.startedSeen) {
|
|
167048
|
-
return
|
|
167314
|
+
return failed2(
|
|
167049
167315
|
"protocol",
|
|
167050
167316
|
`The script runner exited before it started the script (${describeExit(exit)}).`
|
|
167051
167317
|
);
|
|
167052
167318
|
}
|
|
167053
167319
|
if (exit.signal) {
|
|
167054
167320
|
if (input2.deadlinePassed) return timedOut(input2.timeoutMs);
|
|
167055
|
-
return
|
|
167321
|
+
return failed2(
|
|
167056
167322
|
"signal",
|
|
167057
167323
|
`The script process was killed by ${exit.signal} before it returned output. It did not stop itself.`
|
|
167058
167324
|
);
|
|
167059
167325
|
}
|
|
167060
|
-
return
|
|
167326
|
+
return failed2(
|
|
167061
167327
|
"exit",
|
|
167062
167328
|
`The script stopped its own process with exit code ${exit.code ?? 0} instead of returning; no output was captured.`
|
|
167063
167329
|
);
|
|
@@ -167067,7 +167333,7 @@ function redactSecrets(verdict, secrets) {
|
|
|
167067
167333
|
if (verdict.output) {
|
|
167068
167334
|
const collision = scrubDocument(verdict.output, secrets);
|
|
167069
167335
|
if (collision) {
|
|
167070
|
-
return
|
|
167336
|
+
return failed2(
|
|
167071
167337
|
"output",
|
|
167072
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.`
|
|
167073
167339
|
);
|
|
@@ -167114,7 +167380,7 @@ function scrubDocument(root, secrets) {
|
|
|
167114
167380
|
function commitOutput(outputJson) {
|
|
167115
167381
|
const bytes = Buffer.byteLength(outputJson, "utf8");
|
|
167116
167382
|
if (bytes > SCRIPT_MAX_OUTPUT_BYTES) {
|
|
167117
|
-
return
|
|
167383
|
+
return failed2(
|
|
167118
167384
|
"output",
|
|
167119
167385
|
`The script returned ${describeBytes(bytes)} of encoded output; the limit is ${describeBytes(SCRIPT_MAX_OUTPUT_BYTES)}.`
|
|
167120
167386
|
);
|
|
@@ -167123,14 +167389,14 @@ function commitOutput(outputJson) {
|
|
|
167123
167389
|
try {
|
|
167124
167390
|
parsed = JSON.parse(outputJson);
|
|
167125
167391
|
} catch (err) {
|
|
167126
|
-
return
|
|
167392
|
+
return failed2("output", `The script's output did not parse: ${errorMessage(err)}`);
|
|
167127
167393
|
}
|
|
167128
167394
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
167129
|
-
return
|
|
167395
|
+
return failed2("output", "The script's output was not an object.");
|
|
167130
167396
|
}
|
|
167131
167397
|
const polluted = findOwnProtoKey(parsed);
|
|
167132
167398
|
if (polluted !== void 0) {
|
|
167133
|
-
return
|
|
167399
|
+
return failed2(
|
|
167134
167400
|
"output",
|
|
167135
167401
|
`${polluted} has an own "__proto__" key; output must be JSON-compatible data.`
|
|
167136
167402
|
);
|
|
@@ -167191,12 +167457,12 @@ function clampText(text, max) {
|
|
|
167191
167457
|
return marked;
|
|
167192
167458
|
}
|
|
167193
167459
|
function timedOut(timeoutMs) {
|
|
167194
|
-
return
|
|
167460
|
+
return failed2(
|
|
167195
167461
|
"timeout",
|
|
167196
167462
|
`The script did not finish within its ${describeDuration(timeoutMs)} time limit and its process tree was stopped.`
|
|
167197
167463
|
);
|
|
167198
167464
|
}
|
|
167199
|
-
function
|
|
167465
|
+
function failed2(kind, message, stack) {
|
|
167200
167466
|
return { ok: false, failure: { kind, message, ...stack ? { stack } : {} } };
|
|
167201
167467
|
}
|
|
167202
167468
|
function isHeapAbort(exit, heapFatalSeen) {
|
|
@@ -171297,13 +171563,13 @@ async function frontChromiumPage(registry2, device) {
|
|
|
171297
171563
|
}
|
|
171298
171564
|
function summarize(flowName, deviceId, executionPrerequisite, steps, aborted2, timing) {
|
|
171299
171565
|
let passed = 0;
|
|
171300
|
-
let
|
|
171566
|
+
let failed3 = 0;
|
|
171301
171567
|
let skipped = 0;
|
|
171302
171568
|
let errored = 0;
|
|
171303
171569
|
for (const s of steps) {
|
|
171304
171570
|
if (s.kind === "echo") continue;
|
|
171305
171571
|
if (s.status === "pass") passed++;
|
|
171306
|
-
else if (s.status === "fail")
|
|
171572
|
+
else if (s.status === "fail") failed3++;
|
|
171307
171573
|
else if (s.status === "skip") skipped++;
|
|
171308
171574
|
else errored++;
|
|
171309
171575
|
}
|
|
@@ -171315,10 +171581,10 @@ function summarize(flowName, deviceId, executionPrerequisite, steps, aborted2, t
|
|
|
171315
171581
|
// (no fail/error report), so the verdict folds the abort in directly. A
|
|
171316
171582
|
// skip by itself is NOT a failure: an unmet `when:` guard skips its block
|
|
171317
171583
|
// as a successful omission.
|
|
171318
|
-
ok:
|
|
171584
|
+
ok: failed3 === 0 && errored === 0 && !aborted2,
|
|
171319
171585
|
...aborted2 ? { aborted: true } : {},
|
|
171320
171586
|
passed,
|
|
171321
|
-
failed:
|
|
171587
|
+
failed: failed3,
|
|
171322
171588
|
skipped,
|
|
171323
171589
|
errored,
|
|
171324
171590
|
steps,
|
|
@@ -171621,9 +171887,10 @@ async function runFold(state3, step) {
|
|
|
171621
171887
|
const result = await invokeSubTool(registry2, ctx, "fold", args);
|
|
171622
171888
|
const size = result.screen?.width !== void 0 && result.screen?.height !== void 0 ? ` ${result.screen.width}x${result.screen.height}` : "";
|
|
171623
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})`;
|
|
171624
171891
|
return {
|
|
171625
171892
|
ok: true,
|
|
171626
|
-
reason: `${target}:
|
|
171893
|
+
reason: `${target}: ${landed}`,
|
|
171627
171894
|
...result.warning !== void 0 ? { warning: result.warning } : {}
|
|
171628
171895
|
};
|
|
171629
171896
|
} catch (err) {
|
|
@@ -172527,7 +172794,7 @@ var updateArgentTool = {
|
|
|
172527
172794
|
});
|
|
172528
172795
|
child.unref();
|
|
172529
172796
|
}, 2e3);
|
|
172530
|
-
const
|
|
172797
|
+
const targetLabel3 = effectiveTarget === "both" ? "global and project-local installs" : `${effectiveTarget} install`;
|
|
172531
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.` : "";
|
|
172532
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.` : "";
|
|
172533
172800
|
const versionInfo = targetsOnlyRunningInstall ? `(v${currentVersion} -> v${installableVersion}) ` : "";
|
|
@@ -172535,7 +172802,7 @@ var updateArgentTool = {
|
|
|
172535
172802
|
const coversRunningInstall = targetsOnlyRunningInstall || effectiveTarget === "both";
|
|
172536
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.`;
|
|
172537
172804
|
return {
|
|
172538
|
-
message: `Argent update initiated ${versionInfo}for the ${
|
|
172805
|
+
message: `Argent update initiated ${versionInfo}for the ${targetLabel3}.` + crossTargetNote + restartNote + `${otherHint}${bothDegradedNote}`
|
|
172539
172806
|
};
|
|
172540
172807
|
}
|
|
172541
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
|
|