@qawolf/cli 1.30.0 → 1.32.0

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.
@@ -9485,7 +9485,14 @@ var authErrorMessages = {
9485
9485
  rejected401: (noun) => `QA Wolf API rejected the${noun ? ` ${noun}` : ""} request (HTTP 401). Check your API key.`,
9486
9486
  rejected402: (noun) => `QA Wolf API refused the${noun ? ` ${noun}` : ""} request (HTTP 402): billing prevented it.`,
9487
9487
  rejected403: (noun) => `QA Wolf API rejected the${noun ? ` ${noun}` : ""} request (HTTP 403). Check that your API key has access to this environment.`,
9488
- notFound404: (noun) => `QA Wolf API could not find ${noun ? `${noun} for that environment` : "that environment"} (HTTP 404). Check the --env value.`,
9488
+ notFound404Environment: (noun) => `QA Wolf API could not find ${noun ? `${noun} for that environment` : "that environment"} (HTTP 404). Check the --env value.`,
9489
+ notFound404Runner: (runnerId) => runnerId === undefined ? "That runner is not running (HTTP 404)." : `Runner ${runnerId} is not running (HTTP 404).`,
9490
+ runnerIsGone: "It was never launched, or it has since been terminated or idled out.",
9491
+ launchTheRunner: (runnerId) => runnerId === undefined ? "Launch one with qawolf runner launch --id <id>, or name a running one with --runner." : `Launch it with qawolf runner launch --id ${runnerId}, or send this to a different runner with --runner.`,
9492
+ notFound404Run: (runId) => `QA Wolf has no run ${runId ?? "by that id"} on this team (HTTP 404).`,
9493
+ runIdMayBeRunnerLocal: (runId) => `A run id printed by qawolf runner run belongs to that runner rather than to the platform, so this command cannot resolve it. Read that run with qawolf runner events run-status --run ${runId ?? "<id>"}.`,
9494
+ notFound404Record: (noun, id) => `QA Wolf has no ${noun} ${id} (HTTP 404).`,
9495
+ notFound404: (noun) => noun === undefined ? "QA Wolf found nothing matching the request (HTTP 404)." : `QA Wolf found nothing matching the ${noun} request (HTTP 404).`,
9489
9496
  failedWithStatus: (status, noun) => `QA Wolf API${noun ? ` ${noun}` : ""} request failed (HTTP ${status}).`,
9490
9497
  networkUnreachable: (baseUrl, noun) => `Could not reach the QA Wolf API at ${baseUrl}${noun ? ` to fetch ${noun}` : ""}. Check your network connection and QAWOLF_HOST_URL.`,
9491
9498
  timedOut: (timeoutMs, noun) => `The QA Wolf API${noun ? ` ${noun}` : ""} request timed out after ${formatSeconds(timeoutMs)}. The work may still be finishing on the platform.`,
@@ -9836,7 +9843,14 @@ var lifecycleMessages = {
9836
9843
  terminated: (id) => `Terminated runner ${id}.`,
9837
9844
  runStopped: (id) => `Stopped the run on runner ${id}. The runner is still up, on whatever page the run reached.`,
9838
9845
  nothingToStop: (id) => `Runner ${id} had nothing to stop. The run had already finished, or none had been submitted.`,
9839
- runnerUnreachable: "The runner could not be reached. It may still be starting, or it may have terminated after inactivity. Retry, or launch it again."
9846
+ runnerUnreachable: "The runner could not be reached. It may still be starting, or it may have terminated after inactivity. Retry, or launch it again.",
9847
+ runnerIdCameFrom: (id, source) => ({
9848
+ environment: `The id ${id} came from QAWOLF_RUNNER_ID.`,
9849
+ flag: `The id ${id} came from --runner.`,
9850
+ given: `The id ${id} is the one this call named.`,
9851
+ launched: `Runner ${id} was launched for this command.`,
9852
+ stored: `The id ${id} came from the runner this directory last launched, recorded in .qawolf.`
9853
+ })[source]
9840
9854
  };
9841
9855
 
9842
9856
  // src/core/messages/interactiveRunner/list.ts
@@ -9935,7 +9949,9 @@ ${dir}`,
9935
9949
  ].join(`
9936
9950
  `),
9937
9951
  outerHopCarriedOver: (names) => `Carried over ${pluralize(names.length, "undeclared package")} from the project node_modules: ${listNames(names)}. Declare them in package.json "dependencies".`,
9938
- moduleNotFoundHint: (pkg, projectDir) => projectDir === undefined ? `Hint: '${pkg}' could not be resolved. Run from within your flows project so its dependencies can be found.` : `Hint: '${pkg}' could not be resolved. Ensure it is declared in ${projectDir}/package.json "dependencies" and run npm install in that project.`
9952
+ moduleNotFoundHint: (pkg, projectDir) => projectDir === undefined ? `Hint: '${pkg}' could not be resolved. Run from within your flows project so its dependencies can be found.` : `Hint: '${pkg}' could not be resolved. Ensure it is declared in ${projectDir}/package.json "dependencies" and run npm install in that project.`,
9953
+ tsconfigUnparsedNotice: (projectDir) => `Not resolving path aliases: ${projectDir}/tsconfig.json is not valid JSON. Comments and trailing commas are not supported, here or on the platform. Remove them so "compilerOptions.paths" can be read.`,
9954
+ pathAliasNotFoundHint: (specifier, projectDir) => projectDir === undefined ? `Hint: '${specifier}' looks like a tsconfig path alias. Run from within your flows project so its tsconfig.json can be found.` : `Hint: '${specifier}' looks like a tsconfig path alias. Ensure "compilerOptions.paths" in ${projectDir}/tsconfig.json maps it to a file that exists.`
9939
9955
  };
9940
9956
  // src/shell/exit.ts
9941
9957
  var exitCodes = {
@@ -9946,7 +9962,8 @@ var exitCodes = {
9946
9962
  network: 4,
9947
9963
  config: 5,
9948
9964
  timeout: 6,
9949
- payment: 7
9965
+ payment: 7,
9966
+ notFound: 8
9950
9967
  };
9951
9968
 
9952
9969
  // src/core/sleep.ts
@@ -10894,41 +10911,70 @@ function isPathAlias(importPath, paths) {
10894
10911
  // src/core/interactiveRunner/resolveImportPath.ts
10895
10912
  import { dirname as dirname2, join, normalize as normalize2 } from "node:path/posix";
10896
10913
 
10897
- // src/core/interactiveRunner/tsconfigPaths.ts
10914
+ // src/core/aliasImports/tsconfigPaths.ts
10898
10915
  function isRecord(value) {
10899
10916
  return typeof value === "object" && value !== null;
10900
10917
  }
10901
10918
  function isTsconfigPaths(value) {
10902
10919
  return isRecord(value) && Object.values(value).every((targets) => Array.isArray(targets) && targets.every((target) => typeof target === "string"));
10903
10920
  }
10904
- function parseTsconfigPaths(tsconfigContent) {
10921
+ function parseTsconfigContent(tsconfigContent) {
10905
10922
  let parsed;
10906
10923
  try {
10907
10924
  parsed = JSON.parse(tsconfigContent);
10908
10925
  } catch {
10909
- return;
10926
+ return { type: "unparseable" };
10910
10927
  }
10911
10928
  if (!isRecord(parsed))
10912
- return;
10929
+ return { paths: undefined, type: "parsed" };
10913
10930
  const compilerOptions = parsed["compilerOptions"];
10914
10931
  if (!isRecord(compilerOptions))
10915
- return;
10932
+ return { paths: undefined, type: "parsed" };
10916
10933
  const paths = compilerOptions["paths"];
10917
- return isTsconfigPaths(paths) ? paths : undefined;
10934
+ return {
10935
+ paths: isTsconfigPaths(paths) ? paths : undefined,
10936
+ type: "parsed"
10937
+ };
10938
+ }
10939
+ function parseTsconfigPaths(tsconfigContent) {
10940
+ const parsed = parseTsconfigContent(tsconfigContent);
10941
+ return parsed.type === "parsed" ? parsed.paths : undefined;
10942
+ }
10943
+ function parseWildcardPattern(pattern) {
10944
+ const wildcard = pattern.indexOf("*");
10945
+ if (wildcard === -1 || wildcard !== pattern.lastIndexOf("*"))
10946
+ return;
10947
+ return {
10948
+ prefix: pattern.slice(0, wildcard),
10949
+ suffix: pattern.slice(wildcard + 1)
10950
+ };
10951
+ }
10952
+ function matchesWildcard(importPath, { prefix, suffix }) {
10953
+ return importPath.length >= prefix.length + suffix.length && importPath.startsWith(prefix) && importPath.endsWith(suffix);
10954
+ }
10955
+ function longestPrefixMatch(importPath, paths) {
10956
+ return Object.entries(paths).flatMap(([pattern, targets]) => {
10957
+ const parsed = parseWildcardPattern(pattern);
10958
+ if (parsed === undefined || !matchesWildcard(importPath, parsed)) {
10959
+ return [];
10960
+ }
10961
+ return [{ pattern: parsed, targets }];
10962
+ }).reduce((best, candidate) => best === undefined || candidate.pattern.prefix.length > best.pattern.prefix.length ? candidate : best, undefined);
10918
10963
  }
10919
10964
  function resolvePathAlias(importPath, paths) {
10920
10965
  if (paths === undefined)
10921
10966
  return;
10922
- for (const [pattern, targets] of Object.entries(paths)) {
10923
- const prefix = pattern.replace("*", "");
10924
- if (!importPath.startsWith(prefix))
10925
- continue;
10926
- const [target] = targets;
10927
- if (target !== undefined) {
10928
- return target.replace("*", importPath.slice(prefix.length));
10929
- }
10930
- }
10931
- return;
10967
+ const [exactTarget] = paths[importPath] ?? [];
10968
+ if (exactTarget !== undefined)
10969
+ return exactTarget;
10970
+ const match = longestPrefixMatch(importPath, paths);
10971
+ if (match === undefined)
10972
+ return;
10973
+ const [target] = match.targets;
10974
+ if (target === undefined)
10975
+ return;
10976
+ const substituted = importPath.slice(match.pattern.prefix.length, importPath.length - match.pattern.suffix.length);
10977
+ return target.replace("*", substituted);
10932
10978
  }
10933
10979
 
10934
10980
  // src/core/interactiveRunner/resolveImportPath.ts
@@ -11366,6 +11412,47 @@ function makeDefaultFs() {
11366
11412
  };
11367
11413
  }
11368
11414
 
11415
+ // src/core/publicApi/notFoundSubject.ts
11416
+ var runnerRoutesThatNameNoRunner = new Set([
11417
+ "runner.launch",
11418
+ "runner.list"
11419
+ ]);
11420
+ var runRoutesThatResolveOneRun = new Set([
11421
+ "run.diagnose",
11422
+ "run.get",
11423
+ "run.reattempt",
11424
+ "run.stop"
11425
+ ]);
11426
+ var recordFieldNouns = [
11427
+ ["sessionId", "session"],
11428
+ ["triggerId", "trigger"],
11429
+ ["issueId", "issue"],
11430
+ ["flowId", "flow"],
11431
+ ["filePath", "file"]
11432
+ ];
11433
+ function field(input, name) {
11434
+ if (typeof input !== "object" || input === null)
11435
+ return;
11436
+ const value = input[name];
11437
+ return typeof value === "string" ? value : undefined;
11438
+ }
11439
+ function notFoundSubject(contractName, input) {
11440
+ if (contractName.startsWith("runner.") && !runnerRoutesThatNameNoRunner.has(contractName)) {
11441
+ return { kind: "runner", runnerId: field(input, "id") };
11442
+ }
11443
+ if (runRoutesThatResolveOneRun.has(contractName)) {
11444
+ return { kind: "run", runId: field(input, "runId") };
11445
+ }
11446
+ for (const [name, noun] of recordFieldNouns) {
11447
+ const id = field(input, name);
11448
+ if (id !== undefined)
11449
+ return { id, kind: "record", noun };
11450
+ }
11451
+ if (field(input, "environmentId") !== undefined)
11452
+ return { kind: "environment" };
11453
+ return { kind: "other" };
11454
+ }
11455
+
11369
11456
  // src/core/publicApi/flagKind.ts
11370
11457
  function flagKind(schema) {
11371
11458
  if (schema.type === "string")
@@ -11536,6 +11623,42 @@ function applyWorkspaceId(inputSchema, input, workspaceId) {
11536
11623
  return { ...input, workspaceId };
11537
11624
  }
11538
11625
 
11626
+ // src/shell/platform/describeNotFound.ts
11627
+ var m = authMessages.errors.request;
11628
+ var saysNothingMore = (reason) => /^(\w+ )*not ?found\.?$/i.test(reason.trim());
11629
+ function describeNotFound(subject, noun, reason) {
11630
+ const explained = reason && !saysNothingMore(reason) ? reason : undefined;
11631
+ const say = (own) => {
11632
+ const body = [explained ? undefined : own.guess, own.guidance].filter((line) => line);
11633
+ return {
11634
+ error: explained ? `${explained} (HTTP 404)` : own.statement,
11635
+ exitCode: exitCodes.notFound,
11636
+ ...body.length > 0 ? { errorBody: body.join(`
11637
+ `) } : {}
11638
+ };
11639
+ };
11640
+ switch (subject?.kind) {
11641
+ case "runner":
11642
+ return say({
11643
+ guess: m.runnerIsGone,
11644
+ guidance: m.launchTheRunner(subject.runnerId),
11645
+ statement: m.notFound404Runner(subject.runnerId)
11646
+ });
11647
+ case "run":
11648
+ return say({
11649
+ guess: m.runIdMayBeRunnerLocal(subject.runId),
11650
+ statement: m.notFound404Run(subject.runId)
11651
+ });
11652
+ case "record":
11653
+ return say({ statement: m.notFound404Record(subject.noun, subject.id) });
11654
+ case "other":
11655
+ return say({ statement: m.notFound404(noun) });
11656
+ case "environment":
11657
+ case undefined:
11658
+ return say({ statement: m.notFound404Environment(noun) });
11659
+ }
11660
+ }
11661
+
11539
11662
  // src/shell/platform/parseErrorBody.ts
11540
11663
  var defaultMaxLength = 1024;
11541
11664
  function asRecord(value) {
@@ -11578,69 +11701,69 @@ function clip(message, maxLength) {
11578
11701
  }
11579
11702
 
11580
11703
  // src/shell/platform/describeErrors.ts
11581
- var m = authMessages.errors;
11704
+ var m2 = authMessages.errors;
11582
11705
  var inlineReasonMaxLength = 200;
11583
11706
  function describeIdentityError(err) {
11584
11707
  if (err.kind === "http") {
11585
11708
  if (err.status === 401 || err.status === 403) {
11586
11709
  return {
11587
- error: m.identity.invalidOrUnauthorized,
11710
+ error: m2.identity.invalidOrUnauthorized,
11588
11711
  ...err.status === 401 ? { exitCode: exitCodes.auth } : {}
11589
11712
  };
11590
11713
  }
11591
11714
  return {
11592
- error: m.identity.couldNotVerify(parseErrorBody(err.body, inlineReasonMaxLength), err.status)
11715
+ error: m2.identity.couldNotVerify(parseErrorBody(err.body, inlineReasonMaxLength), err.status)
11593
11716
  };
11594
11717
  }
11595
11718
  if (err.kind === "network") {
11596
- return { error: m.identity.couldNotVerifyNetwork(err.cause.message) };
11719
+ return { error: m2.identity.couldNotVerifyNetwork(err.cause.message) };
11597
11720
  }
11598
11721
  if (err.kind === "timeout") {
11599
- return { error: m.identity.timedOut(err.timeoutMs) };
11722
+ return { error: m2.identity.timedOut(err.timeoutMs) };
11600
11723
  }
11601
- return { error: m.identity.unexpectedFormat };
11724
+ return { error: m2.identity.unexpectedFormat };
11602
11725
  }
11603
- function describeRequestError(err, baseUrl, noun) {
11726
+ function describeRequestError(err, baseUrl, noun, notFound) {
11604
11727
  if (err.kind === "http") {
11605
11728
  const reason = parseErrorBody(err.body);
11606
11729
  const body = reason ? { errorBody: reason } : {};
11607
11730
  if (err.status === 401)
11608
11731
  return {
11609
- error: m.request.rejected401(noun),
11732
+ error: m2.request.rejected401(noun),
11610
11733
  exitCode: exitCodes.auth,
11611
11734
  ...body
11612
11735
  };
11613
11736
  if (err.status === 402)
11614
11737
  return {
11615
- error: m.request.rejected402(noun),
11738
+ error: m2.request.rejected402(noun),
11616
11739
  exitCode: exitCodes.payment,
11617
11740
  ...body
11618
11741
  };
11619
11742
  if (err.status === 403)
11620
- return { error: m.request.rejected403(noun), ...body };
11743
+ return { error: m2.request.rejected403(noun), ...body };
11621
11744
  if (err.status === 404)
11622
- return { error: m.request.notFound404(noun), ...body };
11623
- return { error: m.request.failedWithStatus(err.status, noun), ...body };
11745
+ return describeNotFound(notFound, noun, reason);
11746
+ return { error: m2.request.failedWithStatus(err.status, noun), ...body };
11624
11747
  }
11625
11748
  if (err.kind === "network") {
11626
- return { error: m.request.networkUnreachable(baseUrl, noun) };
11749
+ return { error: m2.request.networkUnreachable(baseUrl, noun) };
11627
11750
  }
11628
11751
  if (err.kind === "timeout") {
11629
- return { error: m.request.timedOut(err.timeoutMs, noun) };
11752
+ return { error: m2.request.timedOut(err.timeoutMs, noun) };
11630
11753
  }
11631
- return { error: m.request.unexpectedResponse(noun) };
11754
+ return { error: m2.request.unexpectedResponse(noun) };
11632
11755
  }
11633
11756
  function describeBundleDownloadError(err) {
11634
11757
  if (err.kind === "http") {
11635
11758
  if (err.status === 401 || err.status === 403)
11636
- return m.bundle.linkExpired;
11637
- return m.bundle.failedWithStatus(err.status);
11759
+ return m2.bundle.linkExpired;
11760
+ return m2.bundle.failedWithStatus(err.status);
11638
11761
  }
11639
11762
  if (err.kind === "network")
11640
- return m.bundle.networkUnreachable;
11763
+ return m2.bundle.networkUnreachable;
11641
11764
  if (err.kind === "timeout")
11642
- return m.bundle.timedOut(err.timeoutMs);
11643
- return m.bundle.malformed;
11765
+ return m2.bundle.timedOut(err.timeoutMs);
11766
+ return m2.bundle.malformed;
11644
11767
  }
11645
11768
  function describeTeamStorageDownloadError(path, err) {
11646
11769
  if (err.kind === "http") {
@@ -11667,12 +11790,16 @@ function callPublicApi(trpc, contract, input, options) {
11667
11790
  return trpc.mutation(path, input, contract.output, options);
11668
11791
  }
11669
11792
  function makeCallPublicApiMethod(trpc, deps, readBackoffMs) {
11670
- return async (contract, input, options) => requestWithRetry({
11671
- call: () => callPublicApi(trpc, contract, applyWorkspaceId(contract.input, input, deps.workspaceId), options),
11672
- backoffMs: contract.kind === "read" ? readBackoffMs : [],
11673
- describe: (err) => describeRequestError(err, deps.baseUrl, contract.name),
11674
- sleep: deps.sleep
11675
- });
11793
+ return async (contract, input, options) => {
11794
+ const sent = applyWorkspaceId(contract.input, input, deps.workspaceId);
11795
+ const subject = notFoundSubject(contract.name, sent);
11796
+ return requestWithRetry({
11797
+ call: () => callPublicApi(trpc, contract, sent, options),
11798
+ backoffMs: contract.kind === "read" ? readBackoffMs : [],
11799
+ describe: (err) => describeRequestError(err, deps.baseUrl, contract.name, subject),
11800
+ sleep: deps.sleep
11801
+ });
11802
+ };
11676
11803
  }
11677
11804
 
11678
11805
  // node_modules/superjson/dist/double-indexed-kv.js
@@ -13161,25 +13288,36 @@ function createSdkContext(options) {
13161
13288
  };
13162
13289
  }
13163
13290
 
13291
+ // src/domains/interactiveRunner/runnerRequestFailure.ts
13292
+ function runnerRequestFailure(failure, runner) {
13293
+ const fields = failureFields(failure);
13294
+ if (failure.exitCode !== exitCodes.notFound) {
13295
+ return { ...fields, exitCode: failure.exitCode ?? exitCodes.network };
13296
+ }
13297
+ const cameFrom = interactiveRunnerMessages.runnerIdCameFrom(runner.runnerId, runner.source);
13298
+ return {
13299
+ ...fields,
13300
+ errorBody: fields.errorBody ? `${fields.errorBody}
13301
+ ${cameFrom}` : cameFrom,
13302
+ exitCode: exitCodes.notFound
13303
+ };
13304
+ }
13305
+
13164
13306
  // src/domains/interactiveRunner/readJournal.ts
13165
13307
  var unreachableFailure = {
13166
13308
  error: interactiveRunnerMessages.runnerUnreachable,
13167
13309
  exitCode: exitCodes.network
13168
13310
  };
13169
- async function readJournal(ctx, runnerId, request) {
13311
+ async function readJournal(ctx, runner, request) {
13170
13312
  const result = await ctx.platformClient.callPublicApi(publicContractsV1.runner.readJournal, {
13171
- id: runnerId,
13313
+ id: runner.runnerId,
13172
13314
  stream: request.stream,
13173
13315
  ...request.runId === undefined ? {} : { runId: request.runId },
13174
13316
  ...request.sinceSequence === undefined ? {} : { sinceSequence: request.sinceSequence },
13175
13317
  ...request.tail === undefined ? {} : { tail: request.tail }
13176
13318
  }, runnerCallOptions);
13177
13319
  if (!result.ok) {
13178
- return {
13179
- ...failureFields(result),
13180
- exitCode: exitCodes.network,
13181
- type: "failed"
13182
- };
13320
+ return { ...runnerRequestFailure(result, runner), type: "failed" };
13183
13321
  }
13184
13322
  if (result.value.outcome === "failure") {
13185
13323
  result.value.failureReason;
@@ -13188,9 +13326,24 @@ async function readJournal(ctx, runnerId, request) {
13188
13326
  return { type: "read", value: result.value };
13189
13327
  }
13190
13328
 
13329
+ // src/runnerSdk/givenRunner.ts
13330
+ var givenRunner = (runnerId) => ({
13331
+ runnerId,
13332
+ source: "given"
13333
+ });
13334
+
13335
+ // src/runnerSdk/toSdkFailure.ts
13336
+ function toSdkFailure(failure) {
13337
+ return {
13338
+ error: failure.error,
13339
+ ...failure.errorBody ? { errorDetail: failure.errorBody } : {},
13340
+ ok: false
13341
+ };
13342
+ }
13343
+
13191
13344
  // src/runnerSdk/toSdkResult.ts
13192
13345
  function toSdkResult(result) {
13193
- return result.ok ? { ok: true, value: result.value } : { error: result.error, ok: false };
13346
+ return result.ok ? { ok: true, value: result.value } : toSdkFailure(result);
13194
13347
  }
13195
13348
 
13196
13349
  // src/runnerSdk/lifecycleVerbs.ts
@@ -13201,16 +13354,13 @@ function createLifecycleVerbs({ platformClient }) {
13201
13354
  async keepalive({
13202
13355
  runnerId
13203
13356
  }) {
13204
- const read = await readJournal(ctx, runnerId, {
13357
+ const read = await readJournal(ctx, givenRunner(runnerId), {
13205
13358
  stream: "run-status",
13206
13359
  tail: 1
13207
13360
  });
13208
13361
  if (read.type === "read")
13209
13362
  return { ok: true, value: { id: runnerId } };
13210
- return {
13211
- error: read.type === "unreachable" ? "The runner could not be reached." : read.error,
13212
- ok: false
13213
- };
13363
+ return read.type === "unreachable" ? { error: "The runner could not be reached.", ok: false } : toSdkFailure(read);
13214
13364
  },
13215
13365
  async launch({
13216
13366
  id,
@@ -13692,10 +13842,7 @@ async function sendRunFlowRequest(ctx, options, delta) {
13692
13842
  }, runnerCallOptions);
13693
13843
  if (!result.ok) {
13694
13844
  return {
13695
- failure: {
13696
- ...failureFields(result),
13697
- exitCode: result.exitCode ?? exitCodes.network
13698
- },
13845
+ failure: runnerRequestFailure(result, options.resolved),
13699
13846
  type: "failed"
13700
13847
  };
13701
13848
  }
@@ -13784,17 +13931,14 @@ function createRunVerbs({ deps, platformClient }) {
13784
13931
  stream,
13785
13932
  window
13786
13933
  }) {
13787
- const read = await readJournal(ctx, runnerId, {
13934
+ const read = await readJournal(ctx, givenRunner(runnerId), {
13788
13935
  stream,
13789
13936
  ...runFilter === "all-runs" ? {} : { runId: runFilter.runId },
13790
13937
  ...window === "newest" ? {} : ("tail" in window) ? { tail: window.tail } : { sinceSequence: window.sinceSequence }
13791
13938
  });
13792
13939
  if (read.type === "read")
13793
13940
  return { ok: true, value: read.value };
13794
- return {
13795
- error: read.type === "unreachable" ? "The runner could not be reached." : read.error,
13796
- ok: false
13797
- };
13941
+ return read.type === "unreachable" ? { error: "The runner could not be reached.", ok: false } : toSdkFailure(read);
13798
13942
  },
13799
13943
  async run({
13800
13944
  entryPointPath,
@@ -13816,11 +13960,11 @@ function createRunVerbs({ deps, platformClient }) {
13816
13960
  environment: prepared.environment,
13817
13961
  environmentId: prepared.environmentId,
13818
13962
  files: prepared.files,
13819
- resolved: { runnerId, type: "resolved" },
13963
+ resolved: { ...givenRunner(runnerId), type: "resolved" },
13820
13964
  selection: prepared.selection
13821
13965
  }, deps);
13822
13966
  if (!submitted.ok)
13823
- return { error: submitted.error, ok: false };
13967
+ return toSdkFailure(submitted);
13824
13968
  return {
13825
13969
  ok: true,
13826
13970
  value: {
@@ -13851,4 +13995,4 @@ export {
13851
13995
  createRunnerSdk
13852
13996
  };
13853
13997
 
13854
- //# debugId=395E9F8ECC554DB664756E2164756E21
13998
+ //# debugId=C50E262AB8A599ED64756E2164756E21
@@ -11,6 +11,12 @@ export type RunnerSdkOptions = {
11
11
  };
12
12
  export type SdkResult<Value> = {
13
13
  error: string;
14
+ /**
15
+ * What the platform said beyond the headline, and what to do about it:
16
+ * the reason a runner is gone, which of `--runner`, `QAWOLF_RUNNER_ID` or
17
+ * the stored default named it, the command that brings one back.
18
+ */
19
+ errorDetail?: string;
14
20
  ok: false;
15
21
  } | {
16
22
  ok: true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qawolf/cli",
3
- "version": "1.30.0",
3
+ "version": "1.32.0",
4
4
  "description": "Run and manage QA Wolf flows from the terminal, CI, or an AI agent",
5
5
  "keywords": [
6
6
  "automation",
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: qawolf-cli
3
- description: Manage QA Wolf through the qawolf CLI. Use when asked to create, update, or list coverage requests, bug reports, or maintenance reports; start a run of flows or tags on the QA Wolf platform or read a run's results; list, set, or delete environment variables; manage environments, flows, or tags; request automation of draft flows; run or list flows locally; authenticate; install the local runtime; or drive a live cloud browser (launch a runner, screenshot it, click and type on it, read its recorder) from a shell.
3
+ description: Manage QA Wolf through the qawolf CLI. Use when asked to create, update, or list coverage requests, bug reports, or maintenance reports; start a run of flows or tags on the QA Wolf platform or read a run's results; list, set, or delete environment variables; manage environments, flows, or tags; run or list flows locally; authenticate; install the local runtime; or drive a live cloud browser (launch a runner, screenshot it, click and type on it, read its recorder) from a shell.
4
4
  license: Apache-2.0
5
5
  compatibility: Requires the qawolf CLI on PATH. Install it from @qawolf/cli or use a standalone binary from GitHub Releases.
6
6
  ---
@@ -131,7 +131,6 @@ that `url`; never guess a route and never send a repository link in its place.
131
131
  | `qawolf auth logout` | local | Remove stored credentials |
132
132
  | `qawolf auth switch` | local | Choose which workspace to work in |
133
133
  | `qawolf auth whoami` | read | Show authentication status |
134
- | `qawolf automate` | write | Request automation for draft flows. First create a named local .flow.ts draft for every requested journey that does not already have a matching draft; never reuse a generic starter or placeholder. Each new draft must start with a JSDoc Goal: description, import flow from @qawolf/flows/web, and use export default flow(...); a comment-only file or direct test(...) call is not a valid draft. Commit and push all changes with Git to publish them, then list remote drafts to resolve every selected ID. Do not use patch to create or rename a selected flow. Finally make one automation request containing all requested flow IDs. |
135
134
  | `qawolf doctor` | local | Diagnose problems running flows locally |
136
135
  | `qawolf email find` | read | List the workspace's inbox, or its sent mail, newest first. Read a message body with email.get. |
137
136
  | `qawolf email get` | read | Read one email of the workspace, with its plain text and HTML bodies. Use it to pull a sign-in code or a verification link out of a message. |
@@ -25,6 +25,14 @@ run
25
25
  `runId` in the response is canonical and can differ from the id you asked for.
26
26
  Use the returned value for follow-up calls.
27
27
 
28
+ `run get` resolves platform runs only. A run id printed by `qawolf runner run`
29
+ belongs to that runner, so `run get` answers exit `8` and no such run on this
30
+ team. Read one of those with `qawolf runner events run-status --run <id>`.
31
+
32
+ A run that has been requested but not yet created answers exit `8` too, and
33
+ says it is still being created. That one clears on its own, so read the message
34
+ rather than the code before deciding whether to poll.
35
+
28
36
  Poll `status` until it reaches `passed`, `failed` or `canceled`. The other
29
37
  values mean the run is still going.
30
38
 
@@ -30,8 +30,8 @@ then launched without `--id` ends up with a pod it is not addressing. Pass
30
30
  And a runner id that is set is treated as found, whether or not anything is
31
31
  running under it. So exporting `QAWOLF_RUNNER_ID=agent-1` turns off the
32
32
  auto-launch described next: instead of starting `agent-1`, commands try to reach
33
- it and fail with exit code `4`, which reads as "retry" and never succeeds.
34
- Launch that id once yourself and the rest follows.
33
+ it and fail with exit code `8`, naming the id and saying the variable is what
34
+ chose it. Launch that id once yourself and the rest follows.
35
35
 
36
36
  And launching an id that differs from `QAWOLF_RUNNER_ID` prints a warning on
37
37
  stderr naming both ids: the variable still outranks the directory default, so
@@ -119,11 +119,14 @@ and says so on stderr. Everything else on this page waits for a run.
119
119
 
120
120
  Retry on the exit code, not on the message text:
121
121
 
122
- - `4` is usually transient. The screen is up but cannot serve this instant:
123
- restarting after a display-size change, or busy with another request. Retry in
124
- a second or two — but bound the retries, because `4` also covers a runner that
125
- was reaped after inactivity, which no amount of retrying brings back. If `4`
126
- persists past a few tries, relaunch the id.
122
+ - `4` is transient. The screen is up but cannot serve this instant: restarting
123
+ after a display-size change, or busy with another request. Retry in a second
124
+ or two, and bound the retries.
125
+ - `8` means there is no such runner. It was never launched, or it was
126
+ terminated, or it idled out. Retrying never brings one back, so stop and
127
+ launch the id or name one that is running. The message says which runner was
128
+ meant and whether `--runner`, `QAWOLF_RUNNER_ID` or this directory's stored
129
+ default chose it — read that line before you pick an id to launch.
127
130
  - `2` will not clear on its own. Either nothing has run on this runner yet, so
128
131
  run a flow, or the runner has no browser at all, so launch with
129
132
  `--name playwright` instead. The message says which.
@@ -224,7 +227,8 @@ what the page shows.
224
227
  One failure covers three causes, because a runner cannot tell them apart: no
225
228
  live page, no element matching the selector, no variable under that name. All
226
229
  three exit `2` and none clears by waiting, so read the message, which carries
227
- whatever the runner said. An unreachable runner exits `4` and is worth retrying.
230
+ whatever the runner said. An unreachable runner exits `4` and is worth retrying;
231
+ a runner that is not running at all exits `8` and is not.
228
232
 
229
233
  Use `inspect` before reaching for `exec`. Reading a value through a snippet
230
234
  means printing it and then fishing it back out of the `console` stream, which is