@agent-native/core 0.49.2 → 0.49.4

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 (107) hide show
  1. package/dist/agent/engine/builder-engine.d.ts +1 -1
  2. package/dist/agent/engine/builder-engine.d.ts.map +1 -1
  3. package/dist/agent/engine/index.d.ts +1 -1
  4. package/dist/agent/engine/index.d.ts.map +1 -1
  5. package/dist/agent/engine/index.js +1 -1
  6. package/dist/agent/engine/index.js.map +1 -1
  7. package/dist/agent/engine/registry.d.ts +1 -0
  8. package/dist/agent/engine/registry.d.ts.map +1 -1
  9. package/dist/agent/engine/registry.js +11 -0
  10. package/dist/agent/engine/registry.js.map +1 -1
  11. package/dist/agent/model-config.d.ts +2 -2
  12. package/dist/agent/model-config.d.ts.map +1 -1
  13. package/dist/agent/model-config.js +2 -6
  14. package/dist/agent/model-config.js.map +1 -1
  15. package/dist/agent/production-agent.d.ts.map +1 -1
  16. package/dist/agent/production-agent.js +3 -2
  17. package/dist/agent/production-agent.js.map +1 -1
  18. package/dist/cli/code-agent-executor.d.ts.map +1 -1
  19. package/dist/cli/code-agent-executor.js +3 -2
  20. package/dist/cli/code-agent-executor.js.map +1 -1
  21. package/dist/cli/connect.d.ts.map +1 -1
  22. package/dist/cli/connect.js +117 -33
  23. package/dist/cli/connect.js.map +1 -1
  24. package/dist/cli/pr-visual-recap-workflow.d.ts +1 -1
  25. package/dist/cli/pr-visual-recap-workflow.d.ts.map +1 -1
  26. package/dist/cli/pr-visual-recap-workflow.js +1 -1
  27. package/dist/cli/pr-visual-recap-workflow.js.map +1 -1
  28. package/dist/cli/recap.d.ts +20 -0
  29. package/dist/cli/recap.d.ts.map +1 -1
  30. package/dist/cli/recap.js +256 -40
  31. package/dist/cli/recap.js.map +1 -1
  32. package/dist/cli/skills.d.ts +2 -2
  33. package/dist/cli/skills.d.ts.map +1 -1
  34. package/dist/cli/skills.js +5 -5
  35. package/dist/cli/skills.js.map +1 -1
  36. package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
  37. package/dist/client/MultiTabAssistantChat.js +0 -7
  38. package/dist/client/MultiTabAssistantChat.js.map +1 -1
  39. package/dist/client/blocks/library/annotation-rail.d.ts +14 -0
  40. package/dist/client/blocks/library/annotation-rail.d.ts.map +1 -1
  41. package/dist/client/blocks/library/annotation-rail.js +64 -2
  42. package/dist/client/blocks/library/annotation-rail.js.map +1 -1
  43. package/dist/client/extensions/EmbeddedExtension.d.ts.map +1 -1
  44. package/dist/client/extensions/EmbeddedExtension.js +14 -6
  45. package/dist/client/extensions/EmbeddedExtension.js.map +1 -1
  46. package/dist/client/extensions/ExtensionViewer.d.ts.map +1 -1
  47. package/dist/client/extensions/ExtensionViewer.js +16 -7
  48. package/dist/client/extensions/ExtensionViewer.js.map +1 -1
  49. package/dist/client/extensions/extension-load-error.d.ts +7 -0
  50. package/dist/client/extensions/extension-load-error.d.ts.map +1 -0
  51. package/dist/client/extensions/extension-load-error.js +19 -0
  52. package/dist/client/extensions/extension-load-error.js.map +1 -0
  53. package/dist/client/use-chat-models.d.ts.map +1 -1
  54. package/dist/client/use-chat-models.js +0 -5
  55. package/dist/client/use-chat-models.js.map +1 -1
  56. package/dist/extensions/routes.d.ts.map +1 -1
  57. package/dist/extensions/routes.js +7 -3
  58. package/dist/extensions/routes.js.map +1 -1
  59. package/dist/file-upload/actions/upload-image.js +1 -1
  60. package/dist/file-upload/actions/upload-image.js.map +1 -1
  61. package/dist/file-upload/pre-upload-attachments.js +1 -1
  62. package/dist/file-upload/pre-upload-attachments.js.map +1 -1
  63. package/dist/file-upload/registry.js +1 -1
  64. package/dist/file-upload/registry.js.map +1 -1
  65. package/dist/integrations/webhook-handler.d.ts.map +1 -1
  66. package/dist/integrations/webhook-handler.js +3 -2
  67. package/dist/integrations/webhook-handler.js.map +1 -1
  68. package/dist/jobs/scheduler.d.ts.map +1 -1
  69. package/dist/jobs/scheduler.js +3 -2
  70. package/dist/jobs/scheduler.js.map +1 -1
  71. package/dist/mcp/connect-route.d.ts +2 -0
  72. package/dist/mcp/connect-route.d.ts.map +1 -1
  73. package/dist/mcp/connect-route.js +3 -0
  74. package/dist/mcp/connect-route.js.map +1 -1
  75. package/dist/mcp/server.js +1 -1
  76. package/dist/mcp/server.js.map +1 -1
  77. package/dist/observability/evals.d.ts.map +1 -1
  78. package/dist/observability/evals.js +5 -3
  79. package/dist/observability/evals.js.map +1 -1
  80. package/dist/scripts/agent-engines/list-agent-engines.d.ts.map +1 -1
  81. package/dist/scripts/agent-engines/list-agent-engines.js +6 -3
  82. package/dist/scripts/agent-engines/list-agent-engines.js.map +1 -1
  83. package/dist/scripts/agent-engines/manage-agent-engine.d.ts.map +1 -1
  84. package/dist/scripts/agent-engines/manage-agent-engine.js +5 -1
  85. package/dist/scripts/agent-engines/manage-agent-engine.js.map +1 -1
  86. package/dist/scripts/agent-engines/set-agent-engine.d.ts.map +1 -1
  87. package/dist/scripts/agent-engines/set-agent-engine.js +7 -2
  88. package/dist/scripts/agent-engines/set-agent-engine.js.map +1 -1
  89. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  90. package/dist/server/agent-chat-plugin.js +14 -4
  91. package/dist/server/agent-chat-plugin.js.map +1 -1
  92. package/dist/server/complete-text.d.ts.map +1 -1
  93. package/dist/server/complete-text.js +3 -2
  94. package/dist/server/complete-text.js.map +1 -1
  95. package/dist/server/core-routes-plugin.d.ts +2 -0
  96. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  97. package/dist/server/core-routes-plugin.js +1 -0
  98. package/dist/server/core-routes-plugin.js.map +1 -1
  99. package/dist/templates/workspace-core/.agents/skills/external-agents/SKILL.md +7 -6
  100. package/dist/triggers/dispatcher.d.ts.map +1 -1
  101. package/dist/triggers/dispatcher.js +3 -2
  102. package/dist/triggers/dispatcher.js.map +1 -1
  103. package/docs/content/external-agents.md +1 -1
  104. package/docs/content/plan-plugin.md +2 -2
  105. package/docs/content/pr-visual-recap.md +12 -4
  106. package/package.json +1 -1
  107. package/src/templates/workspace-core/.agents/skills/external-agents/SKILL.md +7 -6
package/dist/cli/recap.js CHANGED
@@ -125,10 +125,8 @@ export function writePrVisualRecapWorkflow(baseDir, options = {}) {
125
125
  */
126
126
  export function buildReusableCallerWorkflow(options = {}) {
127
127
  const ref = (options.ref ?? "main").replace(/^@/, "");
128
- const agentLine = options.agent && options.agent !== "claude"
129
- ? `\n agent: ${options.agent}`
130
- : "";
131
- const modelLine = options.model ? `\n model: ${options.model}` : "";
128
+ const agentValue = options.agent ?? "${{ vars.VISUAL_RECAP_AGENT || 'claude' }}";
129
+ const modelValue = options.model ?? "${{ vars.VISUAL_RECAP_MODEL || '' }}";
132
130
  return (`name: PR Visual Recap\n` +
133
131
  `\n` +
134
132
  `# Thin caller — the full workflow logic lives in BuilderIO/agent-native.\n` +
@@ -142,16 +140,25 @@ export function buildReusableCallerWorkflow(options = {}) {
142
140
  `\n` +
143
141
  `jobs:\n` +
144
142
  ` visual-recap:\n` +
143
+ ` permissions:\n` +
144
+ ` actions: write\n` +
145
+ ` contents: read\n` +
146
+ ` checks: write\n` +
147
+ ` issues: write\n` +
148
+ ` pull-requests: write\n` +
145
149
  ` uses: BuilderIO/agent-native/.github/workflows/pr-visual-recap-reusable.yml@${ref}\n` +
146
150
  ` secrets:\n` +
147
151
  ` PLAN_RECAP_TOKEN: \${{ secrets.PLAN_RECAP_TOKEN }}\n` +
148
152
  ` ANTHROPIC_API_KEY: \${{ secrets.ANTHROPIC_API_KEY }}\n` +
149
- ` # OPENAI_API_KEY: \${{ secrets.OPENAI_API_KEY }} # only when agent: codex\n` +
150
- ` # PLAN_RECAP_APP_URL: \${{ secrets.PLAN_RECAP_APP_URL }} # only when self-hosting\n` +
151
- ` with:${agentLine}${modelLine}\n` +
153
+ ` OPENAI_API_KEY: \${{ secrets.OPENAI_API_KEY }}\n` +
154
+ ` PLAN_RECAP_APP_URL: \${{ secrets.PLAN_RECAP_APP_URL }}\n` +
155
+ ` with:\n` +
156
+ ` agent: ${agentValue}\n` +
157
+ ` model: ${modelValue}\n` +
158
+ ` reasoning: \${{ vars.VISUAL_RECAP_REASONING || '' }}\n` +
159
+ ` skill-source: \${{ vars.VISUAL_RECAP_SKILL_SOURCE || 'auto' }}\n` +
152
160
  ` # cli-version: "latest" # pin to a specific @agent-native/core version\n` +
153
- ` # reasoning: "high" # codex only: none|minimal|low|medium|high|xhigh\n` +
154
- ` # skill-source: "repo" # pin to committed visual-recap skill\n`);
161
+ ``);
155
162
  }
156
163
  /** File name for the reusable caller workflow. */
157
164
  const REUSABLE_CALLER_WORKFLOW_FILE = "pr-visual-recap.yml";
@@ -650,20 +657,23 @@ export function diffContainsSecret(diffText, allowlist = []) {
650
657
  return false;
651
658
  }
652
659
  const AGENT_FAILURE_MAX_CHARS = 1200;
660
+ const STALE_WORKFLOW_FAILURE_SUMMARY = "No agent failure summary was captured. This repo may be using an older PR Visual Recap workflow; refresh `.github/workflows/pr-visual-recap.yml` with `npx -y @agent-native/core@latest recap setup --force`, then rerun the workflow. See the GitHub Actions log for the agent step.";
653
661
  function compactWhitespace(text) {
654
662
  return text.replace(/\s+/g, " ").trim();
655
663
  }
656
664
  export function sanitizeAgentFailureSummary(value, maxChars = AGENT_FAILURE_MAX_CHARS) {
665
+ const redactSecretValues = (line) => line
666
+ .replace(/Authorization:\s*Bearer\s+[A-Za-z0-9._-]{8,}/gi, "Authorization: Bearer [redacted]")
667
+ .replace(/Bearer\s+[A-Za-z0-9._-]{8,}/gi, "Bearer [redacted]")
668
+ .replace(/Authorization:\s*(?!Bearer\s+\[redacted\])[^\s]+/gi, "Authorization: [redacted]")
669
+ .replace(/PLAN_RECAP_TOKEN=([^\s]+)/g, "PLAN_RECAP_TOKEN=[redacted]")
670
+ .replace(/ANTHROPIC_API_KEY=([^\s]+)/g, "ANTHROPIC_API_KEY=[redacted]")
671
+ .replace(/OPENAI_API_KEY=([^\s]+)/g, "OPENAI_API_KEY=[redacted]");
657
672
  const sanitizedLines = value
658
673
  .replace(/\u001b\[[0-9;]*m/g, "")
659
674
  .split("\n")
675
+ .map(redactSecretValues)
660
676
  .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
677
  .join("\n");
668
678
  const compacted = compactWhitespace(sanitizedLines);
669
679
  if (compacted.length <= maxChars)
@@ -689,6 +699,21 @@ function collectStringFields(value, fields, seen = new Set()) {
689
699
  }
690
700
  return out;
691
701
  }
702
+ function isUsefulAgentSummaryCandidate(candidate) {
703
+ const value = candidate.trim();
704
+ if (!value)
705
+ return false;
706
+ if (/^(turn|session|item|response|task)\.[a-z0-9_.-]+$/i.test(value)) {
707
+ return false;
708
+ }
709
+ if (/^(success|completed|result|message|error)$/i.test(value)) {
710
+ return false;
711
+ }
712
+ return value.length > 12;
713
+ }
714
+ function isErrorLikeAgentSummary(candidate) {
715
+ return /error|failed|denied|not found|unavailable|unauthorized|forbidden|tool|exception|timeout|timed out|could not|cannot/i.test(candidate);
716
+ }
692
717
  export function summarizeAgentResult(agent, resultText) {
693
718
  const normalizedAgent = agent.toLowerCase();
694
719
  const text = resultText.trim();
@@ -707,7 +732,10 @@ export function summarizeAgentResult(agent, resultText) {
707
732
  "type",
708
733
  ]),
709
734
  ].filter(Boolean);
710
- const preferred = candidates.find((candidate) => /error|failed|denied|not found|unavailable|unauthorized|forbidden|tool/i.test(candidate)) ?? candidates[0];
735
+ const usefulCandidates = candidates.filter(isUsefulAgentSummaryCandidate);
736
+ const preferred = usefulCandidates.find(isErrorLikeAgentSummary) ??
737
+ usefulCandidates[0] ??
738
+ candidates.find(isErrorLikeAgentSummary);
711
739
  if (preferred)
712
740
  return sanitizeAgentFailureSummary(preferred);
713
741
  }
@@ -726,7 +754,13 @@ export function summarizeAgentResult(agent, resultText) {
726
754
  "text",
727
755
  "delta",
728
756
  "reason",
729
- "type",
757
+ "detail",
758
+ "details",
759
+ "stderr",
760
+ "stdout",
761
+ "summary",
762
+ "result",
763
+ "content",
730
764
  ]));
731
765
  }
732
766
  catch {
@@ -734,13 +768,88 @@ export function summarizeAgentResult(agent, resultText) {
734
768
  }
735
769
  }
736
770
  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];
771
+ const usefulCandidates = newestFirst.filter(isUsefulAgentSummaryCandidate);
772
+ const preferred = usefulCandidates.find(isErrorLikeAgentSummary) ?? usefulCandidates[0];
739
773
  if (preferred)
740
774
  return sanitizeAgentFailureSummary(preferred);
741
775
  }
742
776
  return sanitizeAgentFailureSummary(text);
743
777
  }
778
+ function agentLabel(agent) {
779
+ const normalized = agent.toLowerCase();
780
+ if (normalized === "codex")
781
+ return "Codex";
782
+ if (normalized === "claude")
783
+ return "Claude";
784
+ return agent || "Agent";
785
+ }
786
+ export function summarizeAgentRun(input) {
787
+ const parts = [];
788
+ const exitCode = (input.exitCode ?? "").trim();
789
+ if (exitCode && exitCode !== "0") {
790
+ parts.push(`${agentLabel(input.agent)} exited with code ${exitCode}.`);
791
+ }
792
+ const resultSummary = summarizeAgentResult(input.agent, input.resultText ?? "");
793
+ if (resultSummary)
794
+ parts.push(resultSummary);
795
+ const stderrSummary = sanitizeAgentFailureSummary(input.stderrText ?? "", 500);
796
+ if (stderrSummary)
797
+ parts.push(`stderr: ${stderrSummary}`);
798
+ return sanitizeAgentFailureSummary(parts.join(" "));
799
+ }
800
+ function readTextIfExists(file) {
801
+ try {
802
+ if (!fs.existsSync(file))
803
+ return null;
804
+ return fs.readFileSync(file, "utf8");
805
+ }
806
+ catch {
807
+ return null;
808
+ }
809
+ }
810
+ function localAgentResultCandidates(agent) {
811
+ const all = [
812
+ {
813
+ agent: "claude",
814
+ resultFile: "claude-result.json",
815
+ stderrFile: "claude-stderr.log",
816
+ exitCodeFile: "claude-exit-code.txt",
817
+ },
818
+ {
819
+ agent: "codex",
820
+ resultFile: "codex-events.jsonl",
821
+ stderrFile: "codex-stderr.log",
822
+ exitCodeFile: "codex-exit-code.txt",
823
+ },
824
+ ];
825
+ const normalized = agent.toLowerCase();
826
+ if (normalized === "codex")
827
+ return [all[1], all[0]];
828
+ return all;
829
+ }
830
+ export function summarizeLocalAgentFailure(input = {}) {
831
+ const cwd = input.cwd ?? process.cwd();
832
+ for (const candidate of localAgentResultCandidates(input.agent ?? "")) {
833
+ const resultPath = path.join(cwd, candidate.resultFile);
834
+ const stderrPath = path.join(cwd, candidate.stderrFile);
835
+ const exitCodePath = path.join(cwd, candidate.exitCodeFile);
836
+ const resultText = readTextIfExists(resultPath);
837
+ const stderrText = readTextIfExists(stderrPath);
838
+ const exitCode = readTextIfExists(exitCodePath);
839
+ if (resultText === null && stderrText === null && exitCode === null) {
840
+ continue;
841
+ }
842
+ const summary = summarizeAgentRun({
843
+ agent: candidate.agent,
844
+ resultText: resultText ?? "",
845
+ stderrText: stderrText ?? "",
846
+ exitCode: exitCode ?? "",
847
+ });
848
+ if (summary)
849
+ return summary;
850
+ }
851
+ return "";
852
+ }
744
853
  /* -------------------------------------------------------------------------- */
745
854
  /* Bounded diff collection — was the workflow's "Collect bounded diff" step */
746
855
  /* -------------------------------------------------------------------------- */
@@ -1385,17 +1494,22 @@ export function buildCommentBody(env = process.env) {
1385
1494
  const markerPlanId = trustedPlanId ?? prevPlanId;
1386
1495
  if (!safeUrl) {
1387
1496
  const authFailed = env.RECAP_AUTH_FAILED === "true";
1388
- const agentSummary = sanitizeAgentFailureSummary((env.RECAP_AGENT_SUMMARY || "").trim(), 800);
1497
+ const diagnostic = buildRecapFailureDiagnostic({
1498
+ failureSummary: (env.RECAP_AGENT_SUMMARY || "").trim(),
1499
+ urlReason: (env.RECAP_URL_REASON || "").trim(),
1500
+ });
1389
1501
  lines.push("### Visual recap — generation failed");
1390
1502
  lines.push("");
1391
1503
  if (authFailed) {
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.");
1504
+ lines.push("Recap authentication failed — the `PLAN_RECAP_TOKEN` secret may be expired or revoked. Re-mint it with `npx -y @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.");
1393
1505
  }
1394
1506
  else {
1395
1507
  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) {
1508
+ if (diagnostic) {
1397
1509
  lines.push("");
1398
- lines.push(`Agent output: ${agentSummary}`);
1510
+ lines.push("Diagnostic:");
1511
+ lines.push("");
1512
+ lines.push(diagnostic);
1399
1513
  }
1400
1514
  }
1401
1515
  // Keep a link to the last-good recap so reviewers are not left in the dark.
@@ -1587,7 +1701,7 @@ const RECAP_SHOT_VIEWPORT = {
1587
1701
  width: RECAP_SHOT_WIDTH,
1588
1702
  height: RECAP_SHOT_MAX_HEIGHT,
1589
1703
  };
1590
- const RECAP_SHOT_DEVICE_SCALE_FACTOR = 1;
1704
+ const RECAP_SHOT_DEVICE_SCALE_FACTOR = 2;
1591
1705
  async function defaultImportPlaywright() {
1592
1706
  try {
1593
1707
  return (await import("playwright"));
@@ -1792,7 +1906,7 @@ async function runComment(args, sub) {
1792
1906
  owner,
1793
1907
  repo,
1794
1908
  issue,
1795
- body: buildCommentBody(),
1909
+ body: buildCommentBody(recoverRecapFailureEnv()),
1796
1910
  updateOnly: args["update-only"] === true || args["update-only"] === "true",
1797
1911
  });
1798
1912
  process.stdout.write(`${JSON.stringify(result)}\n`);
@@ -1800,6 +1914,30 @@ async function runComment(args, sub) {
1800
1914
  }
1801
1915
  throw new Error("Usage: npx @agent-native/core@latest recap comment <find-plan-id|upsert> --repo owner/name --issue n --token token");
1802
1916
  }
1917
+ function shouldRecoverRecapFailureDetails(env) {
1918
+ return (!(env.PLAN_URL || "").trim() &&
1919
+ env.DIFF_TINY !== "true" &&
1920
+ env.SUPPRESSED !== "true");
1921
+ }
1922
+ function recoverRecapFailureEnv(env = process.env) {
1923
+ if (!shouldRecoverRecapFailureDetails(env))
1924
+ return env;
1925
+ const recovered = { ...env };
1926
+ if (!recovered.RECAP_AGENT_SUMMARY) {
1927
+ recovered.RECAP_AGENT_SUMMARY = summarizeLocalAgentFailure({
1928
+ agent: recovered.RECAP_AGENT || recovered.VISUAL_RECAP_AGENT,
1929
+ });
1930
+ }
1931
+ if (!recovered.RECAP_URL_REASON) {
1932
+ recovered.RECAP_URL_REASON = inferLocalRecapUrlFailureReason({
1933
+ appUrl: recovered.PLAN_RECAP_APP_URL,
1934
+ });
1935
+ }
1936
+ if (!recovered.RECAP_AGENT_SUMMARY && !recovered.RECAP_URL_REASON) {
1937
+ recovered.RECAP_AGENT_SUMMARY = STALE_WORKFLOW_FAILURE_SUMMARY;
1938
+ }
1939
+ return recovered;
1940
+ }
1803
1941
  /**
1804
1942
  * Files that, if a PR touches them, would let that PR rewrite what the trusted
1805
1943
  * recap job runs (the workflow itself, the skill, the local CLI, or any agent
@@ -2085,8 +2223,10 @@ export function appendGateSkipLine(existingBody, skipLine) {
2085
2223
  */
2086
2224
  export function canonicalRecapUrl(rawUrl, appUrl) {
2087
2225
  try {
2088
- const parsed = new URL(rawUrl);
2089
2226
  const trusted = new URL(appUrl || "https://plan.agent-native.com");
2227
+ const parsed = /^https?:\/\//i.test(rawUrl)
2228
+ ? new URL(rawUrl)
2229
+ : new URL(rawUrl, trusted);
2090
2230
  if (parsed.origin !== trusted.origin)
2091
2231
  return "";
2092
2232
  // Honor a path-prefixed mount (e.g. https://host/agent-native): strip the
@@ -2102,6 +2242,43 @@ export function canonicalRecapUrl(rawUrl, appUrl) {
2102
2242
  return "";
2103
2243
  }
2104
2244
  }
2245
+ export function inferLocalRecapUrlFailureReason(input = {}) {
2246
+ const recapUrlPath = path.join(input.cwd ?? process.cwd(), "recap-url.txt");
2247
+ const raw = readTextIfExists(recapUrlPath);
2248
+ if (raw === null)
2249
+ return "recap-url.txt was not created by the agent.";
2250
+ const value = raw.replace(/[\r\n\s]/g, "");
2251
+ if (!value)
2252
+ return "recap-url.txt was empty.";
2253
+ const appUrl = input.appUrl ||
2254
+ process.env.PLAN_RECAP_APP_URL ||
2255
+ "https://plan.agent-native.com";
2256
+ if (canonicalRecapUrl(value, appUrl))
2257
+ return "";
2258
+ try {
2259
+ const trusted = new URL(appUrl || "https://plan.agent-native.com");
2260
+ const parsed = /^https?:\/\//i.test(value)
2261
+ ? new URL(value)
2262
+ : new URL(value, trusted);
2263
+ if (parsed.origin !== trusted.origin) {
2264
+ return `recap-url.txt points at ${parsed.origin}, expected ${trusted.origin}.`;
2265
+ }
2266
+ return "recap-url.txt did not contain a valid /plans/<id> or /recaps/<id> URL for the configured plan app.";
2267
+ }
2268
+ catch {
2269
+ return "recap-url.txt was not a valid URL or recap path.";
2270
+ }
2271
+ }
2272
+ export function buildRecapFailureDiagnostic(input) {
2273
+ const parts = [];
2274
+ const urlReason = sanitizeAgentFailureSummary(input.urlReason ?? "", 400);
2275
+ const failureSummary = sanitizeAgentFailureSummary(input.failureSummary ?? "", 900);
2276
+ if (urlReason)
2277
+ parts.push(`No plan URL: ${urlReason}`);
2278
+ if (failureSummary)
2279
+ parts.push(`Agent output: ${failureSummary}`);
2280
+ return parts.join("\n\n");
2281
+ }
2105
2282
  /**
2106
2283
  * Map the workflow's terminal recap state to the completed check's
2107
2284
  * conclusion/title/summary/text/details_url. Pure so it can be unit-tested —
@@ -2118,9 +2295,11 @@ export function recapCheckOutcome(input) {
2118
2295
  let conclusion = "neutral";
2119
2296
  let title = "Visual recap not generated";
2120
2297
  let summary = "The visual recap did not produce a plan URL. This is informational only and does not block the PR.";
2121
- let text = input.failureSummary
2122
- ? `Agent output: ${sanitizeAgentFailureSummary(input.failureSummary)}`
2123
- : "";
2298
+ const diagnostic = buildRecapFailureDiagnostic({
2299
+ failureSummary: input.failureSummary,
2300
+ urlReason: input.urlReason,
2301
+ });
2302
+ let text = diagnostic ? `### Diagnostic\n\n${diagnostic}` : "";
2124
2303
  let detailsUrl = input.workflowUrl;
2125
2304
  if (input.planOk) {
2126
2305
  const recapUrl = canonicalRecapUrl(input.planUrl, input.appUrl);
@@ -2163,9 +2342,9 @@ export function recapCheckOutcome(input) {
2163
2342
  summary = `No recap was published because ${reason}.`;
2164
2343
  text = "";
2165
2344
  }
2166
- else if (input.failureSummary) {
2345
+ else if (diagnostic) {
2167
2346
  summary =
2168
- "The visual recap agent ran but did not produce a plan URL. See the agent output below.";
2347
+ "The visual recap agent ran but did not produce a plan URL. See diagnostics below.";
2169
2348
  }
2170
2349
  return { conclusion, title, summary, text, detailsUrl };
2171
2350
  }
@@ -2229,15 +2408,39 @@ async function runCheckComplete(args) {
2229
2408
  process.env.GITHUB_TOKEN ||
2230
2409
  "";
2231
2410
  const checkRunId = optionalArg(args, "check-run-id") ?? "";
2411
+ const planOk = boolFlag(args, "plan-ok");
2412
+ const huge = boolFlag(args, "huge");
2413
+ const tiny = boolFlag(args, "tiny");
2414
+ const suppressed = boolFlag(args, "suppressed");
2415
+ const appUrl = optionalArg(args, "app-url") ?? process.env.PLAN_RECAP_APP_URL ?? "";
2416
+ let failureSummary = optionalArg(args, "failure-summary") ?? "";
2417
+ let urlReason = optionalArg(args, "url-reason") ?? "";
2418
+ if (!planOk && !tiny && !suppressed) {
2419
+ if (!failureSummary) {
2420
+ failureSummary = summarizeLocalAgentFailure({
2421
+ agent: optionalArg(args, "agent") ??
2422
+ process.env.RECAP_AGENT ??
2423
+ process.env.VISUAL_RECAP_AGENT ??
2424
+ "",
2425
+ });
2426
+ }
2427
+ if (!urlReason) {
2428
+ urlReason = inferLocalRecapUrlFailureReason({ appUrl });
2429
+ }
2430
+ if (!failureSummary && !urlReason) {
2431
+ failureSummary = STALE_WORKFLOW_FAILURE_SUMMARY;
2432
+ }
2433
+ }
2232
2434
  const outcome = recapCheckOutcome({
2233
- planOk: boolFlag(args, "plan-ok"),
2435
+ planOk,
2234
2436
  planUrl: optionalArg(args, "plan-url") ?? "",
2235
- appUrl: optionalArg(args, "app-url") ?? process.env.PLAN_RECAP_APP_URL ?? "",
2236
- huge: boolFlag(args, "huge"),
2237
- tiny: boolFlag(args, "tiny"),
2238
- suppressed: boolFlag(args, "suppressed"),
2437
+ appUrl,
2438
+ huge,
2439
+ tiny,
2440
+ suppressed,
2239
2441
  suppressedJson: optionalArg(args, "suppressed-json") ?? "",
2240
- failureSummary: optionalArg(args, "failure-summary") ?? "",
2442
+ failureSummary,
2443
+ urlReason,
2241
2444
  workflowUrl: optionalArg(args, "workflow-url") ?? "",
2242
2445
  });
2243
2446
  try {
@@ -2456,6 +2659,8 @@ function writeGitHubOutput(name, value) {
2456
2659
  function runAgentSummary(args) {
2457
2660
  const agent = optionalArg(args, "agent") ?? "claude";
2458
2661
  const resultFile = stringArg(args, "result-file");
2662
+ const stderrFile = optionalArg(args, "stderr-file");
2663
+ const exitCodeFile = optionalArg(args, "exit-code-file");
2459
2664
  let raw = "";
2460
2665
  try {
2461
2666
  raw = fs.readFileSync(path.resolve(resultFile), "utf8");
@@ -2463,7 +2668,18 @@ function runAgentSummary(args) {
2463
2668
  catch (err) {
2464
2669
  raw = `could not read ${resultFile}: ${String(err)}`;
2465
2670
  }
2466
- const summary = summarizeAgentResult(agent, raw);
2671
+ const stderrText = stderrFile
2672
+ ? (readTextIfExists(path.resolve(stderrFile)) ?? "")
2673
+ : "";
2674
+ const exitCode = exitCodeFile
2675
+ ? (readTextIfExists(path.resolve(exitCodeFile)) ?? "")
2676
+ : "";
2677
+ const summary = summarizeAgentRun({
2678
+ agent,
2679
+ resultText: raw,
2680
+ stderrText,
2681
+ exitCode,
2682
+ });
2467
2683
  writeGitHubOutput("summary", summary);
2468
2684
  process.stdout.write(`${JSON.stringify({ ok: Boolean(summary), summary })}\n`);
2469
2685
  }
@@ -2478,14 +2694,14 @@ Usage:
2478
2694
  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
2695
  npx @agent-native/core@latest recap shot --url <planUrl> [--token <planToken>] [--app-url <url>] [--out recap.png]
2480
2696
  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]
2697
+ npx @agent-native/core@latest recap agent-summary --result-file <path> [--stderr-file <path>] [--exit-code-file <path>] [--agent claude|codex]
2482
2698
  npx @agent-native/core@latest recap comment <find-plan-id|upsert> --repo owner/name --issue <n> --token <github-token>
2483
2699
  npx @agent-native/core@latest recap check start [--repo owner/name] [--sha <headSha>] [--token <github-token>] [--workflow-url <url>]
2484
2700
  Create the in-progress "Visual Recap" GitHub check run and write its id to
2485
2701
  $GITHUB_OUTPUT (check_run_id). repo/sha/token default to GITHUB_REPOSITORY /
2486
2702
  HEAD_SHA / GH_TOKEN (or GITHUB_TOKEN). Best-effort: warns and exits 0 on any
2487
2703
  API error without emitting an id.
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>]
2704
+ 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>] [--failure-summary <text>] [--url-reason <text>] [--workflow-url <url>]
2489
2705
  Mark the "Visual Recap" check run completed with a computed
2490
2706
  conclusion/title/summary/text/details_url (success when the agent published a
2491
2707
  plan whose URL validates against --app-url; neutral/skipped otherwise).