@agent-native/core 0.84.55 → 0.84.56

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/corpus/README.md CHANGED
@@ -28,4 +28,4 @@ rg -n "defineAction|useActionQuery" node_modules/@agent-native/core/corpus
28
28
  ## Generated Counts
29
29
 
30
30
  - core files: 2044
31
- - template files: 5003
31
+ - template files: 5004
@@ -1,5 +1,11 @@
1
1
  # @agent-native/core
2
2
 
3
+ ## 0.84.56
4
+
5
+ ### Patch Changes
6
+
7
+ - a0615f8: Make PR visual recap screenshots link directly to the interactive recap and publish source-author metadata for recap comments.
8
+
3
9
  ## 0.84.55
4
10
 
5
11
  ### Patch Changes
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-native/core",
3
- "version": "0.84.55",
3
+ "version": "0.84.56",
4
4
  "description": "Framework for agent-native application development — where AI agents and UI share SQL state, actions, and context",
5
5
  "homepage": "https://github.com/BuilderIO/agent-native#readme",
6
6
  "bugs": {
@@ -1634,6 +1634,20 @@ type GitHubComment = {
1634
1634
 
1635
1635
  type GitHubPullRequest = {
1636
1636
  head?: { sha?: string | null } | null;
1637
+ user?: { login?: string | null; type?: string | null } | null;
1638
+ };
1639
+
1640
+ type GitHubUserProfile = {
1641
+ login?: string | null;
1642
+ name?: string | null;
1643
+ email?: string | null;
1644
+ };
1645
+
1646
+ type GitHubPullRequestCommit = {
1647
+ author?: { login?: string | null } | null;
1648
+ commit?: {
1649
+ author?: { name?: string | null; email?: string | null } | null;
1650
+ } | null;
1637
1651
  };
1638
1652
 
1639
1653
  function repoParts(repoFullName: string): { owner: string; repo: string } {
@@ -1642,6 +1656,20 @@ function repoParts(repoFullName: string): { owner: string; repo: string } {
1642
1656
  return { owner, repo };
1643
1657
  }
1644
1658
 
1659
+ function nonEmptyTrimmed(value: string | null | undefined): string | undefined {
1660
+ const trimmed = value?.trim();
1661
+ return trimmed || undefined;
1662
+ }
1663
+
1664
+ function normalizeSourceAuthorEmail(
1665
+ email: string | null | undefined,
1666
+ ): string | undefined {
1667
+ const trimmed = email?.trim().toLowerCase();
1668
+ if (!trimmed || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(trimmed)) return undefined;
1669
+ if (trimmed.endsWith("@users.noreply.github.com")) return undefined;
1670
+ return trimmed;
1671
+ }
1672
+
1645
1673
  async function githubRequest<T>(
1646
1674
  token: string,
1647
1675
  apiPath: string,
@@ -1667,6 +1695,71 @@ async function githubRequest<T>(
1667
1695
  return (await res.json()) as T;
1668
1696
  }
1669
1697
 
1698
+ export async function resolveGitHubPullRequestAuthor(input: {
1699
+ token: string;
1700
+ repo: string;
1701
+ pr: string;
1702
+ fetchFn?: typeof fetch;
1703
+ }): Promise<{
1704
+ email?: string;
1705
+ name?: string;
1706
+ login?: string;
1707
+ }> {
1708
+ const fn = input.fetchFn ?? fetch;
1709
+ const { owner, repo } = repoParts(input.repo);
1710
+ const pr = await githubRequest<GitHubPullRequest>(
1711
+ input.token,
1712
+ `/repos/${encodeURIComponent(owner)}/${encodeURIComponent(
1713
+ repo,
1714
+ )}/pulls/${encodeURIComponent(input.pr)}`,
1715
+ {},
1716
+ fn,
1717
+ );
1718
+ const login = nonEmptyTrimmed(pr.user?.login);
1719
+ const profile = login
1720
+ ? await githubRequest<GitHubUserProfile>(
1721
+ input.token,
1722
+ `/users/${encodeURIComponent(login)}`,
1723
+ {},
1724
+ fn,
1725
+ ).catch(() => null)
1726
+ : null;
1727
+ const profileEmail = normalizeSourceAuthorEmail(profile?.email);
1728
+ const profileName = nonEmptyTrimmed(profile?.name);
1729
+ let commitEmail: string | undefined;
1730
+ let commitName: string | undefined;
1731
+ if (!profileEmail) {
1732
+ const commits = await githubRequest<GitHubPullRequestCommit[]>(
1733
+ input.token,
1734
+ `/repos/${encodeURIComponent(owner)}/${encodeURIComponent(
1735
+ repo,
1736
+ )}/pulls/${encodeURIComponent(input.pr)}/commits?per_page=100`,
1737
+ {},
1738
+ fn,
1739
+ ).catch(() => []);
1740
+ for (const commit of commits) {
1741
+ const commitLogin = nonEmptyTrimmed(commit.author?.login);
1742
+ if (
1743
+ login &&
1744
+ commitLogin &&
1745
+ commitLogin.toLowerCase() !== login.toLowerCase()
1746
+ ) {
1747
+ continue;
1748
+ }
1749
+ const email = normalizeSourceAuthorEmail(commit.commit?.author?.email);
1750
+ if (!email) continue;
1751
+ commitEmail = email;
1752
+ commitName = nonEmptyTrimmed(commit.commit?.author?.name);
1753
+ break;
1754
+ }
1755
+ }
1756
+ return {
1757
+ email: profileEmail ?? commitEmail,
1758
+ name: profileName ?? commitName ?? login,
1759
+ login,
1760
+ };
1761
+ }
1762
+
1670
1763
  export async function isPullRequestHeadCurrent(input: {
1671
1764
  token: string;
1672
1765
  owner: string;
@@ -1995,14 +2088,15 @@ export function buildCommentBody(env: NodeJS.ProcessEnv = process.env): string {
1995
2088
 
1996
2089
  lines.push(`Here's a [visual recap](${safeUrl}) of what changed:`);
1997
2090
  lines.push("");
1998
- lines.push(`<picture>`);
2091
+ const pictureParts = [`<picture>`];
1999
2092
  if (lightImageUrl && darkImageUrl) {
2000
- lines.push(
2093
+ pictureParts.push(
2001
2094
  ` <source media="(prefers-color-scheme: dark)" srcset="${darkImageUrl}">`,
2002
2095
  );
2003
2096
  }
2004
- lines.push(` <img alt="Visual recap" src="${fallbackImageUrl}">`);
2005
- lines.push(`</picture>`);
2097
+ pictureParts.push(` <img alt="Visual recap" src="${fallbackImageUrl}">`);
2098
+ pictureParts.push(`</picture>`);
2099
+ lines.push(`<a href="${safeUrl}">${pictureParts.join("")}</a>`);
2006
2100
  lines.push("");
2007
2101
  lines.push(`**Open the [full interactive recap](${safeUrl})**`);
2008
2102
  if (env.DIFF_HUGE === "true") {
@@ -2297,6 +2391,7 @@ function recapPublishIdempotencyKey(input: {
2297
2391
  export async function publishRecapSource(input: {
2298
2392
  appUrl: string;
2299
2393
  token: string;
2394
+ githubToken?: string;
2300
2395
  sourcePath?: string;
2301
2396
  out?: string;
2302
2397
  prevPlanId?: string;
@@ -2308,6 +2403,9 @@ export async function publishRecapSource(input: {
2308
2403
  sourcePrNumber?: string;
2309
2404
  sourcePrState?: string;
2310
2405
  sourcePrMergedAt?: string;
2406
+ sourceAuthorEmail?: string;
2407
+ sourceAuthorName?: string;
2408
+ sourceAuthorLogin?: string;
2311
2409
  fetchFn?: typeof fetch;
2312
2410
  cwd?: string;
2313
2411
  }): Promise<{ ok: true; url: string; out: string }> {
@@ -2330,6 +2428,34 @@ export async function publishRecapSource(input: {
2330
2428
  (sourceRepo && sourcePrNumber ? "pull-request" : undefined);
2331
2429
  const sourcePrState =
2332
2430
  input.sourcePrState ?? (input.sourcePrMergedAt ? "merged" : undefined);
2431
+ const explicitSourceAuthor = {
2432
+ email: normalizeSourceAuthorEmail(input.sourceAuthorEmail),
2433
+ name: nonEmptyTrimmed(input.sourceAuthorName),
2434
+ login: nonEmptyTrimmed(input.sourceAuthorLogin),
2435
+ };
2436
+ let resolvedSourceAuthor:
2437
+ | Awaited<ReturnType<typeof resolveGitHubPullRequestAuthor>>
2438
+ | undefined;
2439
+ if (
2440
+ input.githubToken &&
2441
+ sourceRepo &&
2442
+ sourcePrNumber &&
2443
+ (!explicitSourceAuthor.email ||
2444
+ !explicitSourceAuthor.name ||
2445
+ !explicitSourceAuthor.login)
2446
+ ) {
2447
+ resolvedSourceAuthor = await resolveGitHubPullRequestAuthor({
2448
+ token: input.githubToken,
2449
+ repo: sourceRepo,
2450
+ pr: sourcePrNumber,
2451
+ fetchFn: input.fetchFn,
2452
+ }).catch(() => undefined);
2453
+ }
2454
+ const sourceAuthor = {
2455
+ email: explicitSourceAuthor.email ?? resolvedSourceAuthor?.email,
2456
+ name: explicitSourceAuthor.name ?? resolvedSourceAuthor?.name,
2457
+ login: explicitSourceAuthor.login ?? resolvedSourceAuthor?.login,
2458
+ };
2333
2459
  const idempotencyKey = recapPublishIdempotencyKey({
2334
2460
  prevPlanId: input.prevPlanId,
2335
2461
  repo: input.repo,
@@ -2353,6 +2479,9 @@ export async function publishRecapSource(input: {
2353
2479
  ...(input.sourcePrMergedAt
2354
2480
  ? { sourcePrMergedAt: input.sourcePrMergedAt }
2355
2481
  : {}),
2482
+ ...(sourceAuthor.email ? { sourceAuthorEmail: sourceAuthor.email } : {}),
2483
+ ...(sourceAuthor.name ? { sourceAuthorName: sourceAuthor.name } : {}),
2484
+ ...(sourceAuthor.login ? { sourceAuthorLogin: sourceAuthor.login } : {}),
2356
2485
  currentFocus: "visual recap review",
2357
2486
  status: "review",
2358
2487
  mdx: source.mdx,
@@ -2470,6 +2599,10 @@ async function runPublish(
2470
2599
  const result = await publishRecapSource({
2471
2600
  appUrl,
2472
2601
  token,
2602
+ githubToken:
2603
+ optionalArg(args, "github-token") ??
2604
+ process.env.GH_TOKEN ??
2605
+ process.env.GITHUB_TOKEN,
2473
2606
  sourcePath: optionalArg(args, "source") ?? RECAP_SOURCE_FILENAME,
2474
2607
  out,
2475
2608
  prevPlanId: optionalArg(args, "prev-plan-id"),
@@ -2481,6 +2614,14 @@ async function runPublish(
2481
2614
  sourcePrNumber: optionalArg(args, "source-pr-number"),
2482
2615
  sourcePrState: optionalArg(args, "source-pr-state"),
2483
2616
  sourcePrMergedAt: optionalArg(args, "source-pr-merged-at"),
2617
+ sourceAuthorEmail:
2618
+ optionalArg(args, "source-author-email") ?? process.env.PR_AUTHOR_EMAIL,
2619
+ sourceAuthorName:
2620
+ optionalArg(args, "source-author-name") ?? process.env.PR_AUTHOR_NAME,
2621
+ sourceAuthorLogin:
2622
+ optionalArg(args, "source-author-login") ??
2623
+ process.env.PR_AUTHOR_LOGIN ??
2624
+ process.env.GITHUB_ACTOR,
2484
2625
  });
2485
2626
  writeGitHubOutput("ok", "true");
2486
2627
  writeGitHubOutput("plan_url", result.url);
@@ -4081,7 +4222,7 @@ Usage:
4081
4222
  npx @agent-native/core@latest recap block-reference [--app-url <url>] [--out recap-blocks.md]
4082
4223
  npx @agent-native/core@latest recap scan --diff <path> [--mode off|high-confidence|strict]
4083
4224
  npx @agent-native/core@latest recap build-prompt --pr <n> [--repo owner/name] [--head <sha>] [--app-url <url>] [--diff <path>] [--stat <path>] [--block-reference recap-blocks.md] [--prev-plan-id <id>] [--huge] [--local-files] [--local-dir <folder>] [--skill-source auto|latest|repo] [--out <path>]
4084
- npx @agent-native/core@latest recap publish [--source recap-source.json] [--out recap-url.txt] [--repo owner/name] [--pr <n>] [--prev-plan-id <id>] [--source-pr-state open|closed|merged] [--source-pr-merged-at <iso>] [--app-url <url>] [--token <planToken>]
4225
+ npx @agent-native/core@latest recap publish [--source recap-source.json] [--out recap-url.txt] [--repo owner/name] [--pr <n>] [--prev-plan-id <id>] [--source-pr-state open|closed|merged] [--source-pr-merged-at <iso>] [--source-author-email <email>] [--source-author-name <name>] [--source-author-login <login>] [--app-url <url>] [--token <planToken>] [--github-token <ghToken>]
4085
4226
  npx @agent-native/core@latest recap shot --url <planUrl> [--token <planToken>] [--app-url <url>] [--out recap.png] [--theme light|dark] [--image-cache-key <key>]
4086
4227
  npx @agent-native/core@latest recap usage --plan-url <planUrl> --result-file <path> --app-url <url> --token <planToken> [--agent claude|codex] [--model <id>]
4087
4228
  npx @agent-native/core@latest recap agent-summary --result-file <path> [--stderr-file <path>] [--exit-code-file <path>] [--agent claude|codex]
@@ -44,12 +44,24 @@ const sourceTypeSchema = z
44
44
 
45
45
  const sourcePrStateSchema = z.enum(["open", "closed", "merged", "unknown"]);
46
46
 
47
+ const sourceAuthorEmailSchema = z
48
+ .string()
49
+ .trim()
50
+ .email()
51
+ .transform((email) => email.toLowerCase())
52
+ .optional();
53
+
54
+ const sourceAuthorTextSchema = z.string().trim().min(1).optional();
55
+
47
56
  type RecapSourceMetadata = {
48
57
  sourceType?: string;
49
58
  sourceRepo?: string;
50
59
  sourcePrNumber?: number;
51
60
  sourcePrState?: "open" | "closed" | "merged" | "unknown";
52
61
  sourcePrMergedAt?: string;
62
+ sourceAuthorEmail?: string;
63
+ sourceAuthorName?: string;
64
+ sourceAuthorLogin?: string;
53
65
  };
54
66
 
55
67
  type RecapVisibility = "private" | "org" | "public";
@@ -82,17 +94,26 @@ function normalizeRecapSourceMetadata(args: {
82
94
  sourcePrNumber?: number;
83
95
  sourcePrState?: "open" | "closed" | "merged" | "unknown";
84
96
  sourcePrMergedAt?: string;
97
+ sourceAuthorEmail?: string;
98
+ sourceAuthorName?: string;
99
+ sourceAuthorLogin?: string;
85
100
  }): RecapSourceMetadata {
86
101
  const inferred = inferGithubPullRequestSource(args.sourceUrl);
87
102
  const sourcePrMergedAt = args.sourcePrMergedAt?.trim();
88
103
  const sourcePrState =
89
104
  args.sourcePrState ?? (sourcePrMergedAt ? "merged" : undefined);
105
+ const sourceAuthorEmail = args.sourceAuthorEmail?.trim().toLowerCase();
106
+ const sourceAuthorName = args.sourceAuthorName?.trim();
107
+ const sourceAuthorLogin = args.sourceAuthorLogin?.trim();
90
108
  return {
91
109
  sourceType: args.sourceType ?? inferred.sourceType,
92
110
  sourceRepo: args.sourceRepo ?? inferred.sourceRepo,
93
111
  sourcePrNumber: args.sourcePrNumber ?? inferred.sourcePrNumber,
94
112
  sourcePrState,
95
113
  sourcePrMergedAt: sourcePrMergedAt || undefined,
114
+ sourceAuthorEmail: sourceAuthorEmail || undefined,
115
+ sourceAuthorName: sourceAuthorName || undefined,
116
+ sourceAuthorLogin: sourceAuthorLogin || undefined,
96
117
  };
97
118
  }
98
119
 
@@ -229,6 +250,15 @@ export default defineAction({
229
250
  .min(1)
230
251
  .optional()
231
252
  .describe("ISO timestamp for when the source pull request was merged."),
253
+ sourceAuthorEmail: sourceAuthorEmailSchema.describe(
254
+ "Email address for the human author of the source PR, used as the default human comment target for recap feedback.",
255
+ ),
256
+ sourceAuthorName: sourceAuthorTextSchema.describe(
257
+ "Display name for the human author of the source PR.",
258
+ ),
259
+ sourceAuthorLogin: sourceAuthorTextSchema.describe(
260
+ "GitHub login for the human author of the source PR.",
261
+ ),
232
262
  idempotencyKey: z
233
263
  .string()
234
264
  .trim()
@@ -328,6 +358,15 @@ export default defineAction({
328
358
  ...(sourceMetadata.sourcePrMergedAt !== undefined
329
359
  ? { sourcePrMergedAt: sourceMetadata.sourcePrMergedAt }
330
360
  : {}),
361
+ ...(sourceMetadata.sourceAuthorEmail !== undefined
362
+ ? { sourceAuthorEmail: sourceMetadata.sourceAuthorEmail }
363
+ : {}),
364
+ ...(sourceMetadata.sourceAuthorName !== undefined
365
+ ? { sourceAuthorName: sourceMetadata.sourceAuthorName }
366
+ : {}),
367
+ ...(sourceMetadata.sourceAuthorLogin !== undefined
368
+ ? { sourceAuthorLogin: sourceMetadata.sourceAuthorLogin }
369
+ : {}),
331
370
  ...(idempotencyKey ? { recapIdempotencyKey: idempotencyKey } : {}),
332
371
  };
333
372
  if (Object.keys(planPatch).length > 0) {
@@ -77,6 +77,8 @@ export default defineAction({
77
77
  hostedPlanId: schema.plans.hostedPlanId,
78
78
  hostedPlanUrl: schema.plans.hostedPlanUrl,
79
79
  sourceUrl: schema.plans.sourceUrl,
80
+ sourceAuthorName: schema.plans.sourceAuthorName,
81
+ sourceAuthorLogin: schema.plans.sourceAuthorLogin,
80
82
  createdAt: schema.plans.createdAt,
81
83
  updatedAt: schema.plans.updatedAt,
82
84
  approvedAt: schema.plans.approvedAt,
@@ -152,6 +152,8 @@ export default defineAction({
152
152
  hostedPlanId: schema.plans.hostedPlanId,
153
153
  hostedPlanUrl: schema.plans.hostedPlanUrl,
154
154
  sourceUrl: schema.plans.sourceUrl,
155
+ sourceAuthorName: schema.plans.sourceAuthorName,
156
+ sourceAuthorLogin: schema.plans.sourceAuthorLogin,
155
157
  createdAt: schema.plans.createdAt,
156
158
  updatedAt: schema.plans.updatedAt,
157
159
  approvedAt: schema.plans.approvedAt,