@agent-native/core 0.49.8 → 0.49.10
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/cli/app-skill.d.ts.map +1 -1
- package/dist/cli/app-skill.js +21 -5
- package/dist/cli/app-skill.js.map +1 -1
- package/dist/cli/connect.d.ts.map +1 -1
- package/dist/cli/connect.js +46 -2
- package/dist/cli/connect.js.map +1 -1
- package/dist/cli/pr-visual-recap-workflow.d.ts +1 -1
- package/dist/cli/pr-visual-recap-workflow.d.ts.map +1 -1
- 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 +10 -12
- package/dist/cli/recap.d.ts.map +1 -1
- package/dist/cli/recap.js +110 -46
- package/dist/cli/recap.js.map +1 -1
- package/dist/cli/skills.d.ts +2 -2
- package/dist/cli/skills.d.ts.map +1 -1
- package/dist/cli/skills.js +26 -11
- package/dist/cli/skills.js.map +1 -1
- package/dist/client/blocks/library/annotation-rail.d.ts.map +1 -1
- package/dist/client/blocks/library/annotation-rail.js +16 -6
- package/dist/client/blocks/library/annotation-rail.js.map +1 -1
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/auth.js +57 -35
- package/dist/server/auth.js.map +1 -1
- package/dist/vite/client.d.ts.map +1 -1
- package/dist/vite/client.js +6 -8
- package/dist/vite/client.js.map +1 -1
- package/docs/content/pr-visual-recap.md +15 -16
- package/package.json +2 -1
package/dist/cli/recap.js
CHANGED
|
@@ -192,6 +192,7 @@ export function writePrVisualRecapReusableCallerWorkflow(baseDir, options = {})
|
|
|
192
192
|
return { status: "written", path: rel, existed: false };
|
|
193
193
|
}
|
|
194
194
|
const DEFAULT_RECAP_APP_URL = "https://plan.agent-native.com";
|
|
195
|
+
const RECAP_MCP_CLIENT_HEADER = "agent-native-pr-visual-recap";
|
|
195
196
|
export function normalizeRecapAgent(value) {
|
|
196
197
|
const agent = (value || "claude").toLowerCase();
|
|
197
198
|
if (agent === "codex")
|
|
@@ -1088,7 +1089,11 @@ export function buildRecapClaudeMcpConfig(appUrl, token) {
|
|
|
1088
1089
|
plan: {
|
|
1089
1090
|
type: "http",
|
|
1090
1091
|
url,
|
|
1091
|
-
headers: {
|
|
1092
|
+
headers: {
|
|
1093
|
+
Authorization: "Bearer " + token,
|
|
1094
|
+
"X-Agent-Native-MCP-Client": RECAP_MCP_CLIENT_HEADER,
|
|
1095
|
+
"X-Agent-Native-MCP-Full-Catalog": "1",
|
|
1096
|
+
},
|
|
1092
1097
|
},
|
|
1093
1098
|
},
|
|
1094
1099
|
});
|
|
@@ -1105,7 +1110,8 @@ export function buildRecapCodexMcpConfig(appUrl) {
|
|
|
1105
1110
|
"url = " +
|
|
1106
1111
|
JSON.stringify(url) +
|
|
1107
1112
|
"\n" +
|
|
1108
|
-
'bearer_token_env_var = "PLAN_RECAP_TOKEN"\n'
|
|
1113
|
+
'bearer_token_env_var = "PLAN_RECAP_TOKEN"\n' +
|
|
1114
|
+
'http_headers = { "X-Agent-Native-MCP-Client" = "agent-native-pr-visual-recap", "X-Agent-Native-MCP-Full-Catalog" = "1" }\n');
|
|
1109
1115
|
}
|
|
1110
1116
|
/**
|
|
1111
1117
|
* `recap mcp-config` — write the plan MCP client config for the chosen backend,
|
|
@@ -1319,6 +1325,7 @@ export function buildRecapPrompt(input) {
|
|
|
1319
1325
|
else {
|
|
1320
1326
|
lines.push("## Publish (this is the only way to produce output)");
|
|
1321
1327
|
lines.push(`The \`plan\` MCP server is configured for you. Call its tools by name (your host may expose them as \`get-plan-blocks\` / \`create-visual-recap\` or \`mcp__plan__get-plan-blocks\` / \`mcp__plan__create-visual-recap\` — same tools).`);
|
|
1328
|
+
lines.push("This is a one-shot GitHub Actions run. Do not wait, sleep, back off, schedule wakeups, reminders, follow-ups, or retries in another turn. Either publish the recap and write `recap-url.txt` in this process, or report the MCP/tool failure plainly.");
|
|
1322
1329
|
lines.push("First call `get-plan-blocks`, then call `create-visual-recap`. If `create-visual-recap` is available but `get-plan-blocks` is not, the Plan MCP is connected but the block-registry tool is not visible to this runner. Report that the runner must expose `get-plan-blocks` through the workflow/tool allowlist or compact MCP catalog; do not describe that case as a disconnected Plan MCP.");
|
|
1323
1330
|
lines.push(`1. Call the **create-visual-recap** tool on the \`plan\` MCP server with grounded MDX derived ONLY from the real diff, passing \`visibility: "org"\` so the recap is published org-scoped (never public) server-side${input.prevPlanId
|
|
1324
1331
|
? `, and also passing \`planId: "${input.prevPlanId}"\` so this REPLACES the existing recap plan`
|
|
@@ -1349,6 +1356,9 @@ export function buildRecapPrompt(input) {
|
|
|
1349
1356
|
const MARKER = "<!-- pr-visual-recap -->";
|
|
1350
1357
|
const RECAP_IMAGE_URL_PATH_PATTERN = /\/_agent-native\/recap-image\/[0-9a-f]{32,128}\.png$/;
|
|
1351
1358
|
const RECAP_SCREENSHOT_QUERY_PARAM = "recapScreenshot";
|
|
1359
|
+
const RECAP_SCREENSHOT_THEME_QUERY_PARAM = "recapScreenshotTheme";
|
|
1360
|
+
const GITHUB_LIGHT_CANVAS_BACKGROUND = "#ffffff";
|
|
1361
|
+
const GITHUB_DARK_CANVAS_BACKGROUND = "#0d1117";
|
|
1352
1362
|
function repoParts(repoFullName) {
|
|
1353
1363
|
const [owner, repo] = repoFullName.split("/");
|
|
1354
1364
|
if (!owner || !repo)
|
|
@@ -1436,6 +1446,14 @@ function originOf(url) {
|
|
|
1436
1446
|
return "";
|
|
1437
1447
|
}
|
|
1438
1448
|
}
|
|
1449
|
+
function trustedRecapImageUrl(raw, base) {
|
|
1450
|
+
const value = (raw || "").trim();
|
|
1451
|
+
return value &&
|
|
1452
|
+
sameOrigin(value, base) &&
|
|
1453
|
+
RECAP_IMAGE_URL_PATH_PATTERN.test(value)
|
|
1454
|
+
? value
|
|
1455
|
+
: "";
|
|
1456
|
+
}
|
|
1439
1457
|
/** Build the sticky comment body from the workflow's environment. */
|
|
1440
1458
|
export function buildCommentBody(env = process.env) {
|
|
1441
1459
|
const lines = [MARKER];
|
|
@@ -1512,28 +1530,29 @@ export function buildCommentBody(env = process.env) {
|
|
|
1512
1530
|
lines.push(diagnostic);
|
|
1513
1531
|
}
|
|
1514
1532
|
}
|
|
1515
|
-
// Keep a link to the last-good recap so reviewers are not left in the dark.
|
|
1516
|
-
if (prevPlanId && base) {
|
|
1517
|
-
const prevSafeUrl = `${base}/recaps/${prevPlanId}`;
|
|
1518
|
-
lines.push("", `Previous recap (from an earlier push): [Open recap](${prevSafeUrl})`);
|
|
1519
|
-
}
|
|
1520
1533
|
if (markerPlanId)
|
|
1521
1534
|
lines.push("", `<!-- plan-id: ${markerPlanId} -->`);
|
|
1522
1535
|
return lines.join("\n");
|
|
1523
1536
|
}
|
|
1524
|
-
//
|
|
1537
|
+
// Image URLs are produced by our own recap-image route, but validate each is
|
|
1525
1538
|
// same-origin and matches the canonical hex-token path before embedding it, so
|
|
1526
|
-
//
|
|
1527
|
-
const
|
|
1528
|
-
const
|
|
1529
|
-
|
|
1530
|
-
RECAP_IMAGE_URL_PATH_PATTERN.test(imageUrlRaw)
|
|
1531
|
-
? imageUrlRaw
|
|
1532
|
-
: "";
|
|
1539
|
+
// they likewise cannot inject markdown or HTML.
|
|
1540
|
+
const lightImageUrl = trustedRecapImageUrl(env.RECAP_LIGHT_IMAGE_URL || env.RECAP_IMAGE_URL, base);
|
|
1541
|
+
const darkImageUrl = trustedRecapImageUrl(env.RECAP_DARK_IMAGE_URL, base);
|
|
1542
|
+
const fallbackImageUrl = lightImageUrl || darkImageUrl;
|
|
1533
1543
|
lines.push(`### Here's a [visual recap](${safeUrl}) of what changed:`);
|
|
1534
1544
|
lines.push("");
|
|
1535
|
-
if (
|
|
1536
|
-
lines.push(
|
|
1545
|
+
if (lightImageUrl && darkImageUrl) {
|
|
1546
|
+
lines.push(`<a href="${safeUrl}">`);
|
|
1547
|
+
lines.push(`<picture>`);
|
|
1548
|
+
lines.push(` <source media="(prefers-color-scheme: dark)" srcset="${darkImageUrl}">`);
|
|
1549
|
+
lines.push(` <img alt="Visual recap" src="${lightImageUrl}">`);
|
|
1550
|
+
lines.push(`</picture>`);
|
|
1551
|
+
lines.push(`</a>`);
|
|
1552
|
+
lines.push("");
|
|
1553
|
+
}
|
|
1554
|
+
else if (fallbackImageUrl) {
|
|
1555
|
+
lines.push(`[](${safeUrl})`);
|
|
1537
1556
|
lines.push("");
|
|
1538
1557
|
}
|
|
1539
1558
|
lines.push(`**[Open the full interactive recap](${safeUrl})**`);
|
|
@@ -1710,10 +1729,25 @@ async function defaultImportPlaywright() {
|
|
|
1710
1729
|
return (await import("@playwright/test"));
|
|
1711
1730
|
}
|
|
1712
1731
|
}
|
|
1713
|
-
|
|
1732
|
+
function parseRecapScreenshotTheme(value) {
|
|
1733
|
+
if (value === undefined)
|
|
1734
|
+
return undefined;
|
|
1735
|
+
if (value === "light" || value === "dark")
|
|
1736
|
+
return value;
|
|
1737
|
+
throw new Error("--theme must be light or dark.");
|
|
1738
|
+
}
|
|
1739
|
+
function recapScreenshotBackground(theme) {
|
|
1740
|
+
return theme === "dark"
|
|
1741
|
+
? GITHUB_DARK_CANVAS_BACKGROUND
|
|
1742
|
+
: GITHUB_LIGHT_CANVAS_BACKGROUND;
|
|
1743
|
+
}
|
|
1744
|
+
export function withRecapScreenshotParams(url, options = {}) {
|
|
1714
1745
|
try {
|
|
1715
1746
|
const parsed = new URL(url);
|
|
1716
1747
|
parsed.searchParams.set(RECAP_SCREENSHOT_QUERY_PARAM, "1");
|
|
1748
|
+
if (options.theme) {
|
|
1749
|
+
parsed.searchParams.set(RECAP_SCREENSHOT_THEME_QUERY_PARAM, options.theme);
|
|
1750
|
+
}
|
|
1717
1751
|
return parsed.toString();
|
|
1718
1752
|
}
|
|
1719
1753
|
catch {
|
|
@@ -1727,6 +1761,7 @@ importPlaywright = defaultImportPlaywright) {
|
|
|
1727
1761
|
const out = optionalArg(args, "out") ?? "recap.png";
|
|
1728
1762
|
const token = optionalArg(args, "token");
|
|
1729
1763
|
const appUrl = optionalArg(args, "app-url");
|
|
1764
|
+
const theme = parseRecapScreenshotTheme(optionalArg(args, "theme"));
|
|
1730
1765
|
const done = (obj) => {
|
|
1731
1766
|
process.stdout.write(`${JSON.stringify(obj)}\n`);
|
|
1732
1767
|
};
|
|
@@ -1752,7 +1787,7 @@ importPlaywright = defaultImportPlaywright) {
|
|
|
1752
1787
|
return;
|
|
1753
1788
|
}
|
|
1754
1789
|
}
|
|
1755
|
-
const captureUrl = withRecapScreenshotParams(url);
|
|
1790
|
+
const captureUrl = withRecapScreenshotParams(url, { theme });
|
|
1756
1791
|
let chromium;
|
|
1757
1792
|
try {
|
|
1758
1793
|
({ chromium } = await importPlaywright());
|
|
@@ -1772,7 +1807,33 @@ importPlaywright = defaultImportPlaywright) {
|
|
|
1772
1807
|
const context = await browser.newContext({
|
|
1773
1808
|
viewport: RECAP_SHOT_VIEWPORT,
|
|
1774
1809
|
deviceScaleFactor: RECAP_SHOT_DEVICE_SCALE_FACTOR,
|
|
1810
|
+
...(theme ? { colorScheme: theme } : {}),
|
|
1775
1811
|
});
|
|
1812
|
+
if (theme) {
|
|
1813
|
+
await context.addInitScript(({ background, nextTheme }) => {
|
|
1814
|
+
const applyTheme = () => {
|
|
1815
|
+
try {
|
|
1816
|
+
window.localStorage.setItem("theme", nextTheme);
|
|
1817
|
+
}
|
|
1818
|
+
catch {
|
|
1819
|
+
/* ignore */
|
|
1820
|
+
}
|
|
1821
|
+
const root = document.documentElement;
|
|
1822
|
+
root.classList.remove("light", "dark");
|
|
1823
|
+
root.classList.add(nextTheme);
|
|
1824
|
+
root.setAttribute("data-theme", nextTheme);
|
|
1825
|
+
root.style.colorScheme = nextTheme;
|
|
1826
|
+
root.style.backgroundColor = background;
|
|
1827
|
+
if (document.body) {
|
|
1828
|
+
document.body.style.backgroundColor = background;
|
|
1829
|
+
}
|
|
1830
|
+
};
|
|
1831
|
+
applyTheme();
|
|
1832
|
+
document.addEventListener("DOMContentLoaded", applyTheme, {
|
|
1833
|
+
once: true,
|
|
1834
|
+
});
|
|
1835
|
+
}, { background: recapScreenshotBackground(theme), nextTheme: theme });
|
|
1836
|
+
}
|
|
1776
1837
|
if (attachToken) {
|
|
1777
1838
|
// Attach the bearer ONLY to same-origin requests. Context-wide
|
|
1778
1839
|
// extraHTTPHeaders would also send it to every cross-origin subresource
|
|
@@ -1812,9 +1873,23 @@ importPlaywright = defaultImportPlaywright) {
|
|
|
1812
1873
|
}
|
|
1813
1874
|
}
|
|
1814
1875
|
await page.waitForTimeout(matched ? 1_200 : 500);
|
|
1815
|
-
await page.evaluate(() => {
|
|
1876
|
+
await page.evaluate((background) => {
|
|
1816
1877
|
document.documentElement.style.zoom = "100%";
|
|
1817
|
-
|
|
1878
|
+
if (!background)
|
|
1879
|
+
return;
|
|
1880
|
+
const root = document.documentElement;
|
|
1881
|
+
root.style.backgroundColor = background;
|
|
1882
|
+
document.body.style.backgroundColor = background;
|
|
1883
|
+
for (const selector of [
|
|
1884
|
+
".plans-workspace",
|
|
1885
|
+
"[data-plan-reader]",
|
|
1886
|
+
"[data-plan-document]",
|
|
1887
|
+
]) {
|
|
1888
|
+
const el = document.querySelector(selector);
|
|
1889
|
+
if (el)
|
|
1890
|
+
el.style.backgroundColor = background;
|
|
1891
|
+
}
|
|
1892
|
+
}, theme ? recapScreenshotBackground(theme) : "");
|
|
1818
1893
|
const measuredHeight = await page.evaluate((maxHeight) => {
|
|
1819
1894
|
const readHeights = (selectors) => {
|
|
1820
1895
|
const result = [];
|
|
@@ -1939,18 +2014,13 @@ function recoverRecapFailureEnv(env = process.env) {
|
|
|
1939
2014
|
return recovered;
|
|
1940
2015
|
}
|
|
1941
2016
|
/**
|
|
1942
|
-
* Files that, if a PR touches them, would let that PR rewrite
|
|
1943
|
-
*
|
|
1944
|
-
*
|
|
1945
|
-
*
|
|
1946
|
-
*
|
|
1947
|
-
* The `packages/core/**` rule is scoped to the BuilderIO/agent-native monorepo
|
|
1948
|
-
* (where packages/core IS the recap CLI source) so that consumer repos with an
|
|
1949
|
-
* unrelated `packages/core/` directory are not silently gated. Pass the
|
|
1950
|
-
* `repository` ("owner/name") to apply that scoping; omit it to match the old
|
|
1951
|
-
* unconditional behaviour (safe for the gate's self-test).
|
|
2017
|
+
* Files that, if a PR touches them, would let that PR rewrite the workflow,
|
|
2018
|
+
* skill, or agent config the trusted recap job loads. The workflow runs the
|
|
2019
|
+
* recap CLI from trusted base-branch source (or an installed package), so normal
|
|
2020
|
+
* package code such as `packages/core/**` can be recapped without executing
|
|
2021
|
+
* PR-modified CLI code.
|
|
1952
2022
|
*/
|
|
1953
|
-
export function isRecapSensitivePath(p
|
|
2023
|
+
export function isRecapSensitivePath(p) {
|
|
1954
2024
|
if (p === ".github/workflows/pr-visual-recap.yml" ||
|
|
1955
2025
|
/(^|\/)skills\/visual-(recap|plan|plans)\//.test(p) ||
|
|
1956
2026
|
/(^|\/)\.claude\//.test(p) ||
|
|
@@ -1959,12 +2029,6 @@ export function isRecapSensitivePath(p, repository) {
|
|
|
1959
2029
|
/(^|\/)\.mcp\.json$/.test(p)) {
|
|
1960
2030
|
return true;
|
|
1961
2031
|
}
|
|
1962
|
-
// packages/core is the recap-CLI source only in the agent-native monorepo.
|
|
1963
|
-
// In consumer repos an unrelated packages/core/ must not gate recaps.
|
|
1964
|
-
const isAgentNativeMonorepo = !repository || repository === "BuilderIO/agent-native";
|
|
1965
|
-
if (isAgentNativeMonorepo && /(^|\/)packages\/core\//.test(p)) {
|
|
1966
|
-
return true;
|
|
1967
|
-
}
|
|
1968
2032
|
return false;
|
|
1969
2033
|
}
|
|
1970
2034
|
/**
|
|
@@ -2025,11 +2089,11 @@ export function evaluateRecapGate(input) {
|
|
|
2025
2089
|
reasons.push("invalid VISUAL_RECAP_MODEL value (must match [a-zA-Z0-9._-]{1,80})");
|
|
2026
2090
|
}
|
|
2027
2091
|
// Self-modifying guard: if this PR changes the workflow, the
|
|
2028
|
-
// visual-recap/visual-plan skill,
|
|
2029
|
-
//
|
|
2030
|
-
//
|
|
2031
|
-
//
|
|
2032
|
-
const hits = input.changedFiles.filter((p) => isRecapSensitivePath(p
|
|
2092
|
+
// visual-recap/visual-plan skill, or any agent config the runner would load
|
|
2093
|
+
// (.claude/**, CLAUDE.md, .mcp.json), skip the ENTIRE job — not just the
|
|
2094
|
+
// agent — so a PR can never rewrite what runs (skill, hooks, settings) and
|
|
2095
|
+
// exfiltrate the publish/API secrets.
|
|
2096
|
+
const hits = input.changedFiles.filter((p) => isRecapSensitivePath(p));
|
|
2033
2097
|
if (hits.length) {
|
|
2034
2098
|
reasons.push(`PR modifies recap-control files (${hits.slice(0, 3).join(", ")}${hits.length > 3 ? ", …" : ""}) — skipping so untrusted PR code never runs with secrets`);
|
|
2035
2099
|
}
|
|
@@ -2692,7 +2756,7 @@ Usage:
|
|
|
2692
2756
|
npx @agent-native/core@latest recap mcp-config --agent claude|codex --app-url <url> [--out <path>]
|
|
2693
2757
|
npx @agent-native/core@latest recap scan --diff <path>
|
|
2694
2758
|
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>]
|
|
2695
|
-
npx @agent-native/core@latest recap shot --url <planUrl> [--token <planToken>] [--app-url <url>] [--out recap.png]
|
|
2759
|
+
npx @agent-native/core@latest recap shot --url <planUrl> [--token <planToken>] [--app-url <url>] [--out recap.png] [--theme light|dark]
|
|
2696
2760
|
npx @agent-native/core@latest recap usage --plan-url <planUrl> --result-file <path> --app-url <url> --token <planToken> [--agent claude|codex] [--model <id>]
|
|
2697
2761
|
npx @agent-native/core@latest recap agent-summary --result-file <path> [--stderr-file <path>] [--exit-code-file <path>] [--agent claude|codex]
|
|
2698
2762
|
npx @agent-native/core@latest recap comment <find-plan-id|upsert> --repo owner/name --issue <n> --token <github-token>
|
|
@@ -2716,8 +2780,8 @@ Usage:
|
|
|
2716
2780
|
files from the GitHub REST API (paged, with GH_TOKEN/GITHUB_TOKEN). Skips
|
|
2717
2781
|
drafts, forks, bot authors, the missing-secret case, an invalid agent/model,
|
|
2718
2782
|
and any PR that touches recap-control files (the workflow, the skill,
|
|
2719
|
-
|
|
2720
|
-
|
|
2783
|
+
.claude/**, CLAUDE.md, AGENTS.md, .mcp.json) — failing CLOSED on any
|
|
2784
|
+
file-list error. Writes run=<true|false> and agent=<claude|codex> to
|
|
2721
2785
|
$GITHUB_OUTPUT.
|
|
2722
2786
|
npx @agent-native/core@latest recap agent-summary
|
|
2723
2787
|
Read the captured Claude/Codex result file and write a sanitized one-line
|