@agent-native/core 0.160.1 → 0.161.0

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 (92) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/actions/bigquery.ts +57 -0
  3. package/corpus/templates/analytics/actions/restore-analysis-revision.ts +5 -2
  4. package/corpus/templates/analytics/actions/restore-dashboard-revision.ts +5 -2
  5. package/corpus/templates/analytics/app/components/analysis/AnalysisHistoryPanel.tsx +14 -3
  6. package/corpus/templates/analytics/app/components/dashboard/DashboardHistoryPanel.tsx +14 -3
  7. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +3 -0
  8. package/corpus/templates/analytics/app/hooks/use-analysis-revisions.ts +1 -0
  9. package/corpus/templates/analytics/app/hooks/use-dashboard-revisions.ts +16 -1
  10. package/corpus/templates/analytics/app/lib/use-auto-focus-select.ts +19 -0
  11. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/PanelEditorDialog.tsx +0 -1
  12. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/dashboard-sync.ts +14 -0
  13. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +19 -0
  14. package/corpus/templates/analytics/changelog/2026-08-17-fixed-dashboard-editing-focus-history-recovery-panel-setup-f.md +6 -0
  15. package/corpus/templates/calendar/actions/create-event.ts +7 -3
  16. package/corpus/templates/calendar/actions/manage-event-draft.ts +8 -0
  17. package/corpus/templates/calendar/app/components/calendar/CreateEventDialog.tsx +64 -0
  18. package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +5 -1
  19. package/corpus/templates/calendar/app/hooks/use-events.ts +1 -0
  20. package/corpus/templates/calendar/changelog/2026-08-17-booking-links-now-respect-each-required-host-s-saved-availab.md +6 -0
  21. package/corpus/templates/calendar/server/lib/booking-host-availability.ts +76 -0
  22. package/corpus/templates/calendar/server/lib/google-calendar.ts +2 -0
  23. package/corpus/templates/calendar/shared/api.ts +1 -1
  24. package/corpus/templates/clips/app/components/player/signed-out-share-actions.tsx +47 -0
  25. package/corpus/templates/clips/app/components/player/transcript-panel.tsx +3 -1
  26. package/corpus/templates/clips/app/routes/share.$shareId.tsx +27 -19
  27. package/corpus/templates/clips/changelog/2026-08-17-shared-clips-sign-in-comment-no-audio.md +6 -0
  28. package/corpus/templates/clips/server/routes/api/public-recording.get.ts +6 -5
  29. package/corpus/templates/content/.agents/skills/document-editing/references/databases.md +13 -0
  30. package/corpus/templates/content/AGENTS.md +2 -0
  31. package/corpus/templates/content/actions/_blocks-field-identity.ts +17 -0
  32. package/corpus/templates/content/actions/_database-block-actions.ts +958 -0
  33. package/corpus/templates/content/actions/_database-row-mutation.ts +7 -7
  34. package/corpus/templates/content/actions/list-content-database-blocks.ts +31 -0
  35. package/corpus/templates/content/actions/mutate-content-database-block.ts +49 -0
  36. package/corpus/templates/content/changelog/2026-08-10-agents-can-safely-insert-update-upsert-delete-and-reorder-on.md +6 -0
  37. package/corpus/templates/content/docs/product/capabilities/content.object.block.md +6 -2
  38. package/corpus/templates/content/docs/product/capabilities/content.object.blocks-field.md +2 -1
  39. package/corpus/templates/content/parity/matrix.md +30 -30
  40. package/corpus/templates/content/parity/matrix.ts +4 -1
  41. package/corpus/templates/content/shared/blocks-field-identity.ts +114 -37
  42. package/corpus/templates/content/shared/database-block-actions.ts +82 -0
  43. package/corpus/templates/content/shared/database-block-mutations.ts +366 -0
  44. package/corpus/templates/content/shared/nfm.ts +3 -2
  45. package/corpus/templates/mail/.agents/skills/email-drafts/SKILL.md +24 -0
  46. package/corpus/templates/mail/actions/get-mail-settings.ts +1 -1
  47. package/corpus/templates/mail/actions/update-mail-settings.ts +7 -3
  48. package/corpus/templates/mail/app/i18n/en-US.ts +3 -1
  49. package/corpus/templates/mail/app/pages/SettingsPage.tsx +63 -0
  50. package/corpus/templates/mail/changelog/2026-08-17-gmail-signatures-now-preserve-images-and-support-paste-or-up.md +6 -0
  51. package/corpus/templates/mail/changelog/2026-08-17-mail-now-updates-durable-drafting-preferences-without-openin.md +6 -0
  52. package/corpus/templates/mail/server/plugins/agent-chat.ts +19 -0
  53. package/corpus/templates/mail/shared/gmail-signature.ts +14 -2
  54. package/corpus/templates/mail/shared/signature.ts +1 -10
  55. package/corpus/templates/slides/actions/generate-slides-ai.ts +2 -0
  56. package/corpus/templates/slides/actions/patch-deck.ts +1 -1
  57. package/corpus/templates/slides/app/components/ThemeToggle.tsx +1 -0
  58. package/corpus/templates/slides/app/components/editor/SlideThumbnailContextMenu.tsx +59 -0
  59. package/corpus/templates/slides/app/global.css +1 -0
  60. package/corpus/templates/slides/app/lib/normalize-slide-padding.ts +40 -11
  61. package/corpus/templates/slides/changelog/2026-08-17-stable-layout-and-slide-editing.md +6 -0
  62. package/corpus/templates/slides/shared/deck-title.ts +12 -4
  63. package/dist/agent/production-agent.js +17 -4
  64. package/dist/agent/tool-approval-migrations.d.ts +1 -0
  65. package/dist/agent/tool-approval-migrations.js +10 -0
  66. package/dist/agent/tool-approval-store.d.ts +12 -0
  67. package/dist/agent/tool-approval-store.js +46 -1
  68. package/dist/agent/types.d.ts +3 -1
  69. package/dist/client/AssistantChat.js +3 -1
  70. package/dist/localization/core-messages/ar-SA.js +1 -0
  71. package/dist/localization/core-messages/de-DE.js +1 -0
  72. package/dist/localization/core-messages/en-US.d.ts +1 -0
  73. package/dist/localization/core-messages/en-US.js +1 -0
  74. package/dist/localization/core-messages/es-ES.js +1 -0
  75. package/dist/localization/core-messages/fr-FR.js +1 -0
  76. package/dist/localization/core-messages/hi-IN.js +1 -0
  77. package/dist/localization/core-messages/ja-JP.js +1 -0
  78. package/dist/localization/core-messages/ko-KR.js +1 -0
  79. package/dist/localization/core-messages/pt-BR.js +1 -0
  80. package/dist/localization/core-messages/zh-CN.js +1 -0
  81. package/dist/localization/core-messages/zh-TW.js +1 -0
  82. package/dist/localization/core-messages.d.ts +1 -0
  83. package/dist/resources/handlers.d.ts +1 -1
  84. package/dist/scripts/resources/read.js +4 -4
  85. package/dist/scripts/resources/save-memory.js +15 -2
  86. package/dist/scripts/resources/write.js +3 -3
  87. package/dist/server/agent-chat/script-entries.d.ts +1 -0
  88. package/dist/server/agent-chat/script-entries.js +8 -0
  89. package/dist/server/realtime-token.d.ts +1 -1
  90. package/dist/server/transcribe-voice.d.ts +1 -1
  91. package/docs/content/onboarding.mdx +13 -2
  92. package/package.json +3 -3
@@ -0,0 +1,76 @@
1
+ import { getUserSetting } from "@agent-native/core/settings";
2
+
3
+ import type { AvailabilityConfig, OverlayPerson } from "../../shared/api.js";
4
+ import { safeBookingTimeZone } from "./booking-timezone.js";
5
+
6
+ export interface EligibleHostAvailability {
7
+ email: string;
8
+ displayName?: string;
9
+ /** Present only when the host has saved a working-hours schedule. */
10
+ weeklySchedule?: AvailabilityConfig["weeklySchedule"];
11
+ /**
12
+ * Resolved from calendar-availability.timezone, falling back to
13
+ * calendar-settings.timezone. Present only when one of those is a
14
+ * resolvable IANA time zone — never defaulted, since guessing a peer's
15
+ * zone would be misleading.
16
+ */
17
+ timezone?: string;
18
+ }
19
+
20
+ /**
21
+ * Cross-references booking-link hosts against the owner's calendar overlay
22
+ * ("subscribed peer") list. Only hosts the owner has explicitly overlaid get
23
+ * their real working-hours schedule and time zone used for hard-filtering —
24
+ * everyone else keeps today's free/busy-only behavior.
25
+ */
26
+ export async function getEligibleHostAvailability(
27
+ ownerEmail: string | undefined,
28
+ hostEmails: string[],
29
+ ): Promise<EligibleHostAvailability[]> {
30
+ if (!ownerEmail || hostEmails.length === 0) return [];
31
+
32
+ const overlayData = (await getUserSetting(
33
+ ownerEmail,
34
+ "calendar-overlay-people",
35
+ )) as { people: OverlayPerson[] } | null;
36
+ const overlayEmails = new Set(
37
+ (overlayData?.people ?? []).map((person) => person.email.toLowerCase()),
38
+ );
39
+ if (overlayEmails.size === 0) return [];
40
+
41
+ const owner = ownerEmail.toLowerCase();
42
+ const eligibleEmails = Array.from(
43
+ new Set(
44
+ hostEmails
45
+ .map((email) => email.toLowerCase())
46
+ .filter((email) => email !== owner && overlayEmails.has(email)),
47
+ ),
48
+ );
49
+ if (eligibleEmails.length === 0) return [];
50
+
51
+ return Promise.all(
52
+ eligibleEmails.map(async (email) => {
53
+ const [config, calendarSettings] = await Promise.all([
54
+ getUserSetting(
55
+ email,
56
+ "calendar-availability",
57
+ ) as Promise<AvailabilityConfig | null>,
58
+ getUserSetting(email, "calendar-settings") as Promise<{
59
+ timezone?: string;
60
+ } | null>,
61
+ ]);
62
+ const timezone =
63
+ safeBookingTimeZone(config?.timezone) ||
64
+ safeBookingTimeZone(calendarSettings?.timezone);
65
+
66
+ if (!config?.weeklySchedule) {
67
+ return { email, timezone };
68
+ }
69
+ return {
70
+ email,
71
+ weeklySchedule: config.weeklySchedule,
72
+ timezone,
73
+ };
74
+ }),
75
+ );
76
+ }
@@ -304,6 +304,8 @@ function applyEventOptions(body: any, event: CalendarEvent): void {
304
304
  if (event.visibility !== undefined) body.visibility = event.visibility;
305
305
  if (event.status !== undefined) body.status = event.status;
306
306
  if (event.colorId !== undefined) body.colorId = event.colorId;
307
+ if (event.recurrence !== undefined) body.recurrence = event.recurrence;
308
+ if (event.recurrence !== undefined) body.recurrence = event.recurrence;
307
309
  if (event.remindersUseDefault !== undefined) {
308
310
  body.reminders = event.remindersUseDefault
309
311
  ? { useDefault: true }
@@ -146,9 +146,9 @@ export interface CalendarEventDraft {
146
146
  transparency?: "opaque" | "transparent";
147
147
  visibility?: "default" | "public" | "private" | "confidential";
148
148
  colorId?: string;
149
- recurrence?: string[];
150
149
  reminders?: CalendarEvent["reminders"];
151
150
  remindersUseDefault?: boolean;
151
+ recurrence?: CalendarEvent["recurrence"];
152
152
  attachments?: CalendarEvent["attachments"];
153
153
  attendees?: CalendarEvent["attendees"];
154
154
  addGoogleMeet?: boolean;
@@ -0,0 +1,47 @@
1
+ import { appPath } from "@agent-native/core/client/api-path";
2
+ import { useT } from "@agent-native/core/client/i18n";
3
+ import { buildSignInReturnHref } from "@agent-native/core/client/ui";
4
+ import { IconExternalLink, IconLogin2 } from "@tabler/icons-react";
5
+
6
+ import { Button } from "@/components/ui/button";
7
+
8
+ export type SignedOutShareCta = "signin" | "try_clips";
9
+
10
+ export function buildShareSignInHref(recordingId: string): string {
11
+ return buildSignInReturnHref({ returnTo: `/share/${recordingId}` });
12
+ }
13
+
14
+ export function SignedOutShareActions({
15
+ recordingId,
16
+ onCtaClick,
17
+ }: {
18
+ recordingId: string;
19
+ onCtaClick?: (cta: SignedOutShareCta) => void;
20
+ }) {
21
+ const t = useT();
22
+
23
+ return (
24
+ <>
25
+ <Button variant="outline" size="sm" asChild>
26
+ <a
27
+ href={buildShareSignInHref(recordingId)}
28
+ className="gap-1.5"
29
+ onClick={() => onCtaClick?.("signin")}
30
+ >
31
+ <IconLogin2 className="h-4 w-4 rtl:-scale-x-100" />
32
+ {t("sharePage.signIn")}
33
+ </a>
34
+ </Button>
35
+ <Button variant="ghost" size="sm" asChild>
36
+ <a
37
+ href={appPath("/")}
38
+ className="gap-1.5"
39
+ onClick={() => onCtaClick?.("try_clips")}
40
+ >
41
+ {t("sharePage.tryClips")}
42
+ <IconExternalLink className="h-3.5 w-3.5" />
43
+ </a>
44
+ </Button>
45
+ </>
46
+ );
47
+ }
@@ -513,7 +513,9 @@ function isNoSpeechTranscriptFailure(
513
513
  return (
514
514
  normalized.includes("no speech") ||
515
515
  normalized.includes("no native transcript was captured") ||
516
- normalized.includes("no transcript was captured by native speech")
516
+ normalized.includes("no transcript was captured by native speech") ||
517
+ normalized.includes("no audio track") ||
518
+ normalized.includes("recording was saved without audio")
517
519
  );
518
520
  }
519
521
 
@@ -20,7 +20,6 @@ import {
20
20
  IconDeviceDesktop,
21
21
  IconDownload,
22
22
  IconDots,
23
- IconExternalLink,
24
23
  IconLock,
25
24
  IconLogin2,
26
25
  } from "@tabler/icons-react";
@@ -59,6 +58,8 @@ import { RecordingViewsBadge } from "@/components/player/recording-views-badge";
59
58
  import { RequestAccessDialog } from "@/components/player/request-access-dialog";
60
59
  import { ShareRecordingPopover } from "@/components/player/share-dialog";
61
60
  import { SignInPromptDialog } from "@/components/player/sign-in-prompt-dialog";
61
+ import { SignedOutShareActions } from "@/components/player/signed-out-share-actions";
62
+ import { TimestampedCommentButton } from "@/components/player/timestamped-comment-button";
62
63
  import { TranscriptPanel } from "@/components/player/transcript-panel";
63
64
  import {
64
65
  VideoPlayer,
@@ -555,7 +556,13 @@ export default function ShareRoute() {
555
556
  );
556
557
 
557
558
  const dataQ = useQuery({
558
- queryKey: ["public-recording", shareId, password, agentAccessToken],
559
+ queryKey: [
560
+ "public-recording",
561
+ shareId,
562
+ password,
563
+ agentAccessToken,
564
+ session?.email ?? null,
565
+ ],
559
566
  queryFn: async () => {
560
567
  const url = new URL(
561
568
  `${appBasePath()}/api/public-recording`,
@@ -657,11 +664,7 @@ export default function ShareRoute() {
657
664
  viewerRole === "owner" ||
658
665
  viewerRole === "admin" ||
659
666
  viewerRole === "editor";
660
- const viewerCanComment =
661
- viewerRole === "owner" ||
662
- viewerRole === "admin" ||
663
- viewerRole === "editor" ||
664
- viewerRole === "commenter";
667
+ const viewerCanComment = Boolean(dataQ.data?.data?.viewer?.canComment);
665
668
  const viewerIsOwner = Boolean(dataQ.data?.data?.viewer?.isOwner);
666
669
  const canReshareLink =
667
670
  (viewerRole === "viewer" || viewerRole === "commenter") &&
@@ -1138,18 +1141,10 @@ export default function ShareRoute() {
1138
1141
  onOpenInsights={() => setPanel("insights")}
1139
1142
  />
1140
1143
  {session ? null : (
1141
- <Button variant="ghost" size="sm" asChild>
1142
- <a
1143
- href={appPath("/")}
1144
- target="_blank"
1145
- rel="noopener noreferrer"
1146
- className="gap-1.5"
1147
- onClick={() => fireShareCtaClick("try_clips")}
1148
- >
1149
- {t("sharePage.tryClips")}
1150
- <IconExternalLink className="h-3.5 w-3.5" />
1151
- </a>
1152
- </Button>
1144
+ <SignedOutShareActions
1145
+ recordingId={recording.id}
1146
+ onCtaClick={fireShareCtaClick}
1147
+ />
1153
1148
  )}
1154
1149
  {!viewerCanEdit && canDownloadRecording ? (
1155
1150
  <DropdownMenu
@@ -1256,6 +1251,18 @@ export default function ShareRoute() {
1256
1251
  ) : null}
1257
1252
  </div>
1258
1253
  <div className="flex max-w-full flex-col items-stretch gap-2 sm:items-end">
1254
+ <TimestampedCommentButton
1255
+ enableComments={recording.enableComments}
1256
+ canComment={!session || viewerCanComment}
1257
+ className="shrink-0"
1258
+ onOpen={() => {
1259
+ if (!session) {
1260
+ requireSignIn("comment");
1261
+ return;
1262
+ }
1263
+ if (viewerCanComment) setPanel("comments");
1264
+ }}
1265
+ />
1259
1266
  {recording.enableReactions ? (
1260
1267
  <ReactionsTray
1261
1268
  disabled={!viewerCanComment}
@@ -1401,6 +1408,7 @@ export default function ShareRoute() {
1401
1408
  shareId,
1402
1409
  password,
1403
1410
  agentAccessToken,
1411
+ session?.email ?? null,
1404
1412
  ]}
1405
1413
  selectComments={(d: any) => d?.data?.comments}
1406
1414
  applyComments={(d: any, next) =>
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-08-17
4
+ ---
5
+
6
+ Shared clips now clearly show sign-in, let signed-in viewers comment, and use neutral messaging for confirmed no-audio transcripts.
@@ -268,6 +268,11 @@ export default defineEventHandler(async (event) => {
268
268
  return { error: "Not found" };
269
269
  }
270
270
 
271
+ const viewerCanComment = Boolean(
272
+ session?.email &&
273
+ (rec.visibility === "public" || viewerIsOwner || viewerIsOrgMember),
274
+ );
275
+
271
276
  // Expiry check
272
277
  const recordingExpired = isRecordingExpired(rec.expiresAt);
273
278
  if (recordingExpired) {
@@ -536,11 +541,7 @@ export default defineEventHandler(async (event) => {
536
541
  viewerRole === "owner" ||
537
542
  viewerRole === "admin" ||
538
543
  viewerRole === "editor",
539
- canComment:
540
- viewerRole === "owner" ||
541
- viewerRole === "admin" ||
542
- viewerRole === "editor" ||
543
- viewerRole === "commenter",
544
+ canComment: viewerCanComment,
544
545
  isOwner: viewerRole === "owner",
545
546
  role: viewerRole ?? "viewer",
546
547
  canOpenDashboard,
@@ -96,6 +96,18 @@ its stored name). In table views a Blocks column shows a word count (e.g.
96
96
  database view's column menu (not from the page body); deleting the last
97
97
  Blocks field warns that it removes the body for every object of the type.
98
98
 
99
+ For one-block agent edits, call `list-content-database-blocks` with the exact
100
+ space, database, backing document, membership row, row document, and property
101
+ IDs. Preserve its schema, row, and field revisions. Each returned block names
102
+ the operations its kind supports and carries canonical Notion-flavored Markdown
103
+ (NFM) for that one block. Pass all three revisions to
104
+ `mutate-content-database-block`; its `insert`, `update`, `upsert`, `delete`, and
105
+ `reorder` variants preserve unmentioned fields and sibling blocks. A block
106
+ value must contain exactly one top-level block of the declared kind. Reuse an
107
+ idempotency key only for an exact retry. Unsupported kinds, kind conversion,
108
+ tombstone reuse, cross-parent reorder, schema drift, and stale row or field
109
+ revisions fail explicitly.
110
+
99
111
  Formula properties store their expression in property options and support
100
112
  `{Property name}` substitution plus simple numeric math such as `{MSV} * 2`.
101
113
 
@@ -236,6 +248,7 @@ Use `create-content-database`, `create-inline-content-database`,
236
248
  `duplicate-database-items`, `remove-database-items`, `move-database-item`,
237
249
  `update-content-database-view`, `list-document-properties`,
238
250
  `configure-document-property`, `set-document-property`,
251
+ `list-content-database-blocks`, `mutate-content-database-block`,
239
252
  `duplicate-document-property`, and `delete-document-property`; do not edit
240
253
  property rows or view config via raw SQL when an action can do it.
241
254
 
@@ -79,6 +79,8 @@ Read the relevant skill before deeper work:
79
79
  | `edit-document` | Find/replace edit — preferred for small changes |
80
80
  | `update-document` | Full rewrite of title, content, or description |
81
81
  | `delete-document` | Move a page and its children to Trash |
82
+ | `list-content-database-blocks` | List stable blocks and revisions in one exact database row/property |
83
+ | `mutate-content-database-block` | Insert, update, upsert, delete, or reorder one supported stable block |
82
84
  | `migrate-content-database-rows` | Validate, atomically apply, verify, roll back, or finalize one bounded whole-database row migration |
83
85
 
84
86
  Every action carries its own schema, and the rest of the app-specific surface
@@ -29,6 +29,17 @@ export class BlocksFieldRevisionConflictError extends Error {
29
29
  }
30
30
  }
31
31
 
32
+ export class BlocksFieldIdCollisionError extends Error {
33
+ readonly blockId: string;
34
+ readonly statusCode = 409;
35
+
36
+ constructor(blockId: string) {
37
+ super(`Block ID is already owned by another Blocks field: ${blockId}`);
38
+ this.name = "BlocksFieldIdCollisionError";
39
+ this.blockId = blockId;
40
+ }
41
+ }
42
+
32
43
  function nanoid(size = 12): string {
33
44
  const chars =
34
45
  "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
@@ -265,6 +276,8 @@ export async function persistBlocksFieldIdentity(args: {
265
276
  previousMarkdown: string;
266
277
  markdown: string;
267
278
  expectedRevision?: number;
279
+ preferredIdsByPath?: Readonly<Record<string, string>>;
280
+ rejectCrossFieldIdRemapping?: boolean;
268
281
  now: string;
269
282
  }): Promise<StoredBlocksFieldIdentity> {
270
283
  const fieldId = blocksFieldId(args.documentId, args.propertyId);
@@ -306,6 +319,7 @@ export async function persistBlocksFieldIdentity(args: {
306
319
  previous,
307
320
  markdown: args.markdown,
308
321
  createId: () => `block_${nanoid(16)}`,
322
+ preferredIdsByPath: args.preferredIdsByPath,
309
323
  });
310
324
 
311
325
  if (next.blocks.length > 0) {
@@ -325,6 +339,9 @@ export async function persistBlocksFieldIdentity(args: {
325
339
  const reserved = new Set(next.blocks.map((block) => block.id));
326
340
  for (const existing of existingOwners) {
327
341
  if (existing.fieldId === fieldId) continue;
342
+ if (args.rejectCrossFieldIdRemapping) {
343
+ throw new BlocksFieldIdCollisionError(existing.id);
344
+ }
328
345
  let replacement = `block_${nanoid(16)}`;
329
346
  while (reserved.has(replacement)) replacement = `block_${nanoid(16)}`;
330
347
  reserved.add(replacement);