@agent-native/recap-cli 0.5.66 → 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/pr-visual-recap-workflow.js +1 -1
- package/dist/recap.d.ts +7 -1
- package/dist/recap.d.ts.map +1 -1
- package/dist/recap.js +130 -11
- package/dist/recap.js.map +1 -1
- package/dist/workflows/pr-visual-recap.yml +270 -6
- package/package.json +1 -1
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
|
|
1012
|
-
.replace(/Bearer\s
|
|
1013
|
-
.replace(/Authorization
|
|
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 (
|
|
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
|
-
|
|
3126
|
-
|
|
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
|
-
|
|
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
|
|
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).
|