@flowriki/agent 0.1.0-beta.53 → 0.1.0-beta.55

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.
@@ -14,7 +14,9 @@ import { spawn as spawn2 } from "child_process";
14
14
  import fs3 from "fs";
15
15
  import os3 from "os";
16
16
  import path3 from "path";
17
+ import { execFile as execFile2 } from "child_process";
17
18
  import fs4 from "fs";
19
+ import os4 from "os";
18
20
  import path4 from "path";
19
21
 
20
22
  // ../test-model/dist/chunk-I5COYCQ5.js
@@ -2647,7 +2649,7 @@ async function cloudDevices(account, key) {
2647
2649
  if (!res.ok) throw new Error(`${account.provider} answered ${res.status}.`);
2648
2650
  return res.json();
2649
2651
  };
2650
- const platformOf = (os4) => /android/i.test(os4) ? "android" : /ios/i.test(os4) ? "ios" : null;
2652
+ const platformOf = (os5) => /android/i.test(os5) ? "android" : /ios/i.test(os5) ? "ios" : null;
2651
2653
  const out = [];
2652
2654
  if (account.provider === "browserstack") {
2653
2655
  for (const d of await get("https://api-cloud.browserstack.com/app-automate/devices.json")) {
@@ -2661,11 +2663,11 @@ async function cloudDevices(account, key) {
2661
2663
  if (p) out.push({ name: d.name, platform: p, osVersion: d.osVersion });
2662
2664
  }
2663
2665
  } else {
2664
- for (const os4 of ["android", "ios"]) {
2665
- const listed = await get(`https://mobile-api.lambdatest.com/mobile-automation/api/v1/list?region=us&os=${os4}`);
2666
+ for (const os5 of ["android", "ios"]) {
2667
+ const listed = await get(`https://mobile-api.lambdatest.com/mobile-automation/api/v1/list?region=us&os=${os5}`);
2666
2668
  for (const [, devices] of Object.entries(listed)) {
2667
2669
  if (!Array.isArray(devices)) continue;
2668
- for (const d of devices) out.push({ name: d.deviceName, platform: os4, osVersion: d.platformVersion });
2670
+ for (const d of devices) out.push({ name: d.deviceName, platform: os5, osVersion: d.platformVersion });
2669
2671
  }
2670
2672
  }
2671
2673
  }
@@ -3313,6 +3315,16 @@ var MobileSession = class _MobileSession {
3313
3315
  }
3314
3316
  return png;
3315
3317
  }
3318
+ /** The screen as a picture to LOOK at — the recorder's mirror, the run
3319
+ * page's live phone — at most `maxPx` on its longer side. A phone's own
3320
+ * resolution is three to four times what a mirror draws, and every byte
3321
+ * of it crosses the control channel each time; on macOS `sips` shrinks it
3322
+ * in a few milliseconds, elsewhere the full picture is sent. Evidence
3323
+ * files keep the full size. */
3324
+ async preview(maxPx = 900) {
3325
+ const png = await this.screenshot();
3326
+ return shrink(png, maxPx);
3327
+ }
3316
3328
  /** Android: the activity on screen. iOS has none worth reading. */
3317
3329
  async currentScreenName() {
3318
3330
  if (this.platform !== "android") return "";
@@ -3510,6 +3522,22 @@ var MobileSession = class _MobileSession {
3510
3522
  await this.driver.close();
3511
3523
  }
3512
3524
  };
3525
+ async function shrink(png, maxPx) {
3526
+ if (process.platform !== "darwin") return png;
3527
+ const dir = fs4.mkdtempSync(path4.join(os4.tmpdir(), "flowriki-shot-"));
3528
+ const file = path4.join(dir, "s.png");
3529
+ try {
3530
+ fs4.writeFileSync(file, png);
3531
+ await new Promise((resolve, reject) => {
3532
+ execFile2("/usr/bin/sips", ["-Z", String(maxPx), file], { timeout: 5e3 }, (error) => error ? reject(error) : resolve());
3533
+ });
3534
+ return fs4.readFileSync(file);
3535
+ } catch {
3536
+ return png;
3537
+ } finally {
3538
+ fs4.rmSync(dir, { recursive: true, force: true });
3539
+ }
3540
+ }
3513
3541
  var ANDROID_KEYS = {
3514
3542
  enter: 66,
3515
3543
  return: 66,
@@ -4166,6 +4194,7 @@ export {
4166
4194
  checkMobileAssertion,
4167
4195
  openSessionOn,
4168
4196
  MobileSession,
4197
+ shrink,
4169
4198
  MOBILE_TIMEOUTS,
4170
4199
  performMobileStep,
4171
4200
  runMobileSteps,
@@ -68,7 +68,7 @@ import {
68
68
  textIndependent,
69
69
  validateActors,
70
70
  webCloudOf
71
- } from "./chunk-FIR6NOLA.js";
71
+ } from "./chunk-3XKTIMRP.js";
72
72
 
73
73
  // src/engine/machine.ts
74
74
  import fs from "fs";
@@ -76,7 +76,7 @@ import os from "os";
76
76
  import path from "path";
77
77
  import { createRequire } from "module";
78
78
  import { spawnSync } from "child_process";
79
- var AGENT_VERSION = "0.1.0-beta.53";
79
+ var AGENT_VERSION = "0.1.0-beta.55";
80
80
  async function prepareBrowsers() {
81
81
  if (browsersReady()) {
82
82
  return { ok: true, already: true, message: "chromium is already installed" };
@@ -136,7 +136,7 @@ async function checkMachine() {
136
136
  }
137
137
  } catch {
138
138
  }
139
- const { mobileDoctor: mobileDoctor2 } = await import("./dist-6GKVE5CL.js");
139
+ const { mobileDoctor: mobileDoctor2 } = await import("./dist-RTDFWPOC.js");
140
140
  return {
141
141
  agentVersion: AGENT_VERSION,
142
142
  node: process.versions.node,
@@ -9846,8 +9846,11 @@ var ownerOf = (l) => l.draft ? "recording" : "debug";
9846
9846
  var SECRET_WORDS = /password|passwd|pass[-_\s]?code|pwd|secret|otp|\bpin\b|cvv|cvc/i;
9847
9847
  async function screenOf(l, withImage = false) {
9848
9848
  l.session.changed();
9849
- const screen = await l.session.screen();
9850
- const png = withImage ? await l.session.screenshot() : null;
9849
+ const [screen, png, where] = await Promise.all([
9850
+ l.session.screen(),
9851
+ withImage ? l.session.preview() : Promise.resolve(null),
9852
+ l.session.currentScreenName()
9853
+ ]);
9851
9854
  return {
9852
9855
  ...png ? { image: png.toString("base64") } : {},
9853
9856
  width: screen.size.width,
@@ -9862,7 +9865,7 @@ async function screenOf(l, withImage = false) {
9862
9865
  ...n2.value ? { value: n2.password ? "\u2022\u2022\u2022" : n2.value } : {},
9863
9866
  ...n2.id ? { id: n2.id } : {}
9864
9867
  })),
9865
- where: await l.session.currentScreenName(),
9868
+ where,
9866
9869
  device: l.session.deviceName
9867
9870
  };
9868
9871
  }
@@ -9890,7 +9893,7 @@ function mobileHandlers(ctx) {
9890
9893
  const session = live.get(platform)?.session ?? openSessionOn(platform);
9891
9894
  if (!session) return { ok: true, result: { image: null } };
9892
9895
  try {
9893
- const png = await session.screenshot();
9896
+ const png = await session.preview(720);
9894
9897
  return { ok: true, result: { image: png.toString("base64"), device: session.deviceName } };
9895
9898
  } catch {
9896
9899
  return { ok: true, result: { image: null } };
@@ -10003,18 +10006,18 @@ function mobileHandlers(ctx) {
10003
10006
  if (op === "run" || step.type === "signIn" || step.type === "assert") {
10004
10007
  const done = await runMobileSteps(l.session, [step], { values: l.values, resolveSecret: l.resolveSecret, resolveAuth: l.resolveAuth, ...l.runHelper ? { runHelper: l.runHelper } : {} });
10005
10008
  const result = done.results[0];
10006
- if (op === "run") return { ok: true, result: { result: result ?? null, screen: await screenOf(l) } };
10009
+ if (op === "run") return { ok: true, result: { result: result ?? null } };
10007
10010
  if (done.status !== "passed") throw new Error(result?.detail ?? done.error ?? "it did not work");
10008
10011
  } else {
10009
10012
  await performMobileStep(l.session, step, { values: l.values, resolveSecret: l.resolveSecret, resolveAuth: l.resolveAuth, ...l.runHelper ? { runHelper: l.runHelper } : {} });
10010
10013
  }
10011
10014
  } catch (error) {
10012
10015
  if (secret2) l.typed.delete(ref);
10013
- return { ok: true, result: { kept: false, error: error.message.split("\n")[0], screen: await screenOf(l) } };
10016
+ return { ok: true, result: { kept: false, error: error.message.split("\n")[0] } };
10014
10017
  }
10015
- await new Promise((r) => setTimeout(r, 500));
10018
+ await new Promise((r) => setTimeout(r, 300));
10016
10019
  l.draft?.steps.push(step);
10017
- return { ok: true, result: { kept: true, step, durationMs: Date.now() - t0, steps: l.draft?.steps ?? [], screen: await screenOf(l) } };
10020
+ return { ok: true, result: { kept: true, step, durationMs: Date.now() - t0, steps: l.draft?.steps ?? [] } };
10018
10021
  }
10019
10022
  if (op === "check") {
10020
10023
  const assertion = RecordedStepSchema.shape.proposals.element.parse({ enabled: true, ...command.args["assertion"] });
@@ -10336,75 +10339,15 @@ function createClaudeCli(options) {
10336
10339
  ask(ask) {
10337
10340
  if (current) return { started: false, detail: "The assistant is already answering a question on this machine." };
10338
10341
  fs43.mkdirSync(scratchRoot2, { recursive: true, mode: 448 });
10339
- const dir = fs43.mkdtempSync(path42.join(scratchRoot2, "ask-"));
10342
+ const dir = path42.join(scratchRoot2, `project-${ask.projectId.replace(/[^A-Za-z0-9_-]/g, "_")}`);
10343
+ fs43.mkdirSync(dir, { recursive: true, mode: 448 });
10340
10344
  const mcpConfigPath = path42.join(dir, "mcp.json");
10341
10345
  fs43.writeFileSync(
10342
10346
  mcpConfigPath,
10343
10347
  JSON.stringify({ mcpServers: { [MCP_SERVER_NAME]: { command: options.mcp.command, args: options.mcp.args } } }),
10344
10348
  { mode: 384 }
10345
10349
  );
10346
- let child;
10347
- try {
10348
- child = spawn2(
10349
- binary,
10350
- buildAskArgs({
10351
- prompt: ask.prompt,
10352
- mcpConfigPath,
10353
- projectId: ask.projectId,
10354
- projectName: ask.projectName,
10355
- ...ask.sessionId ? { sessionId: ask.sessionId } : {},
10356
- ...ask.folders?.length ? { folders: ask.folders } : {},
10357
- ...ask.mode ? { mode: ask.mode } : {}
10358
- }),
10359
- // THE SCRATCH FOLDER, not the project: nothing here to read or write.
10360
- { cwd: dir, stdio: ["ignore", "pipe", "pipe"] }
10361
- );
10362
- } catch {
10363
- fs43.rmSync(dir, { recursive: true, force: true });
10364
- return { started: false, detail: "The assistant\u2019s command-line tool could not be started." };
10365
- }
10366
- current = { askId: ask.askId, child };
10367
- let buffer = "", stderr = "", sawDone = false, ended = false;
10368
- const handleLine = (line) => {
10369
- for (const event of eventsFromLine(ask.askId, line)) {
10370
- if (event.name === "assistant.done") sawDone = true;
10371
- ask.emit(event.name, event.data);
10372
- }
10373
- };
10374
- const end = (ok, detail) => {
10375
- if (ended) return;
10376
- ended = true;
10377
- clearTimeout(timer);
10378
- if (buffer.trim()) handleLine(buffer);
10379
- if (!sawDone) ask.emit("assistant.done", { askId: ask.askId, ok, result: detail });
10380
- current = null;
10381
- fs43.rmSync(dir, { recursive: true, force: true });
10382
- };
10383
- const timer = setTimeout(() => {
10384
- child.kill("SIGTERM");
10385
- end(false, `Stopped after ${Math.max(1, Math.round(maxAskMs / 6e4))} minutes. Whatever it saved so far is on the Branches page \u2014 send \u201Ccontinue\u201D to carry on.`);
10386
- }, maxAskMs);
10387
- timer.unref?.();
10388
- child.stdout?.on("data", (chunk) => {
10389
- buffer += chunk.toString();
10390
- let at;
10391
- while ((at = buffer.indexOf("\n")) >= 0) {
10392
- handleLine(buffer.slice(0, at));
10393
- buffer = buffer.slice(at + 1);
10394
- }
10395
- });
10396
- child.stderr?.on("data", (chunk) => {
10397
- stderr = (stderr + chunk.toString()).slice(-2e3);
10398
- });
10399
- child.on("error", () => end(false, "The assistant\u2019s command-line tool could not be started."));
10400
- child.on(
10401
- "close",
10402
- (code) => end(
10403
- code === 0,
10404
- cancelled === ask.askId ? "Stopped." : code === 0 ? "" : stderr.trim().split("\n").slice(-3).join(" ") || `The assistant stopped (exit ${code ?? "?"}).`
10405
- )
10406
- );
10407
- return { started: true };
10350
+ return launch(ask, dir, mcpConfigPath, ask.sessionId);
10408
10351
  },
10409
10352
  cancel(askId) {
10410
10353
  if (!current || current.askId !== askId) return false;
@@ -10414,8 +10357,85 @@ function createClaudeCli(options) {
10414
10357
  setTimeout(() => child.kill("SIGKILL"), 3e3).unref?.();
10415
10358
  return true;
10416
10359
  },
10417
- busy: () => current !== null
10360
+ busy() {
10361
+ return current !== null;
10362
+ }
10418
10363
  };
10364
+ function launch(ask, dir, mcpConfigPath, sessionId) {
10365
+ let child;
10366
+ try {
10367
+ child = spawn2(
10368
+ binary,
10369
+ buildAskArgs({
10370
+ prompt: ask.prompt,
10371
+ mcpConfigPath,
10372
+ projectId: ask.projectId,
10373
+ projectName: ask.projectName,
10374
+ ...sessionId ? { sessionId } : {},
10375
+ ...ask.folders?.length ? { folders: ask.folders } : {},
10376
+ ...ask.mode ? { mode: ask.mode } : {},
10377
+ ...ask.brief ? { brief: ask.brief } : {}
10378
+ }),
10379
+ // THE SCRATCH FOLDER, not the project: nothing here to read or write.
10380
+ { cwd: dir, stdio: ["ignore", "pipe", "pipe"] }
10381
+ );
10382
+ } catch {
10383
+ return { started: false, detail: "The assistant\u2019s command-line tool could not be started." };
10384
+ }
10385
+ current = { askId: ask.askId, child };
10386
+ let buffer = "", stderr = "", sawDone = false, ended = false;
10387
+ let silentFailure = false;
10388
+ const handleLine = (line) => {
10389
+ for (const event of eventsFromLine(ask.askId, line)) {
10390
+ if (event.name === "assistant.done" && event.data["ok"] === false && !String(event.data["result"] ?? "").trim()) {
10391
+ silentFailure = true;
10392
+ continue;
10393
+ }
10394
+ if (event.name === "assistant.done") sawDone = true;
10395
+ ask.emit(event.name, event.data);
10396
+ }
10397
+ };
10398
+ const end = (ok, detail) => {
10399
+ if (ended) return;
10400
+ ended = true;
10401
+ clearTimeout(timer);
10402
+ if (buffer.trim()) handleLine(buffer);
10403
+ current = null;
10404
+ if (silentFailure && sessionId && /no conversation found/i.test(stderr) && cancelled !== ask.askId) {
10405
+ const again = launch(ask, dir, mcpConfigPath, void 0);
10406
+ if (again.started) return;
10407
+ }
10408
+ if (!sawDone) {
10409
+ const why = detail || stderr.trim().split("\n").filter((l) => l && !/^Warning:/.test(l)).slice(-3).join(" ") || "The assistant stopped without saying why.";
10410
+ ask.emit("assistant.done", { askId: ask.askId, ok: ok && !silentFailure, result: ok && !silentFailure ? "" : why });
10411
+ }
10412
+ };
10413
+ const timer = setTimeout(() => {
10414
+ child.kill("SIGTERM");
10415
+ end(false, `Stopped after ${Math.max(1, Math.round(maxAskMs / 6e4))} minutes. Whatever it saved so far is on the Branches page \u2014 send \u201Ccontinue\u201D to carry on.`);
10416
+ }, maxAskMs);
10417
+ timer.unref?.();
10418
+ child.stdout?.on("data", (chunk) => {
10419
+ buffer += chunk.toString();
10420
+ let at;
10421
+ while ((at = buffer.indexOf("\n")) >= 0) {
10422
+ handleLine(buffer.slice(0, at));
10423
+ buffer = buffer.slice(at + 1);
10424
+ }
10425
+ });
10426
+ child.stderr?.on("data", (chunk) => {
10427
+ stderr = (stderr + chunk.toString()).slice(-2e3);
10428
+ });
10429
+ child.on("error", () => end(false, "The assistant\u2019s command-line tool could not be started."));
10430
+ child.on(
10431
+ "close",
10432
+ (code) => end(
10433
+ code === 0,
10434
+ cancelled === ask.askId ? "Stopped." : code === 0 ? "" : stderr.trim().split("\n").filter((l) => l && !/^Warning:/.test(l)).slice(-3).join(" ") || `The assistant stopped (exit ${code ?? "?"}).`
10435
+ )
10436
+ );
10437
+ return { started: true };
10438
+ }
10419
10439
  }
10420
10440
  function createAgentRuntime(options) {
10421
10441
  const lease = new BrowserLease();
package/dist/cli.js CHANGED
@@ -23,7 +23,7 @@ import {
23
23
  runtimeValue,
24
24
  start,
25
25
  status
26
- } from "./chunk-6AR63WTN.js";
26
+ } from "./chunk-NWJWMDVO.js";
27
27
  import {
28
28
  CLOUD_PROVIDERS,
29
29
  CLOUD_PROVIDER_NAME,
@@ -49,7 +49,7 @@ import {
49
49
  screenLines,
50
50
  stepRefusedOn,
51
51
  targetFor
52
- } from "./chunk-FIR6NOLA.js";
52
+ } from "./chunk-3XKTIMRP.js";
53
53
 
54
54
  // src/cli.ts
55
55
  import { parseArgs } from "util";
@@ -40,12 +40,13 @@ import {
40
40
  run,
41
41
  runMobileSteps,
42
42
  screenLines,
43
+ shrink,
43
44
  stopAppium,
44
45
  targetFor,
45
46
  textOf,
46
47
  toolEnv,
47
48
  typesSecrets
48
- } from "./chunk-FIR6NOLA.js";
49
+ } from "./chunk-3XKTIMRP.js";
49
50
  export {
50
51
  AGENT_HOME,
51
52
  APPIUM_DIR,
@@ -87,6 +88,7 @@ export {
87
88
  runMobileSteps,
88
89
  run as runTool,
89
90
  screenLines,
91
+ shrink,
90
92
  stopAppium,
91
93
  targetFor,
92
94
  textOf,
package/dist/engine.js CHANGED
@@ -9,8 +9,8 @@ import {
9
9
  resolveCheckouts,
10
10
  start,
11
11
  status
12
- } from "./chunk-6AR63WTN.js";
13
- import "./chunk-FIR6NOLA.js";
12
+ } from "./chunk-NWJWMDVO.js";
13
+ import "./chunk-3XKTIMRP.js";
14
14
  export {
15
15
  AGENT_VERSION,
16
16
  browsersReady,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flowriki/agent",
3
- "version": "0.1.0-beta.53",
3
+ "version": "0.1.0-beta.55",
4
4
  "description": "Run Flowriki UI tests on your own computer. Connects this machine to your Flowriki account.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "type": "module",