@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 @@
1
+ export { getGoogleAuthUrl as default } from "../../../handlers/google-auth.js";
@@ -1,6 +1,5 @@
1
1
  import {
2
2
  SIDEBAR_STATE_CHANGE_EVENT,
3
- BuilderSetupCard,
4
3
  sendToAgentChat,
5
4
  setAgentChatContextItem,
6
5
  useAgentEngineConfigured,
@@ -8466,23 +8465,12 @@ function CreatePlanDialog({
8466
8465
  </DialogDescription>
8467
8466
  </DialogHeader>
8468
8467
  <div className="grid gap-3">
8469
- {canCreate && agentMissing ? (
8470
- <BuilderSetupCard
8471
- fullWidth
8472
- onConnected={() =>
8473
- window.dispatchEvent(
8474
- new Event("agent-engine:configured-changed"),
8475
- )
8476
- }
8477
- />
8478
- ) : null}
8479
8468
  <div className="rounded-xl border border-border bg-background p-2 shadow-sm">
8480
8469
  <PromptComposer
8481
8470
  autoFocus
8482
8471
  disabled={composerLocked}
8483
8472
  attachmentsEnabled={false}
8484
8473
  showModelSelector={false}
8485
- modelStatusChecksEnabled={false}
8486
8474
  placeholder={t("plansPage.create.placeholder")}
8487
8475
  draftScope="plans:create-plan"
8488
8476
  initialText={promptSeed}
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-08-19
4
+ ---
5
+
6
+ Plan prompts now guide you to connect AI before sending and offer a clear retry after setup.
@@ -0,0 +1,234 @@
1
+ import { createHash } from "node:crypto";
2
+
3
+ import { defineAction } from "@agent-native/core";
4
+ import { verifyScopedAgentAccessToken } from "@agent-native/core/server";
5
+ import { invalidateCollabAccessCache } from "@agent-native/core/server/poll";
6
+ import {
7
+ getRequestOrgId,
8
+ getRequestUserEmail,
9
+ } from "@agent-native/core/server/request-context";
10
+ import { resolveAccess } from "@agent-native/core/sharing";
11
+ import { and, eq, sql } from "drizzle-orm";
12
+ import { z } from "zod";
13
+
14
+ import { getDb, schema } from "../server/db/index.js";
15
+ import { SLIDES_ACCESS_APPROVAL_TOKEN_PREFIX } from "../shared/deck-access.js";
16
+
17
+ function httpError(message: string, statusCode: number): Error {
18
+ return Object.assign(new Error(message), { statusCode });
19
+ }
20
+
21
+ function normalizeEmail(email: string): string {
22
+ return email.trim().toLowerCase();
23
+ }
24
+
25
+ function approvalTokenHash(token: string): string {
26
+ return createHash("sha256").update(token).digest("hex");
27
+ }
28
+
29
+ type AccessRequestPayload = {
30
+ requesterEmail?: string;
31
+ approvalTokenHash?: string;
32
+ accessGrantedAt?: string;
33
+ };
34
+
35
+ function deckViewerShareId(deckId: string, requesterEmail: string): string {
36
+ return (
37
+ "deck-share-" +
38
+ createHash("sha256")
39
+ .update(deckId)
40
+ .update("\0")
41
+ .update(requesterEmail)
42
+ .digest("hex")
43
+ );
44
+ }
45
+
46
+ export default defineAction({
47
+ description:
48
+ "Approve a private Slides deck access request and add the requester as a viewer in the deck's standard sharing list.",
49
+ schema: z.object({
50
+ deckId: z.string().min(1).describe("Deck ID to share."),
51
+ approvalToken: z
52
+ .string()
53
+ .trim()
54
+ .min(1)
55
+ .describe("Signed approval capability from the deck owner email."),
56
+ }),
57
+ agentTool: false,
58
+ run: async ({ deckId, approvalToken }) => {
59
+ const approverEmail = getRequestUserEmail();
60
+ if (!approverEmail) {
61
+ throw httpError("Sign in as the deck owner to allow access.", 401);
62
+ }
63
+
64
+ const token = verifyScopedAgentAccessToken(approvalToken, {
65
+ resourceKind: SLIDES_ACCESS_APPROVAL_TOKEN_PREFIX,
66
+ resourceId: deckId,
67
+ });
68
+ if (!token.ok || !token.viewerEmail) {
69
+ throw httpError("This access request is invalid or expired.", 404);
70
+ }
71
+
72
+ const normalizedApproverEmail = normalizeEmail(approverEmail);
73
+ const access = await resolveAccess("deck", deckId, {
74
+ userEmail: normalizedApproverEmail,
75
+ orgId: getRequestOrgId() ?? undefined,
76
+ });
77
+ if (!access || !["owner", "admin"].includes(access.role)) {
78
+ throw httpError("Only a deck owner or admin can allow access.", 403);
79
+ }
80
+
81
+ const db = getDb();
82
+ const [deck] = await db
83
+ .select({
84
+ id: schema.decks.id,
85
+ title: schema.decks.title,
86
+ visibility: schema.decks.visibility,
87
+ })
88
+ .from(schema.decks)
89
+ .where(eq(schema.decks.id, deckId))
90
+ .limit(1);
91
+ if (!deck || deck.visibility !== "private") {
92
+ throw httpError(`Deck ${deckId} not found`, 404);
93
+ }
94
+
95
+ const requesterEmail = normalizeEmail(token.viewerEmail);
96
+ const accessRequests = await db
97
+ .select({
98
+ id: schema.deckEvents.id,
99
+ payload: schema.deckEvents.payload,
100
+ })
101
+ .from(schema.deckEvents)
102
+ .where(
103
+ and(
104
+ eq(schema.deckEvents.deckId, deckId),
105
+ eq(schema.deckEvents.type, "deck.access_requested"),
106
+ ),
107
+ );
108
+ const request = accessRequests.find((event) => {
109
+ try {
110
+ const payload = JSON.parse(event.payload ?? "") as AccessRequestPayload;
111
+ return (
112
+ typeof payload.requesterEmail === "string" &&
113
+ normalizeEmail(payload.requesterEmail) === requesterEmail &&
114
+ payload.approvalTokenHash === approvalTokenHash(approvalToken)
115
+ );
116
+ } catch {
117
+ // coercion-ok: malformed historical event payload cannot authorize a share.
118
+ return false;
119
+ }
120
+ });
121
+ if (!request) {
122
+ throw httpError("This access request is invalid or expired.", 404);
123
+ }
124
+ let requestPayload: AccessRequestPayload;
125
+ try {
126
+ requestPayload = JSON.parse(
127
+ request.payload ?? "",
128
+ ) as AccessRequestPayload;
129
+ } catch {
130
+ throw httpError("This access request is invalid or expired.", 404);
131
+ }
132
+
133
+ const [existingShare] = await db
134
+ .select({ id: schema.deckShares.id })
135
+ .from(schema.deckShares)
136
+ .where(
137
+ and(
138
+ eq(schema.deckShares.resourceId, deckId),
139
+ eq(schema.deckShares.principalType, "user"),
140
+ // Share email principals are normalized on write, but this keeps
141
+ // approval idempotent for rows created before that convention.
142
+ sql`lower(${schema.deckShares.principalId}) = ${requesterEmail}`,
143
+ ),
144
+ )
145
+ .limit(1);
146
+ if (existingShare) {
147
+ return {
148
+ ok: true as const,
149
+ alreadyAllowed: true,
150
+ requesterEmail,
151
+ deckId,
152
+ deckTitle: deck.title,
153
+ shareId: existingShare.id,
154
+ message: "Access was already granted to this requester.",
155
+ };
156
+ }
157
+ if (requestPayload.accessGrantedAt) {
158
+ throw httpError("This access request is invalid or expired.", 404);
159
+ }
160
+
161
+ // Use a deterministic primary key as the idempotency key. The generic
162
+ // shares table predates a composite unique constraint, but concurrent
163
+ // approvals for this flow still collide atomically on this key.
164
+ const shareId = deckViewerShareId(deckId, requesterEmail);
165
+ const [insertedShare] = await db
166
+ .insert(schema.deckShares)
167
+ .values({
168
+ id: shareId,
169
+ resourceId: deckId,
170
+ principalType: "user",
171
+ principalId: requesterEmail,
172
+ role: "viewer",
173
+ createdBy: normalizedApproverEmail,
174
+ createdAt: new Date().toISOString(),
175
+ })
176
+ .onConflictDoNothing()
177
+ .returning({ id: schema.deckShares.id });
178
+ if (!insertedShare) {
179
+ const [existingShareAfterConflict] = await db
180
+ .select({ id: schema.deckShares.id })
181
+ .from(schema.deckShares)
182
+ .where(
183
+ and(
184
+ eq(schema.deckShares.resourceId, deckId),
185
+ eq(schema.deckShares.principalType, "user"),
186
+ sql`lower(${schema.deckShares.principalId}) = ${requesterEmail}`,
187
+ ),
188
+ )
189
+ .limit(1);
190
+ if (!existingShareAfterConflict) {
191
+ throw new Error("Deck access share conflict could not be resolved.");
192
+ }
193
+ return {
194
+ ok: true as const,
195
+ alreadyAllowed: true,
196
+ requesterEmail,
197
+ deckId,
198
+ deckTitle: deck.title,
199
+ shareId: existingShareAfterConflict.id,
200
+ message: "Access was already granted to this requester.",
201
+ };
202
+ }
203
+ const [markedRequest] = await db
204
+ .update(schema.deckEvents)
205
+ .set({
206
+ payload: JSON.stringify({
207
+ ...requestPayload,
208
+ accessGrantedAt: new Date().toISOString(),
209
+ accessShareId: insertedShare.id,
210
+ }),
211
+ })
212
+ .where(
213
+ and(
214
+ eq(schema.deckEvents.id, request.id),
215
+ eq(schema.deckEvents.payload, request.payload ?? ""),
216
+ ),
217
+ )
218
+ .returning({ id: schema.deckEvents.id });
219
+ if (!markedRequest) {
220
+ throw new Error("Deck access request approval could not be recorded.");
221
+ }
222
+ invalidateCollabAccessCache("deck", deckId);
223
+
224
+ return {
225
+ ok: true as const,
226
+ alreadyAllowed: false,
227
+ requesterEmail,
228
+ deckId,
229
+ deckTitle: deck.title,
230
+ shareId: insertedShare.id,
231
+ message: "Access granted. This requester can now open the deck.",
232
+ };
233
+ },
234
+ });
@@ -1,4 +1,5 @@
1
1
  import { defineAction } from "@agent-native/core";
2
+ import { signScopedAgentAccessToken } from "@agent-native/core/server";
2
3
  import {
3
4
  getRequestUserEmail,
4
5
  getRequestUserName,
@@ -8,6 +9,11 @@ import { eq } from "drizzle-orm";
8
9
  import { z } from "zod";
9
10
 
10
11
  import { getDb, schema } from "../server/db/index.js";
12
+ import {
13
+ SLIDES_ACCESS_REQUEST_FALLBACK_TOKEN_PREFIX,
14
+ SLIDES_ACCESS_REQUEST_TOKEN_PREFIX,
15
+ SLIDES_ACCESS_REQUEST_TOKEN_TTL_SECONDS,
16
+ } from "../shared/deck-access.js";
11
17
 
12
18
  export type DeckAccessStatus = {
13
19
  exists: boolean;
@@ -17,6 +23,7 @@ export type DeckAccessStatus = {
17
23
  viewerName: string | null;
18
24
  role: "owner" | "viewer" | "commenter" | "editor" | "admin" | null;
19
25
  visibility: "private" | "org" | "public" | null;
26
+ accessRequestToken?: string;
20
27
  };
21
28
 
22
29
  export default defineAction({
@@ -53,7 +60,32 @@ export default defineAction({
53
60
  };
54
61
  }
55
62
 
56
- const access = await resolveAccess("deck", deckId, currentAccess());
63
+ let access: Awaited<ReturnType<typeof resolveAccess>> = null;
64
+ let accessProbeFailed = false;
65
+ try {
66
+ access = await resolveAccess("deck", deckId, currentAccess());
67
+ } catch (error) {
68
+ // coercion-ok: an access probe failure must fail closed as no access so
69
+ // the page stays gated while a separate fallback request capability
70
+ // keeps the owner-notification path available.
71
+ accessProbeFailed = true;
72
+ console.warn(
73
+ "[slides] deck access probe failed; treating the deck as private:",
74
+ error,
75
+ );
76
+ }
77
+ const visibility = deck.visibility ?? "private";
78
+ const accessRequestToken =
79
+ !access && visibility === "private"
80
+ ? signScopedAgentAccessToken({
81
+ resourceKind: accessProbeFailed
82
+ ? SLIDES_ACCESS_REQUEST_FALLBACK_TOKEN_PREFIX
83
+ : SLIDES_ACCESS_REQUEST_TOKEN_PREFIX,
84
+ resourceId: deckId,
85
+ ...(viewerEmail ? { viewerEmail } : {}),
86
+ ttlSeconds: SLIDES_ACCESS_REQUEST_TOKEN_TTL_SECONDS,
87
+ })
88
+ : undefined;
57
89
  return {
58
90
  exists: true,
59
91
  hasAccess: Boolean(access),
@@ -61,7 +93,8 @@ export default defineAction({
61
93
  viewerEmail,
62
94
  viewerName,
63
95
  role: access?.role ?? null,
64
- visibility: deck.visibility ?? "private",
96
+ visibility,
97
+ ...(accessRequestToken ? { accessRequestToken } : {}),
65
98
  };
66
99
  },
67
100
  });