@agent-native/core 0.201.1 → 0.202.0-nightly-20261006033736

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 (98) hide show
  1. package/dist/automation/index.d.ts +1 -1
  2. package/dist/client/analytics/index.d.ts +1 -1
  3. package/dist/client/analytics/index.js +1 -1
  4. package/dist/client/analytics.d.ts +23 -3
  5. package/dist/client/analytics.js +189 -46
  6. package/dist/client/client-status-requests.d.ts +4 -1
  7. package/dist/client/client-status-requests.js +21 -5
  8. package/dist/client/embed-auth.d.ts +6 -0
  9. package/dist/client/embed-auth.js +13 -0
  10. package/dist/client/session-replay.js +48 -22
  11. package/dist/client/sharing/index.d.ts +2 -0
  12. package/dist/client/sharing/index.js +2 -0
  13. package/dist/client/sharing/useAccessRequestReview.d.ts +80 -0
  14. package/dist/client/sharing/useAccessRequestReview.js +100 -0
  15. package/dist/client/sharing/useResourceAccessGate.d.ts +57 -0
  16. package/dist/client/sharing/useResourceAccessGate.js +76 -0
  17. package/dist/client/use-agent-engine-configured.d.ts +2 -0
  18. package/dist/client/use-agent-engine-configured.js +4 -1
  19. package/dist/email-catalog/registry.d.ts +1 -1
  20. package/dist/email-catalog/registry.js +1 -1
  21. package/dist/email-catalog/system-emails.d.ts +2 -2
  22. package/dist/email-catalog/system-emails.js +47 -2
  23. package/dist/email-catalog/templates.d.ts +5 -1
  24. package/dist/email-catalog/templates.js +11 -1
  25. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  26. package/dist/framework-tools.js +6 -0
  27. package/dist/identity/rekey.js +9 -0
  28. package/dist/jobs/run-history.d.ts +27 -8
  29. package/dist/jobs/run-history.js +17 -10
  30. package/dist/jobs/scheduler.js +39 -10
  31. package/dist/mcp/build-server.d.ts +1 -0
  32. package/dist/mcp/build-server.js +30 -7
  33. package/dist/mcp/screen-memory-stdio.d.ts +7 -7
  34. package/dist/notifications/routes.d.ts +2 -2
  35. package/dist/observability/routes.d.ts +7 -7
  36. package/dist/org/index.d.ts +1 -1
  37. package/dist/progress/routes.d.ts +1 -1
  38. package/dist/provider-api/actions/custom-provider-registration.d.ts +10 -10
  39. package/dist/provider-api/actions/provider-api.d.ts +11 -11
  40. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  41. package/dist/review/suggestions/actions.js +14 -35
  42. package/dist/review/suggestions/store.d.ts +0 -1
  43. package/dist/review/suggestions/store.js +0 -7
  44. package/dist/secrets/routes.d.ts +11 -11
  45. package/dist/server/action-discovery.js +24 -0
  46. package/dist/server/agent-access.js +7 -3
  47. package/dist/server/agent-chat/mcp-options.d.ts +1 -0
  48. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  49. package/dist/server/agent-engine-default-model-route.d.ts +1 -1
  50. package/dist/server/agent-engine-ollama-models-route.d.ts +1 -1
  51. package/dist/server/attribution.d.ts +33 -1
  52. package/dist/server/attribution.js +93 -21
  53. package/dist/server/auth.js +35 -11
  54. package/dist/server/email-templates.d.ts +34 -0
  55. package/dist/server/email-templates.js +40 -0
  56. package/dist/server/magic-link-attribution.js +2 -1
  57. package/dist/server/onboarding-html.js +35 -67
  58. package/dist/server/release-schema.js +4 -0
  59. package/dist/server/short-lived-token.d.ts +7 -0
  60. package/dist/server/short-lived-token.js +59 -0
  61. package/dist/shared/analytics-anonymous-id.d.ts +1 -0
  62. package/dist/shared/analytics-anonymous-id.js +1 -1
  63. package/dist/shared/attribution-source.d.ts +30 -0
  64. package/dist/shared/attribution-source.js +65 -0
  65. package/dist/sharing/access-request-store.d.ts +96 -0
  66. package/dist/sharing/access-request-store.js +213 -0
  67. package/dist/sharing/access-requests.d.ts +113 -0
  68. package/dist/sharing/access-requests.js +490 -0
  69. package/dist/sharing/access.d.ts +36 -2
  70. package/dist/sharing/access.js +74 -12
  71. package/dist/sharing/actions/approve-resource-access-request.d.ts +10 -0
  72. package/dist/sharing/actions/approve-resource-access-request.js +15 -0
  73. package/dist/sharing/actions/decline-resource-access-request.d.ts +7 -0
  74. package/dist/sharing/actions/decline-resource-access-request.js +14 -0
  75. package/dist/sharing/actions/get-resource-access-request.d.ts +4 -0
  76. package/dist/sharing/actions/get-resource-access-request.js +13 -0
  77. package/dist/sharing/actions/get-resource-access-status.d.ts +5 -0
  78. package/dist/sharing/actions/get-resource-access-status.js +19 -0
  79. package/dist/sharing/actions/list-resource-access-requests.d.ts +5 -0
  80. package/dist/sharing/actions/list-resource-access-requests.js +16 -0
  81. package/dist/sharing/actions/list-resource-shares.d.ts +1 -1
  82. package/dist/sharing/actions/request-resource-access.d.ts +6 -0
  83. package/dist/sharing/actions/request-resource-access.js +17 -0
  84. package/dist/sharing/actions/share-resource.d.ts +1 -1
  85. package/dist/sharing/actions/share-resource.js +18 -144
  86. package/dist/sharing/grant.d.ts +49 -0
  87. package/dist/sharing/grant.js +195 -0
  88. package/dist/sharing/index.d.ts +2 -1
  89. package/dist/sharing/index.js +2 -1
  90. package/dist/sharing/registry.d.ts +36 -0
  91. package/dist/templates/chat/server/plugins/auth.ts +2 -2
  92. package/dist/templates/default/app/routes/access-requests.$requestId.tsx +7 -0
  93. package/dist/templates/default/server/plugins/auth.ts +2 -2
  94. package/dist/vite/action-types-plugin.js +24 -0
  95. package/package.json +11 -3
  96. package/src/templates/chat/server/plugins/auth.ts +2 -2
  97. package/src/templates/default/app/routes/access-requests.$requestId.tsx +7 -0
  98. package/src/templates/default/server/plugins/auth.ts +2 -2
@@ -0,0 +1,80 @@
1
+ import type { ResourceAccessRequestFailure } from "./useResourceAccessGate.js";
2
+ export type AccessRequestRole = "viewer" | "commenter" | "editor" | "admin";
3
+ /** One access request, as someone who manages access reviews it. */
4
+ export interface AccessRequestReview {
5
+ id: string;
6
+ generation: number;
7
+ state: "pending" | "approved" | "declined";
8
+ requester: {
9
+ email: string;
10
+ name: string | null;
11
+ };
12
+ note: string | null;
13
+ requestedAt: string;
14
+ decidedAt: string | null;
15
+ grantedRole: AccessRequestRole | null;
16
+ resource: {
17
+ type: string;
18
+ id: string;
19
+ label: string;
20
+ title: string;
21
+ path: string | null;
22
+ };
23
+ }
24
+ export interface AccessRequestDecisionFailure extends ResourceAccessRequestFailure {
25
+ /**
26
+ * Why the server refused, such as a sharing rule. Absent for unexpected
27
+ * failures, whose server message isn't meant for people.
28
+ */
29
+ message: string | null;
30
+ }
31
+ type DecidedRequest = Pick<AccessRequestReview, "id" | "generation" | "requester">;
32
+ export interface AccessRequestDecisions {
33
+ approve: (request: DecidedRequest, role: AccessRequestRole) => Promise<void>;
34
+ decline: (request: DecidedRequest) => Promise<void>;
35
+ /** The request a decision is in flight for. */
36
+ pendingId: string | null;
37
+ error: AccessRequestDecisionFailure | null;
38
+ /** An allowed request whose requester couldn't be emailed. */
39
+ unemailed: Pick<AccessRequestReview, "id" | "requester"> | null;
40
+ }
41
+ /**
42
+ * Allow and Decline, shared by the approval page and the Share panels. A
43
+ * decision someone else already made calls `onStale`, so the caller reloads
44
+ * instead of offering the same outdated request again.
45
+ */
46
+ export declare function useAccessRequestDecisions(onStale: () => unknown): AccessRequestDecisions;
47
+ export interface AccessRequestReviewController {
48
+ review: AccessRequestReview | undefined;
49
+ isLoading: boolean;
50
+ /** The viewer isn't signed in. */
51
+ isSignedOut: boolean;
52
+ /** The request doesn't exist, or the viewer can't manage its resource. */
53
+ isUnavailable: boolean;
54
+ /** Reading the request failed for another reason; retry with `refetch`. */
55
+ isError: boolean;
56
+ refetch: () => Promise<unknown>;
57
+ decisions: AccessRequestDecisions;
58
+ }
59
+ /**
60
+ * Loads one access request for its approval page. Opening the page only
61
+ * reads; Allow and Decline are explicit calls on `decisions`.
62
+ */
63
+ export declare function useAccessRequestReview(requestId: string): AccessRequestReviewController;
64
+ export interface ResourceAccessRequestsController {
65
+ /** The newest pending requests. */
66
+ requests: AccessRequestReview[];
67
+ /** Older pending requests exist beyond `requests`. */
68
+ hasMore: boolean;
69
+ isLoading: boolean;
70
+ isError: boolean;
71
+ refetch: () => Promise<unknown>;
72
+ decisions: AccessRequestDecisions;
73
+ }
74
+ /** Pending access requests for one resource, for its Share panel. */
75
+ export declare function useResourceAccessRequests({ resourceType, resourceId, enabled, }: {
76
+ resourceType: string;
77
+ resourceId: string;
78
+ enabled?: boolean;
79
+ }): ResourceAccessRequestsController;
80
+ export {};
@@ -0,0 +1,100 @@
1
+ import { useCallback, useMemo, useState } from "react";
2
+ import { useActionMutation, useActionQuery } from "../use-action.js";
3
+ function decisionFailure(error) {
4
+ if (!error)
5
+ return null;
6
+ const failure = error;
7
+ const refused = failure.status === 400 || failure.status === 403;
8
+ return {
9
+ errorCode: typeof failure.errorCode === "string" ? failure.errorCode : null,
10
+ message: refused && typeof failure.actionMessage === "string"
11
+ ? failure.actionMessage
12
+ : null,
13
+ retryAt: typeof failure.details?.retryAt === "string"
14
+ ? failure.details.retryAt
15
+ : null,
16
+ };
17
+ }
18
+ /**
19
+ * Allow and Decline, shared by the approval page and the Share panels. A
20
+ * decision someone else already made calls `onStale`, so the caller reloads
21
+ * instead of offering the same outdated request again.
22
+ */
23
+ export function useAccessRequestDecisions(onStale) {
24
+ const { mutateAsync: approveAsync } = useActionMutation("approve-resource-access-request");
25
+ const { mutateAsync: declineAsync } = useActionMutation("decline-resource-access-request");
26
+ const [pendingId, setPendingId] = useState(null);
27
+ const [error, setError] = useState(null);
28
+ const [unemailed, setUnemailed] = useState(null);
29
+ const decide = useCallback(async (requestId, run) => {
30
+ setPendingId(requestId);
31
+ setError(null);
32
+ setUnemailed(null);
33
+ try {
34
+ await run();
35
+ }
36
+ catch (err) {
37
+ setError(err);
38
+ if (err.errorCode === "access_request_stale") {
39
+ void onStale();
40
+ }
41
+ throw err;
42
+ }
43
+ finally {
44
+ setPendingId((current) => (current === requestId ? null : current));
45
+ }
46
+ }, [onStale]);
47
+ const approve = useCallback((request, role) => decide(request.id, async () => {
48
+ const result = await approveAsync({
49
+ requestId: request.id,
50
+ generation: request.generation,
51
+ role,
52
+ });
53
+ if (result?.email === "failed") {
54
+ setUnemailed({ id: request.id, requester: request.requester });
55
+ }
56
+ }), [approveAsync, decide]);
57
+ const decline = useCallback((request) => decide(request.id, () => declineAsync({ requestId: request.id, generation: request.generation })), [declineAsync, decide]);
58
+ return {
59
+ approve,
60
+ decline,
61
+ pendingId,
62
+ error: decisionFailure(error),
63
+ unemailed,
64
+ };
65
+ }
66
+ /**
67
+ * Loads one access request for its approval page. Opening the page only
68
+ * reads; Allow and Decline are explicit calls on `decisions`.
69
+ */
70
+ export function useAccessRequestReview(requestId) {
71
+ const params = useMemo(() => ({ requestId }), [requestId]);
72
+ const query = useActionQuery("get-resource-access-request", params, { enabled: Boolean(requestId), retry: false });
73
+ const decisions = useAccessRequestDecisions(query.refetch);
74
+ const status = query.error?.status;
75
+ const isSignedOut = query.isError && status === 401;
76
+ const isUnavailable = query.isError && status === 404;
77
+ return {
78
+ review: query.data,
79
+ isLoading: query.isLoading,
80
+ isSignedOut,
81
+ isUnavailable,
82
+ isError: query.isError && !isSignedOut && !isUnavailable,
83
+ refetch: query.refetch,
84
+ decisions,
85
+ };
86
+ }
87
+ /** Pending access requests for one resource, for its Share panel. */
88
+ export function useResourceAccessRequests({ resourceType, resourceId, enabled = true, }) {
89
+ const params = useMemo(() => ({ resourceType, resourceId }), [resourceId, resourceType]);
90
+ const query = useActionQuery("list-resource-access-requests", params, { enabled, retry: false });
91
+ const decisions = useAccessRequestDecisions(query.refetch);
92
+ return {
93
+ requests: query.data?.requests ?? [],
94
+ hasMore: query.data?.hasMore ?? false,
95
+ isLoading: query.isLoading,
96
+ isError: query.isError,
97
+ refetch: query.refetch,
98
+ decisions,
99
+ };
100
+ }
@@ -0,0 +1,57 @@
1
+ /** Mirrors the server's `ResourceAccessState`. */
2
+ export type ResourceAccessGateState = "allowed" | "trashed" | "denied" | "missing" | "signed-out";
3
+ export type ResourceAccessGateRole = "owner" | "admin" | "editor" | "commenter" | "viewer";
4
+ /** The viewer's own open request for access. */
5
+ export interface ResourceAccessGateRequest {
6
+ state: "pending";
7
+ requestedAt: string;
8
+ }
9
+ export interface ResourceAccessGateStatus {
10
+ state: ResourceAccessGateState;
11
+ /** The viewer's role, only when they can open the resource. */
12
+ role?: ResourceAccessGateRole;
13
+ /** Whether the viewer can ask for access now. Only for `denied`. */
14
+ canRequest?: boolean;
15
+ /** The viewer's open request. Only for `denied`. */
16
+ request?: ResourceAccessGateRequest;
17
+ }
18
+ /** Why asking for access failed, for the screen to explain. */
19
+ export interface ResourceAccessRequestFailure {
20
+ /** The server's error code, such as `access_request_rate_limited`. */
21
+ errorCode: string | null;
22
+ /** When the viewer can try again, if the server said. */
23
+ retryAt: string | null;
24
+ }
25
+ export interface ResourceAccessGateOptions {
26
+ resourceType: string;
27
+ resourceId: string;
28
+ enabled?: boolean;
29
+ /**
30
+ * Called once when the status says the viewer can open the resource: either
31
+ * the read that failed should be tried again, or the owner shared it while
32
+ * this screen was open. The status is checked again whenever the window
33
+ * regains focus, including switching back from another browser window, and
34
+ * every half minute while the viewer's request is open.
35
+ */
36
+ onAccessGranted?: () => void;
37
+ }
38
+ export interface ResourceAccessGateController {
39
+ status: ResourceAccessGateStatus | undefined;
40
+ isLoading: boolean;
41
+ isError: boolean;
42
+ refetch: () => Promise<unknown>;
43
+ /**
44
+ * Asks the owner for access, with an optional note. Resolves once the
45
+ * request is stored and the status shows it; rejects when it fails, with
46
+ * the reason in `requestError`.
47
+ */
48
+ requestAccess: (note?: string) => Promise<void>;
49
+ isRequesting: boolean;
50
+ requestError: ResourceAccessRequestFailure | null;
51
+ }
52
+ /**
53
+ * Asks the server what a link the viewer couldn't open should say. Use it
54
+ * after a resource read fails with 403 or 404, then render the matching
55
+ * access screen.
56
+ */
57
+ export declare function useResourceAccessGate({ resourceType, resourceId, enabled, onAccessGranted, }: ResourceAccessGateOptions): ResourceAccessGateController;
@@ -0,0 +1,76 @@
1
+ import { useCallback, useEffect, useMemo, useRef } from "react";
2
+ import { forgetAuthFailures } from "../embed-auth.js";
3
+ import { useActionMutation, useActionQuery } from "../use-action.js";
4
+ // While a request is open, approving it should open the resource without a
5
+ // manual reload, even when the window never loses focus.
6
+ const PENDING_REQUEST_POLL_MS = 30_000;
7
+ function requestFailure(error) {
8
+ const failure = error;
9
+ return {
10
+ errorCode: typeof failure.errorCode === "string" ? failure.errorCode : null,
11
+ retryAt: typeof failure.details?.retryAt === "string"
12
+ ? failure.details.retryAt
13
+ : null,
14
+ };
15
+ }
16
+ /**
17
+ * Asks the server what a link the viewer couldn't open should say. Use it
18
+ * after a resource read fails with 403 or 404, then render the matching
19
+ * access screen.
20
+ */
21
+ export function useResourceAccessGate({ resourceType, resourceId, enabled = true, onAccessGranted, }) {
22
+ const params = useMemo(() => ({ resourceType, resourceId }), [resourceId, resourceType]);
23
+ const query = useActionQuery("get-resource-access-status", params, {
24
+ enabled,
25
+ refetchOnWindowFocus: "always",
26
+ staleTime: 0,
27
+ refetchInterval: (current) => current.state.data?.request ? PENDING_REQUEST_POLL_MS : false,
28
+ });
29
+ const request = useActionMutation("request-resource-access");
30
+ const state = query.data?.state;
31
+ const onAccessGrantedRef = useRef(onAccessGranted);
32
+ onAccessGrantedRef.current = onAccessGranted;
33
+ const refetchRef = useRef(query.refetch);
34
+ refetchRef.current = query.refetch;
35
+ const target = `${resourceType}:${resourceId}`;
36
+ const lastSeenRef = useRef(null);
37
+ useEffect(() => {
38
+ if (!state)
39
+ return;
40
+ const previous = lastSeenRef.current?.target === target ? lastSeenRef.current.state : null;
41
+ lastSeenRef.current = { target, state };
42
+ if (state === "allowed" && previous !== "allowed") {
43
+ forgetAuthFailures();
44
+ onAccessGrantedRef.current?.();
45
+ }
46
+ }, [target, state]);
47
+ // React Query refetches when the tab becomes visible, but not when focus
48
+ // returns from another window that left this tab visible, such as the
49
+ // owner's window beside it.
50
+ useEffect(() => {
51
+ if (!enabled)
52
+ return;
53
+ const refetch = () => void refetchRef.current({ cancelRefetch: false });
54
+ window.addEventListener("focus", refetch);
55
+ return () => window.removeEventListener("focus", refetch);
56
+ }, [enabled]);
57
+ const { mutateAsync: requestAsync } = request;
58
+ const requestAccess = useCallback(async (note) => {
59
+ const trimmed = note?.trim();
60
+ await requestAsync({
61
+ resourceType,
62
+ resourceId,
63
+ ...(trimmed ? { note: trimmed } : {}),
64
+ });
65
+ await refetchRef.current({ cancelRefetch: false });
66
+ }, [requestAsync, resourceId, resourceType]);
67
+ return {
68
+ status: query.data,
69
+ isLoading: query.isLoading,
70
+ isError: query.isError,
71
+ refetch: query.refetch,
72
+ requestAccess,
73
+ isRequesting: request.isPending,
74
+ requestError: request.error ? requestFailure(request.error) : null,
75
+ };
76
+ }
@@ -16,6 +16,8 @@ export interface UseAgentEngineConfiguredResult {
16
16
  export interface FetchAgentEngineConfiguredStateOptions {
17
17
  /** Kept for API compatibility; readiness always comes from chatEligible. */
18
18
  missingFallback?: boolean;
19
+ /** Starts a new status read instead of joining an older in-flight probe. */
20
+ fresh?: boolean;
19
21
  timeoutMs?: number;
20
22
  }
21
23
  export interface UseAgentEngineConfiguredOptions {
@@ -49,7 +49,10 @@ export async function fetchAgentEngineConfiguredState(enabled = true, options) {
49
49
  const timeoutMs = typeof options?.timeoutMs === "number" && options.timeoutMs > 0
50
50
  ? options.timeoutMs
51
51
  : undefined;
52
- const engineResult = await waitForStatus(fetchAgentEngineStatus(), "/_agent-native/agent-engine/status", timeoutMs);
52
+ let engineResult = await waitForStatus(fetchAgentEngineStatus({ fresh: options?.fresh }), "/_agent-native/agent-engine/status", timeoutMs);
53
+ while (engineResult.state === "unavailable" && engineResult.stale) {
54
+ engineResult = await waitForStatus(fetchAgentEngineStatus(), "/_agent-native/agent-engine/status", timeoutMs);
55
+ }
53
56
  if (engineResult.state !== "available" ||
54
57
  !hasChatEligibleFlag(engineResult.value)) {
55
58
  // A reachable legacy server may expose only the broad `configured` flag.
@@ -26,6 +26,6 @@ export declare function renderTransactionalEmailPreview(id: string): RenderedEma
26
26
  * synchronous `renderTransactionalEmailPreview` cannot.
27
27
  */
28
28
  export declare function renderTransactionalEmailPreviewAsync(id: string): Promise<RenderedEmailMessage>;
29
- export { CORE_RESOURCE_SHARED_EMAIL_ID, overrideTransactionalEmail, removeTransactionalEmailOverride, type CoreTransactionalEmailArgs, type CoreTransactionalEmailId, type CoreTransactionalEmailProps, type TransactionalEmailOverride, type TransactionalEmailOverrideResult, } from "./templates.js";
29
+ export { CORE_ACCESS_GRANTED_EMAIL_ID, CORE_ACCESS_REQUESTED_EMAIL_ID, CORE_RESOURCE_SHARED_EMAIL_ID, overrideTransactionalEmail, removeTransactionalEmailOverride, type CoreTransactionalEmailArgs, type CoreTransactionalEmailId, type CoreTransactionalEmailProps, type TransactionalEmailOverride, type TransactionalEmailOverrideResult, } from "./templates.js";
30
30
  export { escapeEmailHtml } from "../server/email-template.js";
31
31
  export declare function resetTransactionalEmailRegistry(): void;
@@ -104,7 +104,7 @@ export async function renderTransactionalEmailPreviewAsync(id) {
104
104
  ? definition.previewAsync()
105
105
  : definition.preview();
106
106
  }
107
- export { CORE_RESOURCE_SHARED_EMAIL_ID, overrideTransactionalEmail, removeTransactionalEmailOverride, } from "./templates.js";
107
+ export { CORE_ACCESS_GRANTED_EMAIL_ID, CORE_ACCESS_REQUESTED_EMAIL_ID, CORE_RESOURCE_SHARED_EMAIL_ID, overrideTransactionalEmail, removeTransactionalEmailOverride, } from "./templates.js";
108
108
  export { escapeEmailHtml } from "../server/email-template.js";
109
109
  export function resetTransactionalEmailRegistry() {
110
110
  registry.clear();
@@ -1,3 +1,3 @@
1
- import { CORE_BUILDER_CREDIT_LIMIT_EMAIL_ID, CORE_CHANGE_EMAIL_CONFIRMATION_EMAIL_ID, CORE_CHANGE_EMAIL_VERIFICATION_EMAIL_ID, CORE_INVITE_EMAIL_ID, CORE_MAGIC_LINK_EMAIL_ID, CORE_RESET_PASSWORD_EMAIL_ID, CORE_RESOURCE_SHARED_EMAIL_ID, CORE_VERIFY_SIGNUP_EMAIL_ID } from "./templates.js";
2
- export { CORE_BUILDER_CREDIT_LIMIT_EMAIL_ID, CORE_CHANGE_EMAIL_CONFIRMATION_EMAIL_ID, CORE_CHANGE_EMAIL_VERIFICATION_EMAIL_ID, CORE_INVITE_EMAIL_ID, CORE_MAGIC_LINK_EMAIL_ID, CORE_RESET_PASSWORD_EMAIL_ID, CORE_RESOURCE_SHARED_EMAIL_ID, CORE_VERIFY_SIGNUP_EMAIL_ID, };
1
+ import { CORE_ACCESS_GRANTED_EMAIL_ID, CORE_ACCESS_REQUESTED_EMAIL_ID, CORE_BUILDER_CREDIT_LIMIT_EMAIL_ID, CORE_CHANGE_EMAIL_CONFIRMATION_EMAIL_ID, CORE_CHANGE_EMAIL_VERIFICATION_EMAIL_ID, CORE_INVITE_EMAIL_ID, CORE_MAGIC_LINK_EMAIL_ID, CORE_RESET_PASSWORD_EMAIL_ID, CORE_RESOURCE_SHARED_EMAIL_ID, CORE_VERIFY_SIGNUP_EMAIL_ID } from "./templates.js";
2
+ export { CORE_ACCESS_GRANTED_EMAIL_ID, CORE_ACCESS_REQUESTED_EMAIL_ID, CORE_BUILDER_CREDIT_LIMIT_EMAIL_ID, CORE_CHANGE_EMAIL_CONFIRMATION_EMAIL_ID, CORE_CHANGE_EMAIL_VERIFICATION_EMAIL_ID, CORE_INVITE_EMAIL_ID, CORE_MAGIC_LINK_EMAIL_ID, CORE_RESET_PASSWORD_EMAIL_ID, CORE_RESOURCE_SHARED_EMAIL_ID, CORE_VERIFY_SIGNUP_EMAIL_ID, };
3
3
  export declare function registerCoreSystemEmails(): void;
@@ -2,10 +2,10 @@ import { BILLING_NOTICE_MESSAGES } from "../localization/billing-notice-messages
2
2
  import { resolveEmailBrandApp } from "../server/email-templates.js";
3
3
  import { builderSubscriptionUpgradeUrl } from "../shared/builder-link-tracking.js";
4
4
  import { defineTransactionalEmail } from "./registry.js";
5
- import { CORE_BUILDER_CREDIT_LIMIT_EMAIL_ID, CORE_CHANGE_EMAIL_CONFIRMATION_EMAIL_ID, CORE_CHANGE_EMAIL_VERIFICATION_EMAIL_ID, CORE_INVITE_EMAIL_ID, CORE_MAGIC_LINK_EMAIL_ID, CORE_RESET_PASSWORD_EMAIL_ID, CORE_RESOURCE_SHARED_EMAIL_ID, CORE_VERIFY_SIGNUP_EMAIL_ID, renderDefaultTransactionalEmail, renderTransactionalEmail, } from "./templates.js";
5
+ import { CORE_ACCESS_GRANTED_EMAIL_ID, CORE_ACCESS_REQUESTED_EMAIL_ID, CORE_BUILDER_CREDIT_LIMIT_EMAIL_ID, CORE_CHANGE_EMAIL_CONFIRMATION_EMAIL_ID, CORE_CHANGE_EMAIL_VERIFICATION_EMAIL_ID, CORE_INVITE_EMAIL_ID, CORE_MAGIC_LINK_EMAIL_ID, CORE_RESET_PASSWORD_EMAIL_ID, CORE_RESOURCE_SHARED_EMAIL_ID, CORE_VERIFY_SIGNUP_EMAIL_ID, renderDefaultTransactionalEmail, renderTransactionalEmail, } from "./templates.js";
6
6
  const SAMPLE_URL = "https://example.com/accept/sample-token";
7
7
  const SAMPLE_EMAIL = "sam.rivera@example.com";
8
- export { CORE_BUILDER_CREDIT_LIMIT_EMAIL_ID, CORE_CHANGE_EMAIL_CONFIRMATION_EMAIL_ID, CORE_CHANGE_EMAIL_VERIFICATION_EMAIL_ID, CORE_INVITE_EMAIL_ID, CORE_MAGIC_LINK_EMAIL_ID, CORE_RESET_PASSWORD_EMAIL_ID, CORE_RESOURCE_SHARED_EMAIL_ID, CORE_VERIFY_SIGNUP_EMAIL_ID, };
8
+ export { CORE_ACCESS_GRANTED_EMAIL_ID, CORE_ACCESS_REQUESTED_EMAIL_ID, CORE_BUILDER_CREDIT_LIMIT_EMAIL_ID, CORE_CHANGE_EMAIL_CONFIRMATION_EMAIL_ID, CORE_CHANGE_EMAIL_VERIFICATION_EMAIL_ID, CORE_INVITE_EMAIL_ID, CORE_MAGIC_LINK_EMAIL_ID, CORE_RESET_PASSWORD_EMAIL_ID, CORE_RESOURCE_SHARED_EMAIL_ID, CORE_VERIFY_SIGNUP_EMAIL_ID, };
9
9
  function corePreview(id, sample) {
10
10
  return {
11
11
  preview: () => renderDefaultTransactionalEmail(id, sample()),
@@ -130,6 +130,51 @@ export function registerCoreSystemEmails() {
130
130
  app: resolveEmailBrandApp(),
131
131
  })),
132
132
  });
133
+ defineTransactionalEmail({
134
+ id: CORE_ACCESS_REQUESTED_EMAIL_ID,
135
+ app: "core",
136
+ name: "Access requested",
137
+ trigger: "A signed-in person who can't open a resource asks for access, in an app whose resource type takes requests. Asking again while the request is pending sends nothing.",
138
+ recipientLabel: "Owner and admins",
139
+ recipient: "The resource owner and each person with admin on it. Synthetic QA addresses are skipped.",
140
+ senderLabel: "Default, resource-branded",
141
+ sender: "The configured EMAIL_FROM, with reply-to set to the requester's signed-in address. A resource registration can set the brand name and logo.",
142
+ ...corePreview(CORE_ACCESS_REQUESTED_EMAIL_ID, () => ({
143
+ recipientEmail: SAMPLE_EMAIL,
144
+ requester: { name: "Alex Chen", email: "alex.chen@example.com" },
145
+ resource: {
146
+ type: "document",
147
+ label: "Document",
148
+ title: "Launch plan",
149
+ url: SAMPLE_URL,
150
+ },
151
+ reviewUrl: SAMPLE_URL,
152
+ note: "I'm presenting the rollout on Friday.",
153
+ app: resolveEmailBrandApp(),
154
+ })),
155
+ });
156
+ defineTransactionalEmail({
157
+ id: CORE_ACCESS_GRANTED_EMAIL_ID,
158
+ app: "core",
159
+ name: "Access granted",
160
+ trigger: "Someone who manages access to a resource allows a pending access request.",
161
+ recipientLabel: "Requester",
162
+ recipient: "The signed-in address that asked for access. Synthetic QA addresses are skipped.",
163
+ senderLabel: "Default, resource-branded",
164
+ sender: "The configured EMAIL_FROM. A resource registration can set the brand name and logo.",
165
+ ...corePreview(CORE_ACCESS_GRANTED_EMAIL_ID, () => ({
166
+ recipientEmail: SAMPLE_EMAIL,
167
+ approver: { name: "Alex Chen", email: "alex.chen@example.com" },
168
+ resource: {
169
+ type: "document",
170
+ label: "Document",
171
+ title: "Launch plan",
172
+ url: SAMPLE_URL,
173
+ },
174
+ role: "viewer",
175
+ app: resolveEmailBrandApp(),
176
+ })),
177
+ });
133
178
  defineTransactionalEmail({
134
179
  id: CORE_MAGIC_LINK_EMAIL_ID,
135
180
  app: "core",
@@ -1,6 +1,6 @@
1
1
  import type { ReactElement } from "react";
2
2
  import { type EmailTemplateApp } from "../server/email-template.js";
3
- import { type RenderBuilderCreditLimitEmailArgs, type RenderChangeEmailConfirmationArgs, type RenderChangeEmailVerificationArgs, type RenderedEmailMessage, type RenderInviteEmailArgs, type RenderMagicLinkEmailArgs, type RenderResetPasswordEmailArgs, type RenderResourceSharedEmailArgs, type RenderVerifySignupEmailArgs } from "../server/email-templates.js";
3
+ import { type RenderAccessGrantedEmailArgs, type RenderAccessRequestedEmailArgs, type RenderBuilderCreditLimitEmailArgs, type RenderChangeEmailConfirmationArgs, type RenderChangeEmailVerificationArgs, type RenderedEmailMessage, type RenderInviteEmailArgs, type RenderMagicLinkEmailArgs, type RenderResetPasswordEmailArgs, type RenderResourceSharedEmailArgs, type RenderVerifySignupEmailArgs } from "../server/email-templates.js";
4
4
  export declare const CORE_INVITE_EMAIL_ID = "core.organization-invite";
5
5
  export declare const CORE_VERIFY_SIGNUP_EMAIL_ID = "core.verify-signup";
6
6
  export declare const CORE_RESET_PASSWORD_EMAIL_ID = "core.reset-password";
@@ -9,6 +9,8 @@ export declare const CORE_CHANGE_EMAIL_CONFIRMATION_EMAIL_ID = "core.change-emai
9
9
  export declare const CORE_CHANGE_EMAIL_VERIFICATION_EMAIL_ID = "core.change-email-verification";
10
10
  export declare const CORE_BUILDER_CREDIT_LIMIT_EMAIL_ID = "core.builder-credit-limit";
11
11
  export declare const CORE_RESOURCE_SHARED_EMAIL_ID = "core.resource-shared";
12
+ export declare const CORE_ACCESS_REQUESTED_EMAIL_ID = "core.access-requested";
13
+ export declare const CORE_ACCESS_GRANTED_EMAIL_ID = "core.access-granted";
12
14
  /**
13
15
  * Data each framework email is rendered from, keyed by catalog id. An
14
16
  * override receives these values raw — escape them before interpolating into
@@ -23,6 +25,8 @@ export interface CoreTransactionalEmailArgs {
23
25
  [CORE_INVITE_EMAIL_ID]: RenderInviteEmailArgs;
24
26
  [CORE_BUILDER_CREDIT_LIMIT_EMAIL_ID]: RenderBuilderCreditLimitEmailArgs;
25
27
  [CORE_RESOURCE_SHARED_EMAIL_ID]: RenderResourceSharedEmailArgs;
28
+ [CORE_ACCESS_REQUESTED_EMAIL_ID]: RenderAccessRequestedEmailArgs;
29
+ [CORE_ACCESS_GRANTED_EMAIL_ID]: RenderAccessGrantedEmailArgs;
26
30
  }
27
31
  export type CoreTransactionalEmailId = keyof CoreTransactionalEmailArgs;
28
32
  export type CoreTransactionalEmailProps<Id extends CoreTransactionalEmailId> = CoreTransactionalEmailArgs[Id] & {
@@ -1,5 +1,5 @@
1
1
  import { emailHtmlToText, } from "../server/email-template.js";
2
- import { renderBuilderCreditLimitEmail, renderChangeEmailConfirmationEmail, renderChangeEmailVerificationEmail, renderInviteEmail, renderMagicLinkEmail, renderResetPasswordEmail, renderResourceSharedEmail, renderVerifySignupEmail, resolveEmailApp, resolveEmailBrandApp, } from "../server/email-templates.js";
2
+ import { renderAccessGrantedEmail, renderAccessRequestedEmail, renderBuilderCreditLimitEmail, renderChangeEmailConfirmationEmail, renderChangeEmailVerificationEmail, renderInviteEmail, renderMagicLinkEmail, renderResetPasswordEmail, renderResourceSharedEmail, renderVerifySignupEmail, resolveEmailApp, resolveEmailBrandApp, } from "../server/email-templates.js";
3
3
  export const CORE_INVITE_EMAIL_ID = "core.organization-invite";
4
4
  export const CORE_VERIFY_SIGNUP_EMAIL_ID = "core.verify-signup";
5
5
  export const CORE_RESET_PASSWORD_EMAIL_ID = "core.reset-password";
@@ -8,6 +8,8 @@ export const CORE_CHANGE_EMAIL_CONFIRMATION_EMAIL_ID = "core.change-email-confir
8
8
  export const CORE_CHANGE_EMAIL_VERIFICATION_EMAIL_ID = "core.change-email-verification";
9
9
  export const CORE_BUILDER_CREDIT_LIMIT_EMAIL_ID = "core.builder-credit-limit";
10
10
  export const CORE_RESOURCE_SHARED_EMAIL_ID = "core.resource-shared";
11
+ export const CORE_ACCESS_REQUESTED_EMAIL_ID = "core.access-requested";
12
+ export const CORE_ACCESS_GRANTED_EMAIL_ID = "core.access-granted";
11
13
  const CORE_EMAIL_DEFAULTS = {
12
14
  [CORE_VERIFY_SIGNUP_EMAIL_ID]: {
13
15
  render: (args) => renderVerifySignupEmail(args),
@@ -41,6 +43,14 @@ const CORE_EMAIL_DEFAULTS = {
41
43
  render: (args) => renderResourceSharedEmail(args),
42
44
  app: (args) => args.app,
43
45
  },
46
+ [CORE_ACCESS_REQUESTED_EMAIL_ID]: {
47
+ render: (args) => renderAccessRequestedEmail(args),
48
+ app: (args) => args.app,
49
+ },
50
+ [CORE_ACCESS_GRANTED_EMAIL_ID]: {
51
+ render: (args) => renderAccessGrantedEmail(args),
52
+ app: (args) => args.app,
53
+ },
44
54
  };
45
55
  // Overrides are registered through the public `@agent-native/core` entry and
46
56
  // read through relative imports; a module-local map would split in two when
@@ -42,8 +42,8 @@ declare const _default: import("../../action.js").ActionDefinition<{
42
42
  url: string;
43
43
  id: string;
44
44
  provider: string;
45
- configured?: undefined;
46
45
  error?: undefined;
46
+ configured?: undefined;
47
47
  } | {
48
48
  url?: undefined;
49
49
  id?: undefined;
@@ -101,6 +101,12 @@ export const CORE_ACTION_GROUPS = {
101
101
  "share-resource": "sharing",
102
102
  "unshare-resource": "sharing",
103
103
  "list-resource-shares": "sharing",
104
+ "get-resource-access-status": "sharing",
105
+ "request-resource-access": "sharing",
106
+ "get-resource-access-request": "sharing",
107
+ "list-resource-access-requests": "sharing",
108
+ "approve-resource-access-request": "sharing",
109
+ "decline-resource-access-request": "sharing",
104
110
  "set-resource-visibility": "sharing",
105
111
  "create-agent-resource-link": "sharing",
106
112
  "get-feature-flags": "featureFlags",
@@ -136,6 +136,15 @@ export const IDENTITY_REKEY_COLUMNS = [
136
136
  { table: "custom_api_providers", column: "scope_id", mode: "custom-scope" },
137
137
  { table: "staged_datasets", column: "owner_email" },
138
138
  { table: "resources", column: "owner", mode: "owner" },
139
+ // A request under an old address can't be approved for the new one, and a
140
+ // removed member's requests have no one to grant.
141
+ {
142
+ table: "resource_access_requests",
143
+ column: "requester_email",
144
+ emailChange: "delete",
145
+ offboard: "delete",
146
+ },
147
+ { table: "resource_access_requests", column: "owner_email" },
139
148
  { table: "agent_review_comments", column: "author_email" },
140
149
  { table: "agent_review_comments", column: "owner_email" },
141
150
  { table: "agent_review_notification_deliveries", column: "recipient_email" },
@@ -15,6 +15,10 @@ export interface AutomationRun {
15
15
  finishedAt: number | null;
16
16
  error: string | null;
17
17
  errorCode: string | null;
18
+ /** A queued "Run now" row that a worker claims before it starts. */
19
+ dispatchPending: boolean;
20
+ /** When a worker last claimed this queued dispatch, if ever. */
21
+ claimedAt: number | null;
18
22
  }
19
23
  export interface StartAutomationRunInput {
20
24
  owner: string;
@@ -28,6 +32,14 @@ export interface StartAutomationRunInput {
28
32
  threadId?: string | null;
29
33
  dispatchPending?: boolean;
30
34
  }
35
+ /**
36
+ * How long a queued dispatch's claim stays valid before another worker may take
37
+ * it over (`claimAutomationRun`). The stale-lock recovery must leave a row
38
+ * inside this lease to its own worker.
39
+ */
40
+ export declare function automationRunClaimLeaseMs(): number;
41
+ /** How many runs each automation retains; pruning bounds unfinished rows to this. */
42
+ export declare const RUNS_RETAINED_PER_AUTOMATION = 50;
31
43
  export declare const AUTOMATION_RUN_MIGRATIONS: MigrationEntry[];
32
44
  export declare function runAutomationRunMigrations(nitroApp: unknown): Promise<void>;
33
45
  export declare function ensureTable(): Promise<void>;
@@ -50,15 +62,22 @@ export declare function processPendingAutomationFailureAlerts(options?: {
50
62
  uncertain: number;
51
63
  failed: number;
52
64
  }>;
53
- export declare function finishAutomationRun(id: string, status: Exclude<AutomationRunStatus, "running">, error?: string, errorCode?: string,
54
- /**
55
- * `notify: false` records the run without queueing the owner email. Used for
56
- * the early failures of a streak that will pause: the owner is told once,
57
- * by the run that pauses it.
58
- */
59
- options?: {
65
+ export interface FinishAutomationRunOptions {
66
+ /**
67
+ * `notify: false` records the run without queueing the owner email. Used for
68
+ * the early failures of a streak that will pause: the owner is told once,
69
+ * by the run that pauses it.
70
+ */
60
71
  notify?: boolean;
61
- }): Promise<void>;
72
+ /**
73
+ * Finish only if the stored claim still equals this snapshot. The stale-lock
74
+ * recovery passes the `claimedAt` it read, so a worker that claims the run
75
+ * between that read and this write keeps its run instead of losing it to a
76
+ * stale terminal write.
77
+ */
78
+ expectedClaimedAt?: number | null;
79
+ }
80
+ export declare function finishAutomationRun(id: string, status: Exclude<AutomationRunStatus, "running">, error?: string, errorCode?: string, options?: FinishAutomationRunOptions): Promise<void>;
62
81
  export declare function attachAutomationRunThread(id: string, threadId: string, runId: string): Promise<void>;
63
82
  export declare function deleteAutomationRuns(owner: string, automation: string): Promise<void>;
64
83
  export declare function listAutomationRuns(options: {