@agent-native/core 0.164.4 → 0.164.12

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 (268) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/calendar/.agents/skills/event-management/SKILL.md +84 -0
  3. package/corpus/templates/calendar/AGENTS.md +5 -0
  4. package/corpus/templates/calendar/actions/delete-event.ts +1 -1
  5. package/corpus/templates/calendar/actions/delete-events.ts +508 -0
  6. package/corpus/templates/calendar/actions/event-action-helpers.ts +1 -1
  7. package/corpus/templates/calendar/actions/list-events.ts +53 -0
  8. package/corpus/templates/calendar/app/components/calendar/CreateEventDialog.tsx +36 -47
  9. package/corpus/templates/calendar/app/components/calendar/DayView.tsx +3 -0
  10. package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +18 -1
  11. package/corpus/templates/calendar/app/components/calendar/InlineEventPickers.tsx +256 -38
  12. package/corpus/templates/calendar/app/hooks/use-google-auth.ts +43 -3
  13. package/corpus/templates/calendar/app/i18n-data.ts +120 -0
  14. package/corpus/templates/calendar/app/lib/event-form-utils.ts +113 -0
  15. package/corpus/templates/calendar/changelog/2026-08-18-the-agent-can-now-remove-many-meetings-at-once-such-as-every.md +6 -0
  16. package/corpus/templates/calendar/server/handlers/google-auth.ts +119 -3
  17. package/corpus/templates/calendar/server/lib/event-weekday.ts +112 -0
  18. package/corpus/templates/calendar/server/routes/[base]/_agent-native/google/add-account/auth-url.post.ts +1 -0
  19. package/corpus/templates/calendar/server/routes/[base]/_agent-native/google/auth-url.post.ts +1 -0
  20. package/corpus/templates/calendar/server/routes/_agent-native/google/add-account/auth-url.post.ts +1 -0
  21. package/corpus/templates/calendar/server/routes/_agent-native/google/auth-url.post.ts +1 -0
  22. package/corpus/templates/clips/app/components/player/video-player.tsx +12 -0
  23. package/corpus/templates/clips/app/routes/_app.dictate.tsx +18 -4
  24. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +114 -20
  25. package/corpus/templates/clips/app/routes/share.$shareId.tsx +83 -78
  26. package/corpus/templates/clips/changelog/2026-08-19-clips-nightly-builds-are-available-for-trying-the-latest-upd.md +6 -0
  27. package/corpus/templates/clips/changelog/2026-08-19-social-preview-video-frames.md +1 -0
  28. package/corpus/templates/clips/desktop/README.md +19 -4
  29. package/corpus/templates/clips/desktop/src/app.tsx +78 -16
  30. package/corpus/templates/clips/desktop/src-tauri/Cargo.toml +3 -2
  31. package/corpus/templates/clips/desktop/src-tauri/tauri.conf.json +1 -0
  32. package/corpus/templates/clips/server/routes/[base]/_agent-native/google/auth-url.post.ts +1 -0
  33. package/corpus/templates/clips/server/routes/_agent-native/google/auth-url.get.ts +41 -3
  34. package/corpus/templates/clips/server/routes/_agent-native/google/auth-url.post.ts +1 -0
  35. package/corpus/templates/clips/server/routes/_agent-native/google/callback.get.ts +22 -1
  36. package/corpus/templates/clips/server/routes/api/clips-updater.json.get.ts +41 -22
  37. package/corpus/templates/factory/AGENTS.md +3 -2
  38. package/corpus/templates/factory/actions/get-factory-graph-version.ts +69 -0
  39. package/corpus/templates/factory/actions/list-factory-automations.ts +4 -0
  40. package/corpus/templates/factory/actions/list-factory-graph-versions.ts +82 -0
  41. package/corpus/templates/factory/actions/restore-factory-graph-version.ts +163 -0
  42. package/corpus/templates/factory/actions/save-factory-graph.ts +93 -60
  43. package/corpus/templates/factory/actions/start-builder-for-item.ts +19 -17
  44. package/corpus/templates/factory/app/components/factory/FactoryAuditView.tsx +100 -36
  45. package/corpus/templates/factory/app/components/factory/FactoryHistoryView.tsx +709 -0
  46. package/corpus/templates/factory/app/components/factory/FactoryInspector.tsx +62 -2
  47. package/corpus/templates/factory/app/components/ui/alert-dialog.tsx +1 -0
  48. package/corpus/templates/factory/app/i18n/en-US.ts +47 -0
  49. package/corpus/templates/factory/app/routes/factory.tsx +324 -61
  50. package/corpus/templates/factory/server/connectors/slack.ts +44 -2
  51. package/corpus/templates/factory/server/plugins/agent-chat.ts +12 -8
  52. package/corpus/templates/factory/server/plugins/factory-scheduler-job.ts +13 -0
  53. package/corpus/templates/factory/server/triage/slack-client.ts +5 -0
  54. package/corpus/templates/factory/server/triage/slack-poller.ts +72 -4
  55. package/corpus/templates/mail/actions/view-screen.ts +1 -1
  56. package/corpus/templates/mail/app/components/GoogleConnectBanner.tsx +100 -18
  57. package/corpus/templates/mail/server/handlers/google-auth.ts +125 -24
  58. package/corpus/templates/mail/server/lib/gmail-query.ts +5 -2
  59. package/corpus/templates/mail/server/routes/[base]/_agent-native/google/add-account/auth-url.post.ts +1 -0
  60. package/corpus/templates/mail/server/routes/[base]/_agent-native/google/auth-url.post.ts +1 -0
  61. package/corpus/templates/mail/server/routes/_agent-native/google/add-account/auth-url.post.ts +1 -0
  62. package/corpus/templates/mail/server/routes/_agent-native/google/auth-url.post.ts +1 -0
  63. package/corpus/templates/plan/app/pages/PlansPage.tsx +0 -12
  64. package/corpus/templates/plan/changelog/2026-08-19-plan-prompts-now-guide-you-to-connect-ai-before-sending-and-.md +6 -0
  65. package/corpus/templates/slides/actions/approve-deck-access-request.ts +234 -0
  66. package/corpus/templates/slides/actions/get-deck-access-status.ts +35 -2
  67. package/corpus/templates/slides/actions/request-deck-access.ts +263 -64
  68. package/corpus/templates/slides/app/components/editor/MissingDeckAccessPane.tsx +196 -0
  69. package/corpus/templates/slides/app/components/editor/RequestDeckAccessDialog.tsx +101 -0
  70. package/corpus/templates/slides/app/hooks/use-deck-access.ts +7 -4
  71. package/corpus/templates/slides/app/i18n/en-US.ts +29 -6
  72. package/corpus/templates/slides/app/pages/DeckEditor.tsx +147 -169
  73. package/corpus/templates/slides/app/routes/access-request.approve.tsx +250 -0
  74. package/corpus/templates/slides/server/db/schema.ts +8 -0
  75. package/corpus/templates/slides/server/lib/access-request-email.ts +36 -0
  76. package/corpus/templates/slides/server/lib/emails.ts +26 -0
  77. package/corpus/templates/slides/server/plugins/agent-chat.ts +7 -0
  78. package/corpus/templates/slides/server/plugins/db.ts +50 -0
  79. package/corpus/templates/slides/shared/deck-access.ts +31 -0
  80. package/dist/a2a/task-store.d.ts +1 -0
  81. package/dist/a2a/task-store.js +1 -1
  82. package/dist/agent/observational-memory/store.d.ts +1 -0
  83. package/dist/agent/observational-memory/store.js +1 -1
  84. package/dist/agent/run-store.d.ts +1 -0
  85. package/dist/agent/run-store.js +1 -1
  86. package/dist/agent/tool-approval-store.d.ts +1 -0
  87. package/dist/agent/tool-approval-store.js +1 -1
  88. package/dist/application-state/store.d.ts +1 -0
  89. package/dist/application-state/store.js +1 -1
  90. package/dist/browser-sessions/store.d.ts +1 -0
  91. package/dist/browser-sessions/store.js +1 -1
  92. package/dist/checkpoints/store.d.ts +1 -0
  93. package/dist/checkpoints/store.js +1 -1
  94. package/dist/cli/code-agent-schedules.d.ts +11 -0
  95. package/dist/cli/code-agent-schedules.js +44 -5
  96. package/dist/client/AgentPanel.d.ts +5 -1
  97. package/dist/client/AgentPanel.js +2 -2
  98. package/dist/client/AssistantChat.d.ts +5 -1
  99. package/dist/client/AssistantChat.js +40 -6
  100. package/dist/client/EnvironmentBadge.d.ts +7 -13
  101. package/dist/client/EnvironmentBadge.js +43 -92
  102. package/dist/client/agent-chat/index.d.ts +1 -0
  103. package/dist/client/agent-chat/index.js +1 -0
  104. package/dist/client/agent-chat-adapter.js +76 -50
  105. package/dist/client/app-providers.js +4 -4
  106. package/dist/client/chat/message-components.d.ts +4 -0
  107. package/dist/client/chat/message-components.js +28 -1
  108. package/dist/client/chat/run-recovery.js +26 -0
  109. package/dist/client/chat-first/apps-rail.d.ts +1 -0
  110. package/dist/client/chat-first/apps-rail.js +28 -2
  111. package/dist/client/sse-event-processor.js +13 -8
  112. package/dist/client/use-active-agent-chat-run.d.ts +2 -0
  113. package/dist/client/use-active-agent-chat-run.js +22 -0
  114. package/dist/coding-tools/sandbox/executions-store.d.ts +1 -0
  115. package/dist/coding-tools/sandbox/executions-store.js +1 -1
  116. package/dist/collab/awareness-store.d.ts +1 -0
  117. package/dist/collab/awareness-store.js +1 -1
  118. package/dist/collab/routes.d.ts +1 -1
  119. package/dist/collab/storage.d.ts +1 -0
  120. package/dist/collab/storage.js +1 -1
  121. package/dist/collab/struct-routes.d.ts +1 -1
  122. package/dist/db/client.js +3 -3
  123. package/dist/db/ddl-guard.js +8 -0
  124. package/dist/db/migration-runtime.d.ts +23 -0
  125. package/dist/db/migration-runtime.js +41 -0
  126. package/dist/db/migrations.d.ts +1 -2
  127. package/dist/db/migrations.js +2 -24
  128. package/dist/email-catalog/log.d.ts +1 -0
  129. package/dist/email-catalog/log.js +1 -1
  130. package/dist/guards/index.d.ts +1 -0
  131. package/dist/guards/index.js +1 -0
  132. package/dist/guards/release-schema-complete.d.ts +22 -0
  133. package/dist/guards/release-schema-complete.js +144 -0
  134. package/dist/integrations/a2a-continuations-store.d.ts +1 -0
  135. package/dist/integrations/a2a-continuations-store.js +1 -1
  136. package/dist/integrations/awaiting-input-store.d.ts +1 -0
  137. package/dist/integrations/awaiting-input-store.js +1 -1
  138. package/dist/integrations/config-store.d.ts +1 -0
  139. package/dist/integrations/config-store.js +1 -1
  140. package/dist/integrations/controls-store.d.ts +1 -0
  141. package/dist/integrations/controls-store.js +1 -1
  142. package/dist/integrations/identity-links-store.d.ts +1 -0
  143. package/dist/integrations/identity-links-store.js +1 -1
  144. package/dist/integrations/installations-store.d.ts +1 -0
  145. package/dist/integrations/installations-store.js +1 -1
  146. package/dist/integrations/remote-commands-store.d.ts +1 -0
  147. package/dist/integrations/remote-commands-store.js +1 -1
  148. package/dist/integrations/remote-devices-store.d.ts +1 -0
  149. package/dist/integrations/remote-devices-store.js +1 -1
  150. package/dist/integrations/remote-push-store.d.ts +1 -0
  151. package/dist/integrations/remote-push-store.js +1 -1
  152. package/dist/integrations/remote-run-events-store.d.ts +1 -0
  153. package/dist/integrations/remote-run-events-store.js +1 -1
  154. package/dist/integrations/scope-store.d.ts +1 -0
  155. package/dist/integrations/scope-store.js +1 -1
  156. package/dist/integrations/thread-mapping-store.d.ts +1 -0
  157. package/dist/integrations/thread-mapping-store.js +1 -1
  158. package/dist/integrations/usage-budget-store.d.ts +1 -0
  159. package/dist/integrations/usage-budget-store.js +1 -1
  160. package/dist/jobs/background-automation-runner.js +111 -9
  161. package/dist/jobs/run-history.d.ts +1 -0
  162. package/dist/jobs/run-history.js +1 -1
  163. package/dist/jobs/scheduler-health.d.ts +1 -0
  164. package/dist/jobs/scheduler-health.js +1 -1
  165. package/dist/mcp/approval-store.d.ts +1 -0
  166. package/dist/mcp/approval-store.js +1 -1
  167. package/dist/mcp/connect-store.d.ts +1 -0
  168. package/dist/mcp/connect-store.js +1 -1
  169. package/dist/mcp/oauth-store.d.ts +1 -0
  170. package/dist/mcp/oauth-store.js +1 -1
  171. package/dist/notifications/store.d.ts +1 -0
  172. package/dist/notifications/store.js +1 -1
  173. package/dist/oauth-tokens/store.d.ts +1 -0
  174. package/dist/oauth-tokens/store.js +1 -1
  175. package/dist/observability/routes.d.ts +5 -5
  176. package/dist/progress/routes.d.ts +1 -1
  177. package/dist/progress/store.d.ts +1 -0
  178. package/dist/progress/store.js +1 -1
  179. package/dist/provider-api/actions/custom-provider-registration.d.ts +5 -5
  180. package/dist/provider-api/actions/provider-api.d.ts +4 -4
  181. package/dist/provider-api/corpus-jobs-store.d.ts +1 -0
  182. package/dist/provider-api/corpus-jobs-store.js +1 -1
  183. package/dist/provider-api/custom-registry.d.ts +1 -0
  184. package/dist/provider-api/custom-registry.js +1 -1
  185. package/dist/provider-api/quota-governor.d.ts +1 -0
  186. package/dist/provider-api/quota-governor.js +1 -1
  187. package/dist/provider-api/staged-datasets-store.d.ts +1 -0
  188. package/dist/provider-api/staged-datasets-store.js +1 -1
  189. package/dist/resources/handlers.d.ts +3 -3
  190. package/dist/resources/store.d.ts +1 -0
  191. package/dist/resources/store.js +1 -1
  192. package/dist/scripts/dev/index.js +15 -1
  193. package/dist/search/index.js +3 -0
  194. package/dist/secrets/routes.d.ts +9 -9
  195. package/dist/secrets/storage.d.ts +1 -0
  196. package/dist/secrets/storage.js +1 -1
  197. package/dist/server/agent-chat-plugin.js +38 -9
  198. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  199. package/dist/server/agent-teams-run-queue.d.ts +1 -0
  200. package/dist/server/agent-teams-run-queue.js +1 -1
  201. package/dist/server/auth-marketing.js +3 -1
  202. package/dist/server/auth.d.ts +18 -1
  203. package/dist/server/auth.js +370 -154
  204. package/dist/server/embed-session.d.ts +1 -0
  205. package/dist/server/embed-session.js +1 -1
  206. package/dist/server/google-auth-plugin.js +3 -1
  207. package/dist/server/google-oauth.d.ts +10 -0
  208. package/dist/server/google-oauth.js +25 -1
  209. package/dist/server/identity-sso-store.d.ts +1 -0
  210. package/dist/server/identity-sso-store.js +1 -1
  211. package/dist/server/index.d.ts +1 -1
  212. package/dist/server/index.js +1 -1
  213. package/dist/server/onboarding-html.js +200 -31
  214. package/dist/server/poll.d.ts +2 -0
  215. package/dist/server/poll.js +19 -1
  216. package/dist/server/realtime-token.d.ts +1 -1
  217. package/dist/server/release-migrations.js +5 -0
  218. package/dist/server/release-schema.d.ts +46 -0
  219. package/dist/server/release-schema.js +307 -0
  220. package/dist/settings/store.d.ts +1 -0
  221. package/dist/settings/store.js +1 -1
  222. package/dist/shared/environment-lanes.d.ts +28 -0
  223. package/dist/shared/environment-lanes.js +61 -0
  224. package/dist/sharing/actions/share-resource.js +22 -2
  225. package/dist/styles/agent-native.css +17 -15
  226. package/dist/triggers/routes.d.ts +11 -0
  227. package/dist/triggers/routes.js +10 -0
  228. package/dist/usage/alerts-store.d.ts +1 -0
  229. package/dist/usage/alerts-store.js +1 -1
  230. package/dist/usage/store.d.ts +1 -0
  231. package/dist/usage/store.js +1 -1
  232. package/docs/content/actions-access-control.mdx +136 -2
  233. package/docs/content/actions-agent-tools.mdx +101 -0
  234. package/docs/content/actions-defining.mdx +2 -2
  235. package/docs/content/audit-log.mdx +1 -1
  236. package/docs/content/authentication.mdx +1 -1
  237. package/docs/content/cloudflare.mdx +43 -0
  238. package/docs/content/creating-templates.mdx +1 -1
  239. package/docs/content/deploy-an-app.mdx +109 -0
  240. package/docs/content/deployment-environment-variables.mdx +101 -0
  241. package/docs/content/deployment.mdx +125 -792
  242. package/docs/content/docs-components.mdx +2 -2
  243. package/docs/content/environment-variables.mdx +41 -41
  244. package/docs/content/file-uploads.mdx +74 -40
  245. package/docs/content/harness-agents.mdx +2 -2
  246. package/docs/content/internationalization.mdx +61 -65
  247. package/docs/content/key-concepts.mdx +3 -3
  248. package/docs/content/multi-app-workspace.mdx +2 -2
  249. package/docs/content/netlify.mdx +47 -0
  250. package/docs/content/node-docker.mdx +138 -0
  251. package/docs/content/organizations-teams-permissions.mdx +5 -5
  252. package/docs/content/other-platforms.mdx +27 -0
  253. package/docs/content/ssr-caching.mdx +45 -0
  254. package/docs/content/template-assets-developers.mdx +2 -2
  255. package/docs/content/template-chat-developers.mdx +2 -2
  256. package/docs/content/template-content-developers.mdx +2 -2
  257. package/docs/content/{local-file-mode.mdx → template-content-local-files.mdx} +2 -1
  258. package/docs/content/template-content-sync.mdx +2 -2
  259. package/docs/content/toolkit-agent-ux.mdx +1 -1
  260. package/docs/content/toolkit-resources.mdx +2 -2
  261. package/docs/content/updating-ui-in-production.mdx +35 -0
  262. package/docs/content/vercel.mdx +38 -0
  263. package/docs/content/what-is-agent-native.mdx +1 -1
  264. package/docs/content/workspace-deployment.mdx +350 -0
  265. package/package.json +2 -2
  266. package/docs/content/agents.mdx +0 -9
  267. package/docs/content/database.mdx +0 -317
  268. package/docs/content/human-approval.mdx +0 -156
@@ -0,0 +1,250 @@
1
+ import { callAction, useSession } from "@agent-native/core/client/hooks";
2
+ import { useT } from "@agent-native/core/client/i18n";
3
+ import { buildSignInReturnHref } from "@agent-native/core/client/ui";
4
+ import { IconAlertTriangle, IconCheck, IconLock } from "@tabler/icons-react";
5
+ import { useEffect, useMemo, useState } from "react";
6
+ import { Link, useSearchParams } from "react-router";
7
+
8
+ import { Button } from "@/components/ui/button";
9
+ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
10
+ import { Skeleton } from "@/components/ui/skeleton";
11
+ import enMessages from "@/i18n/en-US";
12
+
13
+ import {
14
+ deckAccessApprovalContinuationPath,
15
+ deckAccessApprovalSessionKey,
16
+ } from "../../shared/deck-access";
17
+
18
+ interface ApprovalResult {
19
+ ok: true;
20
+ alreadyAllowed: boolean;
21
+ requesterEmail: string;
22
+ deckId: string;
23
+ deckTitle: string;
24
+ shareId: string;
25
+ message: string;
26
+ }
27
+
28
+ type ApprovalState =
29
+ | { kind: "loading" }
30
+ | { kind: "sign-in" }
31
+ | { kind: "success"; result: ApprovalResult }
32
+ | { kind: "error"; message: string };
33
+
34
+ export function meta() {
35
+ return [{ title: enMessages.deckEditor.privateDeckTitle }];
36
+ }
37
+
38
+ export default function ApproveDeckAccessRequestRoute() {
39
+ const t = useT();
40
+ const [searchParams] = useSearchParams();
41
+ const { session, isLoading: sessionLoading } = useSession();
42
+ const deckId = searchParams.get("deckId") ?? "";
43
+ const approvalTokenFromUrl = searchParams.get("token") ?? "";
44
+ const approvalTokenStorageKey = deckAccessApprovalSessionKey(deckId);
45
+ const [approvalToken, setApprovalToken] = useState(() => {
46
+ if (approvalTokenFromUrl) return approvalTokenFromUrl;
47
+ if (typeof window === "undefined" || !deckId) return "";
48
+ try {
49
+ return sessionStorage.getItem(approvalTokenStorageKey) ?? "";
50
+ } catch {
51
+ // coercion-ok: unavailable tab storage is an absent continuation.
52
+ return "";
53
+ }
54
+ });
55
+ const [state, setState] = useState<ApprovalState>({ kind: "loading" });
56
+
57
+ useEffect(() => {
58
+ if (!deckId) {
59
+ setApprovalToken("");
60
+ return;
61
+ }
62
+ if (!approvalTokenFromUrl) {
63
+ try {
64
+ setApprovalToken(sessionStorage.getItem(approvalTokenStorageKey) ?? "");
65
+ } catch {
66
+ setApprovalToken("");
67
+ }
68
+ return;
69
+ }
70
+ try {
71
+ sessionStorage.setItem(approvalTokenStorageKey, approvalTokenFromUrl);
72
+ } catch {
73
+ // coercion-ok: never fall back to putting the bearer token in the URL.
74
+ // Session storage may be unavailable in hardened browser contexts.
75
+ }
76
+ setApprovalToken(approvalTokenFromUrl);
77
+
78
+ // The email link must contain the capability, but it should not remain in
79
+ // the address bar or be copied into the sign-in continuation URL.
80
+ const params = new URLSearchParams(window.location.search);
81
+ params.delete("token");
82
+ const nextSearch = params.toString();
83
+ window.history.replaceState(
84
+ window.history.state,
85
+ "",
86
+ `${window.location.pathname}${nextSearch ? `?${nextSearch}` : ""}${window.location.hash}`,
87
+ );
88
+ }, [approvalTokenFromUrl, approvalTokenStorageKey, deckId]);
89
+
90
+ const signInReturnTo = useMemo(
91
+ () => deckAccessApprovalContinuationPath(deckId),
92
+ [deckId],
93
+ );
94
+
95
+ useEffect(() => {
96
+ if (sessionLoading) return;
97
+
98
+ let cancelled = false;
99
+ if (!session?.email) {
100
+ setState({ kind: "sign-in" });
101
+ return () => {
102
+ cancelled = true;
103
+ };
104
+ }
105
+ if (!deckId || !approvalToken) {
106
+ setState({
107
+ kind: "error",
108
+ message: t("deckEditor.accessApprovalInvalid"),
109
+ });
110
+ return () => {
111
+ cancelled = true;
112
+ };
113
+ }
114
+
115
+ setState({ kind: "loading" });
116
+ void callAction<ApprovalResult>("approve-deck-access-request", {
117
+ deckId,
118
+ approvalToken,
119
+ })
120
+ .then((result) => {
121
+ try {
122
+ sessionStorage.removeItem(approvalTokenStorageKey);
123
+ } catch {
124
+ // coercion-ok: cleanup failure cannot authorize or expose the token.
125
+ // Session storage may be unavailable in hardened browser contexts.
126
+ }
127
+ if (!cancelled) setState({ kind: "success", result });
128
+ })
129
+ .catch((error: unknown) => {
130
+ if (cancelled) return;
131
+ setState({
132
+ kind: "error",
133
+ message:
134
+ error instanceof Error
135
+ ? error.message
136
+ : t("deckEditor.accessApprovalInvalid"),
137
+ });
138
+ });
139
+
140
+ return () => {
141
+ cancelled = true;
142
+ };
143
+ }, [
144
+ approvalToken,
145
+ approvalTokenStorageKey,
146
+ deckId,
147
+ session?.email,
148
+ sessionLoading,
149
+ t,
150
+ ]);
151
+
152
+ const signInHref = buildSignInReturnHref({ returnTo: signInReturnTo });
153
+
154
+ return (
155
+ <div className="flex min-h-screen items-center justify-center bg-background px-6 py-12 text-foreground">
156
+ <Card className="w-full max-w-md">
157
+ <CardHeader>
158
+ <CardTitle className="flex items-center gap-2">
159
+ <IconLock
160
+ className="size-5 text-muted-foreground"
161
+ aria-hidden="true"
162
+ />
163
+ {state.kind === "success"
164
+ ? state.result.alreadyAllowed
165
+ ? t("deckEditor.accessApprovalAlreadyTitle")
166
+ : t("deckEditor.accessApprovalTitle")
167
+ : state.kind === "error"
168
+ ? t("deckEditor.accessApprovalErrorTitle")
169
+ : state.kind === "sign-in"
170
+ ? t("deckEditor.accessApprovalSignInTitle")
171
+ : t("deckEditor.accessApprovalLoading")}
172
+ </CardTitle>
173
+ </CardHeader>
174
+ <CardContent aria-live="polite">
175
+ {state.kind === "loading" ? (
176
+ <div className="space-y-3 py-2">
177
+ <Skeleton className="h-4 w-full" />
178
+ <Skeleton className="h-4 w-4/5" />
179
+ </div>
180
+ ) : state.kind === "sign-in" ? (
181
+ <div className="space-y-4">
182
+ <p className="text-sm text-muted-foreground">
183
+ {t("deckEditor.accessApprovalSignInMessage")}
184
+ </p>
185
+ <Button asChild>
186
+ <a
187
+ href={signInHref}
188
+ onClick={() => {
189
+ if (!approvalToken) return;
190
+ try {
191
+ sessionStorage.setItem(
192
+ approvalTokenStorageKey,
193
+ approvalToken,
194
+ );
195
+ } catch {
196
+ // coercion-ok: never fall back to putting the bearer token in the URL.
197
+ // Session storage may be unavailable in hardened browser contexts.
198
+ }
199
+ }}
200
+ >
201
+ {t("deckEditor.accessApprovalSignIn")}
202
+ </a>
203
+ </Button>
204
+ </div>
205
+ ) : state.kind === "error" ? (
206
+ <div className="space-y-4">
207
+ <div className="flex items-start gap-3 rounded-md border border-destructive/30 bg-destructive/10 p-3 text-sm text-destructive">
208
+ <IconAlertTriangle
209
+ className="mt-0.5 size-4 shrink-0"
210
+ aria-hidden="true"
211
+ />
212
+ <span>{state.message}</span>
213
+ </div>
214
+ {deckId ? (
215
+ <Button asChild variant="outline">
216
+ <Link to={`/deck/${encodeURIComponent(deckId)}`}>
217
+ {t("deckEditor.accessApprovalOpenDeck")}
218
+ </Link>
219
+ </Button>
220
+ ) : null}
221
+ </div>
222
+ ) : (
223
+ <div className="space-y-4">
224
+ <div className="flex items-start gap-3 rounded-md border border-primary/30 bg-primary/10 p-3 text-sm">
225
+ <IconCheck
226
+ className="mt-0.5 size-4 shrink-0 text-primary"
227
+ aria-hidden="true"
228
+ />
229
+ <span>
230
+ {state.result.alreadyAllowed
231
+ ? t("deckEditor.accessApprovalAlreadyMessage", {
232
+ email: state.result.requesterEmail,
233
+ })
234
+ : t("deckEditor.accessApprovalMessage", {
235
+ email: state.result.requesterEmail,
236
+ })}
237
+ </span>
238
+ </div>
239
+ <Button asChild variant="outline">
240
+ <Link to={`/deck/${encodeURIComponent(state.result.deckId)}`}>
241
+ {t("deckEditor.accessApprovalOpenDeck")}
242
+ </Link>
243
+ </Button>
244
+ </div>
245
+ )}
246
+ </CardContent>
247
+ </Card>
248
+ </div>
249
+ );
250
+ }
@@ -93,3 +93,11 @@ export const deckEvents = table("deck_events", {
93
93
  createdBy: text("created_by").notNull().default("human"),
94
94
  createdAt: text("created_at").notNull().default(now()),
95
95
  });
96
+
97
+ // One durable bucket per deck keeps anonymous access-request throttling
98
+ // consistent across serverless instances and cold starts.
99
+ export const deckAccessRequestLimits = table("deck_access_request_limits", {
100
+ deckId: text("deck_id").primaryKey(),
101
+ windowStartedAt: text("window_started_at").notNull(),
102
+ requestCount: integer("request_count").notNull().default(0),
103
+ });
@@ -0,0 +1,36 @@
1
+ import { emailStrong, renderEmail } from "@agent-native/core/server";
2
+
3
+ export const SLIDES_DECK_ACCESS_REQUEST_EMAIL_ID = "slides.deck-access-request";
4
+
5
+ function cleanSubjectPart(value: string): string {
6
+ return value.replace(/[\r\n]+/g, " ").trim();
7
+ }
8
+
9
+ export function renderDeckAccessRequestEmail(input: {
10
+ requesterName: string;
11
+ requesterEmail: string;
12
+ deckTitle: string;
13
+ url: string;
14
+ allowAccessUrl: string;
15
+ }) {
16
+ const subject = `Access request for "${cleanSubjectPart(input.deckTitle)}"`;
17
+ return {
18
+ subject,
19
+ ...renderEmail({
20
+ brandName: "Slides",
21
+ preheader: subject,
22
+ heading: "Access requested",
23
+ paragraphs: [
24
+ `${emailStrong(input.requesterName)} (${emailStrong(input.requesterEmail)}) requested access to ${emailStrong(input.deckTitle)}.`,
25
+ "Select Allow access to add them to this deck's sharing list. You can also open the deck to review sharing first.",
26
+ ],
27
+ cta: { label: "Allow access", url: input.allowAccessUrl },
28
+ secondaryCta: { label: "Open deck", url: input.url },
29
+ closingParagraphs: [
30
+ "This approval link expires in 7 days and requires you to be signed in as a deck owner or admin.",
31
+ ],
32
+ footer:
33
+ "You received this because you own this deck. If you do not recognize the requester, you can ignore this email.",
34
+ }),
35
+ };
36
+ }
@@ -10,6 +10,10 @@ import {
10
10
  getTransactionalEmail,
11
11
  } from "@agent-native/core/email-catalog";
12
12
 
13
+ import {
14
+ renderDeckAccessRequestEmail,
15
+ SLIDES_DECK_ACCESS_REQUEST_EMAIL_ID,
16
+ } from "./access-request-email.js";
13
17
  import { renderDeckCommentEmail } from "./comment-notifications.js";
14
18
 
15
19
  export const SLIDES_DECK_COMMENT_EMAIL_ID = "slides.deck-comment";
@@ -42,4 +46,26 @@ export function registerSlidesEmails(): void {
42
46
  isReply: false,
43
47
  }),
44
48
  });
49
+
50
+ defineTransactionalEmail({
51
+ id: SLIDES_DECK_ACCESS_REQUEST_EMAIL_ID,
52
+ name: "Deck access request",
53
+ trigger:
54
+ "A signed-in viewer requests access to a private Slides deck. One request is recorded per viewer and deck.",
55
+ recipientLabel: "Deck owner",
56
+ recipient:
57
+ "The owner of the private deck. The in-app notification is stored even when email delivery is unavailable.",
58
+ senderLabel: "Agent-Native Slides",
59
+ sender:
60
+ "The configured default sender. This call site sets no custom from or app sender.",
61
+ preview: () =>
62
+ renderDeckAccessRequestEmail({
63
+ requesterName: "Sam Rivera",
64
+ requesterEmail: "sam.rivera@example.com",
65
+ deckTitle: "Quarterly review",
66
+ url: "https://example.com/deck/deck_sample",
67
+ allowAccessUrl:
68
+ "https://example.com/access-request/approve?deckId=deck_sample&token=preview-token",
69
+ }),
70
+ });
45
71
  }
@@ -56,6 +56,13 @@ export default createAgentChatPlugin({
56
56
  const authContext = await resolveSlidesRequestAuthContext(event);
57
57
  return authContext.orgId === undefined ? null : authContext.orgId;
58
58
  },
59
+ // Guest access requests authenticate with a signed deck capability and the
60
+ // requester email, so this action must reach its own validation without a
61
+ // browser session.
62
+ actionRoutePublicPaths: [
63
+ "/_agent-native/actions/get-deck-access-status",
64
+ "/_agent-native/actions/request-deck-access",
65
+ ],
59
66
  prepareRequest: prepareSlidesChatAttachments,
60
67
  systemPrompt: `You are an AI deck assistant. You create, edit, import, export, style, share, and navigate decks through actions and shared application state. For a newly created presentation, use create-deck with slides: [] only when you are creating the deck yourself, then add-slide sequentially with full rendered HTML. The legacy generate-slides-ai action returns Markdown drafts and is not part of the persisted presentation workflow. When speaker notes are requested, keep presenter-only text in each slide's notes field rather than the slide HTML, and preserve notes during source-preserving edits.
61
68
 
@@ -252,6 +252,56 @@ export const runSlidesMigrations = runMigrations(
252
252
  );
253
253
  CREATE INDEX IF NOT EXISTS deck_events_deck_created_idx ON deck_events (deck_id, created_at)`,
254
254
  },
255
+ {
256
+ version: 23,
257
+ name: "slides-deck-access-request-rate-limits",
258
+ sql: `CREATE TABLE IF NOT EXISTS deck_access_request_limits (
259
+ deck_id TEXT PRIMARY KEY,
260
+ window_started_at TEXT NOT NULL,
261
+ request_count INTEGER NOT NULL DEFAULT 0
262
+ )`,
263
+ },
264
+ {
265
+ version: 24,
266
+ name: "slides-deck-shares-user-principal-unique",
267
+ // Reconcile legacy rows before adding the constraint. Older databases
268
+ // could contain case variants or concurrent duplicate user shares, and
269
+ // CREATE UNIQUE INDEX would otherwise fail before this migration is
270
+ // recorded. Keep the strongest grant; id/created_at make equal-role
271
+ // survivors deterministic, and lower the retained principal so the
272
+ // repaired row is canonical as well as uniquely indexed.
273
+ sql: `DELETE FROM deck_shares
274
+ WHERE principal_type = 'user'
275
+ AND id NOT IN (
276
+ SELECT id
277
+ FROM (
278
+ SELECT id,
279
+ ROW_NUMBER() OVER (
280
+ PARTITION BY resource_id, LOWER(principal_id)
281
+ ORDER BY CASE LOWER(role)
282
+ WHEN 'owner' THEN 5
283
+ WHEN 'admin' THEN 4
284
+ WHEN 'editor' THEN 3
285
+ WHEN 'commenter' THEN 2
286
+ WHEN 'viewer' THEN 1
287
+ ELSE 0
288
+ END DESC,
289
+ created_at ASC,
290
+ id ASC
291
+ ) AS row_number
292
+ FROM deck_shares
293
+ WHERE principal_type = 'user'
294
+ ) AS ranked
295
+ WHERE row_number = 1
296
+ );
297
+ UPDATE deck_shares
298
+ SET principal_id = LOWER(principal_id)
299
+ WHERE principal_type = 'user'
300
+ AND principal_id <> LOWER(principal_id);
301
+ CREATE UNIQUE INDEX IF NOT EXISTS deck_shares_resource_user_principal_uidx
302
+ ON deck_shares (resource_id, LOWER(principal_id))
303
+ WHERE principal_type = 'user'`,
304
+ },
255
305
  ],
256
306
  { table: "slides_migrations" },
257
307
  );
@@ -0,0 +1,31 @@
1
+ /** Short-lived capability used by a private deck viewer to request access. */
2
+ export const SLIDES_ACCESS_REQUEST_TOKEN_PREFIX = "slides-access-request";
3
+ /** Capability used when the private-deck access probe is unavailable. */
4
+ export const SLIDES_ACCESS_REQUEST_FALLBACK_TOKEN_PREFIX =
5
+ "slides-access-request-fallback";
6
+ export const SLIDES_ACCESS_REQUEST_TOKEN_TTL_SECONDS = 10 * 60;
7
+
8
+ /** Signed capability used by an owner to approve a private deck access request. */
9
+ export const SLIDES_ACCESS_APPROVAL_TOKEN_PREFIX = "slides-access-approval";
10
+ export const SLIDES_ACCESS_APPROVAL_TOKEN_TTL_SECONDS = 7 * 24 * 60 * 60;
11
+ export const SLIDES_ACCESS_APPROVAL_SESSION_KEY_PREFIX =
12
+ "slides-access-approval-token:";
13
+
14
+ /** Owner-only route used by the one-click approval button in access emails. */
15
+ export function deckAccessApprovalPath(
16
+ deckId: string,
17
+ approvalToken: string,
18
+ ): string {
19
+ const params = new URLSearchParams({ deckId, token: approvalToken });
20
+ return `/access-request/approve?${params.toString()}`;
21
+ }
22
+
23
+ /** Token-free continuation used after the approver signs in. */
24
+ export function deckAccessApprovalContinuationPath(deckId: string): string {
25
+ return `/access-request/approve?${new URLSearchParams({ deckId }).toString()}`;
26
+ }
27
+
28
+ /** Tab-scoped key for carrying an approval token across the sign-in redirect. */
29
+ export function deckAccessApprovalSessionKey(deckId: string): string {
30
+ return `${SLIDES_ACCESS_APPROVAL_SESSION_KEY_PREFIX}${encodeURIComponent(deckId)}`;
31
+ }
@@ -1,6 +1,7 @@
1
1
  import type { Task, Message, TaskState, Artifact } from "./types.js";
2
2
  export declare const MAX_A2A_IDEMPOTENCY_KEY_CHARS = 128;
3
3
  export declare const A2A_PERSONAL_OWNER_SCOPE = "__personal__";
4
+ export declare function ensureTable(): Promise<void>;
4
5
  export interface A2AApprovalRecord {
5
6
  id: string;
6
7
  taskId: string;
@@ -5,7 +5,7 @@ let _initPromise;
5
5
  export const MAX_A2A_IDEMPOTENCY_KEY_CHARS = 128;
6
6
  const A2A_IDEMPOTENCY_INDEX = "idx_a2a_tasks_owner_scope_idempotency";
7
7
  export const A2A_PERSONAL_OWNER_SCOPE = "__personal__";
8
- async function ensureTable() {
8
+ export async function ensureTable() {
9
9
  if (!_initPromise) {
10
10
  _initPromise = (async () => {
11
11
  const client = getDbExec();
@@ -12,6 +12,7 @@
12
12
  * runtime even before the migration plugin is registered.
13
13
  */
14
14
  import type { ObservationalMemoryEntry, ObservationalMemoryOwner, ObservationalMemoryTier } from "./types.js";
15
+ export declare function ensureTable(): Promise<void>;
15
16
  /** Reset the cached ensureTable promise — test-only seam. */
16
17
  export declare function __resetObservationalMemoryTableCache(): void;
17
18
  export interface InsertObservationalMemoryInput extends ObservationalMemoryOwner {
@@ -14,7 +14,7 @@
14
14
  import { getDbExec, isPostgres } from "../../db/client.js";
15
15
  import { ensureTableExists, ensureIndexExists } from "../../db/ddl-guard.js";
16
16
  let tableReady = null;
17
- async function ensureTable() {
17
+ export async function ensureTable() {
18
18
  if (tableReady)
19
19
  return tableReady;
20
20
  tableReady = (async () => {
@@ -217,6 +217,7 @@ export declare const IN_FLIGHT_RUN_STALE_GRACE_MS: number;
217
217
  * is reaped like any other stale run.
218
218
  */
219
219
  export declare const IN_FLIGHT_GRACE_MAX_LIVENESS_GAP_MS = 120000;
220
+ export declare function ensureRunTables(): Promise<void>;
220
221
  /**
221
222
  * Persist a zombie tool-call completion to the ledger. Called by the detached
222
223
  * promise continuation after `Promise.race` abandons it. Best-effort — never
@@ -294,7 +294,7 @@ export const IN_FLIGHT_RUN_STALE_GRACE_MS = 14.5 * 60_000; // 870_000
294
294
  * is reaped like any other stale run.
295
295
  */
296
296
  export const IN_FLIGHT_GRACE_MAX_LIVENESS_GAP_MS = 120_000;
297
- async function ensureRunTables() {
297
+ export async function ensureRunTables() {
298
298
  if (!_initPromise) {
299
299
  _initPromise = (async () => {
300
300
  const client = getDbExec();
@@ -1,3 +1,4 @@
1
+ export declare function ensureAgentToolApprovalTable(): Promise<void>;
1
2
  export interface AgentToolApprovalBinding {
2
3
  ownerEmail: string;
3
4
  orgId?: string | null;
@@ -11,7 +11,7 @@ import { AGENT_TOOL_APPROVAL_INDEX_SQL, AGENT_TOOL_APPROVAL_LOGICAL_INDEX_SQL, A
11
11
  const APPROVAL_TTL_MS = 60 * 60_000;
12
12
  const APPROVAL_CLEANUP_AGE_MS = 24 * 60 * 60_000;
13
13
  let initPromise;
14
- async function ensureAgentToolApprovalTable() {
14
+ export async function ensureAgentToolApprovalTable() {
15
15
  if (!initPromise) {
16
16
  initPromise = (async () => {
17
17
  const createSql = AGENT_TOOL_APPROVAL_TABLE_SQL[isPostgres() ? "postgres" : "sqlite"];
@@ -1,4 +1,5 @@
1
1
  import type { StoreWriteOptions } from "../settings/store.js";
2
+ export declare function ensureTable(): Promise<void>;
2
3
  export declare function appStateGet(sessionId: string, key: string): Promise<Record<string, unknown> | null>;
3
4
  /**
4
5
  * Read several application-state keys for one session in a single SQL query,
@@ -11,7 +11,7 @@ const MAX_HOSTED_APP_STATE_VALUE_BYTES = 1024 * 1024;
11
11
  function escapeLike(s) {
12
12
  return s.replace(/[!%_]/g, (match) => `!${match}`);
13
13
  }
14
- async function ensureTable() {
14
+ export async function ensureTable() {
15
15
  if (!_initPromise) {
16
16
  _initPromise = (async () => {
17
17
  const client = getDbExec();
@@ -2,6 +2,7 @@ import type { AgentNativeBrowserSessionRecord, AgentNativeBrowserSessionRequest,
2
2
  export declare const DEFAULT_BROWSER_SESSION_TTL_MS = 45000;
3
3
  export declare const DEFAULT_BROWSER_SESSION_REQUEST_TIMEOUT_MS = 30000;
4
4
  export declare const DEFAULT_BROWSER_SESSION_REQUEST_POLL_MS = 250;
5
+ export declare function ensureTables(): Promise<void>;
5
6
  export declare function registerBrowserSession(ownerEmailInput: string, input: RegisterAgentNativeBrowserSessionInput): Promise<AgentNativeBrowserSessionRecord>;
6
7
  export declare function listBrowserSessions(ownerEmailInput: string, options?: {
7
8
  includeExpired?: boolean;
@@ -13,7 +13,7 @@ function nowMs() {
13
13
  function sleep(ms) {
14
14
  return new Promise((resolve) => setTimeout(resolve, ms));
15
15
  }
16
- async function ensureTables() {
16
+ export async function ensureTables() {
17
17
  if (!initPromise) {
18
18
  initPromise = (async () => {
19
19
  const client = getDbExec();
@@ -1,3 +1,4 @@
1
+ export declare function ensureCheckpointTable(): Promise<void>;
1
2
  export declare function insertCheckpoint(id: string, threadId: string, runId: string | null, commitSha: string, message: string): Promise<void>;
2
3
  export declare function getCheckpointsByThread(threadId: string): Promise<Array<{
3
4
  id: string;
@@ -1,7 +1,7 @@
1
1
  import { getDbExec, intType, isPostgres } from "../db/client.js";
2
2
  import { ensureIndexExists, ensureTableExists } from "../db/ddl-guard.js";
3
3
  let _initPromise;
4
- async function ensureCheckpointTable() {
4
+ export async function ensureCheckpointTable() {
5
5
  if (!_initPromise) {
6
6
  _initPromise = (async () => {
7
7
  const client = getDbExec();
@@ -52,3 +52,14 @@ export declare function deleteCodeAgentSchedule(scheduleId: string): boolean;
52
52
  export declare function markCodeAgentScheduleRun(scheduleId: string, patch: CodeAgentScheduleRunPatch): CodeAgentScheduleRecord | null;
53
53
  export declare function isCodeAgentScheduleDue(schedule: CodeAgentScheduleRecord, now?: Date): boolean;
54
54
  export declare function nextCodeAgentScheduleRunAt(schedule: CodeAgentScheduleRecord, now?: Date): string;
55
+ /**
56
+ * A schedules file that exists but cannot be understood. Every mutation
57
+ * rewrites the whole file, so treating this as an empty list would delete every
58
+ * stored schedule; callers must fail instead.
59
+ */
60
+ export declare class CodeAgentSchedulesUnreadableError extends Error {
61
+ readonly filePath: string;
62
+ constructor(filePath: string, detail: string, options?: {
63
+ cause?: unknown;
64
+ });
65
+ }
@@ -136,16 +136,55 @@ export function nextCodeAgentScheduleRunAt(schedule, now = new Date()) {
136
136
  next += intervalMs;
137
137
  return new Date(next).toISOString();
138
138
  }
139
+ /**
140
+ * A schedules file that exists but cannot be understood. Every mutation
141
+ * rewrites the whole file, so treating this as an empty list would delete every
142
+ * stored schedule; callers must fail instead.
143
+ */
144
+ export class CodeAgentSchedulesUnreadableError extends Error {
145
+ filePath;
146
+ constructor(filePath, detail, options) {
147
+ super(`Code agent schedules at ${filePath} could not be read (${detail}). ` +
148
+ `Refusing to continue so the existing schedules are not overwritten. ` +
149
+ `Repair or remove the file, then retry.`, options);
150
+ this.name = "CodeAgentSchedulesUnreadableError";
151
+ this.filePath = filePath;
152
+ }
153
+ }
139
154
  function readScheduleFile() {
155
+ const filePath = codeAgentSchedulesPath();
156
+ let contents;
140
157
  try {
141
- const raw = JSON.parse(fs.readFileSync(codeAgentSchedulesPath(), "utf8"));
142
- if (!Array.isArray(raw))
158
+ contents = fs.readFileSync(filePath, "utf8");
159
+ }
160
+ catch (error) {
161
+ // Only a genuinely absent file is an empty schedule list. A permission
162
+ // error or transient I/O failure is unknown, not empty.
163
+ if (error?.code === "ENOENT")
143
164
  return [];
144
- return raw.filter(isScheduleRecord);
165
+ throw new CodeAgentSchedulesUnreadableError(filePath, "read failed", {
166
+ cause: error,
167
+ });
168
+ }
169
+ let parsed;
170
+ try {
171
+ parsed = JSON.parse(contents);
172
+ }
173
+ catch (error) {
174
+ throw new CodeAgentSchedulesUnreadableError(filePath, "invalid JSON", {
175
+ cause: error,
176
+ });
177
+ }
178
+ if (!Array.isArray(parsed)) {
179
+ throw new CodeAgentSchedulesUnreadableError(filePath, `expected an array, found ${parsed === null ? "null" : typeof parsed}`);
145
180
  }
146
- catch {
147
- return [];
181
+ const records = parsed.filter(isScheduleRecord);
182
+ if (records.length !== parsed.length) {
183
+ // Dropping the entries we cannot parse and writing the rest back is the
184
+ // same data loss by a quieter route — a newer schemaVersion included.
185
+ throw new CodeAgentSchedulesUnreadableError(filePath, `${parsed.length - records.length} of ${parsed.length} entries are not valid schedule records`);
148
186
  }
187
+ return records;
149
188
  }
150
189
  function writeScheduleFile(records) {
151
190
  const filePath = codeAgentSchedulesPath();