@agent-native/core 0.48.3 → 0.49.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.
Files changed (148) hide show
  1. package/dist/agent/context-xray/actions/context-evict.d.ts +1 -1
  2. package/dist/agent/context-xray/actions/context-pin.d.ts +1 -1
  3. package/dist/agent/context-xray/actions/context-report.d.ts +4 -4
  4. package/dist/agent/context-xray/actions/context-restore.d.ts +1 -1
  5. package/dist/application-state/handlers.d.ts +2 -2
  6. package/dist/application-state/handlers.d.ts.map +1 -1
  7. package/dist/cli/app-skill.d.ts +157 -0
  8. package/dist/cli/app-skill.d.ts.map +1 -0
  9. package/dist/cli/app-skill.js +17 -7
  10. package/dist/cli/app-skill.js.map +1 -1
  11. package/dist/cli/audit-agent-web.d.ts +2 -0
  12. package/dist/cli/audit-agent-web.d.ts.map +1 -0
  13. package/dist/cli/code-agent-connector.d.ts +17 -0
  14. package/dist/cli/code-agent-connector.d.ts.map +1 -0
  15. package/dist/cli/code.d.ts +66 -0
  16. package/dist/cli/code.d.ts.map +1 -0
  17. package/dist/cli/connect.d.ts +168 -0
  18. package/dist/cli/connect.d.ts.map +1 -0
  19. package/dist/cli/connect.js +118 -30
  20. package/dist/cli/connect.js.map +1 -1
  21. package/dist/cli/context-xray-local.d.ts +16 -0
  22. package/dist/cli/context-xray-local.d.ts.map +1 -0
  23. package/dist/cli/create-workspace.d.ts +8 -0
  24. package/dist/cli/create-workspace.d.ts.map +1 -0
  25. package/dist/cli/index.d.ts +3 -0
  26. package/dist/cli/index.d.ts.map +1 -0
  27. package/dist/cli/info.d.ts +2 -0
  28. package/dist/cli/info.d.ts.map +1 -0
  29. package/dist/cli/mcp-config-writers.d.ts +108 -0
  30. package/dist/cli/mcp-config-writers.d.ts.map +1 -0
  31. package/dist/cli/mcp-config-writers.js +143 -0
  32. package/dist/cli/mcp-config-writers.js.map +1 -1
  33. package/dist/cli/mcp.d.ts +16 -0
  34. package/dist/cli/mcp.d.ts.map +1 -0
  35. package/dist/cli/mcp.js +10 -10
  36. package/dist/cli/mcp.js.map +1 -1
  37. package/dist/cli/migrate.d.ts +38 -0
  38. package/dist/cli/migrate.d.ts.map +1 -0
  39. package/dist/cli/plan-local.d.ts +43 -0
  40. package/dist/cli/plan-local.d.ts.map +1 -0
  41. package/dist/cli/plan-publish-store.d.ts +62 -0
  42. package/dist/cli/plan-publish-store.d.ts.map +1 -0
  43. package/dist/cli/pr-visual-recap-workflow.d.ts +11 -0
  44. package/dist/cli/pr-visual-recap-workflow.d.ts.map +1 -0
  45. package/dist/cli/pr-visual-recap-workflow.js +1 -1
  46. package/dist/cli/pr-visual-recap-workflow.js.map +1 -1
  47. package/dist/cli/recap.d.ts +453 -0
  48. package/dist/cli/recap.d.ts.map +1 -0
  49. package/dist/cli/recap.js +228 -95
  50. package/dist/cli/recap.js.map +1 -1
  51. package/dist/cli/skills.d.ts +193 -0
  52. package/dist/cli/skills.d.ts.map +1 -0
  53. package/dist/cli/skills.js +518 -177
  54. package/dist/cli/skills.js.map +1 -1
  55. package/dist/cli/telemetry.d.ts +13 -0
  56. package/dist/cli/telemetry.d.ts.map +1 -0
  57. package/dist/cli/telemetry.js +115 -0
  58. package/dist/cli/telemetry.js.map +1 -0
  59. package/dist/cli/workspace-dev.d.ts +96 -0
  60. package/dist/cli/workspace-dev.d.ts.map +1 -0
  61. package/dist/client/AssistantChat.d.ts.map +1 -1
  62. package/dist/client/AssistantChat.js +10 -19
  63. package/dist/client/AssistantChat.js.map +1 -1
  64. package/dist/client/ErrorBoundary.d.ts.map +1 -1
  65. package/dist/client/ErrorBoundary.js +34 -1
  66. package/dist/client/ErrorBoundary.js.map +1 -1
  67. package/dist/client/blocks/library/AnnotatedCodeBlock.d.ts.map +1 -1
  68. package/dist/client/blocks/library/AnnotatedCodeBlock.js +15 -7
  69. package/dist/client/blocks/library/AnnotatedCodeBlock.js.map +1 -1
  70. package/dist/client/blocks/library/DiffBlock.d.ts.map +1 -1
  71. package/dist/client/blocks/library/DiffBlock.js +17 -10
  72. package/dist/client/blocks/library/DiffBlock.js.map +1 -1
  73. package/dist/client/blocks/library/annotation-rail.d.ts +5 -0
  74. package/dist/client/blocks/library/annotation-rail.d.ts.map +1 -1
  75. package/dist/client/blocks/library/annotation-rail.js +6 -0
  76. package/dist/client/blocks/library/annotation-rail.js.map +1 -1
  77. package/dist/client/blocks/types.d.ts +5 -0
  78. package/dist/client/blocks/types.d.ts.map +1 -1
  79. package/dist/client/blocks/types.js.map +1 -1
  80. package/dist/client/index.d.ts +1 -1
  81. package/dist/client/index.d.ts.map +1 -1
  82. package/dist/client/index.js +1 -1
  83. package/dist/client/index.js.map +1 -1
  84. package/dist/client/route-chunk-recovery.d.ts +17 -0
  85. package/dist/client/route-chunk-recovery.d.ts.map +1 -1
  86. package/dist/client/route-chunk-recovery.js +67 -0
  87. package/dist/client/route-chunk-recovery.js.map +1 -1
  88. package/dist/deploy/build.d.ts.map +1 -1
  89. package/dist/deploy/build.js +15 -71
  90. package/dist/deploy/build.js.map +1 -1
  91. package/dist/extensions/schema.d.ts +54 -54
  92. package/dist/extensions/slots/schema.d.ts +13 -13
  93. package/dist/file-upload/actions/upload-image.d.ts +4 -4
  94. package/dist/mcp/actions/create-org-service-token.d.ts +1 -1
  95. package/dist/mcp/actions/list-org-service-tokens.d.ts +7 -7
  96. package/dist/mcp/build-server.d.ts +12 -12
  97. package/dist/mcp/build-server.d.ts.map +1 -1
  98. package/dist/mcp/build-server.js.map +1 -1
  99. package/dist/mcp/connect-route.js +1 -1
  100. package/dist/mcp/connect-route.js.map +1 -1
  101. package/dist/mcp/oauth-route.d.ts +10 -0
  102. package/dist/mcp/oauth-route.d.ts.map +1 -1
  103. package/dist/mcp/oauth-route.js +34 -3
  104. package/dist/mcp/oauth-route.js.map +1 -1
  105. package/dist/mcp/oauth-store.d.ts +15 -1
  106. package/dist/mcp/oauth-store.d.ts.map +1 -1
  107. package/dist/mcp/oauth-store.js +60 -4
  108. package/dist/mcp/oauth-store.js.map +1 -1
  109. package/dist/mcp/oauth-token.d.ts +3 -1
  110. package/dist/mcp/oauth-token.d.ts.map +1 -1
  111. package/dist/mcp/oauth-token.js +78 -6
  112. package/dist/mcp/oauth-token.js.map +1 -1
  113. package/dist/mcp/server.d.ts.map +1 -1
  114. package/dist/mcp/server.js +8 -6
  115. package/dist/mcp/server.js.map +1 -1
  116. package/dist/observability/routes.d.ts +11 -11
  117. package/dist/org/handlers.d.ts +7 -11
  118. package/dist/org/handlers.d.ts.map +1 -1
  119. package/dist/secrets/schema.d.ts +7 -7
  120. package/dist/server/auth.d.ts.map +1 -1
  121. package/dist/server/auth.js +8 -5
  122. package/dist/server/auth.js.map +1 -1
  123. package/dist/server/csrf.d.ts +1 -1
  124. package/dist/server/csrf.d.ts.map +1 -1
  125. package/dist/server/onboarding-html.d.ts.map +1 -1
  126. package/dist/server/onboarding-html.js +12 -11
  127. package/dist/server/onboarding-html.js.map +1 -1
  128. package/dist/server/poll-events.d.ts +1 -1
  129. package/dist/server/security-headers.d.ts +1 -1
  130. package/dist/server/security-headers.d.ts.map +1 -1
  131. package/dist/server/ssr-handler.d.ts.map +1 -1
  132. package/dist/server/ssr-handler.js +42 -130
  133. package/dist/server/ssr-handler.js.map +1 -1
  134. package/dist/sharing/actions/list-resource-shares.d.ts +3 -3
  135. package/dist/sharing/actions/set-resource-visibility.d.ts +2 -2
  136. package/dist/sharing/actions/share-resource.d.ts +4 -4
  137. package/dist/sharing/actions/unshare-resource.d.ts +1 -1
  138. package/dist/sharing/schema.d.ts +12 -12
  139. package/dist/templates/workspace-core/.agents/skills/authentication/SKILL.md +2 -2
  140. package/dist/templates/workspace-core/.agents/skills/external-agents/SKILL.md +6 -6
  141. package/dist/templates/workspace-core/.agents/skills/external-agents/references/mcp-apps-embedding.md +2 -2
  142. package/dist/workspace-files/schema.d.ts +8 -8
  143. package/docs/content/external-agents.md +14 -0
  144. package/docs/content/plan-plugin.md +16 -7
  145. package/package.json +5 -1
  146. package/src/templates/workspace-core/.agents/skills/authentication/SKILL.md +2 -2
  147. package/src/templates/workspace-core/.agents/skills/external-agents/SKILL.md +6 -6
  148. package/src/templates/workspace-core/.agents/skills/external-agents/references/mcp-apps-embedding.md +2 -2
package/dist/cli/recap.js CHANGED
@@ -76,7 +76,7 @@ function optionalArg(args, key) {
76
76
  /** GitHub secrets the installed PR Visual Recap workflow needs. */
77
77
  export const PR_VISUAL_RECAP_SETUP = [
78
78
  "Required secrets:",
79
- " PLAN_RECAP_TOKEN — bearer token from `agent-native connect`",
79
+ " PLAN_RECAP_TOKEN — bearer token from `npx @agent-native/core@latest connect`",
80
80
  " ANTHROPIC_API_KEY — the LLM key for the default Claude Code backend",
81
81
  "Optional (only if you change defaults):",
82
82
  " OPENAI_API_KEY (secret) + VISUAL_RECAP_AGENT=codex (variable) — use Codex instead of Claude",
@@ -449,7 +449,7 @@ function runSetup(args) {
449
449
  else if (status === "missing") {
450
450
  lines.push(` ${name}: missing value.`);
451
451
  if (name === "PLAN_RECAP_TOKEN") {
452
- lines.push(` Run agent-native connect ${plan.appUrl} --client codex, then rerun this setup.`);
452
+ lines.push(` Run npx @agent-native/core@latest connect ${plan.appUrl} --client codex, then rerun this setup.`);
453
453
  }
454
454
  lines.push(` Or set manually: ${commandForMissingSecret(name, repo)}`);
455
455
  }
@@ -473,7 +473,7 @@ function runSetup(args) {
473
473
  }
474
474
  }
475
475
  lines.push("");
476
- lines.push(`Next: commit ${plan.workflowPath}, then run agent-native recap doctor.`);
476
+ lines.push(`Next: commit ${plan.workflowPath}, then run npx @agent-native/core@latest recap doctor.`);
477
477
  process.stdout.write(`${lines.join("\n")}\n`);
478
478
  }
479
479
  function runDoctor(args) {
@@ -495,7 +495,7 @@ function runDoctor(args) {
495
495
  if (!fs.existsSync(workflowFile)) {
496
496
  ok = false;
497
497
  lines.push(`[missing] Workflow missing: ${plan.workflowPath}.`);
498
- lines.push(" Run agent-native skills add visual-plan --with-github-action.");
498
+ lines.push(" Run npx @agent-native/skills add --skill visual-plan --with-github-action.");
499
499
  }
500
500
  else {
501
501
  const current = fs.readFileSync(workflowFile, "utf-8");
@@ -505,7 +505,7 @@ function runDoctor(args) {
505
505
  else {
506
506
  ok = false;
507
507
  lines.push(`[missing] Workflow differs from the bundled template: ${plan.workflowPath}.`);
508
- lines.push(" Run agent-native recap setup to refresh it.");
508
+ lines.push(" Run npx @agent-native/core@latest recap setup to refresh it.");
509
509
  }
510
510
  }
511
511
  if (plan.secretValues.PLAN_RECAP_TOKEN) {
@@ -513,7 +513,7 @@ function runDoctor(args) {
513
513
  }
514
514
  else {
515
515
  lines.push("[warn] Local Plans publish token not found.");
516
- lines.push(` Run agent-native connect ${plan.appUrl} --client codex to mint one.`);
516
+ lines.push(` Run npx @agent-native/core@latest connect ${plan.appUrl} --client codex to mint one.`);
517
517
  }
518
518
  if (repo) {
519
519
  lines.push(`[ok] GitHub repo detected: ${repo}.`);
@@ -649,6 +649,98 @@ export function diffContainsSecret(diffText, allowlist = []) {
649
649
  }
650
650
  return false;
651
651
  }
652
+ const AGENT_FAILURE_MAX_CHARS = 1200;
653
+ function compactWhitespace(text) {
654
+ return text.replace(/\s+/g, " ").trim();
655
+ }
656
+ export function sanitizeAgentFailureSummary(value, maxChars = AGENT_FAILURE_MAX_CHARS) {
657
+ const sanitizedLines = value
658
+ .replace(/\u001b\[[0-9;]*m/g, "")
659
+ .split("\n")
660
+ .map((line) => (lineLooksSecret(line) ? "[redacted sensitive line]" : line))
661
+ .map((line) => line
662
+ .replace(/Bearer\s+[A-Za-z0-9._-]{8,}/gi, "Bearer [redacted]")
663
+ .replace(/Authorization:\s*[^\s]+/gi, "Authorization: [redacted]")
664
+ .replace(/PLAN_RECAP_TOKEN=([^\s]+)/g, "PLAN_RECAP_TOKEN=[redacted]")
665
+ .replace(/ANTHROPIC_API_KEY=([^\s]+)/g, "ANTHROPIC_API_KEY=[redacted]")
666
+ .replace(/OPENAI_API_KEY=([^\s]+)/g, "OPENAI_API_KEY=[redacted]"))
667
+ .join("\n");
668
+ const compacted = compactWhitespace(sanitizedLines);
669
+ if (compacted.length <= maxChars)
670
+ return compacted;
671
+ return `${compacted.slice(0, Math.max(0, maxChars - 1)).trimEnd()}…`;
672
+ }
673
+ function collectStringFields(value, fields, seen = new Set()) {
674
+ if (!value || typeof value !== "object" || seen.has(value))
675
+ return [];
676
+ seen.add(value);
677
+ const obj = value;
678
+ const out = [];
679
+ for (const field of fields) {
680
+ const candidate = obj[field];
681
+ if (typeof candidate === "string" && candidate.trim()) {
682
+ out.push(candidate.trim());
683
+ }
684
+ }
685
+ for (const nested of Object.values(obj)) {
686
+ if (nested && typeof nested === "object") {
687
+ out.push(...collectStringFields(nested, fields, seen));
688
+ }
689
+ }
690
+ return out;
691
+ }
692
+ export function summarizeAgentResult(agent, resultText) {
693
+ const normalizedAgent = agent.toLowerCase();
694
+ const text = resultText.trim();
695
+ if (!text)
696
+ return "";
697
+ if (normalizedAgent === "claude") {
698
+ const obj = parseLastJsonObject(text);
699
+ if (obj) {
700
+ const candidates = [
701
+ ...collectStringFields(obj, [
702
+ "error",
703
+ "message",
704
+ "result",
705
+ "reason",
706
+ "subtype",
707
+ "type",
708
+ ]),
709
+ ].filter(Boolean);
710
+ const preferred = candidates.find((candidate) => /error|failed|denied|not found|unavailable|unauthorized|forbidden|tool/i.test(candidate)) ?? candidates[0];
711
+ if (preferred)
712
+ return sanitizeAgentFailureSummary(preferred);
713
+ }
714
+ }
715
+ if (normalizedAgent === "codex") {
716
+ const candidates = [];
717
+ for (const line of text.split("\n")) {
718
+ const trimmed = line.trim();
719
+ if (!trimmed.startsWith("{"))
720
+ continue;
721
+ try {
722
+ const obj = JSON.parse(trimmed);
723
+ candidates.push(...collectStringFields(obj, [
724
+ "error",
725
+ "message",
726
+ "text",
727
+ "delta",
728
+ "reason",
729
+ "type",
730
+ ]));
731
+ }
732
+ catch {
733
+ // Keep scanning.
734
+ }
735
+ }
736
+ const newestFirst = [...candidates].reverse();
737
+ const preferred = newestFirst.find((candidate) => candidate.length > 12 &&
738
+ /error|failed|denied|not found|unavailable|unauthorized|forbidden|tool/i.test(candidate)) ?? newestFirst[0];
739
+ if (preferred)
740
+ return sanitizeAgentFailureSummary(preferred);
741
+ }
742
+ return sanitizeAgentFailureSummary(text);
743
+ }
652
744
  /* -------------------------------------------------------------------------- */
653
745
  /* Bounded diff collection — was the workflow's "Collect bounded diff" step */
654
746
  /* -------------------------------------------------------------------------- */
@@ -1007,7 +1099,7 @@ export function readRepoSkillMd(cwd = process.cwd()) {
1007
1099
  return { text: fs.readFileSync(abs, "utf8"), source: rel };
1008
1100
  }
1009
1101
  }
1010
- throw new Error("Could not find visual-recap/SKILL.md. Run `agent-native skills add visual-plan` first.");
1102
+ throw new Error("Could not find visual-recap/SKILL.md. Run `npx @agent-native/skills add --skill visual-plan` first.");
1011
1103
  }
1012
1104
  function listRecapSkillReferenceFiles(skillDir) {
1013
1105
  const out = {};
@@ -1112,7 +1204,7 @@ export function buildRecapPrompt(input) {
1112
1204
  lines.push("## Local-Files Output (this is the only way to produce output)");
1113
1205
  lines.push("Do NOT call the `plan` MCP server, `create-visual-recap`, `import-visual-plan-source`, `update-visual-plan`, `export-visual-plan`, or any hosted Plan action. This mode exists so the recap data never goes to a Plan app database.");
1114
1206
  lines.push(`1. Create or replace the local MDX folder \`${localDir}\` with \`plan.mdx\` and optional \`canvas.mdx\`, \`prototype.mdx\`, and \`.plan-state.json\` derived ONLY from the real diff. Set \`kind: "recap"\` and \`localOnly: true\` in source metadata/state.`);
1115
- lines.push(`2. Run \`agent-native plan local preview --dir ${JSON.stringify(localDir)} --kind recap --out ${JSON.stringify(path.join(localDir, "preview.html"))}\` to validate the folder and generate the local preview.`);
1207
+ lines.push(`2. Run \`npx @agent-native/core@latest plan local preview --dir ${JSON.stringify(localDir)} --kind recap --out ${JSON.stringify(path.join(localDir, "preview.html"))}\` to validate the folder and generate the local preview.`);
1116
1208
  lines.push("3. Write the returned `url` from that command to `recap-url.txt` at the repo root, containing exactly one line. This file is the workflow's only hand-off.");
1117
1209
  }
1118
1210
  else {
@@ -1147,6 +1239,7 @@ export function buildRecapPrompt(input) {
1147
1239
  /* -------------------------------------------------------------------------- */
1148
1240
  const MARKER = "<!-- pr-visual-recap -->";
1149
1241
  const RECAP_IMAGE_URL_PATH_PATTERN = /\/_agent-native\/recap-image\/[0-9a-f]{32,128}\.png$/;
1242
+ const RECAP_SCREENSHOT_QUERY_PARAM = "recapScreenshot";
1150
1243
  function repoParts(repoFullName) {
1151
1244
  const [owner, repo] = repoFullName.split("/");
1152
1245
  if (!owner || !repo)
@@ -1237,9 +1330,6 @@ function originOf(url) {
1237
1330
  /** Build the sticky comment body from the workflow's environment. */
1238
1331
  export function buildCommentBody(env = process.env) {
1239
1332
  const lines = [MARKER];
1240
- // Short head SHA for the "as of" freshness line.
1241
- const headSha = (env.HEAD_SHA || "").trim();
1242
- const headShort = headSha ? headSha.slice(0, 7) : "";
1243
1333
  // Last-known plan id threaded from the previous run (supplied via PREV_PLAN_ID
1244
1334
  // when the comment is rebuilt from scratch, or parsed from the env on upsert).
1245
1335
  // We always emit the plan-id marker when any plan id is known so that a
@@ -1260,21 +1350,17 @@ export function buildCommentBody(env = process.env) {
1260
1350
  lines.push("The recap was **suppressed** because the diff matched a secret/credential pattern. No plan was published.");
1261
1351
  lines.push("");
1262
1352
  lines.push(`Reason: \`${reason}\`.`);
1263
- if (headShort)
1264
- lines.push("", `_As of \`${headShort}\`_`);
1265
1353
  if (prevPlanId)
1266
1354
  lines.push("", `<!-- plan-id: ${prevPlanId} -->`);
1267
1355
  return lines.join("\n");
1268
1356
  }
1269
1357
  // Tiny diffs aren't worth a recap. Refresh an existing sticky comment to this
1270
- // state (the workflow only updates, never creates, on tiny) so it never lingers
1271
- // pointing at a stale head SHA.
1358
+ // state (the workflow only updates, never creates, on tiny) so stale recap
1359
+ // links do not linger on no-op changes.
1272
1360
  if (env.DIFF_TINY === "true") {
1273
1361
  lines.push("### Visual recap — skipped (diff too small)");
1274
1362
  lines.push("");
1275
1363
  lines.push("The change in this pull request is too small to be worth a visual recap. This is informational only and does **not** block the PR.");
1276
- if (headShort)
1277
- lines.push("", `_As of \`${headShort}\`_`);
1278
1364
  if (prevPlanId)
1279
1365
  lines.push("", `<!-- plan-id: ${prevPlanId} -->`);
1280
1366
  return lines.join("\n");
@@ -1299,16 +1385,19 @@ export function buildCommentBody(env = process.env) {
1299
1385
  const markerPlanId = trustedPlanId ?? prevPlanId;
1300
1386
  if (!safeUrl) {
1301
1387
  const authFailed = env.RECAP_AUTH_FAILED === "true";
1388
+ const agentSummary = sanitizeAgentFailureSummary((env.RECAP_AGENT_SUMMARY || "").trim(), 800);
1302
1389
  lines.push("### Visual recap — generation failed");
1303
1390
  lines.push("");
1304
1391
  if (authFailed) {
1305
- lines.push("Recap authentication failed — the `PLAN_RECAP_TOKEN` secret may be expired or revoked. Re-mint it with `agent-native connect` and update the repo secret.");
1392
+ lines.push("Recap authentication failed — the `PLAN_RECAP_TOKEN` secret may be expired or revoked. Re-mint it with `npx @agent-native/core@latest reconnect <app-url>` (or `npx @agent-native/core@latest connect <app-url>` for first-time setup) and update the repo secret.");
1306
1393
  }
1307
1394
  else {
1308
1395
  lines.push("The visual recap could not be generated for this pull request. This is informational only and does **not** block the PR.");
1396
+ if (agentSummary) {
1397
+ lines.push("");
1398
+ lines.push(`Agent output: ${agentSummary}`);
1399
+ }
1309
1400
  }
1310
- if (headShort)
1311
- lines.push("", `_As of \`${headShort}\`_`);
1312
1401
  // Keep a link to the last-good recap so reviewers are not left in the dark.
1313
1402
  if (prevPlanId && base) {
1314
1403
  const prevSafeUrl = `${base}/recaps/${prevPlanId}`;
@@ -1338,8 +1427,6 @@ export function buildCommentBody(env = process.env) {
1338
1427
  lines.push("");
1339
1428
  lines.push("> Large diff — this recap is a **summarized** view (top files + schema/API deltas).");
1340
1429
  }
1341
- if (headShort)
1342
- lines.push("", `_As of \`${headShort}\`_`);
1343
1430
  lines.push("", `<!-- plan-id: ${planId} -->`);
1344
1431
  return lines.join("\n");
1345
1432
  }
@@ -1494,6 +1581,13 @@ export async function uploadRecapImage(input) {
1494
1581
  }
1495
1582
  /** Mirrors RECAP_IMAGE_MAX_BYTES on the server — the route rejects larger PNGs. */
1496
1583
  const RECAP_SHOT_MAX_BYTES = 5 * 1024 * 1024;
1584
+ const RECAP_SHOT_WIDTH = 950;
1585
+ const RECAP_SHOT_MAX_HEIGHT = 2000;
1586
+ const RECAP_SHOT_VIEWPORT = {
1587
+ width: RECAP_SHOT_WIDTH,
1588
+ height: RECAP_SHOT_MAX_HEIGHT,
1589
+ };
1590
+ const RECAP_SHOT_DEVICE_SCALE_FACTOR = 1;
1497
1591
  async function defaultImportPlaywright() {
1498
1592
  try {
1499
1593
  return (await import("playwright"));
@@ -1502,6 +1596,16 @@ async function defaultImportPlaywright() {
1502
1596
  return (await import("@playwright/test"));
1503
1597
  }
1504
1598
  }
1599
+ export function withRecapScreenshotParams(url) {
1600
+ try {
1601
+ const parsed = new URL(url);
1602
+ parsed.searchParams.set(RECAP_SCREENSHOT_QUERY_PARAM, "1");
1603
+ return parsed.toString();
1604
+ }
1605
+ catch {
1606
+ return url;
1607
+ }
1608
+ }
1505
1609
  export async function runShot(args,
1506
1610
  /** @internal test seam — defaults to dynamic playwright import */
1507
1611
  importPlaywright = defaultImportPlaywright) {
@@ -1534,6 +1638,7 @@ importPlaywright = defaultImportPlaywright) {
1534
1638
  return;
1535
1639
  }
1536
1640
  }
1641
+ const captureUrl = withRecapScreenshotParams(url);
1537
1642
  let chromium;
1538
1643
  try {
1539
1644
  ({ chromium } = await importPlaywright());
@@ -1551,8 +1656,8 @@ importPlaywright = defaultImportPlaywright) {
1551
1656
  try {
1552
1657
  browser = await chromium.launch({ args: ["--no-sandbox"] });
1553
1658
  const context = await browser.newContext({
1554
- viewport: { width: 1450, height: 1450 },
1555
- deviceScaleFactor: 2,
1659
+ viewport: RECAP_SHOT_VIEWPORT,
1660
+ deviceScaleFactor: RECAP_SHOT_DEVICE_SCALE_FACTOR,
1556
1661
  });
1557
1662
  if (attachToken) {
1558
1663
  // Attach the bearer ONLY to same-origin requests. Context-wide
@@ -1573,7 +1678,7 @@ importPlaywright = defaultImportPlaywright) {
1573
1678
  });
1574
1679
  }
1575
1680
  const page = await context.newPage();
1576
- await page.goto(url, { waitUntil: "networkidle", timeout: 45_000 });
1681
+ await page.goto(captureUrl, { waitUntil: "networkidle", timeout: 45_000 });
1577
1682
  const selectors = [
1578
1683
  "[data-plan-document]",
1579
1684
  "[data-plan-block]",
@@ -1593,63 +1698,53 @@ importPlaywright = defaultImportPlaywright) {
1593
1698
  }
1594
1699
  }
1595
1700
  await page.waitForTimeout(matched ? 1_200 : 500);
1596
- // Zoom out slightly so more content fits. Keep the plan title (h1) in frame:
1597
- // the recap reads better led by its own title than cropped to the body.
1598
1701
  await page.evaluate(() => {
1599
- document.documentElement.style.zoom = "90%";
1702
+ document.documentElement.style.zoom = "100%";
1600
1703
  });
1704
+ const measuredHeight = await page.evaluate((maxHeight) => {
1705
+ const readHeights = (selectors) => {
1706
+ const result = [];
1707
+ for (const selector of selectors) {
1708
+ const el = document.querySelector(selector);
1709
+ if (!el)
1710
+ continue;
1711
+ const rect = el.getBoundingClientRect();
1712
+ result.push(el.scrollHeight, rect.top + el.scrollHeight);
1713
+ }
1714
+ return result;
1715
+ };
1716
+ const documentHeights = readHeights([
1717
+ ".plan-document-shell",
1718
+ ".plan-document-flow",
1719
+ ]);
1720
+ const contentHeights = documentHeights.some((height) => height > 0)
1721
+ ? documentHeights
1722
+ : readHeights(["[data-plan-document]", ".plan-content-surface"]);
1723
+ const fallbackHeights = [
1724
+ document.querySelector("[data-plan-reader]")
1725
+ ?.scrollHeight ?? 0,
1726
+ document.scrollingElement?.scrollHeight ?? 0,
1727
+ document.documentElement.scrollHeight,
1728
+ document.body?.scrollHeight ?? 0,
1729
+ ];
1730
+ const heights = contentHeights.some((height) => height > 0)
1731
+ ? contentHeights
1732
+ : fallbackHeights;
1733
+ const documentHeight = Math.ceil(Math.max(...heights.filter((height) => Number.isFinite(height))));
1734
+ return Math.max(1, Math.min(maxHeight, documentHeight || maxHeight));
1735
+ }, RECAP_SHOT_MAX_HEIGHT);
1736
+ await page.setViewportSize({
1737
+ width: RECAP_SHOT_WIDTH,
1738
+ height: measuredHeight,
1739
+ });
1740
+ await page.waitForTimeout(250);
1601
1741
  await page.screenshot({ path: out });
1602
- // If the captured PNG is over the upload cap, retry at half the pixel
1603
- // density (deviceScaleFactor 1) to produce a smaller file.
1742
+ // If the captured PNG is over the upload cap, remove it so the upload step
1743
+ // sees no file and the comment falls back to a link-only recap.
1604
1744
  const firstSize = fs.existsSync(out) ? fs.statSync(out).size : 0;
1605
1745
  if (firstSize > RECAP_SHOT_MAX_BYTES) {
1606
- process.stderr.write(`[recap shot] PNG is ${firstSize} bytes (cap ${RECAP_SHOT_MAX_BYTES}) — retrying at deviceScaleFactor 1\n`);
1607
- const ctx2 = await browser.newContext({
1608
- viewport: { width: 1450, height: 1450 },
1609
- deviceScaleFactor: 1,
1610
- });
1611
- if (attachToken) {
1612
- const appOrigin = new URL(appUrl).origin;
1613
- await ctx2.route("**/*", async (route) => {
1614
- const request = route.request();
1615
- if (new URL(request.url()).origin === appOrigin) {
1616
- await route.continue({
1617
- headers: {
1618
- ...request.headers(),
1619
- authorization: `Bearer ${token}`,
1620
- },
1621
- });
1622
- }
1623
- else {
1624
- await route.continue();
1625
- }
1626
- });
1627
- }
1628
- const page2 = await ctx2.newPage();
1629
- await page2.goto(url, { waitUntil: "networkidle", timeout: 45_000 });
1630
- for (const sel of selectors) {
1631
- try {
1632
- await page2.waitForSelector(sel, {
1633
- timeout: 6_000,
1634
- state: "visible",
1635
- });
1636
- break;
1637
- }
1638
- catch {
1639
- /* try the next selector */
1640
- }
1641
- }
1642
- await page2.waitForTimeout(matched ? 1_200 : 500);
1643
- await page2.evaluate(() => {
1644
- document.documentElement.style.zoom = "90%";
1645
- });
1646
- await page2.screenshot({ path: out });
1647
- const retrySize = fs.existsSync(out) ? fs.statSync(out).size : 0;
1648
- if (retrySize > RECAP_SHOT_MAX_BYTES) {
1649
- process.stderr.write(`[recap shot] retry PNG is still ${retrySize} bytes — skipping upload\n`);
1650
- // Remove oversized file so the upload step sees no file.
1651
- fs.unlinkSync(out);
1652
- }
1746
+ process.stderr.write(`[recap shot] PNG is ${firstSize} bytes (cap ${RECAP_SHOT_MAX_BYTES}) — skipping upload\n`);
1747
+ fs.unlinkSync(out);
1653
1748
  }
1654
1749
  captured = fs.existsSync(out);
1655
1750
  await browser.close();
@@ -1703,7 +1798,7 @@ async function runComment(args, sub) {
1703
1798
  process.stdout.write(`${JSON.stringify(result)}\n`);
1704
1799
  return;
1705
1800
  }
1706
- throw new Error("Usage: agent-native recap comment <find-plan-id|upsert> --repo owner/name --issue n --token token");
1801
+ throw new Error("Usage: npx @agent-native/core@latest recap comment <find-plan-id|upsert> --repo owner/name --issue n --token token");
1707
1802
  }
1708
1803
  /**
1709
1804
  * Files that, if a PR touches them, would let that PR rewrite what the trusted
@@ -2023,7 +2118,9 @@ export function recapCheckOutcome(input) {
2023
2118
  let conclusion = "neutral";
2024
2119
  let title = "Visual recap not generated";
2025
2120
  let summary = "The visual recap did not produce a plan URL. This is informational only and does not block the PR.";
2026
- let text = "";
2121
+ let text = input.failureSummary
2122
+ ? `Agent output: ${sanitizeAgentFailureSummary(input.failureSummary)}`
2123
+ : "";
2027
2124
  let detailsUrl = input.workflowUrl;
2028
2125
  if (input.planOk) {
2029
2126
  const recapUrl = canonicalRecapUrl(input.planUrl, input.appUrl);
@@ -2049,6 +2146,7 @@ export function recapCheckOutcome(input) {
2049
2146
  conclusion = "skipped";
2050
2147
  title = "Visual recap skipped";
2051
2148
  summary = "The diff is too small to need a visual recap.";
2149
+ text = "";
2052
2150
  }
2053
2151
  else if (input.suppressed) {
2054
2152
  let reason = "potential secret in diff";
@@ -2063,6 +2161,11 @@ export function recapCheckOutcome(input) {
2063
2161
  conclusion = "skipped";
2064
2162
  title = "Visual recap suppressed";
2065
2163
  summary = `No recap was published because ${reason}.`;
2164
+ text = "";
2165
+ }
2166
+ else if (input.failureSummary) {
2167
+ summary =
2168
+ "The visual recap agent ran but did not produce a plan URL. See the agent output below.";
2066
2169
  }
2067
2170
  return { conclusion, title, summary, text, detailsUrl };
2068
2171
  }
@@ -2134,6 +2237,7 @@ async function runCheckComplete(args) {
2134
2237
  tiny: boolFlag(args, "tiny"),
2135
2238
  suppressed: boolFlag(args, "suppressed"),
2136
2239
  suppressedJson: optionalArg(args, "suppressed-json") ?? "",
2240
+ failureSummary: optionalArg(args, "failure-summary") ?? "",
2137
2241
  workflowUrl: optionalArg(args, "workflow-url") ?? "",
2138
2242
  });
2139
2243
  try {
@@ -2169,7 +2273,7 @@ async function runCheck(args, sub) {
2169
2273
  await runCheckComplete(args);
2170
2274
  return;
2171
2275
  }
2172
- throw new Error("Usage: agent-native recap check <start|complete> [flags] (see `recap help`)");
2276
+ throw new Error("Usage: npx @agent-native/core@latest recap check <start|complete> [flags] (see `recap help`)");
2173
2277
  }
2174
2278
  /** Parse the last top-level JSON object from a possibly-noisy stdout dump. */
2175
2279
  function parseLastJsonObject(text) {
@@ -2342,30 +2446,52 @@ async function runUsage(args) {
2342
2446
  done({ ok: false, reason: `record-recap-usage error: ${String(err)}` });
2343
2447
  }
2344
2448
  }
2345
- const HELP = `agent-native recap — PR visual recap helpers (used by the GitHub Action)
2449
+ function writeGitHubOutput(name, value) {
2450
+ const out = process.env.GITHUB_OUTPUT;
2451
+ if (!out)
2452
+ return;
2453
+ const delimiter = `__RECAP_${name}_${process.pid}_${Date.now()}__`;
2454
+ fs.appendFileSync(out, `${name}<<${delimiter}\n${value}\n${delimiter}\n`);
2455
+ }
2456
+ function runAgentSummary(args) {
2457
+ const agent = optionalArg(args, "agent") ?? "claude";
2458
+ const resultFile = stringArg(args, "result-file");
2459
+ let raw = "";
2460
+ try {
2461
+ raw = fs.readFileSync(path.resolve(resultFile), "utf8");
2462
+ }
2463
+ catch (err) {
2464
+ raw = `could not read ${resultFile}: ${String(err)}`;
2465
+ }
2466
+ const summary = summarizeAgentResult(agent, raw);
2467
+ writeGitHubOutput("summary", summary);
2468
+ process.stdout.write(`${JSON.stringify({ ok: Boolean(summary), summary })}\n`);
2469
+ }
2470
+ const HELP = `npx @agent-native/core@latest recap — PR visual recap helpers (used by the GitHub Action)
2346
2471
 
2347
2472
  Usage:
2348
- agent-native recap setup [--repo owner/name] [--agent claude|codex] [--app-url <url>] [--skip-secrets] [--dry-run] [--force]
2349
- agent-native recap doctor [--repo owner/name] [--agent claude|codex] [--app-url <url>]
2350
- agent-native recap collect-diff --base <baseSha> --head <headSha> [--out recap.diff] [--stat recap.stat]
2351
- agent-native recap mcp-config --agent claude|codex --app-url <url> [--out <path>]
2352
- agent-native recap scan --diff <path>
2353
- agent-native recap build-prompt --pr <n> [--repo owner/name] [--head <sha>] [--app-url <url>] [--diff <path>] [--stat <path>] [--prev-plan-id <id>] [--huge] [--local-files] [--local-dir <folder>] [--skill-source auto|latest|repo] [--out <path>]
2354
- agent-native recap shot --url <planUrl> [--token <planToken>] [--app-url <url>] [--out recap.png]
2355
- agent-native recap usage --plan-url <planUrl> --result-file <path> --app-url <url> --token <planToken> [--agent claude|codex] [--model <id>]
2356
- agent-native recap comment <find-plan-id|upsert> --repo owner/name --issue <n> --token <github-token>
2357
- agent-native recap check start [--repo owner/name] [--sha <headSha>] [--token <github-token>] [--workflow-url <url>]
2473
+ npx @agent-native/core@latest recap setup [--repo owner/name] [--agent claude|codex] [--app-url <url>] [--skip-secrets] [--dry-run] [--force]
2474
+ npx @agent-native/core@latest recap doctor [--repo owner/name] [--agent claude|codex] [--app-url <url>]
2475
+ npx @agent-native/core@latest recap collect-diff --base <baseSha> --head <headSha> [--out recap.diff] [--stat recap.stat]
2476
+ npx @agent-native/core@latest recap mcp-config --agent claude|codex --app-url <url> [--out <path>]
2477
+ npx @agent-native/core@latest recap scan --diff <path>
2478
+ npx @agent-native/core@latest recap build-prompt --pr <n> [--repo owner/name] [--head <sha>] [--app-url <url>] [--diff <path>] [--stat <path>] [--prev-plan-id <id>] [--huge] [--local-files] [--local-dir <folder>] [--skill-source auto|latest|repo] [--out <path>]
2479
+ npx @agent-native/core@latest recap shot --url <planUrl> [--token <planToken>] [--app-url <url>] [--out recap.png]
2480
+ npx @agent-native/core@latest recap usage --plan-url <planUrl> --result-file <path> --app-url <url> --token <planToken> [--agent claude|codex] [--model <id>]
2481
+ npx @agent-native/core@latest recap agent-summary --result-file <path> [--agent claude|codex]
2482
+ npx @agent-native/core@latest recap comment <find-plan-id|upsert> --repo owner/name --issue <n> --token <github-token>
2483
+ npx @agent-native/core@latest recap check start [--repo owner/name] [--sha <headSha>] [--token <github-token>] [--workflow-url <url>]
2358
2484
  Create the in-progress "Visual Recap" GitHub check run and write its id to
2359
2485
  $GITHUB_OUTPUT (check_run_id). repo/sha/token default to GITHUB_REPOSITORY /
2360
2486
  HEAD_SHA / GH_TOKEN (or GITHUB_TOKEN). Best-effort: warns and exits 0 on any
2361
2487
  API error without emitting an id.
2362
- agent-native 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>] [--workflow-url <url>]
2488
+ npx @agent-native/core@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>] [--workflow-url <url>]
2363
2489
  Mark the "Visual Recap" check run completed with a computed
2364
2490
  conclusion/title/summary/text/details_url (success when the agent published a
2365
2491
  plan whose URL validates against --app-url; neutral/skipped otherwise).
2366
2492
  repo/token/app-url default to GITHUB_REPOSITORY / GH_TOKEN / PLAN_RECAP_APP_URL.
2367
2493
  Best-effort: warns and exits 0 on any API error.
2368
- agent-native recap gate
2494
+ npx @agent-native/core@latest recap gate
2369
2495
  The PR Visual Recap security gate. Decides whether to run the recap at all
2370
2496
  and which (normalized) backend agent to use. Reads the pull_request payload
2371
2497
  from $GITHUB_EVENT_PATH, the secret-presence/agent/model signals from the
@@ -2377,12 +2503,16 @@ Usage:
2377
2503
  packages/core, .claude/**, CLAUDE.md, AGENTS.md, .mcp.json) — failing CLOSED
2378
2504
  on any file-list error. Writes run=<true|false> and agent=<claude|codex> to
2379
2505
  $GITHUB_OUTPUT.
2380
- agent-native recap setup
2506
+ npx @agent-native/core@latest recap agent-summary
2507
+ Read the captured Claude/Codex result file and write a sanitized one-line
2508
+ summary to stdout and $GITHUB_OUTPUT (summary). Used only when no plan URL
2509
+ was produced, so PR comments/checks explain the actual failure.
2510
+ npx @agent-native/core@latest recap setup
2381
2511
  Write/refresh .github/workflows/pr-visual-recap.yml, then configure GitHub
2382
2512
  Actions secrets and variables with gh when values are available from env or
2383
2513
  the local Plans publish-token store. Missing values are printed as exact next
2384
2514
  commands; secret values are sent to gh through stdin, never argv.
2385
- agent-native recap doctor
2515
+ npx @agent-native/core@latest recap doctor
2386
2516
  Check workflow presence/drift, local Plans publish-token availability, gh
2387
2517
  repo access, and required GitHub Actions secrets for the selected backend.
2388
2518
  `;
@@ -2414,6 +2544,9 @@ export async function runRecap(argv) {
2414
2544
  case "usage":
2415
2545
  await runUsage(args);
2416
2546
  return;
2547
+ case "agent-summary":
2548
+ runAgentSummary(args);
2549
+ return;
2417
2550
  case "comment":
2418
2551
  await runComment(parseArgs(rest.slice(1)), rest[0] ?? "");
2419
2552
  return;