@exodus/xqa 1.13.0 → 1.14.1
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/xqa.cjs +211 -171
- package/package.json +3 -3
package/dist/xqa.cjs
CHANGED
|
@@ -52247,7 +52247,6 @@ function createAccessibilitySnapshotTool(udid = "booted") {
|
|
|
52247
52247
|
}
|
|
52248
52248
|
);
|
|
52249
52249
|
}
|
|
52250
|
-
var accessibilitySnapshotTool = createAccessibilitySnapshotTool();
|
|
52251
52250
|
var LAUNCH_APP_TOOL_NAME = "launch_app";
|
|
52252
52251
|
var TERMINATE_APP_TOOL_NAME = "terminate_app";
|
|
52253
52252
|
var LIST_APPS_TOOL_NAME = "list_apps";
|
|
@@ -52348,11 +52347,9 @@ function createListAppsTool(udid = "booted") {
|
|
|
52348
52347
|
async () => handleListApps(udid)
|
|
52349
52348
|
);
|
|
52350
52349
|
}
|
|
52351
|
-
var launchAppTool = createLaunchAppTool();
|
|
52352
|
-
var terminateAppTool = createTerminateAppTool();
|
|
52353
|
-
var listAppsTool = createListAppsTool();
|
|
52354
52350
|
var DEFAULT_LONG_PRESS_DURATION_MS = 500;
|
|
52355
52351
|
var MS_PER_SECOND = 1e3;
|
|
52352
|
+
var DEFAULT_SWIPE_DURATION_SECONDS = 0.1;
|
|
52356
52353
|
var ENTER_KEY_CODE = "0x28";
|
|
52357
52354
|
var TAP_SCHEMA = { x: external_exports.number(), y: external_exports.number() };
|
|
52358
52355
|
var LONG_PRESS_SCHEMA = { x: external_exports.number(), y: external_exports.number(), duration: external_exports.number().optional() };
|
|
@@ -52360,7 +52357,10 @@ var SWIPE_SCHEMA = {
|
|
|
52360
52357
|
x_start: external_exports.number(),
|
|
52361
52358
|
y_start: external_exports.number(),
|
|
52362
52359
|
x_end: external_exports.number(),
|
|
52363
|
-
y_end: external_exports.number()
|
|
52360
|
+
y_end: external_exports.number(),
|
|
52361
|
+
duration: external_exports.number().positive().optional().describe(
|
|
52362
|
+
"Gesture duration in seconds. Smaller values (e.g. 0.1) produce a flick that scrolls lists and dismisses sheets; larger values (e.g. 0.5+) produce a slow drag suitable for reordering or panning. Defaults to 0.1s (flick)."
|
|
52363
|
+
)
|
|
52364
52364
|
};
|
|
52365
52365
|
var TYPE_TEXT_SCHEMA = { text: external_exports.string(), submit: external_exports.boolean().optional() };
|
|
52366
52366
|
var PRESS_BUTTON_SCHEMA = {
|
|
@@ -52443,7 +52443,9 @@ function buildSwipeArguments(resolvedUdid, input) {
|
|
|
52443
52443
|
String(input.xStart),
|
|
52444
52444
|
String(input.yStart),
|
|
52445
52445
|
String(input.xEnd),
|
|
52446
|
-
String(input.yEnd)
|
|
52446
|
+
String(input.yEnd),
|
|
52447
|
+
"--duration",
|
|
52448
|
+
String(input.duration)
|
|
52447
52449
|
];
|
|
52448
52450
|
}
|
|
52449
52451
|
async function handleSwipe(input) {
|
|
@@ -52510,17 +52512,22 @@ function createLongPressTool(udid = "booted") {
|
|
|
52510
52512
|
async ({ x, y: y6, duration: duration3 }) => handleLongPress({ udid, x, y: y6, duration: duration3 ?? DEFAULT_LONG_PRESS_DURATION_MS })
|
|
52511
52513
|
);
|
|
52512
52514
|
}
|
|
52515
|
+
function toSwipeInput(udid, params) {
|
|
52516
|
+
return {
|
|
52517
|
+
udid,
|
|
52518
|
+
xStart: params.x_start,
|
|
52519
|
+
yStart: params.y_start,
|
|
52520
|
+
xEnd: params.x_end,
|
|
52521
|
+
yEnd: params.y_end,
|
|
52522
|
+
duration: params.duration ?? DEFAULT_SWIPE_DURATION_SECONDS
|
|
52523
|
+
};
|
|
52524
|
+
}
|
|
52513
52525
|
function createSwipeTool(udid = "booted") {
|
|
52514
52526
|
return _x(
|
|
52515
52527
|
"swipe",
|
|
52516
|
-
"Swipe on the screen from one point to another.",
|
|
52528
|
+
"Swipe on the screen from one point to another. `duration` (seconds) controls gesture velocity: the default 0.1s produces a flick that scrolls lists, dismisses sheets and triggers paging; pass a larger value (e.g. 0.5+) for a slow drag suitable for reordering or panning. Omitting `duration` uses the flick default.",
|
|
52517
52529
|
SWIPE_SCHEMA,
|
|
52518
|
-
async (
|
|
52519
|
-
x_start,
|
|
52520
|
-
y_start,
|
|
52521
|
-
x_end,
|
|
52522
|
-
y_end
|
|
52523
|
-
}) => handleSwipe({ udid, xStart: x_start, yStart: y_start, xEnd: x_end, yEnd: y_end })
|
|
52530
|
+
async (params) => handleSwipe(toSwipeInput(udid, params))
|
|
52524
52531
|
);
|
|
52525
52532
|
}
|
|
52526
52533
|
function createTypeTextTool(udid = "booted") {
|
|
@@ -52539,12 +52546,6 @@ function createPressButtonTool(udid = "booted") {
|
|
|
52539
52546
|
async ({ button }) => handlePressButton(udid, button)
|
|
52540
52547
|
);
|
|
52541
52548
|
}
|
|
52542
|
-
var tapTool = createTapTool();
|
|
52543
|
-
var doubleTapTool = createDoubleTapTool();
|
|
52544
|
-
var longPressTool = createLongPressTool();
|
|
52545
|
-
var swipeTool = createSwipeTool();
|
|
52546
|
-
var typeTextTool = createTypeTextTool();
|
|
52547
|
-
var pressButtonTool = createPressButtonTool();
|
|
52548
52549
|
var cache2 = /* @__PURE__ */ new Map();
|
|
52549
52550
|
function parseDeviceInfo(raw) {
|
|
52550
52551
|
const parsed = JSON.parse(raw);
|
|
@@ -52620,7 +52621,6 @@ function createScreenshotTool(udid = "booted") {
|
|
|
52620
52621
|
}
|
|
52621
52622
|
);
|
|
52622
52623
|
}
|
|
52623
|
-
var screenshotTool = createScreenshotTool();
|
|
52624
52624
|
var MIN_WAIT_SECONDS = 1;
|
|
52625
52625
|
var MAX_WAIT_SECONDS = 10;
|
|
52626
52626
|
var MS_PER_SECOND2 = 1e3;
|
|
@@ -62198,121 +62198,24 @@ var import_neverthrow33 = __toESM(require_index_cjs(), 1);
|
|
|
62198
62198
|
var import_promises16 = require("node:timers/promises");
|
|
62199
62199
|
|
|
62200
62200
|
// ../../agents/explorer/dist/index.js
|
|
62201
|
-
var import_node_child_process3 = require("node:child_process");
|
|
62202
|
-
var import_node_fs = require("node:fs");
|
|
62203
|
-
var import_node_path4 = __toESM(require("node:path"), 1);
|
|
62204
|
-
var import_neverthrow15 = __toESM(require_index_cjs(), 1);
|
|
62205
62201
|
var import_promises9 = require("node:timers/promises");
|
|
62202
|
+
var import_neverthrow15 = __toESM(require_index_cjs(), 1);
|
|
62206
62203
|
var import_neverthrow16 = __toESM(require_index_cjs(), 1);
|
|
62207
|
-
var import_neverthrow17 = __toESM(require_index_cjs(), 1);
|
|
62208
62204
|
var import_promises10 = require("node:fs/promises");
|
|
62209
|
-
var
|
|
62210
|
-
var
|
|
62211
|
-
var
|
|
62205
|
+
var import_node_path4 = __toESM(require("node:path"), 1);
|
|
62206
|
+
var import_neverthrow17 = __toESM(require_index_cjs(), 1);
|
|
62207
|
+
var import_node_child_process3 = require("node:child_process");
|
|
62212
62208
|
var import_promises11 = require("node:fs/promises");
|
|
62209
|
+
var import_neverthrow18 = __toESM(require_index_cjs(), 1);
|
|
62213
62210
|
var import_neverthrow19 = __toESM(require_index_cjs(), 1);
|
|
62211
|
+
var import_node_child_process4 = require("node:child_process");
|
|
62212
|
+
var import_node_fs = require("node:fs");
|
|
62213
|
+
var import_node_path5 = __toESM(require("node:path"), 1);
|
|
62214
62214
|
var import_neverthrow20 = __toESM(require_index_cjs(), 1);
|
|
62215
62215
|
var import_neverthrow21 = __toESM(require_index_cjs(), 1);
|
|
62216
62216
|
var import_promises12 = require("node:fs/promises");
|
|
62217
62217
|
var import_node_path6 = __toESM(require("node:path"), 1);
|
|
62218
62218
|
var import_neverthrow22 = __toESM(require_index_cjs(), 1);
|
|
62219
|
-
async function runFfmpeg(arguments_) {
|
|
62220
|
-
const { promise: promise2, resolve, reject } = Promise.withResolvers();
|
|
62221
|
-
(0, import_node_child_process3.execFile)("ffmpeg", arguments_, (error48) => {
|
|
62222
|
-
if (error48) {
|
|
62223
|
-
reject(error48);
|
|
62224
|
-
} else {
|
|
62225
|
-
resolve(true);
|
|
62226
|
-
}
|
|
62227
|
-
});
|
|
62228
|
-
await promise2;
|
|
62229
|
-
}
|
|
62230
|
-
function spawnRecorder(outputPath) {
|
|
62231
|
-
const safeMkdirSync = (0, import_neverthrow15.fromThrowable)(import_node_fs.mkdirSync, (cause) => cause);
|
|
62232
|
-
const safeSpawn2 = (0, import_neverthrow15.fromThrowable)(
|
|
62233
|
-
(command, arguments_) => (0, import_node_child_process3.spawn)(command, arguments_),
|
|
62234
|
-
(cause) => cause
|
|
62235
|
-
);
|
|
62236
|
-
const mkdirResult = safeMkdirSync(import_node_path4.default.dirname(outputPath), { recursive: true });
|
|
62237
|
-
if (mkdirResult.isErr()) {
|
|
62238
|
-
return mkdirResult.error;
|
|
62239
|
-
}
|
|
62240
|
-
const spawnResult = safeSpawn2("xcrun", [
|
|
62241
|
-
"simctl",
|
|
62242
|
-
"io",
|
|
62243
|
-
"booted",
|
|
62244
|
-
"recordVideo",
|
|
62245
|
-
"--force",
|
|
62246
|
-
outputPath
|
|
62247
|
-
]);
|
|
62248
|
-
if (spawnResult.isErr()) {
|
|
62249
|
-
return spawnResult.error;
|
|
62250
|
-
}
|
|
62251
|
-
return spawnResult.value;
|
|
62252
|
-
}
|
|
62253
|
-
function earlyExitError(code) {
|
|
62254
|
-
return new RangeError(`simctl exited with code ${String(code)} before recording started`);
|
|
62255
|
-
}
|
|
62256
|
-
function waitForRecordingStart(proc) {
|
|
62257
|
-
const { stderr } = proc;
|
|
62258
|
-
if (!stderr) {
|
|
62259
|
-
return (0, import_neverthrow15.errAsync)({ type: "SPAWN_FAILED", cause: new RangeError("proc has no stderr pipe") });
|
|
62260
|
-
}
|
|
62261
|
-
const { promise: promise2, resolve, reject } = Promise.withResolvers();
|
|
62262
|
-
proc.once("error", reject);
|
|
62263
|
-
proc.once("exit", (code) => {
|
|
62264
|
-
if (code !== 0) {
|
|
62265
|
-
reject(earlyExitError(code));
|
|
62266
|
-
}
|
|
62267
|
-
});
|
|
62268
|
-
let accumulated = "";
|
|
62269
|
-
stderr.on("data", (chunk) => {
|
|
62270
|
-
accumulated += chunk.toString();
|
|
62271
|
-
if (accumulated.includes("Recording started")) {
|
|
62272
|
-
resolve({ process: proc, startedAt: Date.now() });
|
|
62273
|
-
}
|
|
62274
|
-
});
|
|
62275
|
-
return import_neverthrow15.ResultAsync.fromPromise(
|
|
62276
|
-
promise2,
|
|
62277
|
-
(cause) => ({ type: "SPAWN_FAILED", cause })
|
|
62278
|
-
);
|
|
62279
|
-
}
|
|
62280
|
-
function startRecording(outputPath) {
|
|
62281
|
-
const procOrError = spawnRecorder(outputPath);
|
|
62282
|
-
if (procOrError instanceof Error) {
|
|
62283
|
-
return (0, import_neverthrow15.errAsync)({ type: "SPAWN_FAILED", cause: procOrError });
|
|
62284
|
-
}
|
|
62285
|
-
return waitForRecordingStart(procOrError);
|
|
62286
|
-
}
|
|
62287
|
-
function stopRecording(handle) {
|
|
62288
|
-
const { promise: promise2, resolve, reject } = Promise.withResolvers();
|
|
62289
|
-
handle.process.once("exit", () => {
|
|
62290
|
-
resolve(true);
|
|
62291
|
-
});
|
|
62292
|
-
handle.process.once("error", reject);
|
|
62293
|
-
if (handle.process.exitCode == void 0) {
|
|
62294
|
-
handle.process.kill("SIGINT");
|
|
62295
|
-
} else {
|
|
62296
|
-
resolve(true);
|
|
62297
|
-
}
|
|
62298
|
-
return import_neverthrow15.ResultAsync.fromPromise(
|
|
62299
|
-
promise2,
|
|
62300
|
-
(cause) => ({ type: "STOP_FAILED", cause })
|
|
62301
|
-
);
|
|
62302
|
-
}
|
|
62303
|
-
function speedUpVideo({
|
|
62304
|
-
inputPath,
|
|
62305
|
-
factor,
|
|
62306
|
-
outputPath
|
|
62307
|
-
}) {
|
|
62308
|
-
const pts = String(1 / factor);
|
|
62309
|
-
return (0, import_neverthrow15.fromAsyncThrowable)(
|
|
62310
|
-
runFfmpeg,
|
|
62311
|
-
(cause) => ({ type: "PROCESS_FAILED", cause })
|
|
62312
|
-
)(["-i", inputPath, "-filter:v", `setpts=${pts}*PTS`, "-an", "-y", outputPath]).map(
|
|
62313
|
-
() => outputPath
|
|
62314
|
-
);
|
|
62315
|
-
}
|
|
62316
62219
|
var VIEW_UI_TOOL_NAME = "mcp__mobile-ios__view_ui";
|
|
62317
62220
|
var VIEW_UI_DESCRIPTION = `Capture current screen state: accessibility tree (element labels, positions, attributes) and screenshot in one call. Use when you need to tap an element, assert element presence or labels, check attributes, or track screen identity via <screen_id>.
|
|
62318
62221
|
|
|
@@ -62329,8 +62232,8 @@ function deriveScreenLabel(tree, stepIndex) {
|
|
|
62329
62232
|
return match[1].replaceAll(/([A-Z])/g, "-$1").toLowerCase().replaceAll(/[^a-z\d]+/g, "-").replaceAll(/^-|-$/g, "");
|
|
62330
62233
|
}
|
|
62331
62234
|
async function persistScreenshot(params) {
|
|
62332
|
-
const screenshotPath =
|
|
62333
|
-
const safeWriteFile = (0,
|
|
62235
|
+
const screenshotPath = import_node_path4.default.join(params.screenshotsDirectory, `${params.screenLabel}.png`);
|
|
62236
|
+
const safeWriteFile = (0, import_neverthrow17.fromAsyncThrowable)(
|
|
62334
62237
|
import_promises10.writeFile,
|
|
62335
62238
|
(cause) => ({ type: "WRITE_FAILED", cause })
|
|
62336
62239
|
);
|
|
@@ -62395,11 +62298,11 @@ function buildSnapshotRecord(rawElements, { stepIndex, context }) {
|
|
|
62395
62298
|
}
|
|
62396
62299
|
async function handleScreenshotData(data, params) {
|
|
62397
62300
|
if (params.context.screenshotsDir !== void 0) {
|
|
62398
|
-
const safeWrite2 = (0,
|
|
62301
|
+
const safeWrite2 = (0, import_neverthrow17.fromAsyncThrowable)(
|
|
62399
62302
|
import_promises10.writeFile,
|
|
62400
62303
|
(cause) => ({ type: "WRITE_FAILED", cause })
|
|
62401
62304
|
);
|
|
62402
|
-
const snapshotPath =
|
|
62305
|
+
const snapshotPath = import_node_path4.default.join(params.context.screenshotsDir, `${params.screenLabel}.txt`);
|
|
62403
62306
|
await safeWrite2(snapshotPath, params.formatted).match(
|
|
62404
62307
|
() => true,
|
|
62405
62308
|
() => false
|
|
@@ -62562,23 +62465,23 @@ function processMessage(message, state) {
|
|
|
62562
62465
|
}
|
|
62563
62466
|
if (message.type === "result") {
|
|
62564
62467
|
if (message.subtype !== "success" && !state.timedOut.value && !state.aborted.value) {
|
|
62565
|
-
return (0,
|
|
62468
|
+
return (0, import_neverthrow16.err)(message.errors.join("; "));
|
|
62566
62469
|
}
|
|
62567
|
-
return (0,
|
|
62470
|
+
return (0, import_neverthrow16.ok)(true);
|
|
62568
62471
|
}
|
|
62569
|
-
return (0,
|
|
62472
|
+
return (0, import_neverthrow16.ok)(false);
|
|
62570
62473
|
}
|
|
62571
62474
|
async function processMessages(queryRunner, state) {
|
|
62572
62475
|
for await (const message of queryRunner) {
|
|
62573
62476
|
const result = processMessage(message, state);
|
|
62574
62477
|
if (result.isErr()) {
|
|
62575
|
-
return (0,
|
|
62478
|
+
return (0, import_neverthrow16.err)(result.error);
|
|
62576
62479
|
}
|
|
62577
62480
|
if (result.value) {
|
|
62578
62481
|
break;
|
|
62579
62482
|
}
|
|
62580
62483
|
}
|
|
62581
|
-
return (0,
|
|
62484
|
+
return (0, import_neverthrow16.ok)(null);
|
|
62582
62485
|
}
|
|
62583
62486
|
var MessageQueue = class {
|
|
62584
62487
|
pending = [];
|
|
@@ -62620,7 +62523,7 @@ var MessageQueue = class {
|
|
|
62620
62523
|
}
|
|
62621
62524
|
};
|
|
62622
62525
|
function spawnDetached(options) {
|
|
62623
|
-
const child = (0,
|
|
62526
|
+
const child = (0, import_node_child_process3.spawn)(options.command, options.args, {
|
|
62624
62527
|
cwd: options.cwd,
|
|
62625
62528
|
env: options.env,
|
|
62626
62529
|
stdio: ["pipe", "pipe", "pipe"],
|
|
@@ -62688,7 +62591,6 @@ function buildAgentState({
|
|
|
62688
62591
|
pendingToolCallNames: /* @__PURE__ */ new Map(),
|
|
62689
62592
|
snapshots: [],
|
|
62690
62593
|
stepCounter: { value: 0 },
|
|
62691
|
-
lastEmittedHash: void 0,
|
|
62692
62594
|
captureInput: outputTools.captureInput,
|
|
62693
62595
|
sendMessage: buildSendMessageFunction(inputQueue),
|
|
62694
62596
|
closeQueue: () => {
|
|
@@ -62790,7 +62692,7 @@ var INTERRUPT_DRAIN_TIMEOUT_MS = 1e4;
|
|
|
62790
62692
|
async function interruptOrTimeout(queryRunner) {
|
|
62791
62693
|
await Promise.race([queryRunner.interrupt(), (0, import_promises9.setTimeout)(INTERRUPT_DRAIN_TIMEOUT_MS)]);
|
|
62792
62694
|
}
|
|
62793
|
-
var safeInterruptOrTimeout =
|
|
62695
|
+
var safeInterruptOrTimeout = import_neverthrow15.ResultAsync.fromThrowable(
|
|
62794
62696
|
interruptOrTimeout,
|
|
62795
62697
|
(error48) => error48
|
|
62796
62698
|
);
|
|
@@ -62841,18 +62743,18 @@ function startQueryTimers(config3, context) {
|
|
|
62841
62743
|
}
|
|
62842
62744
|
function awaitMessagesAndResolve({ queryRunner, state }, { cleanup: cleanup3, getOutput }) {
|
|
62843
62745
|
const messagesPromise = processMessages(queryRunner, state);
|
|
62844
|
-
return
|
|
62746
|
+
return import_neverthrow15.ResultAsync.fromPromise(messagesPromise, String).andThen((innerResult) => {
|
|
62845
62747
|
cleanup3();
|
|
62846
62748
|
if (innerResult.isErr()) {
|
|
62847
|
-
return (0,
|
|
62749
|
+
return (0, import_neverthrow15.err)(innerResult.error);
|
|
62848
62750
|
}
|
|
62849
|
-
return (0,
|
|
62751
|
+
return (0, import_neverthrow15.ok)(getOutput());
|
|
62850
62752
|
}).orElse((sdkError) => {
|
|
62851
62753
|
cleanup3();
|
|
62852
62754
|
if (!state.timedOut.value && !state.aborted.value) {
|
|
62853
|
-
return (0,
|
|
62755
|
+
return (0, import_neverthrow15.err)(sdkError);
|
|
62854
62756
|
}
|
|
62855
|
-
return (0,
|
|
62757
|
+
return (0, import_neverthrow15.ok)(getOutput());
|
|
62856
62758
|
});
|
|
62857
62759
|
}
|
|
62858
62760
|
function executeQuery({
|
|
@@ -62866,9 +62768,9 @@ function executeQuery({
|
|
|
62866
62768
|
message: { role: "user", content: prompt },
|
|
62867
62769
|
parent_tool_use_id: null
|
|
62868
62770
|
});
|
|
62869
|
-
const queryRunnerResult = (0,
|
|
62771
|
+
const queryRunnerResult = (0, import_neverthrow15.fromThrowable)(Qs, String)({ prompt: inputQueue, options });
|
|
62870
62772
|
if (queryRunnerResult.isErr()) {
|
|
62871
|
-
return (0,
|
|
62773
|
+
return (0, import_neverthrow15.errAsync)(queryRunnerResult.error);
|
|
62872
62774
|
}
|
|
62873
62775
|
const queryRunner = queryRunnerResult.value;
|
|
62874
62776
|
const cleanup3 = startQueryTimers(config3, { state, queryRunner, inputQueue, linkedController });
|
|
@@ -62888,6 +62790,103 @@ function runQuery(prompt, config3) {
|
|
|
62888
62790
|
function collectAgentOutput(prompt, config3) {
|
|
62889
62791
|
return runQuery(prompt, config3).mapErr((cause) => ({ type: "QUERY_FAILED", cause }));
|
|
62890
62792
|
}
|
|
62793
|
+
async function runFfmpeg(arguments_) {
|
|
62794
|
+
const { promise: promise2, resolve, reject } = Promise.withResolvers();
|
|
62795
|
+
(0, import_node_child_process4.execFile)("ffmpeg", arguments_, (error48) => {
|
|
62796
|
+
if (error48) {
|
|
62797
|
+
reject(error48);
|
|
62798
|
+
} else {
|
|
62799
|
+
resolve(true);
|
|
62800
|
+
}
|
|
62801
|
+
});
|
|
62802
|
+
await promise2;
|
|
62803
|
+
}
|
|
62804
|
+
function spawnRecorder(outputPath) {
|
|
62805
|
+
const safeMkdirSync = (0, import_neverthrow20.fromThrowable)(import_node_fs.mkdirSync, (cause) => cause);
|
|
62806
|
+
const safeSpawn2 = (0, import_neverthrow20.fromThrowable)(
|
|
62807
|
+
(command, arguments_) => (0, import_node_child_process4.spawn)(command, arguments_),
|
|
62808
|
+
(cause) => cause
|
|
62809
|
+
);
|
|
62810
|
+
const mkdirResult = safeMkdirSync(import_node_path5.default.dirname(outputPath), { recursive: true });
|
|
62811
|
+
if (mkdirResult.isErr()) {
|
|
62812
|
+
return mkdirResult.error;
|
|
62813
|
+
}
|
|
62814
|
+
const spawnResult = safeSpawn2("xcrun", [
|
|
62815
|
+
"simctl",
|
|
62816
|
+
"io",
|
|
62817
|
+
"booted",
|
|
62818
|
+
"recordVideo",
|
|
62819
|
+
"--force",
|
|
62820
|
+
outputPath
|
|
62821
|
+
]);
|
|
62822
|
+
if (spawnResult.isErr()) {
|
|
62823
|
+
return spawnResult.error;
|
|
62824
|
+
}
|
|
62825
|
+
return spawnResult.value;
|
|
62826
|
+
}
|
|
62827
|
+
function earlyExitError(code) {
|
|
62828
|
+
return new RangeError(`simctl exited with code ${String(code)} before recording started`);
|
|
62829
|
+
}
|
|
62830
|
+
function waitForRecordingStart(proc) {
|
|
62831
|
+
const { stderr } = proc;
|
|
62832
|
+
if (!stderr) {
|
|
62833
|
+
return (0, import_neverthrow20.errAsync)({ type: "SPAWN_FAILED", cause: new RangeError("proc has no stderr pipe") });
|
|
62834
|
+
}
|
|
62835
|
+
const { promise: promise2, resolve, reject } = Promise.withResolvers();
|
|
62836
|
+
proc.once("error", reject);
|
|
62837
|
+
proc.once("exit", (code) => {
|
|
62838
|
+
if (code !== 0) {
|
|
62839
|
+
reject(earlyExitError(code));
|
|
62840
|
+
}
|
|
62841
|
+
});
|
|
62842
|
+
let accumulated = "";
|
|
62843
|
+
stderr.on("data", (chunk) => {
|
|
62844
|
+
accumulated += chunk.toString();
|
|
62845
|
+
if (accumulated.includes("Recording started")) {
|
|
62846
|
+
resolve({ process: proc, startedAt: Date.now() });
|
|
62847
|
+
}
|
|
62848
|
+
});
|
|
62849
|
+
return import_neverthrow20.ResultAsync.fromPromise(
|
|
62850
|
+
promise2,
|
|
62851
|
+
(cause) => ({ type: "SPAWN_FAILED", cause })
|
|
62852
|
+
);
|
|
62853
|
+
}
|
|
62854
|
+
function startRecording(outputPath) {
|
|
62855
|
+
const procOrError = spawnRecorder(outputPath);
|
|
62856
|
+
if (procOrError instanceof Error) {
|
|
62857
|
+
return (0, import_neverthrow20.errAsync)({ type: "SPAWN_FAILED", cause: procOrError });
|
|
62858
|
+
}
|
|
62859
|
+
return waitForRecordingStart(procOrError);
|
|
62860
|
+
}
|
|
62861
|
+
function stopRecording(handle) {
|
|
62862
|
+
const { promise: promise2, resolve, reject } = Promise.withResolvers();
|
|
62863
|
+
handle.process.once("exit", () => {
|
|
62864
|
+
resolve(true);
|
|
62865
|
+
});
|
|
62866
|
+
handle.process.once("error", reject);
|
|
62867
|
+
if (handle.process.exitCode == void 0) {
|
|
62868
|
+
handle.process.kill("SIGINT");
|
|
62869
|
+
} else {
|
|
62870
|
+
resolve(true);
|
|
62871
|
+
}
|
|
62872
|
+
return import_neverthrow20.ResultAsync.fromPromise(
|
|
62873
|
+
promise2,
|
|
62874
|
+
(cause) => ({ type: "STOP_FAILED", cause })
|
|
62875
|
+
);
|
|
62876
|
+
}
|
|
62877
|
+
function speedUpVideo({
|
|
62878
|
+
inputPath,
|
|
62879
|
+
factor,
|
|
62880
|
+
outputPath
|
|
62881
|
+
}) {
|
|
62882
|
+
const pts = String(1 / factor);
|
|
62883
|
+
return (0, import_neverthrow20.fromAsyncThrowable)(
|
|
62884
|
+
runFfmpeg,
|
|
62885
|
+
(cause) => ({ type: "PROCESS_FAILED", cause })
|
|
62886
|
+
)(["-i", inputPath, "-filter:v", `setpts=${pts}*PTS`, "-an", "-y", outputPath]).map(
|
|
62887
|
+
() => outputPath
|
|
62888
|
+
);
|
|
62889
|
+
}
|
|
62891
62890
|
var SPEED_2X = 2;
|
|
62892
62891
|
var SPEED_4X = 4;
|
|
62893
62892
|
function applySpeedUpVariants({
|
|
@@ -62898,7 +62897,7 @@ function applySpeedUpVariants({
|
|
|
62898
62897
|
const { findings, snapshots } = result;
|
|
62899
62898
|
const { videoPath, videoPath2x, videoPath4x, signal } = params;
|
|
62900
62899
|
if (signal?.aborted) {
|
|
62901
|
-
return (0,
|
|
62900
|
+
return (0, import_neverthrow19.okAsync)({ findings, snapshots });
|
|
62902
62901
|
}
|
|
62903
62902
|
return speedUpVideo({ inputPath: videoPath, factor: SPEED_2X, outputPath: videoPath2x }).mapErr(toRecordingError).andThen(
|
|
62904
62903
|
() => speedUpVideo({ inputPath: videoPath, factor: SPEED_4X, outputPath: videoPath4x }).mapErr(toRecordingError).map(() => ({ findings, snapshots }))
|
|
@@ -62912,7 +62911,7 @@ function runWithRecording(handle, collectOutput) {
|
|
|
62912
62911
|
return collectOutput().andThen(
|
|
62913
62912
|
({ findings, snapshots }) => stopRecording(handle).mapErr(toRecordingError).map(() => ({ findings, snapshots }))
|
|
62914
62913
|
).orElse(
|
|
62915
|
-
(error48) => stopRecording(handle).mapErr(toRecordingError).andThen(() => (0,
|
|
62914
|
+
(error48) => stopRecording(handle).mapErr(toRecordingError).andThen(() => (0, import_neverthrow19.errAsync)(error48)).orElse(() => (0, import_neverthrow19.errAsync)(error48))
|
|
62916
62915
|
);
|
|
62917
62916
|
}
|
|
62918
62917
|
function startAndRun(params) {
|
|
@@ -62953,12 +62952,18 @@ At every reasoning step, maintain a mental ledger:
|
|
|
62953
62952
|
|
|
62954
62953
|
Consult the ledger before every action. Always prefer navigating to a QUEUE screen over a VISITED one.`;
|
|
62955
62954
|
var SESSION_START_RULE = `Before taking any other action \u2014 including initializing the Working State ledger or emitting findings \u2014 call \`view_ui\` once to observe the starting screen`;
|
|
62956
|
-
var BACK_NAV_RULE = `After navigating forward to any new screen:
|
|
62957
|
-
var STUCK_LOOP_RULE = `Stuck loop: emit a \`stuck-loop\` finding when any of these occur: (1) \`view_ui\` returns the same \`<screen_id>\` across 3 or more consecutive \`view_ui\` calls, (2) the same element has been tapped more than twice with no screen change confirmed by \`view_ui\`, (3) PATH shows the same screen at two non-adjacent positions \u2014 before emitting, try one alternative action (scroll, long-press, swipe) to rule out a gesture mismatch \u2014 note: \`screenshot\`-only calls do not update the stuck-loop counter; only \`view_ui\` calls count
|
|
62955
|
+
var BACK_NAV_RULE = `After navigating forward to any new screen: attempt to return to the expected parent in PATH \u2014 consult App Knowledge first for the correct exit gesture on this screen, then try in order: (1) any visible back/close button, (2) OS back gesture, (3) swipe up, (4) swipe down, (5) swipe left, (6) swipe right \u2014 confirm return via \`screenshot\` if the parent is visually unambiguous, \`view_ui\` otherwise \u2014 only after ALL attempts fail emit a \`back-nav-failure\` finding, then navigate forward again to continue`;
|
|
62956
|
+
var STUCK_LOOP_RULE = `Stuck loop: emit a \`stuck-loop\` finding when any of these signals occur: (1) \`view_ui\` returns the same \`<screen_id>\` across 3 or more consecutive \`view_ui\` calls, (2) the same element has been tapped more than twice with no screen change confirmed by \`view_ui\`, (3) PATH shows the same screen at two non-adjacent positions \u2014 i.e. you visited a screen, left it, and returned. Example: \`Portfolio \u2192 Asset \u2192 Swap \u2192 Asset\` (Asset revisited). Indicates circular navigation. NOT tripped by adjacent repeats like \`Portfolio \u2192 Asset \u2192 Asset\` (normal state refresh), (4) a scrollable list's element positions \u2014 x for horizontal, y for vertical \u2014 are identical across 2 or more consecutive swipe+\`view_ui\` cycles (zero-delta scroll stall) \u2014 before emitting for (1)\u2013(3), try one alternative action (scroll, long-press, swipe) to rule out a gesture mismatch \u2014 before emitting for (4), baseline element positions via \`view_ui\`, then vary one spatial parameter on the next attempt: shift swipe start coord by \u226540% of visible container dimension, OR reverse direction to probe opposite bound, OR extend throw to span \u226580% of viewport dimension \u2014 then \`view_ui\` and compare. Example: if tab bar positions \`Tokens(-31) ETH(65) ... Tron(352)\` are unchanged across 2 swipes starting near x=350, shift start to x=10 or x=395 and extend end past the opposite viewport edge. If delta still zero, emit \`stuck-loop\` with axis, before/after positions, and variation attempted \u2014 note: \`screenshot\`-only calls do not update the stuck-loop counter; only \`view_ui\` calls count. Zero-delta scroll stall is not a separate finding type \u2014 report as \`stuck-loop\`.`;
|
|
62958
62957
|
var LOADING_STATE_RULE = `Transient loading state: when the screen shows spinners, skeleton screens, progress bars, "Loading..." text, or placeholder content NOT described in spec or app context \u2014 use \`screenshot\` to poll for resolution (up to 3 retries); switch to \`view_ui\` only on the final check or when you need element data to act \u2014 if loading persists after 3 retries, proceed with what is visible; if spec or app context explicitly describes a loading screen as a step, do not retry \u2014 call \`view_ui\` and assert normally`;
|
|
62959
62958
|
var EXPECTED_CONTENT_MISSING_RULE = `Expected content missing: when \`view_ui\` shows no loading indicator yet omits an element named or strongly implied by spec or app context \u2014 and its absence is not semantically consistent with the current screen \u2014 call \`wait_seconds\` with 2\u20135 seconds and retry \`view_ui\` up to 2 times; if element remains absent, emit a \`missing-content\` finding stating what was expected and what was observed`;
|
|
62960
62959
|
var CLIPPED_ELEMENT_RULE = `Never tap an element tagged \`[clipped-top]\`, \`[clipped-bottom]\`, \`[clipped-left]\`, or \`[clipped-right]\` \u2014 scroll to fully reveal it first, then re-call \`view_ui\` before tapping`;
|
|
62961
|
-
var
|
|
62960
|
+
var SCROLL_FOLD_RULE = `Scrollable lists: elements outside the visible viewport are absent from the a11y tree by design \u2014 this applies to elements below the fold in vertical lists AND elements clipped off-left or off-right in horizontal lists \u2014 scroll or swipe in the appropriate axis to reveal before asserting presence or absence; never emit a finding solely because list items, rows, or tabs are missing from the tree on a scrollable screen; if swipe attempts yield no position change across 2+ cycles, apply the scroll-stall path in STUCK_LOOP_RULE.`;
|
|
62961
|
+
var FINDING_TAXONOMY_SECTION = `## Finding Types
|
|
62962
|
+
|
|
62963
|
+
You may emit only these trigger types: \`back-nav-failure\`, \`dead-end\`, \`stuck-modal\`, \`stuck-loop\`, \`missing-a11y-element\`, \`missing-content\`, \`spec-deviation\`, \`destructive-only-exit\`. Do NOT emit \`design-system-violation\`, \`motion-regression\`, \`continuity-regression\`, \`interaction-regression\`, or \`loading-regression\` \u2014 those belong to other agents.`;
|
|
62964
|
+
var A11Y_FALLBACK_RULE = `Missing a11y element: if you intend to tap or interact with a UI element and that element is absent from the most recent \`view_ui\` a11y tree \u2014 visible in the screenshot does NOT imply interactable; the a11y tree is authoritative \u2014 do NOT estimate its coordinates from the screenshot, do NOT attempt any pixel-based tap, do NOT retry at different coordinates, do NOT long-press or swipe in the element's visual region as a fallback; a failed pixel tap is never an \`interaction-regression\` \u2014 it is a \`missing-a11y-element\` \u2014 instead, emit a \`missing-a11y-element\` finding that states: (1) your intent (what you were trying to do), (2) the approximate visual region where the element appeared (coords/size from the screenshot), (3) nearby labeled elements from the a11y tree that serve as landmarks \u2014 then continue: in freestyle mode keep exploring other reachable screens; in spec mode advance to the next step`;
|
|
62965
|
+
var OUTCOME_LITERAL_RULE = `When verifying a step outcome or assertion, interpret all quantifiers literally and apply them exhaustively: any keyword that imposes a universal or count-bound constraint \u2014 including but not limited to \`only\`, \`all\`, \`every\`, \`each\`, \`both\`, \`no\`, \`none\`, \`neither\`, \`exactly N\`, \`at least N\`, \`fewer than N\`, \`more than N\` \u2014 a single counter-example observed in \`view_ui\` or \`screenshot\` constitutes a failed constraint; this rule applies only when the outcome text contains a universal or count-bound quantifier; positive-existence outcomes (\`X appears\`, \`Y becomes active\`, \`screen is visible\`) are not subject to counter-example scanning and are governed by SPEC_STEP_READING_SECTION; on a scrollable list, scroll through the full list before concluding pass or fail on a universal quantifier \u2014 apply SCROLL_FOLD_RULE to reveal all items, then evaluate across the complete visible set; if the screen shows any loading indicator at the time of observation, apply LOADING_STATE_RULE first and re-verify after resolution before emitting; do NOT soften (\`mostly\`, \`essentially\`, \`largely\`) and do NOT narrow the target class post-hoc to exclude the counter-example; when the counter-evidence is an element absent from the a11y tree, A11Y_FALLBACK_RULE takes precedence and determines the finding type (\`missing-a11y-element\`) \u2014 OUTCOME_LITERAL_RULE governs unwanted-presence violations only; if one item violates the constraint, emit \`spec-deviation\` immediately with: (a) the literal outcome text, (b) the specific element(s) that violate it, (c) the constraint keyword that is broken.`;
|
|
62966
|
+
var ANTI_RATIONALIZATION_RULE = `During outcome verification, monitor your own reasoning for reconciliation hypotheses: if you generate any reasoning that re-frames, redefines, or reinterprets the observed counter-example or target class in order to produce agreement with the spec outcome \u2014 regardless of phrasing \u2014 treat that reasoning as a deviation signal, not a resolution; stop, do NOT mark the step complete, and emit \`spec-deviation\` with: (a) the literal outcome text, (b) the specific observation that triggered the hypothesis, (c) the reconciliation reasoning itself verbatim; before marking any quantifier-bearing outcome complete, state explicitly in your reasoning: \`No reconciliation hypothesis generated. Counter-examples found: [list or none].\` If you cannot make that statement honestly, a hypothesis exists \u2014 emit \`spec-deviation\`; when outcome verification is ambiguous and no reconciliation hypothesis is generated, still default to emitting \`spec-deviation\`; silence is not a pass.`;
|
|
62962
62967
|
var WHAT_TO_TEST_SECTION = `## What to Test
|
|
62963
62968
|
|
|
62964
62969
|
Test navigation elements first, interactions second.
|
|
@@ -62989,9 +62994,9 @@ Rules:
|
|
|
62989
62994
|
- When in doubt about reversibility, treat the action as destructive and back out.`;
|
|
62990
62995
|
var DEAD_END_SECTION = `## Dead End and Modal Detection
|
|
62991
62996
|
|
|
62992
|
-
**Dead end** \u2014
|
|
62997
|
+
**Dead end** \u2014 emitted ONLY when every attempted exit fails to leave the screen. Consult App Knowledge first for the exit gesture on this screen, then attempt ALL before emitting: (1) any visible back/close button, (2) OS back gesture, (3) swipe up, (4) swipe down, (5) swipe left, (6) swipe right. If any succeeds, do NOT emit a finding \u2014 absence of an explicit "Cancel" or "Close" button is not a dead-end when a gesture dismisses the screen. If all fail, emit a \`dead-end\` finding describing what was visible and what was attempted.
|
|
62993
62998
|
|
|
62994
|
-
**Stuck modal** \u2014 when a modal or bottom sheet blocks the screen, attempt dismissal in order: (1) close/X button if present, (2) tap outside the modal, (3) swipe down, (4) swipe
|
|
62999
|
+
**Stuck modal** \u2014 when a modal or bottom sheet blocks the screen, attempt dismissal in order: (1) close/X button if present, (2) tap outside the modal, (3) swipe down, (4) swipe up, (5) swipe left, (6) swipe right. If any succeeds, do NOT emit a finding. If all fail, emit a \`stuck-modal\` finding listing the modal, the screen it appeared on, and the methods attempted.`;
|
|
62995
63000
|
var SPEC_WHAT_TO_TEST_SECTION = `## What to Test
|
|
62996
63001
|
|
|
62997
63002
|
Test only the elements and interactions described in the spec. Do not interact with elements outside the spec path.
|
|
@@ -62999,9 +63004,9 @@ Test only the elements and interactions described in the spec. Do not interact w
|
|
|
62999
63004
|
If you observe obvious breakage while navigating to a spec step \u2014 a broken control, unexpected error, missing screen, or crash \u2014 flag it as a passive observation without stopping to investigate it.`;
|
|
63000
63005
|
var SPEC_DEAD_END_SECTION = `## Dead End and Modal Detection
|
|
63001
63006
|
|
|
63002
|
-
**Dead end** \u2014 if a spec step leaves the agent on a screen with no path to the next spec step, attempt: (1) any visible back/close button, (2) swipe
|
|
63007
|
+
**Dead end** \u2014 if a spec step leaves the agent on a screen with no path to the next spec step, consult App Knowledge first for the exit gesture, then attempt ALL: (1) any visible back/close button, (2) OS back gesture, (3) swipe up, (4) swipe down, (5) swipe left, (6) swipe right. If all fail, emit a \`dead-end\` finding and halt \u2014 do not attempt further exploration to recover.
|
|
63003
63008
|
|
|
63004
|
-
**Stuck modal** \u2014 when a modal or bottom sheet blocks spec step execution, attempt dismissal in order: (1) close/X button if present, (2) tap outside the modal, (3) swipe down, (4) swipe
|
|
63009
|
+
**Stuck modal** \u2014 when a modal or bottom sheet blocks spec step execution, attempt dismissal in order: (1) close/X button if present, (2) tap outside the modal, (3) swipe down, (4) swipe up, (5) swipe left, (6) swipe right. If all fail, emit a \`stuck-modal\` finding listing the modal, the screen it appeared on, and the methods attempted.`;
|
|
63005
63010
|
var SPEC_STEP_READING_SECTION = `## Reading Spec Steps
|
|
63006
63011
|
|
|
63007
63012
|
Each step has this shape:
|
|
@@ -63049,7 +63054,10 @@ var SPEC_RULES_SECTION = `## Rules
|
|
|
63049
63054
|
- ${LOADING_STATE_RULE}
|
|
63050
63055
|
- ${EXPECTED_CONTENT_MISSING_RULE}
|
|
63051
63056
|
- ${CLIPPED_ELEMENT_RULE}
|
|
63057
|
+
- ${SCROLL_FOLD_RULE}
|
|
63052
63058
|
- ${A11Y_FALLBACK_RULE}
|
|
63059
|
+
- ${OUTCOME_LITERAL_RULE}
|
|
63060
|
+
- ${ANTI_RATIONALIZATION_RULE}
|
|
63053
63061
|
- Each item in \`**Assertions**\` is a mandatory pass/fail check \u2014 verify using \`view_ui\` when the assertion targets an element attribute, label, or presence in the tree; use \`screenshot\` when the assertion is purely visual; if neither can confirm, emit a \`spec-deviation\` finding based on what is observable
|
|
63054
63062
|
- Flag crash dialogs, unexpected system errors, or navigation failures that occur as a direct result of executing a spec step; if you observe a visibly broken element in passing while navigating, note it without interacting with it`;
|
|
63055
63063
|
function buildSpecModeBody({
|
|
@@ -63079,6 +63087,8 @@ ${SPEC_STEP_READING_SECTION}
|
|
|
63079
63087
|
|
|
63080
63088
|
${SPEC_OPTIONAL_STEPS_SECTION}
|
|
63081
63089
|
|
|
63090
|
+
${FINDING_TAXONOMY_SECTION}
|
|
63091
|
+
|
|
63082
63092
|
## Specs
|
|
63083
63093
|
|
|
63084
63094
|
${specContent}${environmentSection}
|
|
@@ -63112,6 +63122,7 @@ ${TOOL_SELECTION_SECTION}
|
|
|
63112
63122
|
- ${LOADING_STATE_RULE}
|
|
63113
63123
|
- ${EXPECTED_CONTENT_MISSING_RULE}
|
|
63114
63124
|
- ${CLIPPED_ELEMENT_RULE}
|
|
63125
|
+
- ${SCROLL_FOLD_RULE}
|
|
63115
63126
|
- ${A11Y_FALLBACK_RULE}
|
|
63116
63127
|
|
|
63117
63128
|
## Exploration Strategy
|
|
@@ -63122,7 +63133,9 @@ ${WHAT_TO_TEST_SECTION}
|
|
|
63122
63133
|
|
|
63123
63134
|
${DESTRUCTIVE_ACTIONS_SECTION}
|
|
63124
63135
|
|
|
63125
|
-
${DEAD_END_SECTION}
|
|
63136
|
+
${DEAD_END_SECTION}
|
|
63137
|
+
|
|
63138
|
+
${FINDING_TAXONOMY_SECTION}${environmentSection}
|
|
63126
63139
|
|
|
63127
63140
|
## Output
|
|
63128
63141
|
|
|
@@ -63380,7 +63393,7 @@ function buildPrompt(safeConfig, specs) {
|
|
|
63380
63393
|
});
|
|
63381
63394
|
}
|
|
63382
63395
|
function parseSpecs(resolvedSpecs) {
|
|
63383
|
-
return
|
|
63396
|
+
return import_neverthrow18.Result.combine(
|
|
63384
63397
|
resolvedSpecs.map(
|
|
63385
63398
|
(spec) => parseTestSpec(spec.name, spec.content).mapErr(
|
|
63386
63399
|
(cause) => ({ type: "SPEC_PARSE_FAILED", specName: spec.name, cause })
|
|
@@ -63439,7 +63452,7 @@ function collectAndFinalize({
|
|
|
63439
63452
|
}
|
|
63440
63453
|
function resolveAndParseSpecs(safeConfig) {
|
|
63441
63454
|
if (safeConfig.mode === "freestyle") {
|
|
63442
|
-
return (0,
|
|
63455
|
+
return (0, import_neverthrow18.okAsync)([]);
|
|
63443
63456
|
}
|
|
63444
63457
|
return resolveSpecs(safeConfig).mapErr((cause) => ({ type: "SPEC_RESOLVE_FAILED", cause })).andThen((specs) => parseSpecs(specs));
|
|
63445
63458
|
}
|
|
@@ -63462,11 +63475,11 @@ function runExplorer(config3) {
|
|
|
63462
63475
|
date: date5
|
|
63463
63476
|
});
|
|
63464
63477
|
if (runPathsResult.isErr()) {
|
|
63465
|
-
return (0,
|
|
63478
|
+
return (0, import_neverthrow18.errAsync)({ type: "RUN_PATHS_FAILED", cause: runPathsResult.error });
|
|
63466
63479
|
}
|
|
63467
63480
|
const runPaths = runPathsResult.value;
|
|
63468
63481
|
if (config3.signal?.aborted) {
|
|
63469
|
-
return (0,
|
|
63482
|
+
return (0, import_neverthrow18.okAsync)(toArtifacts({ findings: [], snapshots: [] }, runPaths));
|
|
63470
63483
|
}
|
|
63471
63484
|
const safeConfig = {
|
|
63472
63485
|
...config3,
|
|
@@ -63474,7 +63487,7 @@ function runExplorer(config3) {
|
|
|
63474
63487
|
onEvent: config3.onEvent === void 0 ? void 0 : safeHandler(config3.onEvent)
|
|
63475
63488
|
};
|
|
63476
63489
|
safeConfig.onEvent?.({ type: "STAGE_START", agent: "explorer" });
|
|
63477
|
-
return
|
|
63490
|
+
return import_neverthrow18.ResultAsync.fromSafePromise(
|
|
63478
63491
|
(0, import_promises11.mkdir)(runPaths.screenshotsDir, { recursive: true }).catch(() => null)
|
|
63479
63492
|
).andThen(() => runPipeline({ safeConfig, runPaths, start }));
|
|
63480
63493
|
}
|
|
@@ -66969,6 +66982,13 @@ var FsDesignStore = class {
|
|
|
66969
66982
|
// ../../packages/pipeline/dist/index.js
|
|
66970
66983
|
var RETRY_MAX_ATTEMPTS = 3;
|
|
66971
66984
|
var RETRY_BASE_DELAY_MS = 1e3;
|
|
66985
|
+
var CONFIDENCE_THRESHOLD = 0.7;
|
|
66986
|
+
function filterByConfidence(findings, threshold) {
|
|
66987
|
+
return {
|
|
66988
|
+
kept: findings.filter((finding) => finding.confidence >= threshold),
|
|
66989
|
+
dropped: findings.filter((finding) => finding.confidence < threshold).map((finding) => ({ finding, reason: "low-confidence" }))
|
|
66990
|
+
};
|
|
66991
|
+
}
|
|
66972
66992
|
function attemptRetry(options) {
|
|
66973
66993
|
const { factory, config: config3, delayFunction, onRetry, attempt } = options;
|
|
66974
66994
|
return factory().orElse((error48) => {
|
|
@@ -67284,11 +67304,12 @@ function buildExplorerConfig({
|
|
|
67284
67304
|
};
|
|
67285
67305
|
}
|
|
67286
67306
|
function buildOutput(consolidationResult, options) {
|
|
67287
|
-
const { runId, runPaths, signal } = options;
|
|
67307
|
+
const { runId, runPaths, signal, confidenceThreshold } = options;
|
|
67308
|
+
const filtered = filterByConfidence(consolidationResult.findings, confidenceThreshold);
|
|
67288
67309
|
const output = {
|
|
67289
67310
|
runId,
|
|
67290
|
-
findings:
|
|
67291
|
-
dismissed: consolidationResult.dismissed,
|
|
67311
|
+
findings: filtered.kept,
|
|
67312
|
+
dismissed: [...consolidationResult.dismissed, ...filtered.dropped],
|
|
67292
67313
|
findingsPath: runPaths.findingsPath,
|
|
67293
67314
|
aborted: signal?.aborted ? true : void 0
|
|
67294
67315
|
};
|
|
@@ -67342,7 +67363,8 @@ function executePipeline(setup, config3) {
|
|
|
67342
67363
|
(consolidationResult) => buildOutput(consolidationResult, {
|
|
67343
67364
|
runId: setup.runId,
|
|
67344
67365
|
runPaths: setup.runPaths,
|
|
67345
|
-
signal
|
|
67366
|
+
signal,
|
|
67367
|
+
confidenceThreshold: config3.confidenceThreshold ?? CONFIDENCE_THRESHOLD
|
|
67346
67368
|
})
|
|
67347
67369
|
);
|
|
67348
67370
|
}
|
|
@@ -76144,6 +76166,9 @@ var DEFAULT_FREESTYLE_TIMEOUT_SECONDS2 = 300;
|
|
|
76144
76166
|
function buildDeviceInstruction(simulatorUdid) {
|
|
76145
76167
|
return `You MUST use device "${simulatorUdid}" for ALL mobile tool calls. The simulator UDID is already configured \u2014 use it directly.`;
|
|
76146
76168
|
}
|
|
76169
|
+
function composeAppContext(parts) {
|
|
76170
|
+
return parts.filter((part) => part !== void 0 && part.length > 0).join("\n\n");
|
|
76171
|
+
}
|
|
76147
76172
|
function ensureWorkerCwd(simulatorUdid) {
|
|
76148
76173
|
const workerDirectory = import_node_path21.default.join(import_node_os4.default.tmpdir(), "xqa-workers", simulatorUdid);
|
|
76149
76174
|
(0, import_node_fs11.mkdirSync)(workerDirectory, { recursive: true });
|
|
@@ -76157,7 +76182,7 @@ function resolveFreestyleTimeout(item, config3) {
|
|
|
76157
76182
|
}
|
|
76158
76183
|
function buildFreestylePipelineConfig(input) {
|
|
76159
76184
|
const { item, context, signal, simulatorUdid, onEvent } = input;
|
|
76160
|
-
const { config: config3, xqaDirectory, runId, date: date5 } = context;
|
|
76185
|
+
const { config: config3, xqaDirectory, runId, date: date5, appContext } = context;
|
|
76161
76186
|
return {
|
|
76162
76187
|
outputDir: import_node_path21.default.join(xqaDirectory, "output", item.id),
|
|
76163
76188
|
runId,
|
|
@@ -76170,9 +76195,11 @@ function buildFreestylePipelineConfig(input) {
|
|
|
76170
76195
|
mcpServers: createDefaultMcpServers(simulatorUdid),
|
|
76171
76196
|
allowedTools: ALLOWED_TOOLS,
|
|
76172
76197
|
timeoutMs: resolveFreestyleTimeout(item, config3) * MS_PER_SECOND4,
|
|
76173
|
-
appContext:
|
|
76174
|
-
|
|
76175
|
-
|
|
76198
|
+
appContext: composeAppContext([
|
|
76199
|
+
buildDeviceInstruction(simulatorUdid),
|
|
76200
|
+
appContext,
|
|
76201
|
+
item.prompt
|
|
76202
|
+
]),
|
|
76176
76203
|
buildEnv: config3.QA_BUILD_ENV,
|
|
76177
76204
|
cwd: ensureWorkerCwd(simulatorUdid)
|
|
76178
76205
|
}
|
|
@@ -76183,7 +76210,7 @@ function executeFreestyleItem(input) {
|
|
|
76183
76210
|
}
|
|
76184
76211
|
function buildSpecPipelineConfig(input) {
|
|
76185
76212
|
const { item, context, signal, simulatorUdid, onEvent } = input;
|
|
76186
|
-
const { config: config3, xqaDirectory, runId } = context;
|
|
76213
|
+
const { config: config3, xqaDirectory, runId, appContext } = context;
|
|
76187
76214
|
return {
|
|
76188
76215
|
outputDir: import_node_path21.default.join(xqaDirectory, "output", item.id),
|
|
76189
76216
|
runId,
|
|
@@ -76195,7 +76222,7 @@ function buildSpecPipelineConfig(input) {
|
|
|
76195
76222
|
specFiles: [item.specPath],
|
|
76196
76223
|
mcpServers: createDefaultMcpServers(simulatorUdid),
|
|
76197
76224
|
allowedTools: ALLOWED_TOOLS,
|
|
76198
|
-
appContext: buildDeviceInstruction(simulatorUdid),
|
|
76225
|
+
appContext: composeAppContext([buildDeviceInstruction(simulatorUdid), appContext]),
|
|
76199
76226
|
buildEnv: config3.QA_BUILD_ENV,
|
|
76200
76227
|
cwd: ensureWorkerCwd(simulatorUdid)
|
|
76201
76228
|
}
|
|
@@ -76271,17 +76298,30 @@ function deriveSuiteIdFromMode(mode) {
|
|
|
76271
76298
|
}
|
|
76272
76299
|
return deriveSuiteId({ mode: "spec", globs: mode.globs });
|
|
76273
76300
|
}
|
|
76301
|
+
async function loadAppContext(xqaDirectory) {
|
|
76302
|
+
const result = await readAppContext(xqaDirectory);
|
|
76303
|
+
if (result.isErr()) {
|
|
76304
|
+
const cause = result.error.cause instanceof Error ? result.error.cause.message : JSON.stringify(result.error.cause);
|
|
76305
|
+
process.stderr.write(`Failed to read app context: ${result.error.type}
|
|
76306
|
+
${cause}
|
|
76307
|
+
`);
|
|
76308
|
+
return void 0;
|
|
76309
|
+
}
|
|
76310
|
+
return result.value;
|
|
76311
|
+
}
|
|
76274
76312
|
async function buildSuiteRunContext(input) {
|
|
76275
76313
|
const suiteId = deriveSuiteIdFromMode(input.mode);
|
|
76276
76314
|
const date5 = (/* @__PURE__ */ new Date()).toISOString().slice(0, ISO_DATE_LENGTH3);
|
|
76277
76315
|
const outputDirectory = import_node_path22.default.join(input.xqaDirectory, "output");
|
|
76278
76316
|
const existingDirectories = await listRunDirectories({ outputDirectory, suiteId, date: date5 });
|
|
76279
76317
|
const runId = computeNextRunId(existingDirectories);
|
|
76318
|
+
const appContext = await loadAppContext(input.xqaDirectory);
|
|
76280
76319
|
const context = {
|
|
76281
76320
|
config: input.config,
|
|
76282
76321
|
xqaDirectory: input.xqaDirectory,
|
|
76283
76322
|
runId,
|
|
76284
|
-
date: date5
|
|
76323
|
+
date: date5,
|
|
76324
|
+
appContext
|
|
76285
76325
|
};
|
|
76286
76326
|
return { suiteId, date: date5, outputDirectory, runId, context };
|
|
76287
76327
|
}
|
|
@@ -76475,7 +76515,7 @@ function resolveXqaDirectory() {
|
|
|
76475
76515
|
return result.value;
|
|
76476
76516
|
}
|
|
76477
76517
|
var program2 = new Command();
|
|
76478
|
-
program2.name("xqa").description("AI-powered QA agent CLI").version(`${"1.
|
|
76518
|
+
program2.name("xqa").description("AI-powered QA agent CLI").version(`${"1.14.1"}${false ? ` (dev build +${"18d343b"})` : ""}`);
|
|
76479
76519
|
program2.command("init").description("Initialize a new xqa project in the current directory").action(() => {
|
|
76480
76520
|
runInitCommand();
|
|
76481
76521
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exodus/xqa",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.14.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=22"
|
|
@@ -26,11 +26,11 @@
|
|
|
26
26
|
"typescript": "^5.8.3",
|
|
27
27
|
"vitest": "^3.2.1",
|
|
28
28
|
"zod": "^3.0.0",
|
|
29
|
-
"@qa-agents/display": "0.0.0",
|
|
30
29
|
"@qa-agents/explorer": "0.0.0",
|
|
31
|
-
"@qa-agents/eslint-config": "0.0.0",
|
|
32
30
|
"@qa-agents/mobile-ios": "0.0.0",
|
|
33
31
|
"@qa-agents/pipeline": "0.0.0",
|
|
32
|
+
"@qa-agents/display": "0.0.0",
|
|
33
|
+
"@qa-agents/eslint-config": "0.0.0",
|
|
34
34
|
"@qa-agents/shared": "0.0.0",
|
|
35
35
|
"@qa-agents/typescript-config": "0.0.0"
|
|
36
36
|
},
|