@agent-native/recap-cli 0.5.67 → 0.5.68-nightly-20261009011246

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/recap.js CHANGED
@@ -1008,9 +1008,9 @@ function compactWhitespace(text) {
1008
1008
  }
1009
1009
  export function sanitizeAgentFailureSummary(value, maxChars = AGENT_FAILURE_MAX_CHARS) {
1010
1010
  const redactSecretValues = (line) => line
1011
- .replace(/Authorization:\s*Bearer\s+[A-Za-z0-9._-]{8,}/gi, "Authorization: Bearer [redacted]")
1012
- .replace(/Bearer\s+[A-Za-z0-9._-]{8,}/gi, "Bearer [redacted]")
1013
- .replace(/Authorization:\s*(?!Bearer\s+\[redacted\])[^\s]+/gi, "Authorization: [redacted]")
1011
+ .replace(/Authorization\s*[:=]\s*Bearer\s+\S+/gi, "Authorization: Bearer [redacted]")
1012
+ .replace(/Bearer\s+\S+/gi, "Bearer [redacted]")
1013
+ .replace(/Authorization\s*[:=]\s*(?!Bearer\s+\[redacted\])(?:\S+\s+)?\S+/gi, "Authorization: [redacted]")
1014
1014
  .replace(/PLAN_RECAP_TOKEN=([^\s]+)/g, "PLAN_RECAP_TOKEN=[redacted]")
1015
1015
  .replace(/ANTHROPIC_API_KEY=([^\s]+)/g, "ANTHROPIC_API_KEY=[redacted]")
1016
1016
  .replace(/OPENAI_API_KEY=([^\s]+)/g, "OPENAI_API_KEY=[redacted]");
@@ -1751,6 +1751,32 @@ export function buildCommentBody(env = process.env) {
1751
1751
  const lightImageUrl = trustedRecapImageUrl(env.RECAP_LIGHT_IMAGE_URL || env.RECAP_IMAGE_URL, base);
1752
1752
  const darkImageUrl = trustedRecapImageUrl(env.RECAP_DARK_IMAGE_URL, base);
1753
1753
  const fallbackImageUrl = env.RECAP_SHOT_OK === "false" ? undefined : lightImageUrl || darkImageUrl;
1754
+ const readbackFailed = Object.hasOwn(env, "RECAP_READBACK_OK") && env.RECAP_READBACK_OK !== "true";
1755
+ if (readbackFailed) {
1756
+ const readbackDiagnostic = (env.RECAP_READBACK_REASON || "").trim() ||
1757
+ (env.RECAP_SHOT_REASON || "")
1758
+ .trim()
1759
+ .match(/^Published recap readback failed:\s*(.+)$/i)?.[1] ||
1760
+ "";
1761
+ const diagnostic = buildRecapFailureDiagnostic({
1762
+ readbackReason: readbackDiagnostic,
1763
+ });
1764
+ lines.push("### Visual recap — readback failed");
1765
+ lines.push("");
1766
+ lines.push("The recap was published, but the workflow could not verify it. Screenshot capture was skipped. Open the interactive recap directly:");
1767
+ lines.push("");
1768
+ lines.push(`**Open the [full interactive recap](${safeUrl})**`);
1769
+ if (diagnostic)
1770
+ lines.push("", "Diagnostic:", "", diagnostic);
1771
+ if (env.DIFF_HUGE === "true") {
1772
+ lines.push("", "_This PR has a large diff; the recap summarizes its main changes._");
1773
+ }
1774
+ if (markerPlanId)
1775
+ lines.push("", `<!-- plan-id: ${markerPlanId} -->`);
1776
+ if (headMarker)
1777
+ lines.push("", headMarker);
1778
+ return lines.join("\n");
1779
+ }
1754
1780
  if (!fallbackImageUrl) {
1755
1781
  const diagnostic = sanitizeAgentFailureSummary((env.RECAP_SHOT_REASON || "").trim(), 500) ||
1756
1782
  (env.RECAP_SHOT_OK === "true"
@@ -2624,6 +2650,52 @@ importPlaywright = defaultImportPlaywright) {
2624
2650
  });
2625
2651
  }
2626
2652
  const page = await context.newPage();
2653
+ const trustedAppOrigin = new URL(appUrl ?? url).origin;
2654
+ const planActionResponses = [];
2655
+ let pageErrorCount = 0;
2656
+ page.on("pageerror", () => {
2657
+ pageErrorCount += 1;
2658
+ });
2659
+ page.on("response", (response) => {
2660
+ const responseUrl = new URL(response.url());
2661
+ if (responseUrl.origin !== trustedAppOrigin)
2662
+ return;
2663
+ const action = responseUrl.pathname.match(/\/_agent-native\/actions\/(get-visual-plan|get-plan-access-status)$/)?.[1];
2664
+ if (action) {
2665
+ planActionResponses.push({ action, status: response.status() });
2666
+ }
2667
+ });
2668
+ const describeDocumentTimeout = async (error) => {
2669
+ let state;
2670
+ try {
2671
+ const browserState = await page.evaluate(() => {
2672
+ const bodyText = document.body?.innerText ?? "";
2673
+ return {
2674
+ readyState: document.readyState,
2675
+ hasPlanDocument: Boolean(document.querySelector("[data-plan-document]")),
2676
+ hasSignInPrompt: /sign in|log in/i.test(bodyText),
2677
+ hasAccessError: /belongs to|no access|forbidden/i.test(bodyText),
2678
+ };
2679
+ });
2680
+ state = { kind: "available", ...browserState };
2681
+ }
2682
+ catch {
2683
+ state = { kind: "unavailable" };
2684
+ }
2685
+ const actions = ["get-visual-plan", "get-plan-access-status"].map((action) => {
2686
+ const latest = [...planActionResponses]
2687
+ .reverse()
2688
+ .find((response) => response.action === action);
2689
+ return `${action}=${latest ? `HTTP ${latest.status}` : "not requested"}`;
2690
+ });
2691
+ const pageState = state.kind === "available"
2692
+ ? `document=${state.readyState}, plan-document=${state.hasPlanDocument}, sign-in=${state.hasSignInPrompt}, access-error=${state.hasAccessError}`
2693
+ : "document state unavailable";
2694
+ const clientErrors = pageErrorCount
2695
+ ? `, client runtime errors=${pageErrorCount}`
2696
+ : "";
2697
+ return new Error(`${errorMessage(error)}; recap readiness: ${actions.join(", ")}; ${pageState}${clientErrors}`);
2698
+ };
2627
2699
  for (let attempt = 1; attempt <= RECAP_DOCUMENT_LOAD_ATTEMPTS; attempt += 1) {
2628
2700
  try {
2629
2701
  const navigationResponse = await page.goto(captureUrl, {
@@ -2651,10 +2723,12 @@ importPlaywright = defaultImportPlaywright) {
2651
2723
  break;
2652
2724
  }
2653
2725
  catch (err) {
2654
- if (attempt === RECAP_DOCUMENT_LOAD_ATTEMPTS ||
2655
- !shouldRetryRecapDocumentLoad(err)) {
2726
+ if (!shouldRetryRecapDocumentLoad(err)) {
2656
2727
  throw err;
2657
2728
  }
2729
+ if (attempt === RECAP_DOCUMENT_LOAD_ATTEMPTS) {
2730
+ throw await describeDocumentTimeout(err);
2731
+ }
2658
2732
  process.stderr.write(`[recap shot] recap document did not become ready; retrying once\n`);
2659
2733
  }
2660
2734
  }
@@ -3105,9 +3179,15 @@ export function inferLocalRecapUrlFailureReason(input = {}) {
3105
3179
  export function buildRecapFailureDiagnostic(input) {
3106
3180
  const parts = [];
3107
3181
  const urlReason = sanitizeAgentFailureSummary(input.urlReason ?? "", 400);
3182
+ const readbackReason = sanitizeAgentFailureSummary(input.readbackReason ?? "", 400);
3183
+ const shotReason = sanitizeAgentFailureSummary(input.shotReason ?? "", 400);
3108
3184
  const failureSummary = sanitizeAgentFailureSummary(input.failureSummary ?? "", 900);
3109
3185
  if (urlReason)
3110
3186
  parts.push(`No plan URL: ${urlReason}`);
3187
+ if (readbackReason)
3188
+ parts.push(`Published recap readback failed: ${readbackReason}`);
3189
+ if (shotReason)
3190
+ parts.push(`Screenshot failed: ${shotReason}`);
3111
3191
  if (failureSummary)
3112
3192
  parts.push(`Agent output: ${failureSummary}`);
3113
3193
  return parts.join("\n\n");
@@ -3119,18 +3199,40 @@ export function recapCheckOutcome(input) {
3119
3199
  const diagnostic = buildRecapFailureDiagnostic({
3120
3200
  failureSummary: input.failureSummary,
3121
3201
  urlReason: input.urlReason,
3202
+ shotReason: input.readbackOk === false ? undefined : input.shotReason,
3203
+ readbackReason: input.readbackReason,
3122
3204
  });
3123
3205
  let text = diagnostic ? `### Diagnostic\n\n${diagnostic}` : "";
3124
3206
  let detailsUrl = input.workflowUrl;
3125
- if (input.planOk) {
3126
- const recapUrl = canonicalRecapUrl(input.planUrl, input.appUrl);
3207
+ const recapUrl = canonicalRecapUrl(input.planUrl, input.appUrl);
3208
+ if (recapUrl && input.readbackOk === false) {
3209
+ detailsUrl = recapUrl;
3210
+ conclusion = "failure";
3211
+ title = "Visual recap readback failed";
3212
+ summary =
3213
+ "The recap was published, but the workflow could not verify it. Screenshot capture was skipped. This informational check does not block the PR.";
3214
+ text = diagnostic
3215
+ ? `**[Open visual recap](${recapUrl})**\n\n### Diagnostic\n\n${diagnostic}\n\nScreenshot capture was skipped.`
3216
+ : `**[Open visual recap](${recapUrl})**\n\nScreenshot capture was skipped because published recap readback failed.`;
3217
+ }
3218
+ else if (recapUrl && input.screenshotOk === false) {
3219
+ detailsUrl = recapUrl;
3220
+ conclusion = "failure";
3221
+ title = "Visual recap screenshot failed";
3222
+ summary =
3223
+ "The recap was published, but its screenshot could not be captured. This informational check does not block the PR.";
3224
+ text = diagnostic
3225
+ ? `**[Open visual recap](${recapUrl})**\n\n### Diagnostic\n\n${diagnostic}`
3226
+ : `**[Open visual recap](${recapUrl})**\n\nScreenshot capture did not return both theme images.`;
3227
+ }
3228
+ else if (input.planOk) {
3127
3229
  if (recapUrl) {
3230
+ detailsUrl = recapUrl;
3128
3231
  conclusion = "success";
3129
3232
  title = "Visual recap ready";
3130
3233
  summary = input.huge
3131
3234
  ? "A summarized visual recap was generated for this large PR."
3132
3235
  : "A visual code-review recap was generated for this PR.";
3133
- detailsUrl = recapUrl;
3134
3236
  text = `**[Open visual recap](${recapUrl})**`;
3135
3237
  }
3136
3238
  else {
@@ -3215,13 +3317,26 @@ async function runCheckComplete(args) {
3215
3317
  "";
3216
3318
  const checkRunId = optionalArg(args, "check-run-id") ?? "";
3217
3319
  const planOk = boolFlag(args, "plan-ok");
3320
+ const screenshotOk = Object.hasOwn(args, "shot-ok")
3321
+ ? boolFlag(args, "shot-ok")
3322
+ : undefined;
3323
+ const readbackOk = Object.hasOwn(args, "readback-ok")
3324
+ ? boolFlag(args, "readback-ok")
3325
+ : undefined;
3218
3326
  const huge = boolFlag(args, "huge");
3219
3327
  const tiny = boolFlag(args, "tiny");
3220
3328
  const suppressed = boolFlag(args, "suppressed");
3221
3329
  const appUrl = optionalArg(args, "app-url") ?? process.env.PLAN_RECAP_APP_URL ?? "";
3222
3330
  let failureSummary = optionalArg(args, "failure-summary") ?? "";
3223
3331
  let urlReason = optionalArg(args, "url-reason") ?? "";
3224
- if (!planOk && !tiny && !suppressed) {
3332
+ const shotReason = optionalArg(args, "shot-reason") ?? "";
3333
+ const readbackReason = optionalArg(args, "readback-reason") ?? "";
3334
+ const planUrl = optionalArg(args, "plan-url") ?? "";
3335
+ const publishedRecap = canonicalRecapUrl(planUrl, appUrl);
3336
+ if (!planOk &&
3337
+ !tiny &&
3338
+ !suppressed &&
3339
+ !(publishedRecap && (readbackOk === false || screenshotOk === false))) {
3225
3340
  if (!failureSummary) {
3226
3341
  failureSummary = summarizeLocalAgentFailure({
3227
3342
  agent: optionalArg(args, "agent") ??
@@ -3239,12 +3354,16 @@ async function runCheckComplete(args) {
3239
3354
  }
3240
3355
  const outcome = recapCheckOutcome({
3241
3356
  planOk,
3242
- planUrl: optionalArg(args, "plan-url") ?? "",
3357
+ planUrl,
3243
3358
  appUrl,
3244
3359
  huge,
3245
3360
  tiny,
3246
3361
  suppressed,
3247
3362
  suppressedJson: optionalArg(args, "suppressed-json") ?? "",
3363
+ readbackOk,
3364
+ readbackReason,
3365
+ screenshotOk,
3366
+ shotReason,
3248
3367
  failureSummary,
3249
3368
  urlReason,
3250
3369
  workflowUrl: optionalArg(args, "workflow-url") ?? "",
@@ -3537,7 +3656,7 @@ Usage:
3537
3656
  $GITHUB_OUTPUT (check_run_id). repo/sha/token default to GITHUB_REPOSITORY /
3538
3657
  HEAD_SHA / GH_TOKEN (or GITHUB_TOKEN). Best-effort: warns and exits 0 on any
3539
3658
  API error without emitting an id.
3540
- npx @agent-native/recap-cli@latest recap check complete --check-run-id <id> [--repo owner/name] [--token <github-token>] [--plan-ok <bool>] [--plan-url <url>] [--app-url <url>] [--suppressed <bool>] [--suppressed-json <json>] [--huge <bool>] [--tiny <bool>] [--failure-summary <text>] [--url-reason <text>] [--workflow-url <url>]
3659
+ npx @agent-native/recap-cli@latest recap check complete --check-run-id <id> [--repo owner/name] [--token <github-token>] [--plan-ok <bool>] [--plan-url <url>] [--app-url <url>] [--readback-ok <bool>] [--readback-reason <text>] [--shot-ok <bool>] [--shot-reason <text>] [--suppressed <bool>] [--suppressed-json <json>] [--huge <bool>] [--tiny <bool>] [--failure-summary <text>] [--url-reason <text>] [--workflow-url <url>]
3541
3660
  Mark the "Visual Recap" check run completed with a computed
3542
3661
  conclusion/title/summary/text/details_url (success when the agent published a
3543
3662
  plan whose URL validates against --app-url; neutral/skipped otherwise).