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

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.
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ AndroidAppSchema,
3
4
  ArtifactPolicySchema,
4
5
  AuthReferenceSchema,
5
6
  CHECKS,
@@ -13,6 +14,7 @@ import {
13
14
  ExecutionEnvironmentSchema,
14
15
  FeatureSchema,
15
16
  FlowDocumentSchema,
17
+ IosAppSchema,
16
18
  MobileLane,
17
19
  PrerequisiteProblem,
18
20
  RecordedAssertionSchema,
@@ -45,6 +47,7 @@ import {
45
47
  nodeAt,
46
48
  normalizeStartUrl,
47
49
  openMobileSession,
50
+ openSessionOn,
48
51
  parseFlowDocument,
49
52
  performMobileStep,
50
53
  platformsOf,
@@ -65,7 +68,7 @@ import {
65
68
  textIndependent,
66
69
  validateActors,
67
70
  webCloudOf
68
- } from "./chunk-GLZFHC64.js";
71
+ } from "./chunk-FIR6NOLA.js";
69
72
 
70
73
  // src/engine/machine.ts
71
74
  import fs from "fs";
@@ -73,7 +76,7 @@ import os from "os";
73
76
  import path from "path";
74
77
  import { createRequire } from "module";
75
78
  import { spawnSync } from "child_process";
76
- var AGENT_VERSION = "0.1.0-beta.50";
79
+ var AGENT_VERSION = "0.1.0-beta.53";
77
80
  async function prepareBrowsers() {
78
81
  if (browsersReady()) {
79
82
  return { ok: true, already: true, message: "chromium is already installed" };
@@ -133,7 +136,7 @@ async function checkMachine() {
133
136
  }
134
137
  } catch {
135
138
  }
136
- const { mobileDoctor: mobileDoctor2 } = await import("./dist-Z3WSTACX.js");
139
+ const { mobileDoctor: mobileDoctor2 } = await import("./dist-6GKVE5CL.js");
137
140
  return {
138
141
  agentVersion: AGENT_VERSION,
139
142
  node: process.versions.node,
@@ -232,7 +235,7 @@ function forgetConnection(service, env = process.env) {
232
235
  }
233
236
  var fingerprint = (secret2) => crypto2.createHash("sha256").update(secret2).digest("hex").slice(0, 12);
234
237
 
235
- // ../contracts/dist/chunk-S3GLB3QV.js
238
+ // ../contracts/dist/chunk-NTBXG5EZ.js
236
239
  import { z } from "zod";
237
240
  var PROTOCOL_VERSION = 1;
238
241
  var PAIRING_CODE_TTL_MS = 5 * 60 * 1e3;
@@ -281,6 +284,9 @@ var CommandNameSchema = z.enum([
281
284
  // Reading a live phone session: its screen as a picture, and what is under
282
285
  // a point. Not effectful — a picture is far too big to keep as a result.
283
286
  "mobile.screen",
287
+ // The screen of whatever phone session is open on a platform — a run's,
288
+ // a recording's — as a picture, for the live mirror. Reading.
289
+ "mobile.peek",
284
290
  // What this computer has installed for web and phone testing. Reading.
285
291
  "machine.check"
286
292
  ]);
@@ -3960,7 +3966,7 @@ async function replayCase(opts) {
3960
3966
  let status2 = "passed";
3961
3967
  let error = null;
3962
3968
  const browserRows = occurrences.filter((o) => o.platform !== "android" && o.platform !== "ios");
3963
- const laneOf = new Map(sessionLanes(browserRows, opts.defaultActorId).map((plan, i) => [browserRows[i].occurrenceId, plan]));
3969
+ const laneOf2 = new Map(sessionLanes(browserRows, opts.defaultActorId).map((plan, i) => [browserRows[i].occurrenceId, plan]));
3964
3970
  try {
3965
3971
  for (const [index, occurrence] of occurrences.entries()) {
3966
3972
  if (status2 !== "passed" || opts.signal?.aborted) {
@@ -4012,7 +4018,7 @@ async function replayCase(opts) {
4012
4018
  const navigates = navigatesFirst(occurrence);
4013
4019
  const who = occurrence.actorId ?? opts.defaultActorId ?? "default";
4014
4020
  const entry = resolveStartUrl(occurrence.baseUrl ?? baseUrl, occurrence.startPath ?? "/");
4015
- const plan = laneOf.get(occurrence.occurrenceId);
4021
+ const plan = laneOf2.get(occurrence.occurrenceId);
4016
4022
  try {
4017
4023
  if (plan.opens && startOf(occurrence) === "fresh") await actors.restart(who, entry);
4018
4024
  else if (plan.opens) await actors.select(who, entry);
@@ -6157,7 +6163,7 @@ function toSummary(row) {
6157
6163
  };
6158
6164
  }
6159
6165
 
6160
- // ../repository-store/dist/chunk-7ZZULR2K.js
6166
+ // ../repository-store/dist/chunk-QF7BTHZB.js
6161
6167
  var FLOWRIKI_DIR = ".flowriki";
6162
6168
  var FEATURES_DIR = `${FLOWRIKI_DIR}/features`;
6163
6169
  var CONFIG_PATH = `${FLOWRIKI_DIR}/config.json`;
@@ -6219,12 +6225,18 @@ function readEnvironments(raw) {
6219
6225
  const seen = /* @__PURE__ */ new Set();
6220
6226
  for (const entry of raw) {
6221
6227
  if (!entry || typeof entry !== "object") continue;
6222
- const { name, baseUrl } = entry;
6228
+ const { name, baseUrl, android, ios } = entry;
6223
6229
  if (typeof name !== "string" || !name.trim()) continue;
6224
6230
  const key = name.trim().toLowerCase();
6225
6231
  if (seen.has(key)) continue;
6226
6232
  seen.add(key);
6227
- out.push({ name: name.trim(), baseUrl: typeof baseUrl === "string" ? baseUrl : "" });
6233
+ const app = { android: AndroidAppSchema.safeParse(android), ios: IosAppSchema.safeParse(ios) };
6234
+ out.push({
6235
+ name: name.trim(),
6236
+ baseUrl: typeof baseUrl === "string" ? baseUrl : "",
6237
+ ...app.android.success ? { android: app.android.data } : {},
6238
+ ...app.ios.success ? { ios: app.ios.data } : {}
6239
+ });
6228
6240
  }
6229
6241
  return out;
6230
6242
  }
@@ -7982,6 +7994,105 @@ var BrowserBusy = class extends Error {
7982
7994
  this.name = "BrowserBusy";
7983
7995
  }
7984
7996
  };
7997
+ var LANES = ["web", "android", "ios"];
7998
+ var OneLane = class {
7999
+ owner = null;
8000
+ phase = "idle";
8001
+ since = null;
8002
+ /** Take the lane, or report who has it. Never awaits — that is the point. */
8003
+ acquire(owner) {
8004
+ if (this.owner !== null) {
8005
+ return { ok: false, heldBy: this.owner, phase: this.phase };
8006
+ }
8007
+ this.owner = owner;
8008
+ this.phase = "starting";
8009
+ this.since = Date.now();
8010
+ return { ok: true };
8011
+ }
8012
+ /** The holder finished starting and is now doing the thing. */
8013
+ activate(owner) {
8014
+ if (this.owner === owner) this.phase = "active";
8015
+ }
8016
+ /** The holder has begun shutting down. Still not idle: a second stop must
8017
+ * join the first rather than starting its own. */
8018
+ beginRelease(owner) {
8019
+ if (this.owner !== owner || this.phase === "stopping") return false;
8020
+ this.phase = "stopping";
8021
+ return true;
8022
+ }
8023
+ release(owner) {
8024
+ if (this.owner !== owner) return;
8025
+ this.owner = null;
8026
+ this.phase = "idle";
8027
+ this.since = null;
8028
+ }
8029
+ forceRelease() {
8030
+ this.owner = null;
8031
+ this.phase = "idle";
8032
+ this.since = null;
8033
+ }
8034
+ state() {
8035
+ return { owner: this.owner, phase: this.phase, since: this.since };
8036
+ }
8037
+ };
8038
+ var BrowserLease = class {
8039
+ lanes = { web: new OneLane(), android: new OneLane(), ios: new OneLane() };
8040
+ /** The web lane's methods, as they always were: the browser coordinators
8041
+ * drive one platform and need not know about lanes. */
8042
+ acquire(owner, lane = "web") {
8043
+ return this.lanes[lane].acquire(owner);
8044
+ }
8045
+ activate(owner, lane = "web") {
8046
+ this.lanes[lane].activate(owner);
8047
+ }
8048
+ beginRelease(owner, lane = "web") {
8049
+ return this.lanes[lane].beginRelease(owner);
8050
+ }
8051
+ release(owner, lane = "web") {
8052
+ this.lanes[lane].release(owner);
8053
+ }
8054
+ state(lane = "web") {
8055
+ return this.lanes[lane].state();
8056
+ }
8057
+ /** One lane, bound: what a coordinator for that platform is handed. */
8058
+ lane(lane) {
8059
+ return this.lanes[lane];
8060
+ }
8061
+ /** Take several lanes at once — all or none, so a job on two platforms
8062
+ * never holds one while refused the other. */
8063
+ acquireAll(owner, lanes) {
8064
+ const taken = [];
8065
+ for (const lane of new Set(lanes)) {
8066
+ const claim = this.lanes[lane].acquire(owner);
8067
+ if (!claim.ok) {
8068
+ for (const had of taken) this.lanes[had].release(owner);
8069
+ return { ok: false, lane, heldBy: claim.heldBy, phase: claim.phase };
8070
+ }
8071
+ taken.push(lane);
8072
+ }
8073
+ return { ok: true };
8074
+ }
8075
+ activateAll(owner, lanes) {
8076
+ for (const lane of new Set(lanes)) this.lanes[lane].activate(owner);
8077
+ }
8078
+ releaseAll(owner, lanes) {
8079
+ for (const lane of new Set(lanes)) this.lanes[lane].release(owner);
8080
+ }
8081
+ /** Drop every lane whoever holds it — shutdown, and nothing else. */
8082
+ forceRelease() {
8083
+ for (const lane of LANES) this.lanes[lane].forceRelease();
8084
+ }
8085
+ /** The sentence a refused caller is shown. Names what is happening and on
8086
+ * what, because "busy" leaves someone hunting for a window they cannot find. */
8087
+ static refusal(heldBy, phase, lane = "web") {
8088
+ const what = heldBy === "recording" ? "a recording" : heldBy === "debug" ? "a debug session" : "a run";
8089
+ const where = lane === "web" ? "the browser" : lane === "android" ? "the Android phone" : "the iPhone";
8090
+ if (phase === "starting") return `${what} is starting on ${where} \u2014 wait for it to open, or stop it first`;
8091
+ if (phase === "stopping") return `${what} is still closing on ${where} \u2014 try again in a moment`;
8092
+ return `${what} is already using ${where} \u2014 stop it before starting another`;
8093
+ }
8094
+ };
8095
+ var laneOf = (platform) => platform === "android" || platform === "ios" ? platform : "web";
7985
8096
  var COMMANDS = /* @__PURE__ */ new Set([
7986
8097
  "play",
7987
8098
  "pause",
@@ -8158,11 +8269,12 @@ async function verifyFlowSteps(deps, opts) {
8158
8269
  const steps = (opts.steps ?? flow.steps).map((s2) => DraftStepSchema.parse(s2));
8159
8270
  if (!opts.verifyIds.length) return { results: [], error: null, failedAtStepId: null, ok: true };
8160
8271
  const baseUrl = store.baseUrlFor(flow);
8161
- const claim = deps.lease?.acquire("run");
8272
+ const lane = laneOf(flow.platform);
8273
+ const claim = deps.lease?.acquire("run", lane);
8162
8274
  if (claim && !claim.ok) {
8163
- throw new BrowserBusy(`the browser on this machine is in use (${claim.heldBy}, ${claim.phase})`);
8275
+ throw new BrowserBusy(`${lane === "web" ? "the browser" : lane === "android" ? "the Android phone" : "the iPhone"} on this machine is in use (${claim.heldBy}, ${claim.phase})`);
8164
8276
  }
8165
- deps.lease?.activate("run");
8277
+ deps.lease?.activate("run", lane);
8166
8278
  try {
8167
8279
  const secrets = store.readSecrets();
8168
8280
  if (isMobile(flow.platform)) {
@@ -8207,7 +8319,7 @@ async function verifyFlowSteps(deps, opts) {
8207
8319
  resolveSecret: (ref) => secrets[ref]
8208
8320
  });
8209
8321
  } finally {
8210
- deps.lease?.release("run");
8322
+ deps.lease?.release("run", lane);
8211
8323
  }
8212
8324
  }
8213
8325
  function deliveredRun(delivered) {
@@ -8227,55 +8339,6 @@ function deliveredRun(delivered) {
8227
8339
  resolveFile: () => void 0
8228
8340
  };
8229
8341
  }
8230
- var BrowserLease = class {
8231
- owner = null;
8232
- phase = "idle";
8233
- since = null;
8234
- /** Take the lease, or report who has it. Never awaits — that is the point. */
8235
- acquire(owner) {
8236
- if (this.owner !== null) {
8237
- return { ok: false, heldBy: this.owner, phase: this.phase };
8238
- }
8239
- this.owner = owner;
8240
- this.phase = "starting";
8241
- this.since = Date.now();
8242
- return { ok: true };
8243
- }
8244
- /** The holder finished starting and is now doing the thing. */
8245
- activate(owner) {
8246
- if (this.owner === owner) this.phase = "active";
8247
- }
8248
- /** The holder has begun shutting down. Still not idle: a second stop must
8249
- * join the first rather than starting its own. */
8250
- beginRelease(owner) {
8251
- if (this.owner !== owner || this.phase === "stopping") return false;
8252
- this.phase = "stopping";
8253
- return true;
8254
- }
8255
- release(owner) {
8256
- if (this.owner !== owner) return;
8257
- this.owner = null;
8258
- this.phase = "idle";
8259
- this.since = null;
8260
- }
8261
- /** Drop the lease whoever holds it — shutdown, and nothing else. */
8262
- forceRelease() {
8263
- this.owner = null;
8264
- this.phase = "idle";
8265
- this.since = null;
8266
- }
8267
- state() {
8268
- return { owner: this.owner, phase: this.phase, since: this.since };
8269
- }
8270
- /** The sentence a refused caller is shown. Names what is happening, because
8271
- * "busy" leaves someone hunting for a window they cannot find. */
8272
- static refusal(heldBy, phase) {
8273
- const what = heldBy === "recording" ? "a recording" : heldBy === "debug" ? "a debug session" : "a run";
8274
- if (phase === "starting") return `${what} is starting \u2014 wait for it to open, or stop it first`;
8275
- if (phase === "stopping") return `${what} is still closing \u2014 try again in a moment`;
8276
- return `${what} is already using the browser \u2014 stop it before starting another`;
8277
- }
8278
- };
8279
8342
 
8280
8343
  // src/engine/checkouts.ts
8281
8344
  function resolveCheckouts(dirs) {
@@ -9232,11 +9295,12 @@ function executionHandlers(ctx) {
9232
9295
  detail: `this flow needs ${missing.length} secret value(s) that are not on this machine (${missing.map((m) => m.ref || m.field).join(", ")}) \u2014 set them on the machine that runs it`
9233
9296
  };
9234
9297
  }
9235
- const claim = lease?.acquire("run");
9298
+ const lanes = [laneOf(flow.platform)];
9299
+ const claim = lease?.acquireAll("run", lanes);
9236
9300
  if (claim && !claim.ok) {
9237
- return { ok: false, reason: "busy", detail: `the browser on this machine is in use (${claim.heldBy})` };
9301
+ return { ok: false, reason: "busy", detail: BrowserLease.refusal(claim.heldBy, claim.phase, claim.lane) };
9238
9302
  }
9239
- lease?.activate("run");
9303
+ lease?.activateAll("run", lanes);
9240
9304
  onActivity?.({ kind: "run", projectId: project.id, detail: flow.name });
9241
9305
  const aborter = new AbortController();
9242
9306
  cancels.set(command.id, { projectId: project.id, cancel: () => aborter.abort() });
@@ -9271,6 +9335,8 @@ function executionHandlers(ctx) {
9271
9335
  ok: true,
9272
9336
  result: {
9273
9337
  runId: outcome.run?.id ?? null,
9338
+ // What it was called, for a service that only knew its id.
9339
+ flowName: flow.name,
9274
9340
  // The WHOLE outcome, not a summary of it. A dashboard renders the
9275
9341
  // per-step report exactly as the local app does, and a result
9276
9342
  // carrying only counts makes that screen crash rather than degrade.
@@ -9286,7 +9352,7 @@ function executionHandlers(ctx) {
9286
9352
  if (typeof cancelKey === "string" && cancelKey.startsWith("suite:")) cancels.delete(cancelKey);
9287
9353
  if (runId) cancels.delete(runId);
9288
9354
  onActivity?.(null);
9289
- lease?.release("run");
9355
+ lease?.releaseAll("run", lanes);
9290
9356
  }
9291
9357
  },
9292
9358
  /** Run a test case: several flows, one browser, in order.
@@ -9304,11 +9370,12 @@ function executionHandlers(ctx) {
9304
9370
  if (testCase.occurrences.length === 0) {
9305
9371
  return { ok: false, reason: "failed", detail: `"${testCase.name}" has no flows in it to run` };
9306
9372
  }
9307
- const claim = lease?.acquire("run");
9373
+ const lanes = [...new Set(testCase.occurrences.map((o) => laneOf(o.platform)))];
9374
+ const claim = lease?.acquireAll("run", lanes);
9308
9375
  if (claim && !claim.ok) {
9309
- return { ok: false, reason: "busy", detail: `the browser on this machine is in use (${claim.heldBy})` };
9376
+ return { ok: false, reason: "busy", detail: BrowserLease.refusal(claim.heldBy, claim.phase, claim.lane) };
9310
9377
  }
9311
- lease?.activate("run");
9378
+ lease?.activateAll("run", lanes);
9312
9379
  onActivity?.({ kind: "run", projectId: project.id, detail: testCase.name });
9313
9380
  const aborter = new AbortController();
9314
9381
  cancels.set(command.id, { projectId: project.id, cancel: () => aborter.abort() });
@@ -9347,6 +9414,8 @@ function executionHandlers(ctx) {
9347
9414
  ok: true,
9348
9415
  result: {
9349
9416
  runId: run?.id ?? null,
9417
+ // What it was called, for a service that only knew its id.
9418
+ caseName: testCase.name,
9350
9419
  // The whole outcome, per-flow groups included: a dashboard renders
9351
9420
  // the report from this, and a result carrying only counts makes
9352
9421
  // that screen crash rather than degrade.
@@ -9358,7 +9427,7 @@ function executionHandlers(ctx) {
9358
9427
  if (typeof cancelKey === "string" && cancelKey.startsWith("suite:")) cancels.delete(cancelKey);
9359
9428
  if (runId) cancels.delete(runId);
9360
9429
  onActivity?.(null);
9361
- lease?.release("run");
9430
+ lease?.releaseAll("run", lanes);
9362
9431
  }
9363
9432
  },
9364
9433
  "run.cancel": async (command, project) => {
@@ -9726,6 +9795,7 @@ function assistantHandlers({ deps }) {
9726
9795
  const askId = randomUUID();
9727
9796
  const linked = foldersFor(project.id).filter((f) => f.exists).map((f) => f.path);
9728
9797
  const mode = command.args["mode"] === "ask" ? "ask" : command.args["mode"] === "do" ? "do" : void 0;
9798
+ const brief = typeof command.args["brief"] === "string" && command.args["brief"].trim() ? command.args["brief"].slice(0, 4e4) : void 0;
9729
9799
  const started = assistant.ask({
9730
9800
  askId,
9731
9801
  prompt,
@@ -9733,6 +9803,7 @@ function assistantHandlers({ deps }) {
9733
9803
  projectName: project.name,
9734
9804
  ...linked.length ? { folders: linked } : {},
9735
9805
  ...mode ? { mode } : {},
9806
+ ...brief ? { brief } : {},
9736
9807
  ...typeof sessionId === "string" ? { sessionId } : {},
9737
9808
  emit
9738
9809
  });
@@ -9769,7 +9840,9 @@ var runFile = (file2, args, timeoutMs) => new Promise((resolve2, reject) => {
9769
9840
  resolve2({ code, stdout: String(stdout), stderr: String(stderr) });
9770
9841
  });
9771
9842
  });
9772
- var live = null;
9843
+ var live = /* @__PURE__ */ new Map();
9844
+ var liveById = (sessionId) => [...live.values()].find((l) => l.id === sessionId);
9845
+ var ownerOf = (l) => l.draft ? "recording" : "debug";
9773
9846
  var SECRET_WORDS = /password|passwd|pass[-_\s]?code|pwd|secret|otp|\bpin\b|cvv|cvc/i;
9774
9847
  async function screenOf(l, withImage = false) {
9775
9848
  l.session.changed();
@@ -9795,8 +9868,9 @@ async function screenOf(l, withImage = false) {
9795
9868
  }
9796
9869
  function mobileHandlers(ctx) {
9797
9870
  const require2 = (command) => {
9798
- if (!live || command.args["sessionId"] !== live.id) throw new Error("that phone session has ended \u2014 open it again");
9799
- return live;
9871
+ const l = liveById(command.args["sessionId"]);
9872
+ if (!l) throw new Error("that phone session has ended \u2014 open it again");
9873
+ return l;
9800
9874
  };
9801
9875
  return {
9802
9876
  // Everything this computer has for testing — web and phones — for the Guide.
@@ -9807,6 +9881,21 @@ function mobileHandlers(ctx) {
9807
9881
  const devices = await listDevices();
9808
9882
  return { ok: true, result: { devices, ...command.args["doctor"] === true ? { doctor: await mobileDoctor() } : {} } };
9809
9883
  },
9884
+ // THE PHONE A RUN IS DRIVING, as a picture: whatever session is open on
9885
+ // that platform — a run's, a recording's. Not effectful, never kept, and
9886
+ // nothing when no phone is open: the mirror then shows that.
9887
+ "mobile.peek": async (command) => {
9888
+ const platform = command.args["platform"];
9889
+ if (!isMobile(platform)) return { ok: false, reason: "failed", detail: "say which phone: android or ios" };
9890
+ const session = live.get(platform)?.session ?? openSessionOn(platform);
9891
+ if (!session) return { ok: true, result: { image: null } };
9892
+ try {
9893
+ const png = await session.screenshot();
9894
+ return { ok: true, result: { image: png.toString("base64"), device: session.deviceName } };
9895
+ } catch {
9896
+ return { ok: true, result: { image: null } };
9897
+ }
9898
+ },
9810
9899
  "mobile.screen": async (command) => {
9811
9900
  try {
9812
9901
  const l = require2(command);
@@ -9823,14 +9912,21 @@ function mobileHandlers(ctx) {
9823
9912
  },
9824
9913
  "mobile.session": async (command, project) => {
9825
9914
  const op = String(command.args["op"] ?? "");
9915
+ let claimed = null;
9826
9916
  try {
9827
9917
  if (op === "start") {
9828
- if (live) {
9829
- await live.session.close().catch(() => void 0);
9830
- live = null;
9831
- }
9832
9918
  const platform = command.args["platform"];
9833
9919
  if (!isMobile(platform)) return { ok: false, reason: "failed", detail: "say which phone: android or ios" };
9920
+ const prior = live.get(platform);
9921
+ if (prior) {
9922
+ live.delete(prior.platform);
9923
+ await prior.session.close().catch(() => void 0);
9924
+ ctx.lease?.release(ownerOf(prior), prior.platform);
9925
+ }
9926
+ const owner = command.args["record"] === true ? "recording" : "debug";
9927
+ const claim = ctx.lease?.acquire(owner, platform);
9928
+ if (claim && !claim.ok) return { ok: false, reason: "busy", detail: BrowserLease.refusal(claim.heldBy, claim.phase, platform) };
9929
+ claimed = { owner, platform };
9834
9930
  const config = command.args["config"] ?? {};
9835
9931
  const secrets = command.args["secrets"] ?? {};
9836
9932
  const device = command.args["device"] ? DeviceChoiceSchema.parse(command.args["device"]) : void 0;
@@ -9865,7 +9961,8 @@ function mobileHandlers(ctx) {
9865
9961
  ...auth?.profileId ? { signIn: auth } : {}
9866
9962
  };
9867
9963
  }
9868
- live = l2;
9964
+ live.set(l2.platform, l2);
9965
+ ctx.lease?.activate(owner, l2.platform);
9869
9966
  if (/^[a-z][\w+.-]*:\/\//i.test(startPath)) await session.openLink(startPath);
9870
9967
  const opening = [
9871
9968
  ...l2.draft?.signIn ? [RecordedStepSchema.parse({ id: "sign-in", type: "signIn", auth: l2.draft.signIn, role: "", name: "Sign in", scope: "", value: "", secret: false, legacySelector: "", proposals: [] })] : [],
@@ -9948,7 +10045,8 @@ function mobileHandlers(ctx) {
9948
10045
  return { ok: true, result: { steps: l.draft.steps } };
9949
10046
  }
9950
10047
  if (op === "stop" || op === "discard") {
9951
- live = null;
10048
+ live.delete(l.platform);
10049
+ ctx.lease?.release(ownerOf(l), l.platform);
9952
10050
  await l.session.close().catch(() => void 0);
9953
10051
  if (op === "discard" || !l.draft) return { ok: true, result: { ended: true } };
9954
10052
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -9972,6 +10070,7 @@ function mobileHandlers(ctx) {
9972
10070
  }
9973
10071
  return { ok: false, reason: "failed", detail: `unknown phone session operation "${op}"` };
9974
10072
  } catch (error) {
10073
+ if (claimed && !live.has(claimed.platform)) ctx.lease?.release(claimed.owner, claimed.platform);
9975
10074
  return { ok: false, reason: "failed", detail: error.message.split("\n")[0] ?? "the phone session failed" };
9976
10075
  }
9977
10076
  }
@@ -10068,10 +10167,13 @@ var ASK_MODE_TOOLS = [
10068
10167
  function buildAskArgs(opts) {
10069
10168
  const ask = opts.mode === "ask";
10070
10169
  const folders = opts.folders ?? [];
10170
+ const brief = opts.brief?.trim();
10071
10171
  const guidance = [
10072
- `You are the Flowriki assistant for the project "${opts.projectName}" (id ${opts.projectId}). Use the Flowriki tools, and pass projectId "${opts.projectId}" wherever a tool takes one. Start with project_brief: it is this project's memory.`,
10172
+ `You are the Flowriki assistant for the project "${opts.projectName}" (id ${opts.projectId}). Use the Flowriki tools, and pass projectId "${opts.projectId}" wherever a tool takes one. ` + (brief ? "The project brief below is this project's memory for this job: read it first." : "Start with project_brief: it is this project's memory."),
10073
10173
  ask ? "ASK MODE: answer the question; change nothing. Cite what you rely on as [[testCase:<its id, tc_\u2026>]], [[knowledge:features/checkout.md]] or [[file:path/in/repo.ts]]." : "DO MODE: do what is asked, on Claude's working branch, for a person to review. Ask in the chat and wait before publishing, deleting, or sending anything to another system. End by writing a short summary of what you did with write_knowledge (memory/sessions/<date>-<topic>.md, append) so the next session knows.",
10074
- folders.length ? `The project's code is in ${folders.join(" and ")}. Read it with Read, Grep and Glob when it helps; you cannot change it.` : ""
10174
+ folders.length ? `The project's code is in ${folders.join(" and ")}. Read it with Read, Grep and Glob when it helps; you cannot change it.` : "",
10175
+ brief ? `
10176
+ ${brief}` : ""
10075
10177
  ].filter(Boolean).join("\n");
10076
10178
  const flowriki = ask ? ASK_MODE_TOOLS.map((t) => `mcp__${MCP_SERVER_NAME}__${t}`) : [`mcp__${MCP_SERVER_NAME}`];
10077
10179
  const files = folders.length ? ["Read", "Grep", "Glob"] : [];
@@ -17,7 +17,7 @@ import path3 from "path";
17
17
  import fs4 from "fs";
18
18
  import path4 from "path";
19
19
 
20
- // ../test-model/dist/chunk-QDXJLOQJ.js
20
+ // ../test-model/dist/chunk-I5COYCQ5.js
21
21
  import { z as z2 } from "zod";
22
22
  import { z } from "zod";
23
23
  var PLATFORMS = ["web", "android", "ios"];
@@ -86,8 +86,8 @@ var DeviceChoiceSchema = z.discriminatedUnion("kind", [
86
86
  }).strict()
87
87
  ]);
88
88
  var MobileConfigSchema = z.object({
89
- android: AndroidAppSchema.optional(),
90
- ios: IosAppSchema.optional(),
89
+ android: AndroidAppSchema.partial({ appPackage: true }).optional(),
90
+ ios: IosAppSchema.partial({ bundleId: true }).optional(),
91
91
  /** Between runs: keep the app's data (stays signed in), or start clean. */
92
92
  reset: z.enum(["keepData", "fresh"]).default("keepData"),
93
93
  /** Where runs go when nobody says — absent means whatever is available. */
@@ -98,6 +98,10 @@ var mobileOf = (config) => {
98
98
  const parsed = MobileConfigSchema.safeParse(config?.mobile ?? {});
99
99
  return parsed.success ? parsed.data : MobileConfigSchema.parse({});
100
100
  };
101
+ var EnvironmentAppsSchema = z.object({
102
+ android: AndroidAppSchema.optional(),
103
+ ios: IosAppSchema.optional()
104
+ });
101
105
  var DeviceInfoSchema = z.object({
102
106
  id: z.string(),
103
107
  name: z.string(),
@@ -1684,6 +1688,14 @@ var BugSchema = z7.object({
1684
1688
  updatedAt: z7.string()
1685
1689
  }).strict();
1686
1690
  var STOP = new Set("a an the and or of to in on for with by is are be as at it this that from user users can should must shall will when then given".split(" "));
1691
+ function exploreSummary(markdown) {
1692
+ const screens = /^Screens:\s*(\d+)\s*$/im.exec(markdown)?.[1];
1693
+ return {
1694
+ exploredAt: /^Explored:\s*(\S+)\s*$/im.exec(markdown)?.[1] ?? null,
1695
+ environment: /^Environment:\s*(.+?)\s*$/im.exec(markdown)?.[1] ?? null,
1696
+ screens: screens ? Number(screens) : null
1697
+ };
1698
+ }
1687
1699
  var CODEBASE_TAGS = ["all", "web", "android", "ios", "mobile", "other"];
1688
1700
  var CODEBASE_TAG_NAME = { all: "All combined", web: "Web", android: "Android", ios: "iOS", mobile: "Mobile", other: "Other" };
1689
1701
  var CodebaseIndexSchema = z8.object({
@@ -2518,11 +2530,11 @@ var CLOUD_HUBS = {
2518
2530
  function appOf(target) {
2519
2531
  if (target.platform === "android") {
2520
2532
  const app2 = target.mobile.android;
2521
- if (!app2) throw new Error("This project has no Android app set up \u2014 add it in Settings \u2192 Mobile.");
2533
+ if (!app2?.appPackage) throw new Error("This environment has not named its Android app \u2014 add its package in Settings \u2192 Environments.");
2522
2534
  return { id: app2.appPackage, ...app2.appActivity ? { activity: app2.appActivity } : {}, ...app2.build ? { build: app2.build } : {} };
2523
2535
  }
2524
2536
  const app = target.mobile.ios;
2525
- if (!app) throw new Error("This project has no iOS app set up \u2014 add it in Settings \u2192 Mobile.");
2537
+ if (!app?.bundleId) throw new Error("This environment has not named its iOS app \u2014 add its bundle ID in Settings \u2192 Environments.");
2526
2538
  return { id: app.bundleId, ...app.build ? { build: app.build } : {} };
2527
2539
  }
2528
2540
  async function localBuild(build) {
@@ -2556,6 +2568,10 @@ async function localCapabilities(target, udid, opts = {}) {
2556
2568
  "appium:appPackage": app.id,
2557
2569
  ...app.activity ? { "appium:appActivity": app.activity } : {},
2558
2570
  "appium:appWaitActivity": "*",
2571
+ // Its own ports: two phones on one Appium each need their own driver
2572
+ // server and picture stream, or the second session steals the first's.
2573
+ ...opts.ports?.system ? { "appium:systemPort": opts.ports.system } : {},
2574
+ ...opts.ports?.mjpeg ? { "appium:mjpegServerPort": opts.ports.mjpeg } : {},
2559
2575
  "appium:autoGrantPermissions": false,
2560
2576
  "appium:disableWindowAnimation": true,
2561
2577
  "appium:uiautomator2ServerLaunchTimeout": 12e4,
@@ -2572,6 +2588,8 @@ async function localCapabilities(target, udid, opts = {}) {
2572
2588
  "appium:bundleId": app.id,
2573
2589
  "appium:wdaLaunchTimeout": 24e4,
2574
2590
  "appium:wdaConnectionTimeout": 24e4,
2591
+ ...opts.ports?.wda ? { "appium:wdaLocalPort": opts.ports.wda } : {},
2592
+ ...opts.ports?.mjpeg ? { "appium:mjpegServerPort": opts.ports.mjpeg } : {},
2575
2593
  ...target.mobile.ios?.teamId ? { "appium:xcodeOrgId": target.mobile.ios.teamId, "appium:xcodeSigningId": "Apple Development" } : {}
2576
2594
  };
2577
2595
  }
@@ -3191,6 +3209,8 @@ async function checkMobileAssertion(session, a, timeoutMs = 3e3) {
3191
3209
  throw last;
3192
3210
  }
3193
3211
  var uploaded = /* @__PURE__ */ new Map();
3212
+ var OPEN = /* @__PURE__ */ new Map();
3213
+ var openSessionOn = (platform) => OPEN.get(platform) ?? null;
3194
3214
  var MobileSession = class _MobileSession {
3195
3215
  constructor(driver, platform, appId, deviceName, cloud) {
3196
3216
  this.driver = driver;
@@ -3231,6 +3251,7 @@ var MobileSession = class _MobileSession {
3231
3251
  throw new Error(`${CLOUD_PROVIDER_NAME[choice2.provider]} could not open ${choice2.device}: ${e.message}`);
3232
3252
  });
3233
3253
  const cloudSession = new _MobileSession(driver2, target.platform, app.id, choice2.device, true);
3254
+ OPEN.set(target.platform, cloudSession);
3234
3255
  await cloudSession.settle(2e4);
3235
3256
  return cloudSession;
3236
3257
  }
@@ -3244,12 +3265,14 @@ var MobileSession = class _MobileSession {
3244
3265
  const endpoint = await appiumServer();
3245
3266
  const buildFile = app.build ? await localBuild(app.build) : null;
3246
3267
  say(`Opening the app on ${known.name}\u2026`);
3247
- const capabilities = await localCapabilities(target, udid, { buildFile });
3268
+ const ports = target.platform === "android" ? { system: await freePort(), mjpeg: await freePort() } : { wda: await freePort(), mjpeg: await freePort() };
3269
+ const capabilities = await localCapabilities(target, udid, { buildFile, ports });
3248
3270
  const driver = await WebDriverSession.create(endpoint, capabilities).catch((e) => {
3249
3271
  const tail = appiumLog().filter((l) => /error/i.test(l)).slice(-2).join(" ");
3250
3272
  throw new Error(explainStartFailure(e.message, target.platform) + (tail ? ` (${tail.slice(0, 300)})` : ""));
3251
3273
  });
3252
3274
  const session = new _MobileSession(driver, target.platform, app.id, known.name, false);
3275
+ OPEN.set(target.platform, session);
3253
3276
  await session.terminateApp().catch(() => {
3254
3277
  });
3255
3278
  await session.launchApp().catch(() => {
@@ -3482,6 +3505,7 @@ var MobileSession = class _MobileSession {
3482
3505
  return { relPath: path4.basename(file), bytes: bytes.length };
3483
3506
  }
3484
3507
  async close() {
3508
+ if (OPEN.get(this.platform) === this) OPEN.delete(this.platform);
3485
3509
  if (this.recording) await this.stopVideo(null);
3486
3510
  await this.driver.close();
3487
3511
  }
@@ -4053,6 +4077,8 @@ export {
4053
4077
  isMobile,
4054
4078
  stepRefusedOn,
4055
4079
  platformsOf,
4080
+ AndroidAppSchema,
4081
+ IosAppSchema,
4056
4082
  CLOUD_PROVIDERS,
4057
4083
  CLOUD_PROVIDER_NAME,
4058
4084
  DeviceChoiceSchema,
@@ -4086,6 +4112,7 @@ export {
4086
4112
  PrerequisiteProblem,
4087
4113
  expandForRun,
4088
4114
  DeliveredHelperSchema,
4115
+ exploreSummary,
4089
4116
  CODEBASE_TAG_NAME,
4090
4117
  codebaseIndexOf,
4091
4118
  runWhereOf,
@@ -4137,6 +4164,7 @@ export {
4137
4164
  CheckError,
4138
4165
  textOf,
4139
4166
  checkMobileAssertion,
4167
+ openSessionOn,
4140
4168
  MobileSession,
4141
4169
  MOBILE_TIMEOUTS,
4142
4170
  performMobileStep,
package/dist/cli.js CHANGED
@@ -23,7 +23,7 @@ import {
23
23
  runtimeValue,
24
24
  start,
25
25
  status
26
- } from "./chunk-KRWHHKSS.js";
26
+ } from "./chunk-6AR63WTN.js";
27
27
  import {
28
28
  CLOUD_PROVIDERS,
29
29
  CLOUD_PROVIDER_NAME,
@@ -32,6 +32,7 @@ import {
32
32
  RecordedStepSchema,
33
33
  checkMobileAssertion,
34
34
  codebaseIndexOf,
35
+ exploreSummary,
35
36
  isMobile,
36
37
  listDevices,
37
38
  locate,
@@ -48,7 +49,7 @@ import {
48
49
  screenLines,
49
50
  stepRefusedOn,
50
51
  targetFor
51
- } from "./chunk-GLZFHC64.js";
52
+ } from "./chunk-FIR6NOLA.js";
52
53
 
53
54
  // src/cli.ts
54
55
  import { parseArgs } from "util";
@@ -325,6 +326,10 @@ var ServiceClient = class {
325
326
  return this.call(this.scoped(`/bugs/${encodeURIComponent(id)}`, projectId), { method: "PATCH", body: JSON.stringify(body) });
326
327
  }
327
328
  // ── Knowledge, sources, requirements ──────────────────────
329
+ /** The project's brief for a job: its slice of the knowledge. */
330
+ brief(projectId, scope) {
331
+ return this.call(this.scoped("/brief", projectId, { ...scope.platform ? { platform: scope.platform } : {}, ...scope.features?.length ? { features: scope.features.join(",") } : {} }));
332
+ }
328
333
  knowledgePages(projectId) {
329
334
  return this.call(this.scoped("/knowledge", projectId));
330
335
  }
@@ -985,7 +990,7 @@ Review and publish it here: ${review}`;
985
990
  async startMobile(platform, catalog) {
986
991
  const draft = this.draft;
987
992
  const mobile = catalog.mobile;
988
- if (!mobile) throw new Error("this project has no phone apps set up \u2014 turn on Android or iOS in Settings \u2192 Mobile");
993
+ if (!mobile) throw new Error("this project has no phone apps set up \u2014 turn on Android or iOS in Settings \u2192 Platforms");
989
994
  const resolvers = draft.signIn || draft.device?.kind === "cloud" ? await this.prepare() : null;
990
995
  this.mobile = await openMobileSession({
991
996
  platform,
@@ -1233,7 +1238,10 @@ codebase/<folder>.md, which must start "# <folder>", then "Commit: <full sha>"
1233
1238
  and "Learned: <yyyy-mm-dd>" lines: that is how the Knowledge screen shows it
1234
1239
  as learned. codebase.json says which platform's code each folder holds (Web,
1235
1240
  Android, iOS, Mobile, All combined, Other): read the web folder's page for a
1236
- website test and the phone folder's for an app test. Keep what you learn with write_knowledge (features/<name>.md,
1241
+ website test and the phone folder's for an app test. explore/<platform>.md is
1242
+ what the assistant saw WALKING that platform \u2014 screens, the words on them,
1243
+ journeys, rules it saw refused, vocabulary: read it before writing a test for
1244
+ that platform, because it says what a person sees; the code does not. Keep what you learn with write_knowledge (features/<name>.md,
1237
1245
  memory/decisions.md, a session summary), on Claude's branch for review \u2014
1238
1246
  never a password, key or token.
1239
1247
 
@@ -1412,7 +1420,7 @@ It is now the project these tools work on. Open it: ${service.origin}/projects/$
1412
1420
  const here = phones.filter((d) => d.platform === p);
1413
1421
  const farms = (c.mobile?.cloud ?? []).map((f) => CLOUD_PROVIDER_NAME[f.provider]);
1414
1422
  const chosen = c.mobile?.devices[p];
1415
- return `${p === "android" ? "Android" : "iOS"} app: ${app ?? "(not set up \u2014 Settings \u2192 Mobile)"}
1423
+ return `${p === "android" ? "Android" : "iOS"} app: ${app ?? "(not named \u2014 Settings \u2192 Environments)"}
1416
1424
  Phones on this computer: ${here.length ? here.map((d) => `${d.name}${d.state === "ready" ? "" : ` (${d.state})`}`).join(", ") : "none"}` + (farms.length ? `
1417
1425
  Cloud device farms: ${farms.join(", ")}` : "") + `
1418
1426
  Runs: ${c.runWhere?.[p] === "cloud" ? "in the cloud" : "on this computer"}` + (chosen ? ` \u2014 ${chosen.kind === "local" ? chosen.name || chosen.id : `${CLOUD_PROVIDER_NAME[chosen.provider]}: ${chosen.device}`}` : "");
@@ -2009,11 +2017,19 @@ Use it with start_flow signIn {"profileId": "\u2026", "accountId": "${made.accou
2009
2017
  {
2010
2018
  name: "project_brief",
2011
2019
  title: "Read the project's memory",
2012
- description: "The project's own knowledge, in one call: About the product (product.md, and product/<web|android|ios>.md where a platform differs), memory/decisions.md, what each linked codebase folder was learned as and which platform's code it holds, and the list of feature pages and session summaries. Call it first in every conversation.",
2013
- inputSchema: { type: "object", properties: { projectId: { type: "string" } }, additionalProperties: false },
2020
+ description: "The project's own knowledge, in one call: About the product (product.md, and product/<web|android|ios>.md where a platform differs), memory/decisions.md, what each linked codebase folder was learned as and which platform's code it holds, what each platform looked like when explored (explore/<platform>.md), and the list of feature pages and session summaries. Call it first in every conversation. With platform and/or features, the brief is the slice for that job: that platform's part and exploration, those features' notes, the code tagged for the platform, and who can sign in.",
2021
+ inputSchema: { type: "object", properties: { projectId: { type: "string" }, platform: { type: "string", enum: ["web", "android", "ios"], description: "The platform the job is about." }, features: { type: "array", items: { type: "string" }, description: "The features (or case folders) the job is about, by name." } }, additionalProperties: false },
2014
2022
  annotations: { readOnlyHint: true },
2015
2023
  run: async (args) => {
2016
2024
  const projectId = await projectFor(args["projectId"]);
2025
+ const platform = str(args["platform"]);
2026
+ const features = Array.isArray(args["features"]) ? args["features"].filter((f) => typeof f === "string") : [];
2027
+ if (platform || features.length) {
2028
+ try {
2029
+ return text((await service.brief(projectId, { ...platform ? { platform } : {}, features })).brief);
2030
+ } catch {
2031
+ }
2032
+ }
2017
2033
  const { pages } = await service.knowledgePages(projectId);
2018
2034
  const read = async (path2) => pages.some((p) => p.path === path2) ? (await service.knowledgePage(projectId, path2)).content : null;
2019
2035
  const [product, glossary, decisions, web, android, ios, tagged] = await Promise.all(["product.md", "glossary.md", "memory/decisions.md", "product/web.md", "product/android.md", "product/ios.md", "codebase.json"].map(read));
@@ -2023,7 +2039,12 @@ Use it with start_flow signIn {"profileId": "\u2026", "accountId": "${made.accou
2023
2039
  const holds = tags[p.path.slice("codebase/".length, -".md".length)]?.tags ?? [];
2024
2040
  return `- ${p.path}${holds.length ? ` (${holds.map((t) => CODEBASE_TAG_NAME[t]).join(", ")} code)` : ""}: ${(await read(p.path))?.split("\n").filter((l) => l.trim()).slice(0, 3).join(" \xB7 ") ?? ""}`;
2025
2041
  }));
2026
- const shown = /* @__PURE__ */ new Set(["product.md", "glossary.md", "memory/decisions.md", "product/web.md", "product/android.md", "product/ios.md", "codebase.json", ...codebases.map((p) => p.path)]);
2042
+ const explored = pages.filter((p) => p.path.startsWith("explore/"));
2043
+ const walks = await Promise.all(explored.map(async (p) => {
2044
+ const s = exploreSummary(await read(p.path) ?? "");
2045
+ return `- ${p.path}: explored ${s.exploredAt ?? "?"}${s.environment ? ` in ${s.environment}` : ""}${s.screens !== null ? `, ${s.screens} screens` : ""} \u2014 read it with knowledge_read before testing that platform`;
2046
+ }));
2047
+ const shown = /* @__PURE__ */ new Set(["product.md", "glossary.md", "memory/decisions.md", "product/web.md", "product/android.md", "product/ios.md", "codebase.json", ...codebases.map((p) => p.path), ...explored.map((p) => p.path)]);
2027
2048
  const parts = [
2028
2049
  product ? `## About the product (product.md)
2029
2050
  ${product}` : "No About the product yet \u2014 the person writes it on the Knowledge screen; offer to help draft it.",
@@ -2039,6 +2060,8 @@ ${glossary}` : "",
2039
2060
  ${decisions}` : "",
2040
2061
  codebases.length ? `## Codebase, as learned
2041
2062
  ${headers.join("\n")}` : "No codebase learned yet.",
2063
+ walks.length ? `## The product, as explored
2064
+ ${walks.join("\n")}` : "",
2042
2065
  `## Other pages
2043
2066
  ${pages.filter((p) => !shown.has(p.path)).map((p) => `- ${p.path}`).join("\n") || "none"}`
2044
2067
  ];
@@ -2074,7 +2097,7 @@ ${pages.filter((p) => !shown.has(p.path)).map((p) => `- ${p.path}`).join("\n") |
2074
2097
  {
2075
2098
  name: "write_knowledge",
2076
2099
  title: "Write a knowledge page",
2077
- description: `Write what the project should remember, on Claude's working branch for review: product.md (About the product \u2014 the person's own description; write it only when they ask) and product/<web|android|ios>.md (what is particular to that platform's app), codebase/<folder>.md (what a linked folder holds \u2014 starting with "# <folder>", "Commit: <sha>" and "Learned: <yyyy-mm-dd>" lines, then routes, screens, forms and their validation, roles, APIs, flags; summarised, never copied code), features/<name>.md (screens, rules, quirks), memory/decisions.md (decisions and conventions), memory/sessions/<yyyy-mm-dd>-<topic>.md (what a job did). append: true adds to the end instead of replacing. Never a password, key or token \u2014 those are refused.`,
2100
+ description: `Write what the project should remember, on Claude's working branch for review: product.md (About the product \u2014 the person's own description; write it only when they ask) and product/<web|android|ios>.md (what is particular to that platform's app), explore/<web|android|ios>.md (what you saw walking that platform: "# Explored: <Platform>", "Explored: <yyyy-mm-dd>", "Environment: <name>", "Screens: <n>" lines, then Screens, Journeys, Rules observed, Vocabulary, Suggested features), codebase/<folder>.md (what a linked folder holds \u2014 starting with "# <folder>", "Commit: <sha>" and "Learned: <yyyy-mm-dd>" lines, then routes, screens, forms and their validation, roles, APIs, flags; summarised, never copied code), features/<name>.md (screens, rules, quirks), memory/decisions.md (decisions and conventions), memory/sessions/<yyyy-mm-dd>-<topic>.md (what a job did). append: true adds to the end instead of replacing. Never a password, key or token \u2014 those are refused.`,
2078
2101
  inputSchema: {
2079
2102
  type: "object",
2080
2103
  properties: { projectId: { type: "string" }, path: { type: "string" }, content: { type: "string" }, append: { type: "boolean" }, branch: { type: "string" } },
@@ -31,6 +31,7 @@ import {
31
31
  mobileSetup,
32
32
  nodeAt,
33
33
  openMobileSession,
34
+ openSessionOn,
34
35
  performMobileStep,
35
36
  readScreen,
36
37
  replayMobileCase,
@@ -44,7 +45,7 @@ import {
44
45
  textOf,
45
46
  toolEnv,
46
47
  typesSecrets
47
- } from "./chunk-GLZFHC64.js";
48
+ } from "./chunk-FIR6NOLA.js";
48
49
  export {
49
50
  AGENT_HOME,
50
51
  APPIUM_DIR,
@@ -77,6 +78,7 @@ export {
77
78
  mobileSetup,
78
79
  nodeAt,
79
80
  openMobileSession,
81
+ openSessionOn,
80
82
  performMobileStep,
81
83
  readScreen,
82
84
  replayMobileCase,
package/dist/engine.js CHANGED
@@ -9,8 +9,8 @@ import {
9
9
  resolveCheckouts,
10
10
  start,
11
11
  status
12
- } from "./chunk-KRWHHKSS.js";
13
- import "./chunk-GLZFHC64.js";
12
+ } from "./chunk-6AR63WTN.js";
13
+ import "./chunk-FIR6NOLA.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.50",
3
+ "version": "0.1.0-beta.53",
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",