@agent-native/core 0.48.4 → 0.49.1
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/agent/context-xray/actions/context-evict.d.ts +1 -1
- package/dist/agent/context-xray/actions/context-pin.d.ts +1 -1
- package/dist/agent/context-xray/actions/context-report.d.ts +4 -4
- package/dist/agent/context-xray/actions/context-restore.d.ts +1 -1
- package/dist/application-state/handlers.d.ts +2 -2
- package/dist/application-state/handlers.d.ts.map +1 -1
- package/dist/cli/app-skill.d.ts +157 -0
- package/dist/cli/app-skill.d.ts.map +1 -0
- package/dist/cli/app-skill.js +17 -7
- package/dist/cli/app-skill.js.map +1 -1
- package/dist/cli/audit-agent-web.d.ts +2 -0
- package/dist/cli/audit-agent-web.d.ts.map +1 -0
- package/dist/cli/code-agent-connector.d.ts +17 -0
- package/dist/cli/code-agent-connector.d.ts.map +1 -0
- package/dist/cli/code.d.ts +66 -0
- package/dist/cli/code.d.ts.map +1 -0
- package/dist/cli/connect.d.ts +168 -0
- package/dist/cli/connect.d.ts.map +1 -0
- package/dist/cli/connect.js +118 -30
- package/dist/cli/connect.js.map +1 -1
- package/dist/cli/context-xray-local.d.ts +16 -0
- package/dist/cli/context-xray-local.d.ts.map +1 -0
- package/dist/cli/create-workspace.d.ts +8 -0
- package/dist/cli/create-workspace.d.ts.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/info.d.ts +2 -0
- package/dist/cli/info.d.ts.map +1 -0
- package/dist/cli/mcp-config-writers.d.ts +108 -0
- package/dist/cli/mcp-config-writers.d.ts.map +1 -0
- package/dist/cli/mcp-config-writers.js +143 -0
- package/dist/cli/mcp-config-writers.js.map +1 -1
- package/dist/cli/mcp.d.ts +16 -0
- package/dist/cli/mcp.d.ts.map +1 -0
- package/dist/cli/mcp.js +10 -10
- package/dist/cli/mcp.js.map +1 -1
- package/dist/cli/migrate.d.ts +38 -0
- package/dist/cli/migrate.d.ts.map +1 -0
- package/dist/cli/plan-local.d.ts +43 -0
- package/dist/cli/plan-local.d.ts.map +1 -0
- package/dist/cli/plan-publish-store.d.ts +62 -0
- package/dist/cli/plan-publish-store.d.ts.map +1 -0
- package/dist/cli/pr-visual-recap-workflow.d.ts +11 -0
- package/dist/cli/pr-visual-recap-workflow.d.ts.map +1 -0
- package/dist/cli/pr-visual-recap-workflow.js +1 -1
- package/dist/cli/pr-visual-recap-workflow.js.map +1 -1
- package/dist/cli/recap.d.ts +453 -0
- package/dist/cli/recap.d.ts.map +1 -0
- package/dist/cli/recap.js +228 -95
- package/dist/cli/recap.js.map +1 -1
- package/dist/cli/skills.d.ts +193 -0
- package/dist/cli/skills.d.ts.map +1 -0
- package/dist/cli/skills.js +369 -171
- package/dist/cli/skills.js.map +1 -1
- package/dist/cli/telemetry.d.ts +13 -0
- package/dist/cli/telemetry.d.ts.map +1 -0
- package/dist/cli/telemetry.js +115 -0
- package/dist/cli/telemetry.js.map +1 -0
- package/dist/cli/workspace-dev.d.ts +96 -0
- package/dist/cli/workspace-dev.d.ts.map +1 -0
- package/dist/client/blocks/library/AnnotatedCodeBlock.d.ts.map +1 -1
- package/dist/client/blocks/library/AnnotatedCodeBlock.js +15 -7
- package/dist/client/blocks/library/AnnotatedCodeBlock.js.map +1 -1
- package/dist/client/blocks/library/DiffBlock.d.ts.map +1 -1
- package/dist/client/blocks/library/DiffBlock.js +17 -10
- package/dist/client/blocks/library/DiffBlock.js.map +1 -1
- package/dist/client/blocks/library/annotation-rail.d.ts +5 -0
- package/dist/client/blocks/library/annotation-rail.d.ts.map +1 -1
- package/dist/client/blocks/library/annotation-rail.js +6 -0
- package/dist/client/blocks/library/annotation-rail.js.map +1 -1
- package/dist/client/blocks/types.d.ts +5 -0
- package/dist/client/blocks/types.d.ts.map +1 -1
- package/dist/client/blocks/types.js.map +1 -1
- package/dist/extensions/schema.d.ts +54 -54
- package/dist/extensions/slots/schema.d.ts +13 -13
- package/dist/file-upload/actions/upload-image.d.ts +4 -4
- package/dist/mcp/actions/create-org-service-token.d.ts +1 -1
- package/dist/mcp/actions/list-org-service-tokens.d.ts +7 -7
- package/dist/mcp/build-server.d.ts +12 -12
- package/dist/mcp/build-server.d.ts.map +1 -1
- package/dist/mcp/build-server.js.map +1 -1
- package/dist/mcp/connect-route.js +1 -1
- package/dist/mcp/connect-route.js.map +1 -1
- package/dist/mcp/oauth-route.d.ts +10 -0
- package/dist/mcp/oauth-route.d.ts.map +1 -1
- package/dist/mcp/oauth-route.js +34 -3
- package/dist/mcp/oauth-route.js.map +1 -1
- package/dist/mcp/oauth-store.d.ts +15 -1
- package/dist/mcp/oauth-store.d.ts.map +1 -1
- package/dist/mcp/oauth-store.js +60 -4
- package/dist/mcp/oauth-store.js.map +1 -1
- package/dist/mcp/oauth-token.d.ts +3 -1
- package/dist/mcp/oauth-token.d.ts.map +1 -1
- package/dist/mcp/oauth-token.js +78 -6
- package/dist/mcp/oauth-token.js.map +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +8 -6
- package/dist/mcp/server.js.map +1 -1
- package/dist/observability/routes.d.ts +11 -11
- package/dist/org/handlers.d.ts +7 -11
- package/dist/org/handlers.d.ts.map +1 -1
- package/dist/secrets/schema.d.ts +7 -7
- package/dist/server/csrf.d.ts +1 -1
- package/dist/server/csrf.d.ts.map +1 -1
- package/dist/server/poll-events.d.ts +1 -1
- package/dist/server/security-headers.d.ts +1 -1
- package/dist/server/security-headers.d.ts.map +1 -1
- package/dist/sharing/actions/list-resource-shares.d.ts +3 -3
- package/dist/sharing/actions/set-resource-visibility.d.ts +2 -2
- package/dist/sharing/actions/share-resource.d.ts +4 -4
- package/dist/sharing/actions/unshare-resource.d.ts +1 -1
- package/dist/sharing/schema.d.ts +12 -12
- package/dist/templates/workspace-core/.agents/skills/authentication/SKILL.md +2 -2
- package/dist/templates/workspace-core/.agents/skills/external-agents/SKILL.md +6 -6
- package/dist/templates/workspace-core/.agents/skills/external-agents/references/mcp-apps-embedding.md +2 -2
- package/dist/workspace-files/schema.d.ts +8 -8
- package/docs/content/deployment.md +32 -8
- package/docs/content/drop-in-agent.md +24 -17
- package/docs/content/external-agents.md +14 -0
- package/docs/content/plan-plugin.md +22 -7
- package/docs/content/template-content.md +36 -0
- package/docs/content/template-plan.md +22 -0
- package/package.json +5 -1
- package/src/templates/workspace-core/.agents/skills/authentication/SKILL.md +2 -2
- package/src/templates/workspace-core/.agents/skills/external-agents/SKILL.md +6 -6
- 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
|
|
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
|
|
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
|
|
1271
|
-
//
|
|
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
|
|
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:
|
|
1555
|
-
deviceScaleFactor:
|
|
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(
|
|
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 = "
|
|
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,
|
|
1603
|
-
//
|
|
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}) —
|
|
1607
|
-
|
|
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
|
-
|
|
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
|
|
2357
|
-
agent-native recap
|
|
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
|
|
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;
|