@qawolf/cli 1.36.0 → 1.38.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.
package/dist/cli.js CHANGED
@@ -16617,6 +16617,10 @@ var publicRunStatusValues = [
16617
16617
  "failed",
16618
16618
  "canceled"
16619
16619
  ];
16620
+ var publicRunSummaryStatusValues = [
16621
+ ...publicRunStatusValues,
16622
+ "superseded"
16623
+ ];
16620
16624
  var makeRunSummaryFields = (ids) => ({
16621
16625
  completedAt: exports_iso.datetime().optional().describe("When the run finished executing. Absent while queued or running, and also absent for a terminal run that never completed execution (e.g. every flow was canceled or skipped)."),
16622
16626
  createdAt: exports_iso.datetime(),
@@ -16628,7 +16632,11 @@ var makeRunSummaryFields = (ids) => ({
16628
16632
  }).describe("The branch and commit under test. The fields are present when a deploy notification started the run, and absent for runs started another way, for example manually or with run.create."),
16629
16633
  needsReview: boolean2().optional().describe("True once every flow has finished its attempts and at least one failed flow still has no diagnosis, including one whose investigation carried over to a later run. `status` stays `running` until that failure is diagnosed, so a caller that is not waiting for a diagnosis can stop polling here and read the run's failed flows."),
16630
16634
  runId: ids.run,
16631
- status: _enum(publicRunStatusValues).describe("Whole-run status, and the value to gate a pipeline on. Terminal statuses are passed, failed, and canceled; poll until one is reached. `failed` means the run found a bug that is still open and serious enough to block — a lower-priority bug, or one since resolved, reads `passed`, and the per-flow diagnoses are still in `flows`. The run stays `running` while any investigation is unresolved, including one that carried over to a later run, so a terminal status always means a settled answer. `needsReview` tells such a run apart from one that is still executing.")
16635
+ status: _enum(publicRunSummaryStatusValues).describe("Whole-run status, and the value to gate a pipeline on. Terminal statuses are passed, failed, canceled, and superseded; poll until one is reached. `failed` means the run found a bug that is still open and serious enough to block — a lower-priority bug, or one since resolved, reads `passed`, and the per-flow diagnoses are still in `flows`. The run stays `running` while any investigation is unresolved, including one that carried over to a later run, so a terminal status always means a settled answer, and `needsReview` tells such a run apart from one that is still executing. `superseded` means a newer run replaced this one before it finished: it is not a pass, this run will never change again, and the verdict belongs to the run in `supersededBy`."),
16636
+ supersededBy: object({
16637
+ runId: ids.run,
16638
+ url: url().describe("Absolute URL of the replacement run's page.")
16639
+ }).optional().describe("The run that replaced this one, present only when `status` is `superseded`. A newer deployment to the same branch and environment (and service, when the deployment named one) cancels the older run's unfinished flows and takes over. Deduplication ignores the commit, so the replacement may be testing a later commit than this run did. Only the direct replacement is named; that run can itself be superseded.")
16632
16640
  });
16633
16641
 
16634
16642
  // node_modules/@qawolf/api-contracts/dist/v1/deployment/didNotRunReason.js
@@ -16843,6 +16851,7 @@ var defaultIdSchemas = {
16843
16851
  issue: string2().min(1).describe("The id of the issue."),
16844
16852
  legacyTrigger: string2().min(1).describe("The id of the legacy trigger, from legacyTrigger.find."),
16845
16853
  run: string2().min(1).describe("The id of the run."),
16854
+ runAttempt: string2().min(1).describe("The id of the run attempt."),
16846
16855
  trigger: string2().min(1).describe("The id of the trigger."),
16847
16856
  workspace: string2().min(1).describe("The id of the workspace.")
16848
16857
  };
@@ -16894,6 +16903,72 @@ var makeCreateRunContract = (ids) => {
16894
16903
  };
16895
16904
  };
16896
16905
 
16906
+ // node_modules/@qawolf/api-contracts/dist/v1/run/listScreenshotComparisons.js
16907
+ var maxListedScreenshotAttempts = 20;
16908
+ var makeListScreenshotComparisonsContract = (ids) => {
16909
+ const input = object({
16910
+ flowIds: array(ids.flow).min(1).max(100).refine((flowIds) => new Set(flowIds).size === flowIds.length, "flowIds must be unique").optional().describe("Only list the comparisons of these flows. Each flow must have a result in the run. Omit to list every failed flow of the run."),
16911
+ runId: ids.run
16912
+ });
16913
+ const imageUrl = (image) => url().describe(`Signed URL of the full-size ${image} PNG. It stops working at expiresAt.`);
16914
+ const previewUrl = (image) => url().optional().describe(`Signed URL of a JPEG copy of the ${image} image, scaled down so its long edge is at most 1568 pixels. ` + "Fetch this one to look at the image: it is small enough to view inline, while the full-size PNG can be too large. " + "It stops working at expiresAt. Absent when QA Wolf could not make the copy, for example because the full-size image is gone.");
16915
+ const passedEnvironment = object({
16916
+ environmentId: ids.environment,
16917
+ lastPassedAt: exports_iso.datetime().describe("When the latest passing comparison in this environment ran.")
16918
+ });
16919
+ const passingElsewhere = discriminatedUnion("status", [
16920
+ object({
16921
+ status: literal("not-tracked").describe("QA Wolf does not record screenshot comparisons for this workspace, so it cannot tell.")
16922
+ }),
16923
+ object({
16924
+ environments: array(passedEnvironment).describe("Other environments where the current baseline passed this screenshot in the last 14 days, most recent first. " + "Empty when it passed nowhere else, or when the baseline no longer exists."),
16925
+ status: literal("tracked")
16926
+ })
16927
+ ]).describe("Whether the current baseline still passes in other environments. " + "A baseline that passes elsewhere points to a difference in this environment rather than an outdated baseline.");
16928
+ const comparison = object({
16929
+ actualPreviewUrl: previewUrl("actual"),
16930
+ actualUrl: imageUrl("actual (the screenshot the flow took)"),
16931
+ attemptCompletedAt: exports_iso.datetime().describe("When the run attempt that made the comparison finished."),
16932
+ baselineStatus: _enum([
16933
+ "unchanged",
16934
+ "accepted",
16935
+ "changed-since-run",
16936
+ "deleted",
16937
+ "unknown"
16938
+ ]).describe('How the current baseline relates to the expected image this comparison used. "unchanged": the baseline is still the expected image. ' + `"accepted": the baseline is now this comparison's actual screenshot, because someone accepted it, so a reattempt should pass. ` + '"changed-since-run": someone replaced the baseline with another image after this run, so rerun the flow before deciding anything. ' + '"deleted": the baseline no longer exists, and the next run will save its screenshot as the new baseline. ' + `"unknown": the run's copy of the expected image is gone, so QA Wolf cannot compare.`),
16939
+ comparisonId: string2().describe("Identifies this comparison: the run attempt id and the position of the comparison in that attempt."),
16940
+ diffPreviewUrl: previewUrl("diff"),
16941
+ diffUrl: imageUrl("diff (the pixels that differ are highlighted)"),
16942
+ expectedPreviewUrl: previewUrl("expected"),
16943
+ expectedUrl: imageUrl("expected (the baseline as it was when the flow ran)"),
16944
+ filePath: string2().optional().describe("The flow file that made the comparison."),
16945
+ flowId: ids.flow,
16946
+ lineNumber: number2().int().optional().describe("The line of filePath that made the comparison."),
16947
+ name: string2().describe("The screenshot name the flow passed to toHaveScreenshot. The baseline is stored as _screenshots_/<name>.png in team storage."),
16948
+ passingElsewhere,
16949
+ runAttemptId: string2(),
16950
+ similarity: number2().describe("How much of the actual image matches the expected image, in percent."),
16951
+ sizeMismatch: boolean2().describe("The actual and expected images have different dimensions. That fails the comparison whatever the pixels show, and similarity reads 0.")
16952
+ });
16953
+ const output = object({
16954
+ comparisons: array(comparison).describe(`Each screenshot comparison that failed its flow, from the ${maxListedScreenshotAttempts} most recent failed attempts of the selected flows, oldest attempt first.`),
16955
+ expiresAt: exports_iso.datetime().describe("When the image URLs stop working. Call run.listScreenshotComparisons again for fresh ones after it."),
16956
+ truncated: boolean2().describe(`True when the selected flows have more than ${maxListedScreenshotAttempts} failed attempts. Only the ${maxListedScreenshotAttempts} most recent are listed, and the older attempts cannot be listed.`)
16957
+ });
16958
+ return {
16959
+ annotations: {
16960
+ destructiveHint: false,
16961
+ openWorldHint: false,
16962
+ readOnlyHint: false
16963
+ },
16964
+ description: "List the screenshot comparisons that failed a run's flows, with the expected, actual and diff images of each. " + "Only comparisons that caused a failure are listed: a screenshot that differs within tolerance, or that differs on a line that did not fail, is left out. " + "Each comparison tells whether its baseline changed since the run and whether the baseline still passes in other environments. " + "The image URLs expire after a few minutes; fetch the preview URLs to look at the images. " + "The first call for an image makes its missing JPEG preview and keeps it in team storage, so later calls reuse it.",
16965
+ input,
16966
+ kind: "read",
16967
+ name: "run.listScreenshotComparisons",
16968
+ output
16969
+ };
16970
+ };
16971
+
16897
16972
  // node_modules/@qawolf/api-contracts/dist/v1/runner/actionSequence.js
16898
16973
  var maxActionsPerRequest = 10;
16899
16974
  var screenshotModes = ["none", "final", "each"];
@@ -18959,11 +19034,22 @@ var makeGetIssueContract = (ids) => {
18959
19034
  var investigationFindingStateDescription = 'What the investigation concluded about the cause. Known values: "investigating", "fixed", "fix-proposed", "reported", "report-proposed", "flake", "question" and "dismissed". New values can appear.';
18960
19035
  var makeInvestigationFindingSchema = (ids) => object({
18961
19036
  actual: string2().optional(),
18962
- commitHash: string2().optional(),
19037
+ commitHash: string2().optional().describe("The commit of a commit fix. Same as `fix.commitHash`."),
18963
19038
  decidedBy: _enum(["agent", "user"]).optional().describe("Who decided: the AI on its own, or a person who approved or dismissed it. Absent until someone decides."),
18964
19039
  disputeReason: string2().optional().describe("Why a person disputed what the AI did. Present on a proposal the AI made again after that dispute."),
18965
19040
  expected: string2().optional(),
18966
19041
  findingId: string2(),
19042
+ fix: discriminatedUnion("type", [
19043
+ object({
19044
+ commitHash: string2().describe("The commit that changes the flow code."),
19045
+ type: literal("commit")
19046
+ }),
19047
+ object({
19048
+ baselineChangeId: string2().optional().describe('The baseline change that made the new screenshot the baseline, on a "fixed" finding. `run restoreScreenshotBaseline` undoes it.'),
19049
+ comparisonId: string2().describe("The failed screenshot comparison whose new screenshot becomes the baseline, as `run listScreenshotComparisons` names it."),
19050
+ type: literal("baseline")
19051
+ })
19052
+ ]).optional().describe('The fix of a "fixed" or "fix-proposed" finding: a commit to the flow code, or a new screenshot baseline.'),
18967
19053
  flowIds: array(ids.flow),
18968
19054
  headline: string2().optional(),
18969
19055
  issueId: ids.issue.optional(),
@@ -18974,8 +19060,9 @@ var makeInvestigationFindingSchema = (ids) => object({
18974
19060
  answers: array(object({
18975
19061
  description: string2().optional(),
18976
19062
  title: string2(),
18977
- value: string2().describe('What choosing this answer means. Known values: "apply", "dismiss", "report", "maintenance" and "bug". New values can appear.')
19063
+ value: string2().describe('What choosing this answer means. Known values: "apply", "dismiss", "report", "maintenance", "bug" and "baseline". New values can appear.')
18978
19064
  })),
19065
+ comparisonId: string2().optional().describe('The failed screenshot comparison a screenshot question is about, as `run listScreenshotComparisons` names it. Its answers are "bug" (the old screenshot is right), "maintenance" (both are right) and "baseline" (the new screenshot is right).'),
18979
19066
  text: string2()
18980
19067
  }).optional().describe("The question waiting for a person, with the answers offered."),
18981
19068
  response: object({
@@ -18995,6 +19082,7 @@ var makeInvestigationFindingSchema = (ids) => object({
18995
19082
  var makeGetInvestigationContract = (ids) => {
18996
19083
  const input = object({ runId: ids.run });
18997
19084
  const output = resource({
19085
+ baselineChanges: _enum(["automatic", "needs-approval"]).describe('How the investigation can make a new screenshot the baseline. "automatic": it can accept a baseline on its own. ' + '"needs-approval": it proposes the baseline on a fix-proposed finding, and accepts it only after a person applies the proposal.'),
18998
19086
  findings: array(makeInvestigationFindingSchema(ids)),
18999
19087
  runId: ids.run,
19000
19088
  sessionId: ids.chatSession.optional().describe("The investigation session. Absent when the run has none."),
@@ -19045,7 +19133,7 @@ var makeAddFlowsToIssueContract = (ids) => {
19045
19133
  // node_modules/@qawolf/api-contracts/dist/v1/issue/create.js
19046
19134
  var makeCreateIssueContract = (ids) => {
19047
19135
  const commonFields = {
19048
- description: string2().optional().describe("The issue description as plain text; each line becomes a paragraph. Markdown is not parsed."),
19136
+ description: string2().optional().describe("The issue description in Markdown, which is stored as rich text. Text without Markdown is stored as written, one paragraph per line."),
19049
19137
  name: string2().trim().min(1).max(255),
19050
19138
  priority: issuePrioritySchema.optional().describe('Defaults to "unprioritized" for bug reports and coverage requests.'),
19051
19139
  workspaceId: ids.workspace.optional().describe("The workspace to create the issue in. Required when authenticating with an organization or user API key.")
@@ -19088,7 +19176,8 @@ var makeCreateIssueContract = (ids) => {
19088
19176
  // node_modules/@qawolf/api-contracts/dist/v1/issue/removeFlows.js
19089
19177
  var makeRemoveFlowsFromIssueContract = (ids) => {
19090
19178
  const input = object({
19091
- flowIds: array(ids.flow).min(1).max(300).refine((flowIds) => new Set(flowIds).size === flowIds.length, "flowIds must be unique").describe("The flows the coverage request no longer covers."),
19179
+ environmentId: ids.environment.optional().describe("The environment whose bug or maintenance reproductions to remove. Required when a selected flow is linked in more than one environment."),
19180
+ flowIds: array(ids.flow).min(1).max(300).refine((flowIds) => new Set(flowIds).size === flowIds.length, "flowIds must be unique").describe("The flows to remove from the issue."),
19092
19181
  issueId: ids.issue
19093
19182
  });
19094
19183
  const output = object({
@@ -19097,10 +19186,10 @@ var makeRemoveFlowsFromIssueContract = (ids) => {
19097
19186
  return {
19098
19187
  annotations: {
19099
19188
  destructiveHint: true,
19100
- openWorldHint: false,
19189
+ openWorldHint: true,
19101
19190
  readOnlyHint: false
19102
19191
  },
19103
- description: "Remove flows from a coverage request owned by the caller's team. " + "Flows the request does not cover are left alone. " + "Bug and maintenance reports link to flows through the runs that reproduce them, so their flows cannot be set directly.",
19192
+ description: "Remove flows from a coverage request or remove bug/maintenance reproductions in an environment. " + "Unlinked flows are left alone. Reports auto-close when no active reproductions remain and auto-close is enabled. " + "For reports, omit environmentId only when each selected flow is linked in at most one environment.",
19104
19193
  input,
19105
19194
  kind: "write",
19106
19195
  name: "issue.removeFlows",
@@ -19110,7 +19199,7 @@ var makeRemoveFlowsFromIssueContract = (ids) => {
19110
19199
 
19111
19200
  // node_modules/@qawolf/api-contracts/dist/v1/issue/update.js
19112
19201
  var makeUpdateIssueContract = (ids) => {
19113
- const descriptionSchema = string2().describe("The issue description as plain text; each line becomes a paragraph. Markdown is not parsed.");
19202
+ const descriptionSchema = string2().describe("The issue description in Markdown, which is stored as rich text. Text without Markdown is stored as written, one paragraph per line.");
19114
19203
  const nameSchema = string2().trim().min(1).max(255);
19115
19204
  const input = object({
19116
19205
  description: descriptionSchema.optional(),
@@ -19260,6 +19349,49 @@ var makeLegacyTriggerContracts = (ids) => ({
19260
19349
  resume: makeResumeLegacyTriggerContract(ids)
19261
19350
  });
19262
19351
 
19352
+ // node_modules/@qawolf/api-contracts/dist/v1/run/investigationSessionId.js
19353
+ var investigationSessionId = string2().trim().min(1).max(100).optional();
19354
+
19355
+ // node_modules/@qawolf/api-contracts/dist/v1/run/acceptScreenshotBaseline.js
19356
+ var makeAcceptScreenshotBaselineContract = (ids) => {
19357
+ const input = object({
19358
+ chatSessionId: investigationSessionId.describe("The chat session of the investigation that accepts the baseline. A workspace or organization API key acts as an automation, so it must send the session that investigates the run. A person's call ignores it."),
19359
+ comparisonId: string2().trim().min(1).max(100).describe("The failed screenshot comparison whose new screenshot becomes the baseline, as `run.listScreenshotComparisons` names it."),
19360
+ runId: ids.run
19361
+ });
19362
+ const output = discriminatedUnion("outcome", [
19363
+ object({
19364
+ baselineChangeId: string2().describe("The baseline change. Record it on the fixed finding, and pass it to `run.restoreScreenshotBaseline` with this runId to put the old baseline back."),
19365
+ name: string2().describe("The screenshot name whose baseline changed."),
19366
+ outcome: literal("accepted").describe("The new screenshot is now the baseline. The old baseline is kept, so the change can be restored.")
19367
+ }),
19368
+ object({
19369
+ outcome: literal("baseline-changed").describe("The baseline is no longer the one the run compared with: someone changed or deleted it since. Nothing changed. List the comparisons again to see the current baseline.")
19370
+ }),
19371
+ object({
19372
+ outcome: literal("needs-approval").describe("Only a person can approve this change. Nothing changed. Propose the new baseline on a fix-proposed finding and wait for a person to apply it.")
19373
+ }),
19374
+ object({
19375
+ outcome: literal("not-investigation-session").describe("An API key that is not a person's can change a baseline only as the investigation of the run, and chatSessionId is missing or is not the session that investigates the run. Nothing changed.")
19376
+ }),
19377
+ object({
19378
+ outcome: literal("nothing-to-accept").describe("The comparison did not fail, or has no new screenshot to accept. Nothing changed.")
19379
+ })
19380
+ ]);
19381
+ return {
19382
+ annotations: {
19383
+ destructiveHint: true,
19384
+ openWorldHint: false,
19385
+ readOnlyHint: false
19386
+ },
19387
+ description: "Make the new screenshot of a failed screenshot comparison the baseline, so later runs compare with it. " + "Use it only when the difference is an intended change of the app, not a bug, noise, or a page that was not ready. " + "Every later run of every environment of the workspace compares with the new baseline. " + "Refuses without changing anything when the baseline changed since the run.",
19388
+ input,
19389
+ kind: "write",
19390
+ name: "run.acceptScreenshotBaseline",
19391
+ output
19392
+ };
19393
+ };
19394
+
19263
19395
  // node_modules/@qawolf/api-contracts/dist/v1/run/diagnose.js
19264
19396
  var makeDiagnoseRunContract = (ids) => {
19265
19397
  const input = object({
@@ -19276,7 +19408,7 @@ var makeDiagnoseRunContract = (ids) => {
19276
19408
  openWorldHint: true,
19277
19409
  readOnlyHint: false
19278
19410
  },
19279
- description: "Diagnose failed flows in a run as reproductions of a bug or maintenance report owned by the caller's team. " + "The issue's type selects the diagnosis. " + "Each flow must have failed in the run. " + "A flow that is already diagnosed moves to this issue. " + "The diagnosis appears on the run, and the reproduction appears under the issue's reproductions. " + "Coverage requests cannot be diagnosed; use issue.addFlows to cover flows instead.",
19411
+ description: "Diagnose failed flows in a run as reproductions of a bug or maintenance report owned by the caller's team. " + "The issue's type selects the diagnosis. " + "Each flow must have failed in the run. " + "A flow that is already diagnosed in the run moves to this issue, and may stay linked to other open reports. " + "The diagnosis appears on the run, and the reproduction appears under the issue's reproductions. " + "Coverage requests cannot be diagnosed; use issue.addFlows to cover flows instead.",
19280
19412
  input,
19281
19413
  kind: "write",
19282
19414
  name: "run.diagnose",
@@ -19294,9 +19426,11 @@ var makeFindRunsContract = (ids) => {
19294
19426
  });
19295
19427
  const output = object({
19296
19428
  nextCursor: nextCursorSchema,
19297
- runs: array(resource(makeRunSummaryFields(ids), {
19298
- urlFieldDescription: "Absolute URL of the run page."
19299
- })).describe("The environment's runs, newest first. Per-flow results are available via run.get.")
19429
+ runs: array(resource({
19430
+ ...makeRunSummaryFields(ids),
19431
+ failedAttemptCount: number2().int().nonnegative().optional(),
19432
+ hasFailedAttempts: boolean2().optional()
19433
+ }, { urlFieldDescription: "Absolute URL of the run page." })).describe("The environment's runs, newest first. Per-flow results are available via run.get.")
19300
19434
  });
19301
19435
  return {
19302
19436
  annotations: {
@@ -19304,7 +19438,7 @@ var makeFindRunsContract = (ids) => {
19304
19438
  openWorldHint: false,
19305
19439
  readOnlyHint: true
19306
19440
  },
19307
- description: "List an environment's recent runs, newest first.",
19441
+ description: "List an environment's recent runs, newest first, including failed-attempt discovery metadata.",
19308
19442
  input,
19309
19443
  kind: "read",
19310
19444
  name: "run.find",
@@ -19312,6 +19446,22 @@ var makeFindRunsContract = (ids) => {
19312
19446
  };
19313
19447
  };
19314
19448
 
19449
+ // node_modules/@qawolf/api-contracts/dist/v1/run/flowFailure.js
19450
+ var makeFlowFailureSchema = (ids) => {
19451
+ const diagnosis = object({
19452
+ issueId: ids.issue,
19453
+ type: _enum(["bug", "maintenance"])
19454
+ }).optional().describe("QA Wolf's investigation verdict for the failure: `bug` means the " + "application is broken, `maintenance` means the test needed an " + "update and the failure does not indicate an application problem. " + "Absent until the investigation reaches a verdict, and absent when " + "the flow was opted out of investigation. Pass issueId to issue.get " + "for details.");
19455
+ const optedOutOfInvestigation = object({
19456
+ by: _enum(["user", "system"]).describe("Who stopped the investigation: `user` is a person, in the app or " + "with a user API key, or the workspace's automation user for a " + "team or organization API key, for example through " + "run.optOutOfInvestigation; `system` is QA Wolf, when the " + "investigation expired.")
19457
+ }).optional().describe('Present when the failure was marked "do not investigate" (DNI): ' + "it will get no bug or maintenance verdict.");
19458
+ return object({
19459
+ diagnosis,
19460
+ error: string2(),
19461
+ optedOutOfInvestigation
19462
+ });
19463
+ };
19464
+
19315
19465
  // node_modules/@qawolf/api-contracts/dist/v1/run/get.js
19316
19466
  var makeGetRunContract = (ids) => {
19317
19467
  const flowStatus = _enum(publicRunStatusValues);
@@ -19328,6 +19478,7 @@ var makeGetRunContract = (ids) => {
19328
19478
  const automatedAttempt = discriminatedUnion("status", [
19329
19479
  object({
19330
19480
  ...artifactUrls,
19481
+ attemptId: ids.runAttempt.optional(),
19331
19482
  completedAt: exports_iso.datetime(),
19332
19483
  kind: automatedKind,
19333
19484
  startedAt: exports_iso.datetime(),
@@ -19335,12 +19486,14 @@ var makeGetRunContract = (ids) => {
19335
19486
  }),
19336
19487
  object({
19337
19488
  ...artifactUrls,
19489
+ attemptId: ids.runAttempt.optional(),
19338
19490
  completedAt: exports_iso.datetime(),
19339
19491
  kind: automatedKind,
19340
19492
  startedAt: exports_iso.datetime().optional().describe("Absent when the attempt failed before it could start."),
19341
19493
  status: literal("failed")
19342
19494
  }),
19343
19495
  object({
19496
+ attemptId: ids.runAttempt.optional(),
19344
19497
  completedAt: exports_iso.datetime().optional(),
19345
19498
  kind: automatedKind,
19346
19499
  startedAt: exports_iso.datetime().optional(),
@@ -19348,6 +19501,7 @@ var makeGetRunContract = (ids) => {
19348
19501
  })
19349
19502
  ]);
19350
19503
  const manualAttempt = object({
19504
+ attemptId: ids.runAttempt.optional(),
19351
19505
  completedAt: exports_iso.datetime(),
19352
19506
  kind: literal("manual").describe("The attempt ran by hand in the Wolf Browser."),
19353
19507
  startedAt: exports_iso.datetime(),
@@ -19355,16 +19509,9 @@ var makeGetRunContract = (ids) => {
19355
19509
  });
19356
19510
  const attempt = union([automatedAttempt, manualAttempt]);
19357
19511
  const attempts = array(attempt).optional().describe("The flow's finished execution attempts, oldest first, including " + "manual Wolf Browser attempts. Present once at least one attempt has " + "finished, so a flow that passed after retries also lists its failed " + "attempts. Artifact URLs appear only on automated attempts that " + "reached a verdict, stay valid for at least a day (call run.get " + "again for fresh ones), and can return 404 when the attempt did not " + "produce that artifact.");
19358
- const diagnosis = object({
19359
- issueId: ids.issue,
19360
- type: _enum(["bug", "maintenance"])
19361
- }).optional().describe("QA Wolf's investigation verdict for the failure: `bug` means the " + "application is broken, `maintenance` means the test needed an " + "update and the failure does not indicate an application problem. " + "Absent until the investigation reaches a verdict. Pass issueId to " + "issue.get for details.");
19362
19512
  const failedFlow = object({
19363
19513
  attempts,
19364
- failure: object({
19365
- diagnosis,
19366
- error: string2()
19367
- }),
19514
+ failure: makeFlowFailureSchema(ids),
19368
19515
  flowId: ids.flow,
19369
19516
  name: string2(),
19370
19517
  status: literal("failed")
@@ -19396,6 +19543,107 @@ var makeGetRunContract = (ids) => {
19396
19543
  };
19397
19544
  };
19398
19545
 
19546
+ // node_modules/@qawolf/api-contracts/dist/v1/run/getAttemptArtifacts.js
19547
+ var makeGetRunAttemptArtifactsContract = (ids) => {
19548
+ const input = object({ attemptId: ids.runAttempt });
19549
+ const attemptStatus = _enum([
19550
+ "passed",
19551
+ "failed",
19552
+ "canceled",
19553
+ "aborted",
19554
+ "skipped"
19555
+ ]);
19556
+ const attemptNeighbor = object({
19557
+ attemptId: ids.runAttempt,
19558
+ status: attemptStatus
19559
+ });
19560
+ const response = object({
19561
+ flowId: ids.flow,
19562
+ flowStatus: _enum(publicRunStatusValues),
19563
+ retryContext: object({
19564
+ currentOrdinal: number2().int().min(1),
19565
+ next: attemptNeighbor.optional(),
19566
+ previous: attemptNeighbor.optional(),
19567
+ total: number2().int().min(1)
19568
+ }),
19569
+ runId: ids.run,
19570
+ runStatus: _enum(publicRunSummaryStatusValues)
19571
+ });
19572
+ const attempt = object({
19573
+ attemptId: ids.runAttempt,
19574
+ completedAt: exports_iso.datetime().optional(),
19575
+ createdAt: exports_iso.datetime(),
19576
+ error: string2().optional(),
19577
+ startedAt: exports_iso.datetime().optional()
19578
+ });
19579
+ const output = discriminatedUnion("artifactStatus", [
19580
+ response.extend({
19581
+ artifacts: object({
19582
+ logsUrl: url().optional(),
19583
+ traceUrl: url().optional(),
19584
+ videoUrl: url().optional()
19585
+ }),
19586
+ artifactStatus: literal("signed"),
19587
+ attempt: attempt.extend({
19588
+ kind: literal("automated"),
19589
+ status: attemptStatus
19590
+ })
19591
+ }),
19592
+ response.extend({
19593
+ artifacts: object({}).strict(),
19594
+ artifactStatus: literal("signing-failed"),
19595
+ attempt: attempt.extend({
19596
+ kind: literal("automated"),
19597
+ status: attemptStatus
19598
+ })
19599
+ }),
19600
+ response.extend({
19601
+ artifacts: object({}).strict(),
19602
+ artifactStatus: literal("not-captured"),
19603
+ attempt: attempt.extend({
19604
+ kind: literal("manual"),
19605
+ status: literal("passed")
19606
+ })
19607
+ })
19608
+ ]);
19609
+ return {
19610
+ annotations: {
19611
+ destructiveHint: false,
19612
+ openWorldHint: false,
19613
+ readOnlyHint: true
19614
+ },
19615
+ description: "Get metadata and signed artifact URLs for one finished run attempt.",
19616
+ input,
19617
+ kind: "read",
19618
+ name: "run.getAttemptArtifacts",
19619
+ output
19620
+ };
19621
+ };
19622
+
19623
+ // node_modules/@qawolf/api-contracts/dist/v1/run/optOutOfInvestigation.js
19624
+ var makeOptOutOfInvestigationRunContract = (ids) => {
19625
+ const input = object({
19626
+ flowIds: array(ids.flow).min(1).max(100).refine((flowIds) => new Set(flowIds).size === flowIds.length, "flowIds must be unique").describe("The failed flows to stop investigating. Every named flow must be " + "eligible or the whole request is rejected."),
19627
+ runId: ids.run
19628
+ });
19629
+ const output = resource({
19630
+ optedOutFlowIds: array(ids.flow).describe("The flows that no longer need investigation."),
19631
+ runId: ids.run
19632
+ }, { urlFieldDescription: "Absolute URL of the run page." });
19633
+ return {
19634
+ annotations: {
19635
+ destructiveHint: true,
19636
+ openWorldHint: true,
19637
+ readOnlyHint: false
19638
+ },
19639
+ description: 'Mark failed flows in a run as "do not investigate" (DNI): the failures need no bug or maintenance report. ' + "Each flow must have failed in the run and still be under investigation. " + "A flow that already has a diagnosis cannot be opted out. " + "A team or organization key records the opt-out as the workspace's automation user. " + "To link a failure to a report instead, use run.diagnose.",
19640
+ input,
19641
+ kind: "write",
19642
+ name: "run.optOutOfInvestigation",
19643
+ output
19644
+ };
19645
+ };
19646
+
19399
19647
  // node_modules/@qawolf/api-contracts/dist/v1/run/reattempt.js
19400
19648
  var makeReattemptRunContract = (ids) => {
19401
19649
  const input = object({
@@ -19420,6 +19668,45 @@ var makeReattemptRunContract = (ids) => {
19420
19668
  };
19421
19669
  };
19422
19670
 
19671
+ // node_modules/@qawolf/api-contracts/dist/v1/run/restoreScreenshotBaseline.js
19672
+ var makeRestoreScreenshotBaselineContract = (ids) => {
19673
+ const input = object({
19674
+ baselineChangeId: string2().trim().min(1).max(100).describe("The baseline change to undo, as `run.acceptScreenshotBaseline` returned it for this run."),
19675
+ chatSessionId: investigationSessionId.describe("The chat session of the investigation that restores the baseline. A workspace or organization API key acts as an automation, so it must send the session that investigates the run. A person's call ignores it."),
19676
+ runId: ids.run
19677
+ });
19678
+ const output = discriminatedUnion("outcome", [
19679
+ object({
19680
+ name: string2().describe("The screenshot name whose baseline changed."),
19681
+ outcome: literal("restored").describe("The baseline the change replaced is the baseline again.")
19682
+ }),
19683
+ object({
19684
+ outcome: literal("baseline-changed").describe("The baseline changed again after this change, so restoring it would undo someone else's change. Nothing changed.")
19685
+ }),
19686
+ object({
19687
+ outcome: literal("needs-dispute").describe("Only a person can restore this change, unless a person disputed the finding that made it. Nothing changed.")
19688
+ }),
19689
+ object({
19690
+ outcome: literal("not-investigation-session").describe("An API key that is not a person's can change a baseline only as the investigation of the run, and chatSessionId is missing or is not the session that investigates the run. Nothing changed.")
19691
+ }),
19692
+ object({
19693
+ outcome: literal("nothing-to-restore").describe("The change was already restored, the name had no baseline before it, or the kept copy of the old baseline is missing or is not the baseline the change replaced. Nothing changed.")
19694
+ })
19695
+ ]);
19696
+ return {
19697
+ annotations: {
19698
+ destructiveHint: true,
19699
+ openWorldHint: false,
19700
+ readOnlyHint: false
19701
+ },
19702
+ description: "Undo a screenshot baseline change that accepted a failed comparison of the run: put back the baseline it replaced, so later runs compare with the old baseline again. " + "Use it when a person disputes a baseline the AI accepted. " + "Refuses without changing anything when the baseline changed again since.",
19703
+ input,
19704
+ kind: "write",
19705
+ name: "run.restoreScreenshotBaseline",
19706
+ output
19707
+ };
19708
+ };
19709
+
19423
19710
  // node_modules/@qawolf/api-contracts/dist/v1/run/stop.js
19424
19711
  var makeStopRunContract = (ids) => {
19425
19712
  const input = object({ runId: ids.run });
@@ -19740,7 +20027,7 @@ var makeRunFlowOnRunnerContract = (ids) => {
19740
20027
  env: runEnvironmentSchema.optional().describe("Environment variables to make available to the run."),
19741
20028
  environmentId: ids.environmentRef.optional().describe("A QA Wolf environment whose variables the run is given, by id or alias. QA Wolf reads and decrypts them itself, so they never travel in the request and the count and length limits on `env` do not apply to them. Send this or `env`, not both."),
19742
20029
  files: runFilesSchema.describe("Every file the run needs keyed by its path — the flow file, everything it imports, package.json and tsconfig.json. A runner holds no copy of your project, so what runs is exactly what is sent here."),
19743
- flowId: ids.flow.optional().describe("The QA Wolf flow this run is for. The run then sees it as `QAWOLF_WORKFLOW_ID`, the same value a platform run of that flow sees, so fixtures named and cleaned up by it match across both. Omit it when the flow is not in QA Wolf yet, and the run has no `QAWOLF_WORKFLOW_ID`."),
20030
+ flowId: ids.flow.optional().describe("The QA Wolf flow this run is for. The run then sees it as `QAWOLF_WORKFLOW_ID`, the same value a platform run of that flow sees, so fixtures named and cleaned up by it match across both. Omitted, the flow whose file is at `entryPointPath` on the named environment's flow-code branch stands in; a file not in QA Wolf yet, or a run given no environment, leaves the run with no `QAWOLF_WORKFLOW_ID`."),
19744
20031
  id: runnerIdSchema.describe("Id of the runner to run the flow on."),
19745
20032
  selection: runSelectionSchema.optional().describe("Run only these lines, inside the page the runner is already on, instead of running the whole flow from a fresh browser. Omit to run the whole entry point."),
19746
20033
  unchangedFiles: unchangedFilesSchema.optional().describe("Files this runner already holds from an earlier run, keyed by path with a hex SHA-256 of the content it should hold. Send this to make `files` only what changed. The runner refuses with `needs-full-sync` if it holds none of a referenced path, so it never runs something other than what was asked for. Omit it to send every file."),
@@ -19956,7 +20243,11 @@ var makeListTagsContract = (ids) => {
19956
20243
 
19957
20244
  // node_modules/@qawolf/api-contracts/dist/v1/index.js
19958
20245
  var makeContractsV1 = (ids) => {
19959
- const resolvedIds = { ...defaultIdSchemas, ...ids };
20246
+ const resolvedIds = {
20247
+ ...defaultIdSchemas,
20248
+ ...ids,
20249
+ runAttempt: ids.runAttempt ?? defaultIdSchemas.runAttempt
20250
+ };
19960
20251
  return {
19961
20252
  agent: {
19962
20253
  get: makeAgentGetContract(resolvedIds),
@@ -20003,11 +20294,16 @@ var makeContractsV1 = (ids) => {
20003
20294
  },
20004
20295
  legacyTrigger: makeLegacyTriggerContracts(resolvedIds),
20005
20296
  run: {
20297
+ acceptScreenshotBaseline: makeAcceptScreenshotBaselineContract(resolvedIds),
20006
20298
  create: makeCreateRunContract(resolvedIds),
20007
20299
  diagnose: makeDiagnoseRunContract(resolvedIds),
20008
20300
  find: makeFindRunsContract(resolvedIds),
20009
20301
  get: makeGetRunContract(resolvedIds),
20302
+ getAttemptArtifacts: makeGetRunAttemptArtifactsContract(resolvedIds),
20303
+ listScreenshotComparisons: makeListScreenshotComparisonsContract(resolvedIds),
20304
+ optOutOfInvestigation: makeOptOutOfInvestigationRunContract(resolvedIds),
20010
20305
  reattempt: makeReattemptRunContract(resolvedIds),
20306
+ restoreScreenshotBaseline: makeRestoreScreenshotBaselineContract(resolvedIds),
20011
20307
  stop: makeStopRunContract(resolvedIds)
20012
20308
  },
20013
20309
  runner: makeRunnerContracts(resolvedIds),
@@ -22524,7 +22820,7 @@ function startUpdateCheck(deps) {
22524
22820
  // package.json
22525
22821
  var package_default = {
22526
22822
  name: "@qawolf/cli",
22527
- version: "1.36.0",
22823
+ version: "1.38.0",
22528
22824
  description: "Run and manage QA Wolf flows from the terminal, CI, or an AI agent",
22529
22825
  keywords: [
22530
22826
  "automation",
@@ -22595,7 +22891,7 @@ var package_default = {
22595
22891
  "@clack/prompts": "1.5.1",
22596
22892
  "@napi-rs/keyring": "1.3.0",
22597
22893
  "@oxc-node/core": "0.1.0",
22598
- "@qawolf/api-contracts": "0.73.0",
22894
+ "@qawolf/api-contracts": "0.77.0",
22599
22895
  "@qawolf/emails": "1.1.1",
22600
22896
  "@qawolf/flow-targets": "1.0.0",
22601
22897
  "@qawolf/flows": "0.1.4",
@@ -38846,4 +39142,4 @@ createProgram({ signals }).parseAsync().catch(() => {
38846
39142
  process.exitCode = 1;
38847
39143
  }).finally(() => exitWhenIdle(typeof process.exitCode === "number" ? process.exitCode : 0));
38848
39144
 
38849
- //# debugId=9A39ADF2531708B264756E2164756E21
39145
+ //# debugId=4535AB8728B9D01A64756E2164756E21
@@ -6979,6 +6979,10 @@ var publicRunStatusValues = [
6979
6979
  "failed",
6980
6980
  "canceled"
6981
6981
  ];
6982
+ var publicRunSummaryStatusValues = [
6983
+ ...publicRunStatusValues,
6984
+ "superseded"
6985
+ ];
6982
6986
  var makeRunSummaryFields = (ids) => ({
6983
6987
  completedAt: exports_iso.datetime().optional().describe("When the run finished executing. Absent while queued or running, and also absent for a terminal run that never completed execution (e.g. every flow was canceled or skipped)."),
6984
6988
  createdAt: exports_iso.datetime(),
@@ -6990,7 +6994,11 @@ var makeRunSummaryFields = (ids) => ({
6990
6994
  }).describe("The branch and commit under test. The fields are present when a deploy notification started the run, and absent for runs started another way, for example manually or with run.create."),
6991
6995
  needsReview: boolean2().optional().describe("True once every flow has finished its attempts and at least one failed flow still has no diagnosis, including one whose investigation carried over to a later run. `status` stays `running` until that failure is diagnosed, so a caller that is not waiting for a diagnosis can stop polling here and read the run's failed flows."),
6992
6996
  runId: ids.run,
6993
- status: _enum(publicRunStatusValues).describe("Whole-run status, and the value to gate a pipeline on. Terminal statuses are passed, failed, and canceled; poll until one is reached. `failed` means the run found a bug that is still open and serious enough to block — a lower-priority bug, or one since resolved, reads `passed`, and the per-flow diagnoses are still in `flows`. The run stays `running` while any investigation is unresolved, including one that carried over to a later run, so a terminal status always means a settled answer. `needsReview` tells such a run apart from one that is still executing.")
6997
+ status: _enum(publicRunSummaryStatusValues).describe("Whole-run status, and the value to gate a pipeline on. Terminal statuses are passed, failed, canceled, and superseded; poll until one is reached. `failed` means the run found a bug that is still open and serious enough to block — a lower-priority bug, or one since resolved, reads `passed`, and the per-flow diagnoses are still in `flows`. The run stays `running` while any investigation is unresolved, including one that carried over to a later run, so a terminal status always means a settled answer, and `needsReview` tells such a run apart from one that is still executing. `superseded` means a newer run replaced this one before it finished: it is not a pass, this run will never change again, and the verdict belongs to the run in `supersededBy`."),
6998
+ supersededBy: object({
6999
+ runId: ids.run,
7000
+ url: url().describe("Absolute URL of the replacement run's page.")
7001
+ }).optional().describe("The run that replaced this one, present only when `status` is `superseded`. A newer deployment to the same branch and environment (and service, when the deployment named one) cancels the older run's unfinished flows and takes over. Deduplication ignores the commit, so the replacement may be testing a later commit than this run did. Only the direct replacement is named; that run can itself be superseded.")
6994
7002
  });
6995
7003
 
6996
7004
  // node_modules/@qawolf/api-contracts/dist/v1/deployment/didNotRunReason.js
@@ -7205,6 +7213,7 @@ var defaultIdSchemas = {
7205
7213
  issue: string2().min(1).describe("The id of the issue."),
7206
7214
  legacyTrigger: string2().min(1).describe("The id of the legacy trigger, from legacyTrigger.find."),
7207
7215
  run: string2().min(1).describe("The id of the run."),
7216
+ runAttempt: string2().min(1).describe("The id of the run attempt."),
7208
7217
  trigger: string2().min(1).describe("The id of the trigger."),
7209
7218
  workspace: string2().min(1).describe("The id of the workspace.")
7210
7219
  };
@@ -7256,6 +7265,72 @@ var makeCreateRunContract = (ids) => {
7256
7265
  };
7257
7266
  };
7258
7267
 
7268
+ // node_modules/@qawolf/api-contracts/dist/v1/run/listScreenshotComparisons.js
7269
+ var maxListedScreenshotAttempts = 20;
7270
+ var makeListScreenshotComparisonsContract = (ids) => {
7271
+ const input = object({
7272
+ flowIds: array(ids.flow).min(1).max(100).refine((flowIds) => new Set(flowIds).size === flowIds.length, "flowIds must be unique").optional().describe("Only list the comparisons of these flows. Each flow must have a result in the run. Omit to list every failed flow of the run."),
7273
+ runId: ids.run
7274
+ });
7275
+ const imageUrl = (image) => url().describe(`Signed URL of the full-size ${image} PNG. It stops working at expiresAt.`);
7276
+ const previewUrl = (image) => url().optional().describe(`Signed URL of a JPEG copy of the ${image} image, scaled down so its long edge is at most 1568 pixels. ` + "Fetch this one to look at the image: it is small enough to view inline, while the full-size PNG can be too large. " + "It stops working at expiresAt. Absent when QA Wolf could not make the copy, for example because the full-size image is gone.");
7277
+ const passedEnvironment = object({
7278
+ environmentId: ids.environment,
7279
+ lastPassedAt: exports_iso.datetime().describe("When the latest passing comparison in this environment ran.")
7280
+ });
7281
+ const passingElsewhere = discriminatedUnion("status", [
7282
+ object({
7283
+ status: literal("not-tracked").describe("QA Wolf does not record screenshot comparisons for this workspace, so it cannot tell.")
7284
+ }),
7285
+ object({
7286
+ environments: array(passedEnvironment).describe("Other environments where the current baseline passed this screenshot in the last 14 days, most recent first. " + "Empty when it passed nowhere else, or when the baseline no longer exists."),
7287
+ status: literal("tracked")
7288
+ })
7289
+ ]).describe("Whether the current baseline still passes in other environments. " + "A baseline that passes elsewhere points to a difference in this environment rather than an outdated baseline.");
7290
+ const comparison = object({
7291
+ actualPreviewUrl: previewUrl("actual"),
7292
+ actualUrl: imageUrl("actual (the screenshot the flow took)"),
7293
+ attemptCompletedAt: exports_iso.datetime().describe("When the run attempt that made the comparison finished."),
7294
+ baselineStatus: _enum([
7295
+ "unchanged",
7296
+ "accepted",
7297
+ "changed-since-run",
7298
+ "deleted",
7299
+ "unknown"
7300
+ ]).describe('How the current baseline relates to the expected image this comparison used. "unchanged": the baseline is still the expected image. ' + `"accepted": the baseline is now this comparison's actual screenshot, because someone accepted it, so a reattempt should pass. ` + '"changed-since-run": someone replaced the baseline with another image after this run, so rerun the flow before deciding anything. ' + '"deleted": the baseline no longer exists, and the next run will save its screenshot as the new baseline. ' + `"unknown": the run's copy of the expected image is gone, so QA Wolf cannot compare.`),
7301
+ comparisonId: string2().describe("Identifies this comparison: the run attempt id and the position of the comparison in that attempt."),
7302
+ diffPreviewUrl: previewUrl("diff"),
7303
+ diffUrl: imageUrl("diff (the pixels that differ are highlighted)"),
7304
+ expectedPreviewUrl: previewUrl("expected"),
7305
+ expectedUrl: imageUrl("expected (the baseline as it was when the flow ran)"),
7306
+ filePath: string2().optional().describe("The flow file that made the comparison."),
7307
+ flowId: ids.flow,
7308
+ lineNumber: number2().int().optional().describe("The line of filePath that made the comparison."),
7309
+ name: string2().describe("The screenshot name the flow passed to toHaveScreenshot. The baseline is stored as _screenshots_/<name>.png in team storage."),
7310
+ passingElsewhere,
7311
+ runAttemptId: string2(),
7312
+ similarity: number2().describe("How much of the actual image matches the expected image, in percent."),
7313
+ sizeMismatch: boolean2().describe("The actual and expected images have different dimensions. That fails the comparison whatever the pixels show, and similarity reads 0.")
7314
+ });
7315
+ const output = object({
7316
+ comparisons: array(comparison).describe(`Each screenshot comparison that failed its flow, from the ${maxListedScreenshotAttempts} most recent failed attempts of the selected flows, oldest attempt first.`),
7317
+ expiresAt: exports_iso.datetime().describe("When the image URLs stop working. Call run.listScreenshotComparisons again for fresh ones after it."),
7318
+ truncated: boolean2().describe(`True when the selected flows have more than ${maxListedScreenshotAttempts} failed attempts. Only the ${maxListedScreenshotAttempts} most recent are listed, and the older attempts cannot be listed.`)
7319
+ });
7320
+ return {
7321
+ annotations: {
7322
+ destructiveHint: false,
7323
+ openWorldHint: false,
7324
+ readOnlyHint: false
7325
+ },
7326
+ description: "List the screenshot comparisons that failed a run's flows, with the expected, actual and diff images of each. " + "Only comparisons that caused a failure are listed: a screenshot that differs within tolerance, or that differs on a line that did not fail, is left out. " + "Each comparison tells whether its baseline changed since the run and whether the baseline still passes in other environments. " + "The image URLs expire after a few minutes; fetch the preview URLs to look at the images. " + "The first call for an image makes its missing JPEG preview and keeps it in team storage, so later calls reuse it.",
7327
+ input,
7328
+ kind: "read",
7329
+ name: "run.listScreenshotComparisons",
7330
+ output
7331
+ };
7332
+ };
7333
+
7259
7334
  // node_modules/@qawolf/api-contracts/dist/v1/runner/actionSequence.js
7260
7335
  var maxActionsPerRequest = 10;
7261
7336
  var screenshotModes = ["none", "final", "each"];
@@ -9328,11 +9403,22 @@ var makeGetIssueContract = (ids) => {
9328
9403
  var investigationFindingStateDescription = 'What the investigation concluded about the cause. Known values: "investigating", "fixed", "fix-proposed", "reported", "report-proposed", "flake", "question" and "dismissed". New values can appear.';
9329
9404
  var makeInvestigationFindingSchema = (ids) => object({
9330
9405
  actual: string2().optional(),
9331
- commitHash: string2().optional(),
9406
+ commitHash: string2().optional().describe("The commit of a commit fix. Same as `fix.commitHash`."),
9332
9407
  decidedBy: _enum(["agent", "user"]).optional().describe("Who decided: the AI on its own, or a person who approved or dismissed it. Absent until someone decides."),
9333
9408
  disputeReason: string2().optional().describe("Why a person disputed what the AI did. Present on a proposal the AI made again after that dispute."),
9334
9409
  expected: string2().optional(),
9335
9410
  findingId: string2(),
9411
+ fix: discriminatedUnion("type", [
9412
+ object({
9413
+ commitHash: string2().describe("The commit that changes the flow code."),
9414
+ type: literal("commit")
9415
+ }),
9416
+ object({
9417
+ baselineChangeId: string2().optional().describe('The baseline change that made the new screenshot the baseline, on a "fixed" finding. `run restoreScreenshotBaseline` undoes it.'),
9418
+ comparisonId: string2().describe("The failed screenshot comparison whose new screenshot becomes the baseline, as `run listScreenshotComparisons` names it."),
9419
+ type: literal("baseline")
9420
+ })
9421
+ ]).optional().describe('The fix of a "fixed" or "fix-proposed" finding: a commit to the flow code, or a new screenshot baseline.'),
9336
9422
  flowIds: array(ids.flow),
9337
9423
  headline: string2().optional(),
9338
9424
  issueId: ids.issue.optional(),
@@ -9343,8 +9429,9 @@ var makeInvestigationFindingSchema = (ids) => object({
9343
9429
  answers: array(object({
9344
9430
  description: string2().optional(),
9345
9431
  title: string2(),
9346
- value: string2().describe('What choosing this answer means. Known values: "apply", "dismiss", "report", "maintenance" and "bug". New values can appear.')
9432
+ value: string2().describe('What choosing this answer means. Known values: "apply", "dismiss", "report", "maintenance", "bug" and "baseline". New values can appear.')
9347
9433
  })),
9434
+ comparisonId: string2().optional().describe('The failed screenshot comparison a screenshot question is about, as `run listScreenshotComparisons` names it. Its answers are "bug" (the old screenshot is right), "maintenance" (both are right) and "baseline" (the new screenshot is right).'),
9348
9435
  text: string2()
9349
9436
  }).optional().describe("The question waiting for a person, with the answers offered."),
9350
9437
  response: object({
@@ -9364,6 +9451,7 @@ var makeInvestigationFindingSchema = (ids) => object({
9364
9451
  var makeGetInvestigationContract = (ids) => {
9365
9452
  const input = object({ runId: ids.run });
9366
9453
  const output = resource({
9454
+ baselineChanges: _enum(["automatic", "needs-approval"]).describe('How the investigation can make a new screenshot the baseline. "automatic": it can accept a baseline on its own. ' + '"needs-approval": it proposes the baseline on a fix-proposed finding, and accepts it only after a person applies the proposal.'),
9367
9455
  findings: array(makeInvestigationFindingSchema(ids)),
9368
9456
  runId: ids.run,
9369
9457
  sessionId: ids.chatSession.optional().describe("The investigation session. Absent when the run has none."),
@@ -9414,7 +9502,7 @@ var makeAddFlowsToIssueContract = (ids) => {
9414
9502
  // node_modules/@qawolf/api-contracts/dist/v1/issue/create.js
9415
9503
  var makeCreateIssueContract = (ids) => {
9416
9504
  const commonFields = {
9417
- description: string2().optional().describe("The issue description as plain text; each line becomes a paragraph. Markdown is not parsed."),
9505
+ description: string2().optional().describe("The issue description in Markdown, which is stored as rich text. Text without Markdown is stored as written, one paragraph per line."),
9418
9506
  name: string2().trim().min(1).max(255),
9419
9507
  priority: issuePrioritySchema.optional().describe('Defaults to "unprioritized" for bug reports and coverage requests.'),
9420
9508
  workspaceId: ids.workspace.optional().describe("The workspace to create the issue in. Required when authenticating with an organization or user API key.")
@@ -9457,7 +9545,8 @@ var makeCreateIssueContract = (ids) => {
9457
9545
  // node_modules/@qawolf/api-contracts/dist/v1/issue/removeFlows.js
9458
9546
  var makeRemoveFlowsFromIssueContract = (ids) => {
9459
9547
  const input = object({
9460
- flowIds: array(ids.flow).min(1).max(300).refine((flowIds) => new Set(flowIds).size === flowIds.length, "flowIds must be unique").describe("The flows the coverage request no longer covers."),
9548
+ environmentId: ids.environment.optional().describe("The environment whose bug or maintenance reproductions to remove. Required when a selected flow is linked in more than one environment."),
9549
+ flowIds: array(ids.flow).min(1).max(300).refine((flowIds) => new Set(flowIds).size === flowIds.length, "flowIds must be unique").describe("The flows to remove from the issue."),
9461
9550
  issueId: ids.issue
9462
9551
  });
9463
9552
  const output = object({
@@ -9466,10 +9555,10 @@ var makeRemoveFlowsFromIssueContract = (ids) => {
9466
9555
  return {
9467
9556
  annotations: {
9468
9557
  destructiveHint: true,
9469
- openWorldHint: false,
9558
+ openWorldHint: true,
9470
9559
  readOnlyHint: false
9471
9560
  },
9472
- description: "Remove flows from a coverage request owned by the caller's team. " + "Flows the request does not cover are left alone. " + "Bug and maintenance reports link to flows through the runs that reproduce them, so their flows cannot be set directly.",
9561
+ description: "Remove flows from a coverage request or remove bug/maintenance reproductions in an environment. " + "Unlinked flows are left alone. Reports auto-close when no active reproductions remain and auto-close is enabled. " + "For reports, omit environmentId only when each selected flow is linked in at most one environment.",
9473
9562
  input,
9474
9563
  kind: "write",
9475
9564
  name: "issue.removeFlows",
@@ -9479,7 +9568,7 @@ var makeRemoveFlowsFromIssueContract = (ids) => {
9479
9568
 
9480
9569
  // node_modules/@qawolf/api-contracts/dist/v1/issue/update.js
9481
9570
  var makeUpdateIssueContract = (ids) => {
9482
- const descriptionSchema = string2().describe("The issue description as plain text; each line becomes a paragraph. Markdown is not parsed.");
9571
+ const descriptionSchema = string2().describe("The issue description in Markdown, which is stored as rich text. Text without Markdown is stored as written, one paragraph per line.");
9483
9572
  const nameSchema = string2().trim().min(1).max(255);
9484
9573
  const input = object({
9485
9574
  description: descriptionSchema.optional(),
@@ -9629,6 +9718,49 @@ var makeLegacyTriggerContracts = (ids) => ({
9629
9718
  resume: makeResumeLegacyTriggerContract(ids)
9630
9719
  });
9631
9720
 
9721
+ // node_modules/@qawolf/api-contracts/dist/v1/run/investigationSessionId.js
9722
+ var investigationSessionId = string2().trim().min(1).max(100).optional();
9723
+
9724
+ // node_modules/@qawolf/api-contracts/dist/v1/run/acceptScreenshotBaseline.js
9725
+ var makeAcceptScreenshotBaselineContract = (ids) => {
9726
+ const input = object({
9727
+ chatSessionId: investigationSessionId.describe("The chat session of the investigation that accepts the baseline. A workspace or organization API key acts as an automation, so it must send the session that investigates the run. A person's call ignores it."),
9728
+ comparisonId: string2().trim().min(1).max(100).describe("The failed screenshot comparison whose new screenshot becomes the baseline, as `run.listScreenshotComparisons` names it."),
9729
+ runId: ids.run
9730
+ });
9731
+ const output = discriminatedUnion("outcome", [
9732
+ object({
9733
+ baselineChangeId: string2().describe("The baseline change. Record it on the fixed finding, and pass it to `run.restoreScreenshotBaseline` with this runId to put the old baseline back."),
9734
+ name: string2().describe("The screenshot name whose baseline changed."),
9735
+ outcome: literal("accepted").describe("The new screenshot is now the baseline. The old baseline is kept, so the change can be restored.")
9736
+ }),
9737
+ object({
9738
+ outcome: literal("baseline-changed").describe("The baseline is no longer the one the run compared with: someone changed or deleted it since. Nothing changed. List the comparisons again to see the current baseline.")
9739
+ }),
9740
+ object({
9741
+ outcome: literal("needs-approval").describe("Only a person can approve this change. Nothing changed. Propose the new baseline on a fix-proposed finding and wait for a person to apply it.")
9742
+ }),
9743
+ object({
9744
+ outcome: literal("not-investigation-session").describe("An API key that is not a person's can change a baseline only as the investigation of the run, and chatSessionId is missing or is not the session that investigates the run. Nothing changed.")
9745
+ }),
9746
+ object({
9747
+ outcome: literal("nothing-to-accept").describe("The comparison did not fail, or has no new screenshot to accept. Nothing changed.")
9748
+ })
9749
+ ]);
9750
+ return {
9751
+ annotations: {
9752
+ destructiveHint: true,
9753
+ openWorldHint: false,
9754
+ readOnlyHint: false
9755
+ },
9756
+ description: "Make the new screenshot of a failed screenshot comparison the baseline, so later runs compare with it. " + "Use it only when the difference is an intended change of the app, not a bug, noise, or a page that was not ready. " + "Every later run of every environment of the workspace compares with the new baseline. " + "Refuses without changing anything when the baseline changed since the run.",
9757
+ input,
9758
+ kind: "write",
9759
+ name: "run.acceptScreenshotBaseline",
9760
+ output
9761
+ };
9762
+ };
9763
+
9632
9764
  // node_modules/@qawolf/api-contracts/dist/v1/run/diagnose.js
9633
9765
  var makeDiagnoseRunContract = (ids) => {
9634
9766
  const input = object({
@@ -9645,7 +9777,7 @@ var makeDiagnoseRunContract = (ids) => {
9645
9777
  openWorldHint: true,
9646
9778
  readOnlyHint: false
9647
9779
  },
9648
- description: "Diagnose failed flows in a run as reproductions of a bug or maintenance report owned by the caller's team. " + "The issue's type selects the diagnosis. " + "Each flow must have failed in the run. " + "A flow that is already diagnosed moves to this issue. " + "The diagnosis appears on the run, and the reproduction appears under the issue's reproductions. " + "Coverage requests cannot be diagnosed; use issue.addFlows to cover flows instead.",
9780
+ description: "Diagnose failed flows in a run as reproductions of a bug or maintenance report owned by the caller's team. " + "The issue's type selects the diagnosis. " + "Each flow must have failed in the run. " + "A flow that is already diagnosed in the run moves to this issue, and may stay linked to other open reports. " + "The diagnosis appears on the run, and the reproduction appears under the issue's reproductions. " + "Coverage requests cannot be diagnosed; use issue.addFlows to cover flows instead.",
9649
9781
  input,
9650
9782
  kind: "write",
9651
9783
  name: "run.diagnose",
@@ -9663,9 +9795,11 @@ var makeFindRunsContract = (ids) => {
9663
9795
  });
9664
9796
  const output = object({
9665
9797
  nextCursor: nextCursorSchema,
9666
- runs: array(resource(makeRunSummaryFields(ids), {
9667
- urlFieldDescription: "Absolute URL of the run page."
9668
- })).describe("The environment's runs, newest first. Per-flow results are available via run.get.")
9798
+ runs: array(resource({
9799
+ ...makeRunSummaryFields(ids),
9800
+ failedAttemptCount: number2().int().nonnegative().optional(),
9801
+ hasFailedAttempts: boolean2().optional()
9802
+ }, { urlFieldDescription: "Absolute URL of the run page." })).describe("The environment's runs, newest first. Per-flow results are available via run.get.")
9669
9803
  });
9670
9804
  return {
9671
9805
  annotations: {
@@ -9673,7 +9807,7 @@ var makeFindRunsContract = (ids) => {
9673
9807
  openWorldHint: false,
9674
9808
  readOnlyHint: true
9675
9809
  },
9676
- description: "List an environment's recent runs, newest first.",
9810
+ description: "List an environment's recent runs, newest first, including failed-attempt discovery metadata.",
9677
9811
  input,
9678
9812
  kind: "read",
9679
9813
  name: "run.find",
@@ -9681,6 +9815,22 @@ var makeFindRunsContract = (ids) => {
9681
9815
  };
9682
9816
  };
9683
9817
 
9818
+ // node_modules/@qawolf/api-contracts/dist/v1/run/flowFailure.js
9819
+ var makeFlowFailureSchema = (ids) => {
9820
+ const diagnosis = object({
9821
+ issueId: ids.issue,
9822
+ type: _enum(["bug", "maintenance"])
9823
+ }).optional().describe("QA Wolf's investigation verdict for the failure: `bug` means the " + "application is broken, `maintenance` means the test needed an " + "update and the failure does not indicate an application problem. " + "Absent until the investigation reaches a verdict, and absent when " + "the flow was opted out of investigation. Pass issueId to issue.get " + "for details.");
9824
+ const optedOutOfInvestigation = object({
9825
+ by: _enum(["user", "system"]).describe("Who stopped the investigation: `user` is a person, in the app or " + "with a user API key, or the workspace's automation user for a " + "team or organization API key, for example through " + "run.optOutOfInvestigation; `system` is QA Wolf, when the " + "investigation expired.")
9826
+ }).optional().describe('Present when the failure was marked "do not investigate" (DNI): ' + "it will get no bug or maintenance verdict.");
9827
+ return object({
9828
+ diagnosis,
9829
+ error: string2(),
9830
+ optedOutOfInvestigation
9831
+ });
9832
+ };
9833
+
9684
9834
  // node_modules/@qawolf/api-contracts/dist/v1/run/get.js
9685
9835
  var makeGetRunContract = (ids) => {
9686
9836
  const flowStatus = _enum(publicRunStatusValues);
@@ -9697,6 +9847,7 @@ var makeGetRunContract = (ids) => {
9697
9847
  const automatedAttempt = discriminatedUnion("status", [
9698
9848
  object({
9699
9849
  ...artifactUrls,
9850
+ attemptId: ids.runAttempt.optional(),
9700
9851
  completedAt: exports_iso.datetime(),
9701
9852
  kind: automatedKind,
9702
9853
  startedAt: exports_iso.datetime(),
@@ -9704,12 +9855,14 @@ var makeGetRunContract = (ids) => {
9704
9855
  }),
9705
9856
  object({
9706
9857
  ...artifactUrls,
9858
+ attemptId: ids.runAttempt.optional(),
9707
9859
  completedAt: exports_iso.datetime(),
9708
9860
  kind: automatedKind,
9709
9861
  startedAt: exports_iso.datetime().optional().describe("Absent when the attempt failed before it could start."),
9710
9862
  status: literal("failed")
9711
9863
  }),
9712
9864
  object({
9865
+ attemptId: ids.runAttempt.optional(),
9713
9866
  completedAt: exports_iso.datetime().optional(),
9714
9867
  kind: automatedKind,
9715
9868
  startedAt: exports_iso.datetime().optional(),
@@ -9717,6 +9870,7 @@ var makeGetRunContract = (ids) => {
9717
9870
  })
9718
9871
  ]);
9719
9872
  const manualAttempt = object({
9873
+ attemptId: ids.runAttempt.optional(),
9720
9874
  completedAt: exports_iso.datetime(),
9721
9875
  kind: literal("manual").describe("The attempt ran by hand in the Wolf Browser."),
9722
9876
  startedAt: exports_iso.datetime(),
@@ -9724,16 +9878,9 @@ var makeGetRunContract = (ids) => {
9724
9878
  });
9725
9879
  const attempt = union([automatedAttempt, manualAttempt]);
9726
9880
  const attempts = array(attempt).optional().describe("The flow's finished execution attempts, oldest first, including " + "manual Wolf Browser attempts. Present once at least one attempt has " + "finished, so a flow that passed after retries also lists its failed " + "attempts. Artifact URLs appear only on automated attempts that " + "reached a verdict, stay valid for at least a day (call run.get " + "again for fresh ones), and can return 404 when the attempt did not " + "produce that artifact.");
9727
- const diagnosis = object({
9728
- issueId: ids.issue,
9729
- type: _enum(["bug", "maintenance"])
9730
- }).optional().describe("QA Wolf's investigation verdict for the failure: `bug` means the " + "application is broken, `maintenance` means the test needed an " + "update and the failure does not indicate an application problem. " + "Absent until the investigation reaches a verdict. Pass issueId to " + "issue.get for details.");
9731
9881
  const failedFlow = object({
9732
9882
  attempts,
9733
- failure: object({
9734
- diagnosis,
9735
- error: string2()
9736
- }),
9883
+ failure: makeFlowFailureSchema(ids),
9737
9884
  flowId: ids.flow,
9738
9885
  name: string2(),
9739
9886
  status: literal("failed")
@@ -9765,6 +9912,107 @@ var makeGetRunContract = (ids) => {
9765
9912
  };
9766
9913
  };
9767
9914
 
9915
+ // node_modules/@qawolf/api-contracts/dist/v1/run/getAttemptArtifacts.js
9916
+ var makeGetRunAttemptArtifactsContract = (ids) => {
9917
+ const input = object({ attemptId: ids.runAttempt });
9918
+ const attemptStatus = _enum([
9919
+ "passed",
9920
+ "failed",
9921
+ "canceled",
9922
+ "aborted",
9923
+ "skipped"
9924
+ ]);
9925
+ const attemptNeighbor = object({
9926
+ attemptId: ids.runAttempt,
9927
+ status: attemptStatus
9928
+ });
9929
+ const response = object({
9930
+ flowId: ids.flow,
9931
+ flowStatus: _enum(publicRunStatusValues),
9932
+ retryContext: object({
9933
+ currentOrdinal: number2().int().min(1),
9934
+ next: attemptNeighbor.optional(),
9935
+ previous: attemptNeighbor.optional(),
9936
+ total: number2().int().min(1)
9937
+ }),
9938
+ runId: ids.run,
9939
+ runStatus: _enum(publicRunSummaryStatusValues)
9940
+ });
9941
+ const attempt = object({
9942
+ attemptId: ids.runAttempt,
9943
+ completedAt: exports_iso.datetime().optional(),
9944
+ createdAt: exports_iso.datetime(),
9945
+ error: string2().optional(),
9946
+ startedAt: exports_iso.datetime().optional()
9947
+ });
9948
+ const output = discriminatedUnion("artifactStatus", [
9949
+ response.extend({
9950
+ artifacts: object({
9951
+ logsUrl: url().optional(),
9952
+ traceUrl: url().optional(),
9953
+ videoUrl: url().optional()
9954
+ }),
9955
+ artifactStatus: literal("signed"),
9956
+ attempt: attempt.extend({
9957
+ kind: literal("automated"),
9958
+ status: attemptStatus
9959
+ })
9960
+ }),
9961
+ response.extend({
9962
+ artifacts: object({}).strict(),
9963
+ artifactStatus: literal("signing-failed"),
9964
+ attempt: attempt.extend({
9965
+ kind: literal("automated"),
9966
+ status: attemptStatus
9967
+ })
9968
+ }),
9969
+ response.extend({
9970
+ artifacts: object({}).strict(),
9971
+ artifactStatus: literal("not-captured"),
9972
+ attempt: attempt.extend({
9973
+ kind: literal("manual"),
9974
+ status: literal("passed")
9975
+ })
9976
+ })
9977
+ ]);
9978
+ return {
9979
+ annotations: {
9980
+ destructiveHint: false,
9981
+ openWorldHint: false,
9982
+ readOnlyHint: true
9983
+ },
9984
+ description: "Get metadata and signed artifact URLs for one finished run attempt.",
9985
+ input,
9986
+ kind: "read",
9987
+ name: "run.getAttemptArtifacts",
9988
+ output
9989
+ };
9990
+ };
9991
+
9992
+ // node_modules/@qawolf/api-contracts/dist/v1/run/optOutOfInvestigation.js
9993
+ var makeOptOutOfInvestigationRunContract = (ids) => {
9994
+ const input = object({
9995
+ flowIds: array(ids.flow).min(1).max(100).refine((flowIds) => new Set(flowIds).size === flowIds.length, "flowIds must be unique").describe("The failed flows to stop investigating. Every named flow must be " + "eligible or the whole request is rejected."),
9996
+ runId: ids.run
9997
+ });
9998
+ const output = resource({
9999
+ optedOutFlowIds: array(ids.flow).describe("The flows that no longer need investigation."),
10000
+ runId: ids.run
10001
+ }, { urlFieldDescription: "Absolute URL of the run page." });
10002
+ return {
10003
+ annotations: {
10004
+ destructiveHint: true,
10005
+ openWorldHint: true,
10006
+ readOnlyHint: false
10007
+ },
10008
+ description: 'Mark failed flows in a run as "do not investigate" (DNI): the failures need no bug or maintenance report. ' + "Each flow must have failed in the run and still be under investigation. " + "A flow that already has a diagnosis cannot be opted out. " + "A team or organization key records the opt-out as the workspace's automation user. " + "To link a failure to a report instead, use run.diagnose.",
10009
+ input,
10010
+ kind: "write",
10011
+ name: "run.optOutOfInvestigation",
10012
+ output
10013
+ };
10014
+ };
10015
+
9768
10016
  // node_modules/@qawolf/api-contracts/dist/v1/run/reattempt.js
9769
10017
  var makeReattemptRunContract = (ids) => {
9770
10018
  const input = object({
@@ -9789,6 +10037,45 @@ var makeReattemptRunContract = (ids) => {
9789
10037
  };
9790
10038
  };
9791
10039
 
10040
+ // node_modules/@qawolf/api-contracts/dist/v1/run/restoreScreenshotBaseline.js
10041
+ var makeRestoreScreenshotBaselineContract = (ids) => {
10042
+ const input = object({
10043
+ baselineChangeId: string2().trim().min(1).max(100).describe("The baseline change to undo, as `run.acceptScreenshotBaseline` returned it for this run."),
10044
+ chatSessionId: investigationSessionId.describe("The chat session of the investigation that restores the baseline. A workspace or organization API key acts as an automation, so it must send the session that investigates the run. A person's call ignores it."),
10045
+ runId: ids.run
10046
+ });
10047
+ const output = discriminatedUnion("outcome", [
10048
+ object({
10049
+ name: string2().describe("The screenshot name whose baseline changed."),
10050
+ outcome: literal("restored").describe("The baseline the change replaced is the baseline again.")
10051
+ }),
10052
+ object({
10053
+ outcome: literal("baseline-changed").describe("The baseline changed again after this change, so restoring it would undo someone else's change. Nothing changed.")
10054
+ }),
10055
+ object({
10056
+ outcome: literal("needs-dispute").describe("Only a person can restore this change, unless a person disputed the finding that made it. Nothing changed.")
10057
+ }),
10058
+ object({
10059
+ outcome: literal("not-investigation-session").describe("An API key that is not a person's can change a baseline only as the investigation of the run, and chatSessionId is missing or is not the session that investigates the run. Nothing changed.")
10060
+ }),
10061
+ object({
10062
+ outcome: literal("nothing-to-restore").describe("The change was already restored, the name had no baseline before it, or the kept copy of the old baseline is missing or is not the baseline the change replaced. Nothing changed.")
10063
+ })
10064
+ ]);
10065
+ return {
10066
+ annotations: {
10067
+ destructiveHint: true,
10068
+ openWorldHint: false,
10069
+ readOnlyHint: false
10070
+ },
10071
+ description: "Undo a screenshot baseline change that accepted a failed comparison of the run: put back the baseline it replaced, so later runs compare with the old baseline again. " + "Use it when a person disputes a baseline the AI accepted. " + "Refuses without changing anything when the baseline changed again since.",
10072
+ input,
10073
+ kind: "write",
10074
+ name: "run.restoreScreenshotBaseline",
10075
+ output
10076
+ };
10077
+ };
10078
+
9792
10079
  // node_modules/@qawolf/api-contracts/dist/v1/run/stop.js
9793
10080
  var makeStopRunContract = (ids) => {
9794
10081
  const input = object({ runId: ids.run });
@@ -10109,7 +10396,7 @@ var makeRunFlowOnRunnerContract = (ids) => {
10109
10396
  env: runEnvironmentSchema.optional().describe("Environment variables to make available to the run."),
10110
10397
  environmentId: ids.environmentRef.optional().describe("A QA Wolf environment whose variables the run is given, by id or alias. QA Wolf reads and decrypts them itself, so they never travel in the request and the count and length limits on `env` do not apply to them. Send this or `env`, not both."),
10111
10398
  files: runFilesSchema.describe("Every file the run needs keyed by its path — the flow file, everything it imports, package.json and tsconfig.json. A runner holds no copy of your project, so what runs is exactly what is sent here."),
10112
- flowId: ids.flow.optional().describe("The QA Wolf flow this run is for. The run then sees it as `QAWOLF_WORKFLOW_ID`, the same value a platform run of that flow sees, so fixtures named and cleaned up by it match across both. Omit it when the flow is not in QA Wolf yet, and the run has no `QAWOLF_WORKFLOW_ID`."),
10399
+ flowId: ids.flow.optional().describe("The QA Wolf flow this run is for. The run then sees it as `QAWOLF_WORKFLOW_ID`, the same value a platform run of that flow sees, so fixtures named and cleaned up by it match across both. Omitted, the flow whose file is at `entryPointPath` on the named environment's flow-code branch stands in; a file not in QA Wolf yet, or a run given no environment, leaves the run with no `QAWOLF_WORKFLOW_ID`."),
10113
10400
  id: runnerIdSchema.describe("Id of the runner to run the flow on."),
10114
10401
  selection: runSelectionSchema.optional().describe("Run only these lines, inside the page the runner is already on, instead of running the whole flow from a fresh browser. Omit to run the whole entry point."),
10115
10402
  unchangedFiles: unchangedFilesSchema.optional().describe("Files this runner already holds from an earlier run, keyed by path with a hex SHA-256 of the content it should hold. Send this to make `files` only what changed. The runner refuses with `needs-full-sync` if it holds none of a referenced path, so it never runs something other than what was asked for. Omit it to send every file."),
@@ -10325,7 +10612,11 @@ var makeListTagsContract = (ids) => {
10325
10612
 
10326
10613
  // node_modules/@qawolf/api-contracts/dist/v1/index.js
10327
10614
  var makeContractsV1 = (ids) => {
10328
- const resolvedIds = { ...defaultIdSchemas, ...ids };
10615
+ const resolvedIds = {
10616
+ ...defaultIdSchemas,
10617
+ ...ids,
10618
+ runAttempt: ids.runAttempt ?? defaultIdSchemas.runAttempt
10619
+ };
10329
10620
  return {
10330
10621
  agent: {
10331
10622
  get: makeAgentGetContract(resolvedIds),
@@ -10372,11 +10663,16 @@ var makeContractsV1 = (ids) => {
10372
10663
  },
10373
10664
  legacyTrigger: makeLegacyTriggerContracts(resolvedIds),
10374
10665
  run: {
10666
+ acceptScreenshotBaseline: makeAcceptScreenshotBaselineContract(resolvedIds),
10375
10667
  create: makeCreateRunContract(resolvedIds),
10376
10668
  diagnose: makeDiagnoseRunContract(resolvedIds),
10377
10669
  find: makeFindRunsContract(resolvedIds),
10378
10670
  get: makeGetRunContract(resolvedIds),
10671
+ getAttemptArtifacts: makeGetRunAttemptArtifactsContract(resolvedIds),
10672
+ listScreenshotComparisons: makeListScreenshotComparisonsContract(resolvedIds),
10673
+ optOutOfInvestigation: makeOptOutOfInvestigationRunContract(resolvedIds),
10379
10674
  reattempt: makeReattemptRunContract(resolvedIds),
10675
+ restoreScreenshotBaseline: makeRestoreScreenshotBaselineContract(resolvedIds),
10380
10676
  stop: makeStopRunContract(resolvedIds)
10381
10677
  },
10382
10678
  runner: makeRunnerContracts(resolvedIds),
@@ -15011,4 +15307,4 @@ export {
15011
15307
  createRunnerSdk
15012
15308
  };
15013
15309
 
15014
- //# debugId=CBFC365D381BD6E864756E2164756E21
15310
+ //# debugId=6A1EED78FDDC38F864756E2164756E21
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qawolf/cli",
3
- "version": "1.36.0",
3
+ "version": "1.38.0",
4
4
  "description": "Run and manage QA Wolf flows from the terminal, CI, or an AI agent",
5
5
  "keywords": [
6
6
  "automation",
@@ -71,7 +71,7 @@
71
71
  "@clack/prompts": "1.5.1",
72
72
  "@napi-rs/keyring": "1.3.0",
73
73
  "@oxc-node/core": "0.1.0",
74
- "@qawolf/api-contracts": "0.73.0",
74
+ "@qawolf/api-contracts": "0.77.0",
75
75
  "@qawolf/emails": "1.1.1",
76
76
  "@qawolf/flow-targets": "1.0.0",
77
77
  "@qawolf/flows": "0.1.4",
@@ -171,16 +171,21 @@ that `url`; never guess a route and never send a repository link in its place.
171
171
  | `qawolf issue create` | write | Create a bug report, maintenance report, or coverage request issue for the caller's team. A user can create a maintenance report only when they can see the workspace's maintenance reports; a team or organization API key credits it to the workspace's automation user. Link its failed flows afterwards with run.diagnose. |
172
172
  | `qawolf issue find` | read | List the team's bug reports, maintenance reports, or coverage requests, newest first. |
173
173
  | `qawolf issue get` | read | Get an issue by id. |
174
- | `qawolf issue removeFlows` | write | Remove flows from a coverage request owned by the caller's team. Flows the request does not cover are left alone. Bug and maintenance reports link to flows through the runs that reproduce them, so their flows cannot be set directly. |
174
+ | `qawolf issue removeFlows` | write | Remove flows from a coverage request or remove bug/maintenance reproductions in an environment. Unlinked flows are left alone. Reports auto-close when no active reproductions remain and auto-close is enabled. For reports, omit environmentId only when each selected flow is linked in at most one environment. |
175
175
  | `qawolf issue update` | write | Update an issue owned by the caller's team. Omitted fields remain unchanged. |
176
176
  | `qawolf legacyTrigger find` | read | List a workspace's legacy per-environment triggers; trigger.find lists the current ones. For migration only; it goes when legacy triggers go. adaptiveFlowSelection becomes a generativeSuite action, which takes no tags. |
177
177
  | `qawolf legacyTrigger pause` | write | Pause one legacy trigger, the older per-environment kind, so it stops firing; use trigger.pause for a current one. Its copies in pull request environments pause too. For migration only; it goes when legacy triggers go. |
178
178
  | `qawolf legacyTrigger resume` | write | Resume one legacy trigger, the older per-environment kind; use trigger.resume for a current one. Its copies in pull request environments resume too, and a scheduled one jumps to the next upcoming slot even if it was not paused. For migration only; it goes when legacy triggers go. |
179
+ | `qawolf run acceptScreenshotBaseline` | write | Make the new screenshot of a failed screenshot comparison the baseline, so later runs compare with it. Use it only when the difference is an intended change of the app, not a bug, noise, or a page that was not ready. Every later run of every environment of the workspace compares with the new baseline. Refuses without changing anything when the baseline changed since the run. |
179
180
  | `qawolf run create` | write | Create a run for the selected flows and/or tags in an environment. |
180
- | `qawolf run diagnose` | write | Diagnose failed flows in a run as reproductions of a bug or maintenance report owned by the caller's team. The issue's type selects the diagnosis. Each flow must have failed in the run. A flow that is already diagnosed moves to this issue. The diagnosis appears on the run, and the reproduction appears under the issue's reproductions. Coverage requests cannot be diagnosed; use issue.addFlows to cover flows instead. |
181
- | `qawolf run find` | read | List an environment's recent runs, newest first. |
181
+ | `qawolf run diagnose` | write | Diagnose failed flows in a run as reproductions of a bug or maintenance report owned by the caller's team. The issue's type selects the diagnosis. Each flow must have failed in the run. A flow that is already diagnosed in the run moves to this issue, and may stay linked to other open reports. The diagnosis appears on the run, and the reproduction appears under the issue's reproductions. Coverage requests cannot be diagnosed; use issue.addFlows to cover flows instead. |
182
+ | `qawolf run find` | read | List an environment's recent runs, newest first, including failed-attempt discovery metadata. |
182
183
  | `qawolf run get` | read | Get a run's status, per-flow results, links, and how many of its bugs are blocking. |
184
+ | `qawolf run getAttemptArtifacts` | read | Get metadata and signed artifact URLs for one finished run attempt. |
185
+ | `qawolf run listScreenshotComparisons` | read | List the screenshot comparisons that failed a run's flows, with the expected, actual and diff images of each. Only comparisons that caused a failure are listed: a screenshot that differs within tolerance, or that differs on a line that did not fail, is left out. Each comparison tells whether its baseline changed since the run and whether the baseline still passes in other environments. The image URLs expire after a few minutes; fetch the preview URLs to look at the images. The first call for an image makes its missing JPEG preview and keeps it in team storage, so later calls reuse it. |
186
+ | `qawolf run optOutOfInvestigation` | write | Mark failed flows in a run as "do not investigate" (DNI): the failures need no bug or maintenance report. Each flow must have failed in the run and still be under investigation. A flow that already has a diagnosis cannot be opted out. A team or organization key records the opt-out as the workspace's automation user. To link a failure to a report instead, use run.diagnose. |
183
187
  | `qawolf run reattempt` | write | Request new attempts for a run's flows, in the same run. A flow is eligible once its result is failed or canceled and QA Wolf's automatic retries have finished. A fully investigated run no longer accepts reattempts. Attempts run with the latest flow code. Poll run.get for results. |
188
+ | `qawolf run restoreScreenshotBaseline` | write | Undo a screenshot baseline change that accepted a failed comparison of the run: put back the baseline it replaced, so later runs compare with the old baseline again. Use it when a person disputes a baseline the AI accepted. Refuses without changing anything when the baseline changed again since. |
184
189
  | `qawolf run stop` | write | Stop a run, including its queued flows and automatic retries. Stopping is asynchronous and can update run-status messages and commit statuses in connected integrations. Repeated requests are safe, and finished runs keep their results. A run that is still being created returns not found; retry once run.get returns the run. If run.get returns a different runId, use that ID. Poll run.get for results. |
185
190
  | `qawolf runner act` | write | Perform one raw action on a runner's screen. A browser runner takes click, double_click, scroll, move, drag, keypress, navigate and type; a mobile runner takes tap, swipe, fill and type. A browser runner answers mobile actions with action-not-supported-on-browser; a mobile runner answers the other browser actions with action-not-supported-on-mobile. Use - to read a whole action as JSON from stdin |
186
191
  | `qawolf runner actions` | write | Perform a sequence of up to ten raw actions on a runner's screen in one request, as a JSON array of the same actions `runner act` takes. Use - to read the array from stdin. Actions run back to back, so batch only steps whose targets are on the screen you last saw, and end the sequence at the step that changes the page. Each result carries an effect: performed, not-performed, or unknown when the runner stopped answering and the action may have landed |
@@ -59,8 +59,10 @@ A run that has been requested but not yet created answers exit `8` too, and
59
59
  says it is still being created. That one clears on its own, so read the message
60
60
  rather than the code before deciding whether to poll.
61
61
 
62
- Poll `status` until it reaches `passed`, `failed` or `canceled`. The other
63
- values mean the run is still going.
62
+ Poll `status` until it reaches `passed`, `failed`, `canceled` or `superseded`.
63
+ When it is `superseded`, this run will never change again. Read the replacement
64
+ run in `supersededBy.runId` for the verdict. `queued` and `running` mean the run
65
+ is still going.
64
66
 
65
67
  ## Fields a passing run does not show you
66
68
 
@@ -185,19 +187,25 @@ Every documented field of the `run.get` response. `[]` marks an array, so
185
187
  - `git.commitUrl` — Link to the commit on the code host.
186
188
  - `needsReview` — True once every flow has finished its attempts and at least one failed flow still has no diagnosis, including one whose investigation carried over to a later run. `status` stays `running` until that failure is diagnosed, so a caller that is not waiting for a diagnosis can stop polling here and read the run's failed flows.
187
189
  - `runId` — The run this response describes. Treat it as canonical: it can differ from the id you asked for. A deploy notification returns a run id before the run exists, and if a second notification for the same commit is folded into an earlier run, that id resolves to the earlier run instead.
188
- - `status` — One of: queued, running, passed, failed, canceled
190
+ - `status` — One of: queued, running, passed, failed, canceled, superseded
191
+ - `supersededBy` — The run that replaced this one, present only when `status` is `superseded`. A newer deployment to the same branch and environment (and service, when the deployment named one) cancels the older run's unfinished flows and takes over. Deduplication ignores the commit, so the replacement may be testing a later commit than this run did. Only the direct replacement is named; that run can itself be superseded.
192
+ - `supersededBy.runId` — The id of the run.
193
+ - `supersededBy.url` — Absolute URL of the replacement run's page.
189
194
  - `blockingBugCount` — How many bugs this run found are blocking: still open, and priority urgent, high, or unprioritized — unprioritized counts because nobody has ruled it out yet. This is what `failed` is derived from, so it explains a failure rather than adding a second verdict: gate on `status`, then read this to say how many bugs are holding the build. It counts a bug filed against a later run by an investigation that carried over from this one, and it drops a bug once that bug is resolved.
190
195
  - `flows` — The run's flows, ordered alphabetically by name. Only the flows matching flowStatuses when the request set it.
191
196
  - `flows[].attempts` — The flow's finished execution attempts, oldest first, including manual Wolf Browser attempts. Present once at least one attempt has finished, so a flow that passed after retries also lists its failed attempts. Artifact URLs appear only on automated attempts that reached a verdict, stay valid for at least a day (call run.get again for fresh ones), and can return 404 when the attempt did not produce that artifact.
192
197
  - `flows[].attempts[].logsUrl` — Signed URL for the attempt's execution logs.
193
198
  - `flows[].attempts[].traceUrl` — Signed URL for the attempt's Playwright trace (a trace.zip; open it with `npx playwright show-trace`).
194
199
  - `flows[].attempts[].videoUrl` — Signed URL for the attempt's screen recording.
200
+ - `flows[].attempts[].attemptId` — The id of the run attempt.
195
201
  - `flows[].attempts[].kind` — One of: automated, manual
196
202
  - `flows[].attempts[].startedAt` — Absent when the attempt failed before it could start.
197
203
  - `flows[].attempts[].status` — One of: passed, failed, canceled
198
- - `flows[].failure.diagnosis` — QA Wolf's investigation verdict for the failure: `bug` means the application is broken, `maintenance` means the test needed an update and the failure does not indicate an application problem. Absent until the investigation reaches a verdict. Pass issueId to issue.get for details.
204
+ - `flows[].failure.diagnosis` — QA Wolf's investigation verdict for the failure: `bug` means the application is broken, `maintenance` means the test needed an update and the failure does not indicate an application problem. Absent until the investigation reaches a verdict, and absent when the flow was opted out of investigation. Pass issueId to issue.get for details.
199
205
  - `flows[].failure.diagnosis.issueId` — The id of the issue.
200
206
  - `flows[].failure.diagnosis.type` — One of: bug, maintenance
207
+ - `flows[].failure.optedOutOfInvestigation` — Present when the failure was marked "do not investigate" (DNI): it will get no bug or maintenance verdict.
208
+ - `flows[].failure.optedOutOfInvestigation.by` — One of: user, system
201
209
  - `flows[].flowId` — The id of the flow.
202
210
  - `flows[].status` — One of: failed, queued, running, passed, canceled
203
211
  - `url` — Absolute URL of the run page.