@agent-native/core 0.164.3 → 0.164.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 (28) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/clips/.agents/skills/video-sharing/SKILL.md +1 -1
  3. package/corpus/templates/clips/app/routes/share.$shareId.tsx +3 -0
  4. package/corpus/templates/clips/changelog/2026-08-19-social-preview-video-frames.md +5 -0
  5. package/corpus/templates/clips/server/lib/slack-unfurls.ts +7 -13
  6. package/corpus/templates/clips/shared/share-meta.ts +67 -3
  7. package/corpus/templates/content/actions/list-content-databases.ts +95 -1
  8. package/corpus/templates/content/changelog/2026-08-11-content-mutations-delegated-from-verified-slack-dms-now-use-.md +6 -0
  9. package/corpus/templates/content/shared/api.ts +11 -0
  10. package/dist/a2a/artifact-response.d.ts +50 -0
  11. package/dist/a2a/artifact-response.js +266 -39
  12. package/dist/a2a/index.d.ts +2 -0
  13. package/dist/a2a/index.js +1 -0
  14. package/dist/collab/awareness.d.ts +2 -2
  15. package/dist/integrations/adapters/slack.js +58 -7
  16. package/dist/notifications/routes.d.ts +3 -3
  17. package/dist/progress/routes.d.ts +1 -1
  18. package/dist/server/agent-chat/action-filters-a2a.d.ts +2 -1
  19. package/dist/server/agent-chat/action-filters-a2a.js +10 -2
  20. package/dist/server/agent-chat-plugin.js +33 -11
  21. package/dist/server/realtime-token.d.ts +1 -1
  22. package/docs/content/drop-in-agent.mdx +4 -4
  23. package/docs/content/faq.mdx +15 -15
  24. package/docs/content/getting-started.mdx +173 -41
  25. package/docs/content/key-concepts.mdx +10 -10
  26. package/docs/content/using-your-agent.mdx +2 -2
  27. package/docs/content/what-is-agent-native.mdx +79 -228
  28. package/package.json +1 -1
package/corpus/README.md CHANGED
@@ -31,4 +31,4 @@ rg -n "defineAction|useActionQuery" node_modules/@agent-native/core/corpus
31
31
 
32
32
  ## Generated Counts
33
33
 
34
- - template files: 8445
34
+ - template files: 8447
@@ -176,7 +176,7 @@ The playable Slack embed is deliberately narrower than the share page:
176
176
 
177
177
  - Only `ready` recordings with `visibility === "public"` can produce a video block.
178
178
  - Password-protected, expired, archived, trashed, private, org-only, or still-processing clips must not produce a playable Slack block.
179
- - Slack thumbnails use the stored thumbnail (or animated thumbnail as fallback) and normal share-page metadata remains the fallback when no Slack app is installed.
179
+ - Slack thumbnails use the same-origin proxy for stored thumbnails, or a public video frame when no stored thumbnail exists; normal share-page metadata remains the fallback when no Slack app is installed.
180
180
  - Do not put passwords, short-lived share tokens, raw provider URLs, or transcript text in Slack unfurl payloads.
181
181
 
182
182
  Required Slack app setup:
@@ -124,6 +124,7 @@ type SharePageMetaRecording = {
124
124
  animatedThumbnailUrl: string | null;
125
125
  visibility: "private" | "org" | "public";
126
126
  status: "uploading" | "processing" | "ready" | "failed";
127
+ hasPassword: boolean;
127
128
  archivedAt: string | null;
128
129
  trashedAt: string | null;
129
130
  };
@@ -270,6 +271,7 @@ export async function loader({ params, url }: LoaderFunctionArgs) {
270
271
  animatedThumbnailUrl: null,
271
272
  visibility: rec.visibility,
272
273
  status: rec.status,
274
+ hasPassword: Boolean(rec.password),
273
275
  archivedAt: rec.archivedAt,
274
276
  trashedAt: rec.trashedAt,
275
277
  };
@@ -315,6 +317,7 @@ export const meta: MetaFunction<typeof loader> = ({ loaderData }) => {
315
317
  return buildClipsShareMeta({
316
318
  recording: loaderData?.recording ?? null,
317
319
  origin: loaderData?.origin ?? null,
320
+ basePath: appBasePath(),
318
321
  shareUrl: loaderData?.shareUrl ?? null,
319
322
  });
320
323
  };
@@ -0,0 +1,5 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-08-19
4
+ ---
5
+ Shared clips now show a video frame in social previews when no thumbnail is stored.
@@ -6,6 +6,7 @@ import { eq } from "drizzle-orm";
6
6
  import {
7
7
  clipsShareDescription,
8
8
  displayRecordingTitle,
9
+ resolveClipsSocialImageUrl,
9
10
  } from "../../shared/share-meta.js";
10
11
  import { getDb, schema } from "../db/index.js";
11
12
 
@@ -191,15 +192,6 @@ function appPath(path: string, basePath: string): string {
191
192
  return base ? `/${base}${path}` : path;
192
193
  }
193
194
 
194
- function absoluteUrl(value: string | null | undefined, origin: string): string {
195
- if (!value) return AGENT_NATIVE_DEFAULT_SOCIAL_IMAGE;
196
- try {
197
- return new URL(value, origin).toString();
198
- } catch {
199
- return AGENT_NATIVE_DEFAULT_SOCIAL_IMAGE;
200
- }
201
- }
202
-
203
195
  function isExpired(value: string | null): boolean {
204
196
  if (!value) return false;
205
197
  const expires = new Date(value).getTime();
@@ -255,10 +247,12 @@ export function buildSlackVideoBlock(options: {
255
247
  const description = duration
256
248
  ? `${duration} · ${shareDescription}`
257
249
  : shareDescription;
258
- const thumbnailUrl = absoluteUrl(
259
- recording.thumbnailUrl || recording.animatedThumbnailUrl,
260
- origin,
261
- );
250
+ const thumbnailUrl =
251
+ resolveClipsSocialImageUrl({
252
+ recording,
253
+ origin,
254
+ basePath,
255
+ }) ?? AGENT_NATIVE_DEFAULT_SOCIAL_IMAGE;
262
256
 
263
257
  return {
264
258
  type: "video",
@@ -8,15 +8,25 @@ import {
8
8
  type SocialMetaDescriptor,
9
9
  } from "@agent-native/core/shared";
10
10
 
11
+ import { buildAgentApiUrls } from "./agent-context";
12
+
11
13
  export const CLIPS_DEFAULT_TITLE = "Untitled recording";
12
14
 
13
15
  export type ClipsShareMetaRecording = {
16
+ id?: string | null;
14
17
  title?: string | null;
15
18
  description?: string | null;
16
19
  thumbnailUrl?: string | null;
17
20
  animatedThumbnailUrl?: string | null;
21
+ visibility?: string | null;
22
+ status?: string | null;
23
+ hasPassword?: boolean;
24
+ archivedAt?: string | null;
25
+ trashedAt?: string | null;
18
26
  };
19
27
 
28
+ const SOCIAL_FRAME_AT_MS = 350;
29
+
20
30
  export type PreferredThumbnailVariant = "still" | "animated";
21
31
 
22
32
  export function preferredThumbnailVariant(
@@ -81,16 +91,70 @@ function absoluteUrl(value: string, origin: string | null): string {
81
91
  }
82
92
  }
83
93
 
94
+ function appPath(path: string, basePath: string): string {
95
+ const normalizedBasePath = basePath.trim().replace(/\/+$/, "");
96
+ return normalizedBasePath ? `${normalizedBasePath}${path}` : path;
97
+ }
98
+
99
+ function canUseGeneratedSocialFrame(
100
+ recording: ClipsShareMetaRecording | null,
101
+ ): recording is ClipsShareMetaRecording & {
102
+ id: string;
103
+ visibility: "public";
104
+ status: "ready";
105
+ } {
106
+ return Boolean(
107
+ recording?.id &&
108
+ recording.visibility === "public" &&
109
+ recording.status === "ready" &&
110
+ !recording.hasPassword &&
111
+ !recording.archivedAt &&
112
+ !recording.trashedAt,
113
+ );
114
+ }
115
+
116
+ export function resolveClipsSocialImageUrl(options: {
117
+ recording: ClipsShareMetaRecording | null;
118
+ origin?: string | null;
119
+ basePath?: string;
120
+ }): string | undefined {
121
+ const { recording, origin = null, basePath = "" } = options;
122
+ if (recording?.hasPassword) return undefined;
123
+
124
+ const storedImage = preferredSocialImage(recording);
125
+
126
+ if (storedImage) {
127
+ if (recording?.id && recording.visibility === "public") {
128
+ return absoluteUrl(
129
+ appPath(`/api/thumbnail/${encodeURIComponent(recording.id)}`, basePath),
130
+ origin,
131
+ );
132
+ }
133
+ return absoluteUrl(storedImage, origin);
134
+ }
135
+
136
+ if (!origin || !canUseGeneratedSocialFrame(recording)) return undefined;
137
+
138
+ return buildAgentApiUrls(recording.id, {
139
+ origin,
140
+ basePath,
141
+ }).frameUrl(SOCIAL_FRAME_AT_MS);
142
+ }
143
+
84
144
  export function buildClipsShareMeta(options: {
85
145
  recording: ClipsShareMetaRecording | null;
86
146
  origin?: string | null;
147
+ basePath?: string;
87
148
  shareUrl?: string | null;
88
149
  }): SocialMetaDescriptor[] {
89
- const { recording, origin = null, shareUrl = null } = options;
150
+ const { recording, origin = null, basePath = "", shareUrl = null } = options;
90
151
  const title = clipsSharePageTitle(recording?.title);
91
152
  const description = clipsShareDescription(recording);
92
- const image = preferredSocialImage(recording);
93
- const absoluteImage = image ? absoluteUrl(image, origin) : undefined;
153
+ const absoluteImage = resolveClipsSocialImageUrl({
154
+ recording,
155
+ origin,
156
+ basePath,
157
+ });
94
158
  const alt = hasGeneratedTitle(recording?.title)
95
159
  ? recording!.title!.trim()
96
160
  : AGENT_NATIVE_SOCIAL_IMAGE_ALT;
@@ -1,10 +1,12 @@
1
1
  import { defineAction } from "@agent-native/core";
2
+ import { getRequestUserEmail } from "@agent-native/core/server/request-context";
2
3
  import { accessFilter } from "@agent-native/core/sharing";
3
4
  import {
4
5
  and,
5
6
  asc,
6
7
  eq,
7
8
  inArray,
9
+ isNotNull,
8
10
  isNull,
9
11
  notInArray,
10
12
  or,
@@ -15,6 +17,11 @@ import { z } from "zod";
15
17
  import { getDb, schema } from "../server/db/index.js";
16
18
  import { documentDiscoveryFilter } from "../server/lib/documents.js";
17
19
  import type { ListContentDatabasesResponse } from "../shared/api.js";
20
+ import {
21
+ listContentOrganizationMemberships,
22
+ normalizeContentSpaceEmail,
23
+ resolveContentSpaceAccess,
24
+ } from "./_content-space-access.js";
18
25
  import { documentDiscoveryPagination } from "./_document-discovery-query.js";
19
26
 
20
27
  const DEFAULT_CONTENT_DATABASE_DISCOVERY_LIMIT = 50;
@@ -27,7 +34,7 @@ export class ContentDatabaseResolutionError extends Error {}
27
34
 
28
35
  export default defineAction({
29
36
  description:
30
- "Discover one bounded page of ordinary Content databases the user can access from their live title and user-authored description. Returns stable database, document, and space IDs with explicit pagination; follow nextOffset until hasMore is false. Use exact filters before reading a selected database's schema.",
37
+ "Discover one bounded page of ordinary Content databases the user can access from their live title and user-authored description. Returns stable database, document, and space IDs with explicit pagination; follow nextOffset until hasMore is false. Use exact filters before reading a selected database's schema. Set includeSystemCollections to classify Files, Favorites, Workspaces, and other system chrome separately from ordinary databases.",
31
38
  schema: z.object({
32
39
  spaceId: z
33
40
  .string()
@@ -39,6 +46,12 @@ export default defineAction({
39
46
  .min(1)
40
47
  .optional()
41
48
  .describe("Exact Content database ID to resolve."),
49
+ includeSystemCollections: z
50
+ .boolean()
51
+ .optional()
52
+ .describe(
53
+ "Return system-role databases in systemCollections instead of treating them as ordinary databases.",
54
+ ),
42
55
  documentId: z
43
56
  .string()
44
57
  .min(1)
@@ -80,7 +93,21 @@ export default defineAction({
80
93
  readOnly: true,
81
94
  publicAgent: { expose: true, readOnly: true, requiresAuth: true },
82
95
  run: async (args): Promise<ListContentDatabasesResponse> => {
96
+ if (args.includeSystemCollections && !args.spaceId) {
97
+ throw new ContentDatabaseResolutionError(
98
+ "An exact spaceId is required to inventory system collections.",
99
+ );
100
+ }
83
101
  const db = getDb();
102
+ const requestUserEmail = getRequestUserEmail();
103
+ if (!requestUserEmail) throw new Error("no authenticated user");
104
+ const normalizedUserEmail = normalizeContentSpaceEmail(requestUserEmail);
105
+ const authorizedOrganizationIds = (
106
+ await listContentOrganizationMemberships(normalizedUserEmail)
107
+ ).map((membership) => membership.orgId);
108
+ if (args.spaceId) {
109
+ await resolveContentSpaceAccess(args.spaceId, "viewer", { db });
110
+ }
84
111
  const query = args.query?.trim();
85
112
  const pattern = query ? `%${escapeLike(query.toLowerCase())}%` : null;
86
113
  const exactTitle = args.title?.toLowerCase();
@@ -160,6 +187,19 @@ export default defineAction({
160
187
  isNull(schema.documents.hideFromSearch),
161
188
  ),
162
189
  isNull(schema.contentDatabases.deletedAt),
190
+ isNull(schema.contentSpaces.archivedAt),
191
+ or(
192
+ isNull(schema.contentDatabases.spaceId),
193
+ and(
194
+ isNotNull(schema.contentSpaces.id),
195
+ or(
196
+ sql`LOWER(${schema.contentSpaces.ownerEmail}) = ${normalizedUserEmail}`,
197
+ authorizedOrganizationIds.length > 0
198
+ ? inArray(schema.contentSpaces.orgId, authorizedOrganizationIds)
199
+ : undefined,
200
+ ),
201
+ ),
202
+ ),
163
203
  isNull(schema.contentDatabases.systemRole),
164
204
  args.spaceId
165
205
  ? eq(schema.contentDatabases.spaceId, args.spaceId)
@@ -206,6 +246,10 @@ export default defineAction({
206
246
  schema.documents,
207
247
  eq(schema.contentDatabases.documentId, schema.documents.id),
208
248
  )
249
+ .leftJoin(
250
+ schema.contentSpaces,
251
+ eq(schema.contentDatabases.spaceId, schema.contentSpaces.id),
252
+ )
209
253
  .where(where)
210
254
  .orderBy(
211
255
  asc(schema.documents.position),
@@ -252,10 +296,51 @@ export default defineAction({
252
296
  schema.documents,
253
297
  eq(schema.contentDatabases.documentId, schema.documents.id),
254
298
  )
299
+ .leftJoin(
300
+ schema.contentSpaces,
301
+ eq(schema.contentDatabases.spaceId, schema.contentSpaces.id),
302
+ )
255
303
  .where(where)
256
304
  )[0]?.count ?? 0,
257
305
  );
258
306
 
307
+ const systemCollections = args.includeSystemCollections
308
+ ? await db
309
+ .select({
310
+ databaseId: schema.contentDatabases.id,
311
+ documentId: schema.contentDatabases.documentId,
312
+ title: schema.documents.title,
313
+ spaceId: schema.contentDatabases.spaceId,
314
+ spaceName: schema.contentSpaces.name,
315
+ spaceKind: schema.contentSpaces.kind,
316
+ systemRole: schema.contentDatabases.systemRole,
317
+ })
318
+ .from(schema.contentDatabases)
319
+ .innerJoin(
320
+ schema.documents,
321
+ eq(schema.contentDatabases.documentId, schema.documents.id),
322
+ )
323
+ .leftJoin(
324
+ schema.contentSpaces,
325
+ eq(schema.contentDatabases.spaceId, schema.contentSpaces.id),
326
+ )
327
+ .where(
328
+ and(
329
+ accessFilter(schema.documents, schema.documentShares),
330
+ isNull(schema.documents.trashedAt),
331
+ isNull(schema.contentDatabases.deletedAt),
332
+ isNull(schema.contentSpaces.archivedAt),
333
+ isNotNull(schema.contentSpaces.id),
334
+ isNotNull(schema.contentDatabases.systemRole),
335
+ eq(schema.contentDatabases.spaceId, args.spaceId!),
336
+ ),
337
+ )
338
+ .orderBy(
339
+ asc(schema.documents.position),
340
+ asc(schema.contentDatabases.id),
341
+ )
342
+ : undefined;
343
+
259
344
  return {
260
345
  databases,
261
346
  pagination: documentDiscoveryPagination({
@@ -264,6 +349,15 @@ export default defineAction({
264
349
  totalItems,
265
350
  returnedItems: databases.length,
266
351
  }),
352
+ ...(systemCollections
353
+ ? {
354
+ systemCollections: systemCollections.map((collection) => ({
355
+ ...collection,
356
+ title: collection.title ?? "Untitled database",
357
+ systemRole: collection.systemRole!,
358
+ })),
359
+ }
360
+ : {}),
267
361
  };
268
362
  },
269
363
  });
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-08-11
4
+ ---
5
+
6
+ Content mutations delegated from verified Slack DMs now use the member's exact Personal scope and return verified row receipts.
@@ -1082,6 +1082,16 @@ export interface ContentDatabaseSummary {
1082
1082
  description: string;
1083
1083
  }
1084
1084
 
1085
+ export interface ContentSystemCollectionSummary {
1086
+ databaseId: string;
1087
+ documentId: string;
1088
+ title: string;
1089
+ spaceId: string | null;
1090
+ spaceName: string | null;
1091
+ spaceKind: string | null;
1092
+ systemRole: string;
1093
+ }
1094
+
1085
1095
  export interface ContentDatabaseDescriptionResponse {
1086
1096
  database: ContentDatabase;
1087
1097
  contextPath: ContentContextPathEntry[];
@@ -1091,6 +1101,7 @@ export interface ContentDatabaseDescriptionResponse {
1091
1101
  export interface ListContentDatabasesResponse {
1092
1102
  databases: ContentDatabaseSummary[];
1093
1103
  pagination: DocumentDiscoveryPagination;
1104
+ systemCollections?: ContentSystemCollectionSummary[];
1094
1105
  }
1095
1106
 
1096
1107
  export interface TrashedContentDatabaseSummary {
@@ -9,6 +9,7 @@ export interface A2AArtifactResponseOptions {
9
9
  includeReferencedArtifacts?: boolean;
10
10
  includePersistedArtifactMarker?: boolean;
11
11
  persistedArtifactSecret?: string;
12
+ delegatedTaskId?: string;
12
13
  }
13
14
  export interface GuardedA2AArtifactResponse {
14
15
  text: string;
@@ -16,6 +17,7 @@ export interface GuardedA2AArtifactResponse {
16
17
  }
17
18
  export interface A2AArtifactIdentityOptions {
18
19
  persistedArtifactSecrets?: readonly string[];
20
+ expectedDelegatedTaskId?: string;
19
21
  }
20
22
  export interface A2AArtifactIdentity {
21
23
  resourceType: "document" | "deck" | "dashboard" | "analysis" | "image" | "design" | "monitor" | "form";
@@ -24,6 +26,48 @@ export interface A2AArtifactIdentity {
24
26
  titleAtAction?: string;
25
27
  url?: string;
26
28
  }
29
+ export interface A2APersistedMutationReceipt {
30
+ receiptId: string;
31
+ sourceAction: string;
32
+ operation: string;
33
+ outcome: string;
34
+ target: {
35
+ authorityScopeKind: "personal" | "organization";
36
+ authorityScopeId: string;
37
+ spaceId: string;
38
+ databaseId: string;
39
+ databaseDocumentId: string;
40
+ itemId?: string;
41
+ rowDocumentId?: string;
42
+ propertyId?: string;
43
+ };
44
+ row: {
45
+ itemId?: string;
46
+ documentId: string;
47
+ urlPath: string;
48
+ };
49
+ idempotency: {
50
+ key: string;
51
+ result: "applied" | "replayed";
52
+ payloadDigest: string;
53
+ };
54
+ revisions: {
55
+ before?: string | null;
56
+ after?: string;
57
+ rowBefore?: string;
58
+ rowAfter?: string;
59
+ fieldBefore?: number;
60
+ fieldAfter?: number;
61
+ };
62
+ affected?: {
63
+ title?: boolean;
64
+ propertyIds?: string[];
65
+ blockIds?: string[];
66
+ deletedBlockIds?: string[];
67
+ order?: string[];
68
+ };
69
+ readbackVerified: true;
70
+ }
27
71
  export declare function stripA2APersistedArtifactMarkers(text: string): string;
28
72
  /**
29
73
  * Extract a compact, verified identity ledger from successful artifact tools.
@@ -31,6 +75,12 @@ export declare function stripA2APersistedArtifactMarkers(text: string): string;
31
75
  * long-lived thread context and stable even when a resource is later renamed.
32
76
  */
33
77
  export declare function extractA2AArtifactIdentities(results: A2AToolResultSummary[], options?: A2AArtifactIdentityOptions): A2AArtifactIdentity[];
78
+ /**
79
+ * Extract bounded, read-back-verified mutation receipts. Nested agent results
80
+ * are accepted only through the signed persisted ledger.
81
+ */
82
+ export declare function extractA2APersistedMutationReceipts(results: A2AToolResultSummary[], options?: A2AArtifactIdentityOptions): A2APersistedMutationReceipt[];
83
+ export declare function appendA2APersistedMutationReceipts(text: string, toolResults: A2AToolResultSummary[], options?: A2AArtifactResponseOptions): string;
34
84
  export declare function guardA2AArtifactResponse(responseText: string, toolResults: A2AToolResultSummary[], options?: A2AArtifactResponseOptions): GuardedA2AArtifactResponse;
35
85
  export declare function appendA2AArtifactLinks(responseText: string, toolResults: A2AToolResultSummary[], options?: A2AArtifactResponseOptions): string;
36
86
  export declare function buildA2ARecoverableArtifactMessage(toolResults: A2AToolResultSummary[], options?: A2AArtifactResponseOptions): string | null;