@agent-native/core 0.157.2 → 0.157.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +1 -1
- package/corpus/templates/analytics/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/corpus/templates/analytics/.agents/skills/sharing/SKILL.md +21 -0
- package/corpus/templates/analytics/server/lib/first-party-analytics-backend.ts +40 -1
- package/corpus/templates/assets/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/corpus/templates/assets/.agents/skills/sharing/SKILL.md +21 -0
- package/corpus/templates/brain/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/corpus/templates/brain/.agents/skills/sharing/SKILL.md +21 -0
- package/corpus/templates/calendar/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/corpus/templates/calendar/.agents/skills/sharing/SKILL.md +21 -0
- package/corpus/templates/calendar/app/components/layout/AppLayout.tsx +3 -1
- package/corpus/templates/chat/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/corpus/templates/chat/.agents/skills/sharing/SKILL.md +21 -0
- package/corpus/templates/clips/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/corpus/templates/clips/.agents/skills/meetings/SKILL.md +7 -7
- package/corpus/templates/clips/actions/approve-recording-access-request.ts +147 -0
- package/corpus/templates/clips/actions/request-recording-access.ts +121 -23
- package/corpus/templates/clips/actions/view-screen.ts +1 -1
- package/corpus/templates/clips/app/components/meetings/share-meeting-dialog.tsx +34 -53
- package/corpus/templates/clips/app/components/player/request-access-dialog.tsx +104 -0
- package/corpus/templates/clips/app/components/player/share-dialog.tsx +36 -45
- package/corpus/templates/clips/app/components/sharing/share-ui.tsx +3 -6
- package/corpus/templates/clips/app/i18n/en-US.ts +26 -1
- package/corpus/templates/clips/app/lib/public-meeting.ts +1 -1
- package/corpus/templates/clips/app/routes/access-request.approve.tsx +250 -0
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +9 -32
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +123 -37
- package/corpus/templates/clips/app/routes/share.meeting.$meetingId.tsx +41 -1
- package/corpus/templates/clips/server/lib/calendar-event-meetings.ts +1 -0
- package/corpus/templates/clips/server/lib/emails.ts +40 -11
- package/corpus/templates/clips/shared/recording-link.ts +32 -1
- package/corpus/templates/clips/shared/share-attribution.ts +13 -0
- package/corpus/templates/content/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/corpus/templates/content/.agents/skills/sharing/SKILL.md +21 -0
- package/corpus/templates/content/actions/add-database-item.ts +9 -0
- package/corpus/templates/content/actions/describe-content-database.ts +92 -0
- package/corpus/templates/content/actions/list-content-databases.ts +213 -72
- package/corpus/templates/content/actions/set-document-property.ts +9 -0
- package/corpus/templates/content/actions/submit-content-database-form.ts +9 -0
- package/corpus/templates/content/actions/update-document.ts +9 -0
- package/corpus/templates/content/app/components/editor/DocumentBlockFields.tsx +22 -10
- package/corpus/templates/content/app/components/editor/SlashCommandMenu.tsx +2 -1
- package/corpus/templates/content/app/components/editor/VisualEditor.tsx +245 -9
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +192 -134
- package/corpus/templates/content/app/components/editor/database/settings.tsx +7 -0
- package/corpus/templates/content/app/components/editor/extensions/ImageBlock.tsx +61 -45
- package/corpus/templates/content/app/components/editor/extensions/ImageNode.ts +6 -0
- package/corpus/templates/content/app/components/editor/image-upload.ts +137 -5
- package/corpus/templates/content/app/global.css +8 -0
- package/corpus/templates/content/app/hooks/use-content-database.ts +91 -11
- package/corpus/templates/content/changelog/2026-08-12-image-uploads-now-confirm-the-image-can-be-displayed-before-.md +6 -0
- package/corpus/templates/content/changelog/2026-08-14-images-fill-the-page-width-by-default-and-remain-visible-whe.md +6 -0
- package/corpus/templates/content/changelog/2026-08-14-images-inserted-from-the-slash-command-stay-saved-after-uplo.md +6 -0
- package/corpus/templates/content/changelog/2026-08-14-svg-files-can-be-selected-reliably-from-the-image-picker.md +6 -0
- package/corpus/templates/content/parity/matrix.md +1 -1
- package/corpus/templates/content/parity/matrix.ts +7 -1
- package/corpus/templates/content/server/plugins/agent-chat.ts +3 -0
- package/corpus/templates/content/shared/api.ts +9 -0
- package/corpus/templates/crm/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/corpus/templates/crm/.agents/skills/sharing/SKILL.md +21 -0
- package/corpus/templates/design/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/corpus/templates/design/.agents/skills/sharing/SKILL.md +21 -0
- package/corpus/templates/dispatch/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/corpus/templates/dispatch/.agents/skills/sharing/SKILL.md +21 -0
- package/corpus/templates/factory/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/corpus/templates/factory/.agents/skills/sharing/SKILL.md +21 -0
- package/corpus/templates/forms/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/corpus/templates/forms/.agents/skills/sharing/SKILL.md +21 -0
- package/corpus/templates/macros/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/corpus/templates/macros/.agents/skills/sharing/SKILL.md +21 -0
- package/corpus/templates/mail/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/corpus/templates/mail/.agents/skills/sharing/SKILL.md +21 -0
- package/corpus/templates/mail/app/components/layout/AppLayout.tsx +6 -1
- package/corpus/templates/plan/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/corpus/templates/plan/.agents/skills/sharing/SKILL.md +21 -0
- package/corpus/templates/plan/app/pages/PlansPage.tsx +25 -30
- package/corpus/templates/slides/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/corpus/templates/slides/.agents/skills/sharing/SKILL.md +21 -0
- package/corpus/templates/slides/app/components/deck/SlideRenderer.tsx +2 -1
- package/corpus/templates/slides/app/components/editor/ShareDialog.tsx +124 -180
- package/corpus/templates/slides/app/components/onboarding/FirstDeckOnboardingFlow.tsx +1 -0
- package/corpus/templates/slides/app/global.css +99 -3
- package/corpus/templates/slides/app/i18n/en-US.ts +1 -0
- package/corpus/templates/tasks/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/corpus/templates/tasks/.agents/skills/sharing/SKILL.md +21 -0
- package/dist/chat-threads/schema.d.ts +1 -1
- package/dist/cli/code-agent-executor.js +318 -0
- package/dist/client/AgentPanel.d.ts +13 -1
- package/dist/client/AgentPanel.js +41 -3
- package/dist/client/AssistantChat.d.ts +9 -1
- package/dist/client/AssistantChat.js +26 -13
- package/dist/client/MultiTabAssistantChat.js +18 -5
- package/dist/client/agent-chat.d.ts +8 -0
- package/dist/client/agent-chat.js +15 -0
- package/dist/client/app-chat-sidebar.d.ts +23 -0
- package/dist/client/app-chat-sidebar.js +53 -0
- package/dist/client/app-providers.js +31 -2
- package/dist/client/frame-protocol.d.ts +20 -2
- package/dist/client/hooks/index.d.ts +1 -0
- package/dist/client/hooks/index.js +1 -0
- package/dist/client/sharing/AgentShareSection.d.ts +8 -0
- package/dist/client/sharing/AgentShareSection.js +50 -0
- package/dist/client/sharing/ShareButton.js +2 -1
- package/dist/client/sharing/ShareDialog.d.ts +2 -2
- package/dist/client/sharing/ShareDialog.js +21 -29
- package/dist/client/sharing/index.d.ts +1 -0
- package/dist/client/sharing/index.js +1 -0
- package/dist/client/sharing/useShareButtonController.d.ts +1 -0
- package/dist/client/sharing/useShareDialogController.d.ts +3 -0
- package/dist/client/sharing/useShareDialogController.js +2 -1
- package/dist/client/theme.d.ts +21 -0
- package/dist/client/theme.js +60 -0
- package/dist/client/ui/index.d.ts +1 -1
- package/dist/client/ui/index.js +1 -1
- package/dist/client/use-session.js +23 -0
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/routes.d.ts +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/dashboard-storage/schema.d.ts +1 -1
- package/dist/data-programs/schema.d.ts +1 -1
- package/dist/extensions/schema.d.ts +1 -1
- package/dist/localization/core-messages/ar-SA.js +6 -0
- package/dist/localization/core-messages/de-DE.js +6 -0
- package/dist/localization/core-messages/en-US.d.ts +6 -0
- package/dist/localization/core-messages/en-US.js +6 -0
- package/dist/localization/core-messages/es-ES.js +6 -0
- package/dist/localization/core-messages/fr-FR.js +6 -0
- package/dist/localization/core-messages/hi-IN.js +6 -0
- package/dist/localization/core-messages/ja-JP.js +6 -0
- package/dist/localization/core-messages/ko-KR.js +6 -0
- package/dist/localization/core-messages/pt-BR.js +6 -0
- package/dist/localization/core-messages/zh-CN.js +6 -0
- package/dist/localization/core-messages/zh-TW.js +6 -0
- package/dist/localization/core-messages.d.ts +6 -0
- package/dist/localization/default-messages.d.ts +1 -0
- package/dist/localization/default-messages.js +1 -0
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +5 -5
- package/dist/provider-api/actions/custom-provider-registration.d.ts +14 -14
- package/dist/provider-api/actions/provider-api.d.ts +14 -14
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/auth.js +8 -5
- package/dist/server/identity-sso-store.d.ts +6 -1
- package/dist/server/identity-sso-store.js +57 -14
- package/dist/server/identity-sso.d.ts +7 -8
- package/dist/server/identity-sso.js +15 -12
- package/dist/sharing/actions/list-resource-shares.d.ts +2 -0
- package/dist/sharing/actions/list-resource-shares.js +1 -0
- package/dist/sharing/schema.d.ts +1 -1
- package/dist/sharing/schema.js +4 -1
- package/dist/templates/chat/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/dist/templates/chat/.agents/skills/sharing/SKILL.md +21 -0
- package/dist/templates/default/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/dist/templates/default/.agents/skills/sharing/SKILL.md +21 -0
- package/dist/templates/headless/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/dist/templates/workspace-core/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/dist/templates/workspace-core/.agents/skills/sharing/SKILL.md +21 -0
- package/docs/content/cross-app-sso.mdx +24 -17
- package/package.json +3 -3
- package/src/templates/chat/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/src/templates/chat/.agents/skills/sharing/SKILL.md +21 -0
- package/src/templates/default/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/src/templates/default/.agents/skills/sharing/SKILL.md +21 -0
- package/src/templates/headless/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/src/templates/workspace-core/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/src/templates/workspace-core/.agents/skills/sharing/SKILL.md +21 -0
|
@@ -52,12 +52,39 @@ const CLIPS_SENDER =
|
|
|
52
52
|
'From is the configured EMAIL_FROM with the display name "Agent-Native Clips"; on first-party agent-native.com deployments it becomes clips@agent-native.com. Reply-to is hello@agent-native.com.';
|
|
53
53
|
|
|
54
54
|
let registered = false;
|
|
55
|
+
const GLOBAL_REGISTRATION_KEY =
|
|
56
|
+
"__agentNativeClipsTransactionalEmailCatalogRegistered";
|
|
57
|
+
const globalRegistrationState = globalThis as typeof globalThis &
|
|
58
|
+
Record<string, boolean | undefined>;
|
|
59
|
+
|
|
60
|
+
function defineClipsTransactionalEmail(
|
|
61
|
+
definition: Parameters<typeof defineTransactionalEmail>[0],
|
|
62
|
+
): boolean {
|
|
63
|
+
try {
|
|
64
|
+
defineTransactionalEmail(definition);
|
|
65
|
+
return true;
|
|
66
|
+
} catch (error) {
|
|
67
|
+
if (
|
|
68
|
+
error instanceof Error &&
|
|
69
|
+
error.message.startsWith('Duplicate transactional email id "clips.')
|
|
70
|
+
) {
|
|
71
|
+
// The same Clips catalog can be evaluated twice by Nitro/Vite. The
|
|
72
|
+
// first definition is canonical, so a duplicate is already registered.
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
throw error;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
55
78
|
|
|
56
79
|
export function registerClipsEmails(): void {
|
|
57
|
-
|
|
80
|
+
// Nitro/Vite can evaluate the same server module through two module
|
|
81
|
+
// instances during dev reloads. Keep the catalog registration process-wide
|
|
82
|
+
// so those instances cannot submit the same id twice.
|
|
83
|
+
if (registered || globalRegistrationState[GLOBAL_REGISTRATION_KEY]) return;
|
|
58
84
|
registered = true;
|
|
85
|
+
globalRegistrationState[GLOBAL_REGISTRATION_KEY] = true;
|
|
59
86
|
|
|
60
|
-
|
|
87
|
+
defineClipsTransactionalEmail({
|
|
61
88
|
id: CLIPS_ACCESS_REQUEST_EMAIL_ID,
|
|
62
89
|
name: "Clip access request",
|
|
63
90
|
trigger:
|
|
@@ -73,10 +100,12 @@ export function registerClipsEmails(): void {
|
|
|
73
100
|
requesterEmail: "sam.rivera@example.com",
|
|
74
101
|
recordingTitle: SAMPLE_TITLE,
|
|
75
102
|
url: "https://example.com/share/rec_sample",
|
|
103
|
+
allowAccessUrl:
|
|
104
|
+
"https://example.com/access-request/approve?recordingId=rec_sample&token=preview-token",
|
|
76
105
|
}),
|
|
77
106
|
});
|
|
78
107
|
|
|
79
|
-
|
|
108
|
+
defineClipsTransactionalEmail({
|
|
80
109
|
id: CLIPS_FIRST_VIEW_EMAIL_ID,
|
|
81
110
|
name: "First view on a Clip",
|
|
82
111
|
trigger:
|
|
@@ -96,7 +125,7 @@ export function registerClipsEmails(): void {
|
|
|
96
125
|
}),
|
|
97
126
|
});
|
|
98
127
|
|
|
99
|
-
|
|
128
|
+
defineClipsTransactionalEmail({
|
|
100
129
|
id: CLIPS_UNVIEWED_REMINDER_EMAIL_ID,
|
|
101
130
|
name: "Unviewed Clip reminder",
|
|
102
131
|
trigger:
|
|
@@ -118,7 +147,7 @@ export function registerClipsEmails(): void {
|
|
|
118
147
|
}),
|
|
119
148
|
});
|
|
120
149
|
|
|
121
|
-
|
|
150
|
+
defineClipsTransactionalEmail({
|
|
122
151
|
id: CLIPS_FIRST_AGENT_VIEW_EMAIL_ID,
|
|
123
152
|
name: "First agent read of a Clip",
|
|
124
153
|
trigger:
|
|
@@ -138,7 +167,7 @@ export function registerClipsEmails(): void {
|
|
|
138
167
|
}),
|
|
139
168
|
});
|
|
140
169
|
|
|
141
|
-
|
|
170
|
+
defineClipsTransactionalEmail({
|
|
142
171
|
id: CLIPS_FIRST_IMPORT_EMAIL_ID,
|
|
143
172
|
name: "First imported video is ready",
|
|
144
173
|
trigger:
|
|
@@ -157,7 +186,7 @@ export function registerClipsEmails(): void {
|
|
|
157
186
|
}),
|
|
158
187
|
});
|
|
159
188
|
|
|
160
|
-
|
|
189
|
+
defineClipsTransactionalEmail({
|
|
161
190
|
id: CLIPS_MONTHLY_RECAP_EMAIL_ID,
|
|
162
191
|
name: "Monthly Clips recap",
|
|
163
192
|
trigger:
|
|
@@ -191,7 +220,7 @@ export function registerClipsEmails(): void {
|
|
|
191
220
|
}),
|
|
192
221
|
});
|
|
193
222
|
|
|
194
|
-
|
|
223
|
+
defineClipsTransactionalEmail({
|
|
195
224
|
id: CLIPS_TWO_CLIPS_EMAIL_ID,
|
|
196
225
|
name: "Two Clips received",
|
|
197
226
|
trigger:
|
|
@@ -210,7 +239,7 @@ export function registerClipsEmails(): void {
|
|
|
210
239
|
}),
|
|
211
240
|
});
|
|
212
241
|
|
|
213
|
-
|
|
242
|
+
defineClipsTransactionalEmail({
|
|
214
243
|
id: CLIPS_ACTIVITY_COMMENT_EMAIL_ID,
|
|
215
244
|
name: "Clip comment",
|
|
216
245
|
trigger:
|
|
@@ -234,7 +263,7 @@ export function registerClipsEmails(): void {
|
|
|
234
263
|
}),
|
|
235
264
|
});
|
|
236
265
|
|
|
237
|
-
|
|
266
|
+
defineClipsTransactionalEmail({
|
|
238
267
|
id: CLIPS_ACTIVITY_REACTION_EMAIL_ID,
|
|
239
268
|
name: "Clip reaction",
|
|
240
269
|
trigger: "A viewer reacts with an emoji on a Clip.",
|
|
@@ -256,7 +285,7 @@ export function registerClipsEmails(): void {
|
|
|
256
285
|
}),
|
|
257
286
|
});
|
|
258
287
|
|
|
259
|
-
|
|
288
|
+
defineClipsTransactionalEmail({
|
|
260
289
|
id: CLIPS_ORGANIZATION_INVITE_EMAIL_ID,
|
|
261
290
|
name: "Organization invitation",
|
|
262
291
|
trigger:
|
|
@@ -12,15 +12,46 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import { withShareAttribution } from "./share-attribution";
|
|
14
14
|
|
|
15
|
-
/** Short-lived capability used by a
|
|
15
|
+
/** Short-lived capability used by a private share viewer to request access. */
|
|
16
16
|
export const CLIPS_ACCESS_REQUEST_TOKEN_PREFIX = "clips-access-request";
|
|
17
17
|
export const CLIPS_ACCESS_REQUEST_TOKEN_TTL_SECONDS = 10 * 60;
|
|
18
18
|
|
|
19
|
+
/** Signed capability embedded in the owner's access-request email. */
|
|
20
|
+
export const CLIPS_ACCESS_APPROVAL_TOKEN_PREFIX = "clips-access-approval";
|
|
21
|
+
export const CLIPS_ACCESS_APPROVAL_TOKEN_TTL_SECONDS = 7 * 24 * 60 * 60;
|
|
22
|
+
export const CLIPS_ACCESS_APPROVAL_SESSION_KEY_PREFIX =
|
|
23
|
+
"clips-access-approval-token:";
|
|
24
|
+
|
|
19
25
|
/** Public share path for a recording, relative to the app base path. */
|
|
20
26
|
export function recordingSharePath(recordingId: string): string {
|
|
21
27
|
return `/share/${encodeURIComponent(recordingId)}`;
|
|
22
28
|
}
|
|
23
29
|
|
|
30
|
+
/** Owner-only route used by the one-click approval button in access emails. */
|
|
31
|
+
export function recordingAccessApprovalPath(
|
|
32
|
+
recordingId: string,
|
|
33
|
+
approvalToken: string,
|
|
34
|
+
): string {
|
|
35
|
+
const params = new URLSearchParams({
|
|
36
|
+
recordingId,
|
|
37
|
+
token: approvalToken,
|
|
38
|
+
});
|
|
39
|
+
return `/access-request/approve?${params.toString()}`;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Token-free continuation used after the approver signs in. */
|
|
43
|
+
export function recordingAccessApprovalContinuationPath(
|
|
44
|
+
recordingId: string,
|
|
45
|
+
): string {
|
|
46
|
+
const params = new URLSearchParams({ recordingId });
|
|
47
|
+
return `/access-request/approve?${params.toString()}`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Tab-scoped key for carrying an approval token across the sign-in redirect. */
|
|
51
|
+
export function recordingAccessApprovalSessionKey(recordingId: string): string {
|
|
52
|
+
return `${CLIPS_ACCESS_APPROVAL_SESSION_KEY_PREFIX}${encodeURIComponent(recordingId)}`;
|
|
53
|
+
}
|
|
54
|
+
|
|
24
55
|
export interface RecordingShareUrlParams {
|
|
25
56
|
recordingId: string;
|
|
26
57
|
/** Absolute origin, e.g. `https://clips.example.com` or a desktop serverUrl. */
|
|
@@ -51,6 +51,19 @@ export type ShareAttribution = {
|
|
|
51
51
|
via: string | undefined;
|
|
52
52
|
};
|
|
53
53
|
|
|
54
|
+
/**
|
|
55
|
+
* Keep only non-sensitive attribution state when a share page redirects to
|
|
56
|
+
* sign-in. Passwords and capability tokens must never enter auth URLs.
|
|
57
|
+
*/
|
|
58
|
+
export function buildShareContinuationQuery(
|
|
59
|
+
attribution: ShareAttribution,
|
|
60
|
+
): string {
|
|
61
|
+
const params = new URLSearchParams();
|
|
62
|
+
if (attribution.ref) params.set(REF_PARAM, attribution.ref);
|
|
63
|
+
if (attribution.via) params.set(VIA_PARAM, attribution.via);
|
|
64
|
+
return params.toString();
|
|
65
|
+
}
|
|
66
|
+
|
|
54
67
|
/**
|
|
55
68
|
* Read `ref`/`via` from a query string (e.g. `window.location.search`). Falls
|
|
56
69
|
* back to `ref=clip_share` so downstream attribution stays meaningful even when
|
|
@@ -182,6 +182,11 @@ in the moment of agent use.
|
|
|
182
182
|
five-item sidebar preview and a footer row with New chat followed by an
|
|
183
183
|
ellipsis disclosure up to fifteen. Apps inject routing, labels, and domain
|
|
184
184
|
actions.
|
|
185
|
+
- **Data grid kit**: provider-agnostic spreadsheet mechanics belong in
|
|
186
|
+
`@agent-native/toolkit/data-grid`. Apps provide rows, typed columns, editor
|
|
187
|
+
slots, selection and width state, persistence callbacks, and product-level
|
|
188
|
+
row/body slots. Keep database models, access checks, grouping, drag/drop,
|
|
189
|
+
and domain actions in the app adapter.
|
|
185
190
|
- **Agent page kit**: the full-page `/agent` surface (`AgentTabsPage` from
|
|
186
191
|
`@agent-native/core/client`) with Context, Files, Connections, Jobs, and
|
|
187
192
|
Access tabs plus a Personal/Organization scope toggle. The canonical home
|
|
@@ -184,6 +184,27 @@ import { ShareButton } from "@agent-native/core/client/sharing";
|
|
|
184
184
|
|
|
185
185
|
For list views, show `<VisibilityBadge visibility={row.visibility} />` next to each resource.
|
|
186
186
|
|
|
187
|
+
## Standard share surface
|
|
188
|
+
|
|
189
|
+
All app share popovers should use the same compact surface contract:
|
|
190
|
+
|
|
191
|
+
- Use the text-only `ShareTrigger` from `@agent-native/toolkit/sharing`.
|
|
192
|
+
- Render ordinary links with `ShareCopyRow`, which exposes a Copy action without
|
|
193
|
+
printing the raw URL.
|
|
194
|
+
- Keep general access and individual people access in the standard Core sharing
|
|
195
|
+
flow. The people flow supports email invites, roles, notifications, and
|
|
196
|
+
removal through the shared share actions.
|
|
197
|
+
- Add `ShareAgentsSection` only when the resource has a real agent-readable
|
|
198
|
+
link or prompt. Keep it collapsed by default and supply domain-specific
|
|
199
|
+
content through the shared section shell.
|
|
200
|
+
- App-specific tabs or controls may remain when they represent a real domain
|
|
201
|
+
action (for example, an embed-code editor), but they should retain the same
|
|
202
|
+
trigger, copy-row, access, and spacing language.
|
|
203
|
+
|
|
204
|
+
`ShareDisclosureSection` is the toolkit-owned shell for optional expandable
|
|
205
|
+
share details; use its `ShareAgentsSection` or `SharePeopleSection` wrappers
|
|
206
|
+
instead of creating another collapsible share panel in a template.
|
|
207
|
+
|
|
187
208
|
## Actions available everywhere
|
|
188
209
|
|
|
189
210
|
The framework auto-mounts these actions in every template — no per-template boilerplate:
|
|
@@ -26,6 +26,15 @@ const schema = databaseMutationEnvelopeSchema.extend({
|
|
|
26
26
|
export default defineAction({
|
|
27
27
|
description:
|
|
28
28
|
"Create one row in an exact ordinary Content database using its discovered schema revision. Strictly validates every non-Blocks property, applies the side effect once per idempotency key, and returns a verified receipt with stable row identity.",
|
|
29
|
+
publicAgent: {
|
|
30
|
+
expose: true,
|
|
31
|
+
readOnly: false,
|
|
32
|
+
requiresAuth: true,
|
|
33
|
+
isConsequential: true,
|
|
34
|
+
title: "Add Content Database Item",
|
|
35
|
+
description:
|
|
36
|
+
"Delegate creation of one page item in an existing Content database.",
|
|
37
|
+
},
|
|
29
38
|
schema,
|
|
30
39
|
audit: {
|
|
31
40
|
recordInputs: false,
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { defineAction } from "@agent-native/core";
|
|
2
|
+
import { accessFilter } from "@agent-native/core/sharing";
|
|
3
|
+
import { and, eq } from "drizzle-orm";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
|
|
6
|
+
import { getDb, schema } from "../server/db/index.js";
|
|
7
|
+
import { getDocumentContextPath } from "../server/lib/document-context.js";
|
|
8
|
+
import type {
|
|
9
|
+
ContentDatabaseDescriptionResponse,
|
|
10
|
+
ContentDatabaseUnavailableResponse,
|
|
11
|
+
} from "../shared/api.js";
|
|
12
|
+
import { resolveContentDatabaseRead } from "./_database-utils.js";
|
|
13
|
+
import {
|
|
14
|
+
listPropertiesForDatabase,
|
|
15
|
+
serializeDatabase,
|
|
16
|
+
} from "./_property-utils.js";
|
|
17
|
+
import listContentDatabases, {
|
|
18
|
+
ContentDatabaseResolutionError,
|
|
19
|
+
} from "./list-content-databases.js";
|
|
20
|
+
|
|
21
|
+
export default defineAction({
|
|
22
|
+
description:
|
|
23
|
+
"Describe one exact ordinary Content database, including its live metadata, views, and property schema but not its rows. Resolve the stable database or document ID with list-content-databases first.",
|
|
24
|
+
schema: z
|
|
25
|
+
.object({
|
|
26
|
+
databaseId: z.string().min(1).optional().describe("Exact database ID"),
|
|
27
|
+
documentId: z
|
|
28
|
+
.string()
|
|
29
|
+
.min(1)
|
|
30
|
+
.optional()
|
|
31
|
+
.describe("Exact database document/page ID"),
|
|
32
|
+
})
|
|
33
|
+
.refine(
|
|
34
|
+
(input) => Boolean(input.databaseId) !== Boolean(input.documentId),
|
|
35
|
+
"Provide exactly one of databaseId or documentId.",
|
|
36
|
+
),
|
|
37
|
+
http: { method: "GET" },
|
|
38
|
+
readOnly: true,
|
|
39
|
+
publicAgent: { expose: true, readOnly: true, requiresAuth: true },
|
|
40
|
+
run: async ({
|
|
41
|
+
databaseId,
|
|
42
|
+
documentId,
|
|
43
|
+
}): Promise<
|
|
44
|
+
ContentDatabaseDescriptionResponse | ContentDatabaseUnavailableResponse
|
|
45
|
+
> => {
|
|
46
|
+
let selection: Awaited<ReturnType<typeof listContentDatabases.run>>;
|
|
47
|
+
try {
|
|
48
|
+
selection = await listContentDatabases.run({ databaseId, documentId });
|
|
49
|
+
} catch (error) {
|
|
50
|
+
if (!(error instanceof ContentDatabaseResolutionError)) throw error;
|
|
51
|
+
throw new Error("Content database not found.");
|
|
52
|
+
}
|
|
53
|
+
const selected = selection.databases[0];
|
|
54
|
+
if (!selected) throw new Error("Content database not found.");
|
|
55
|
+
|
|
56
|
+
const resolved = await resolveContentDatabaseRead({
|
|
57
|
+
databaseId: selected.databaseId,
|
|
58
|
+
});
|
|
59
|
+
if (!resolved.available) return resolved;
|
|
60
|
+
if (resolved.database.systemRole) {
|
|
61
|
+
throw new Error("Content database not found.");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const db = getDb();
|
|
65
|
+
const [databaseDocument] = await db
|
|
66
|
+
.select({
|
|
67
|
+
id: schema.documents.id,
|
|
68
|
+
parentId: schema.documents.parentId,
|
|
69
|
+
})
|
|
70
|
+
.from(schema.documents)
|
|
71
|
+
.where(
|
|
72
|
+
and(
|
|
73
|
+
eq(schema.documents.id, selected.documentId),
|
|
74
|
+
accessFilter(schema.documents, schema.documentShares),
|
|
75
|
+
),
|
|
76
|
+
);
|
|
77
|
+
if (!databaseDocument) throw new Error("Content database not found.");
|
|
78
|
+
|
|
79
|
+
const [properties, contextPath] = await Promise.all([
|
|
80
|
+
listPropertiesForDatabase(resolved.database.id),
|
|
81
|
+
getDocumentContextPath(databaseDocument),
|
|
82
|
+
]);
|
|
83
|
+
return {
|
|
84
|
+
database: serializeDatabase(
|
|
85
|
+
{ ...resolved.database, title: selected.title },
|
|
86
|
+
selected.description,
|
|
87
|
+
),
|
|
88
|
+
contextPath,
|
|
89
|
+
properties,
|
|
90
|
+
};
|
|
91
|
+
},
|
|
92
|
+
});
|
|
@@ -1,20 +1,55 @@
|
|
|
1
1
|
import { defineAction } from "@agent-native/core";
|
|
2
2
|
import { accessFilter } from "@agent-native/core/sharing";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
and,
|
|
5
|
+
asc,
|
|
6
|
+
eq,
|
|
7
|
+
inArray,
|
|
8
|
+
isNull,
|
|
9
|
+
notInArray,
|
|
10
|
+
or,
|
|
11
|
+
sql,
|
|
12
|
+
} from "drizzle-orm";
|
|
4
13
|
import { z } from "zod";
|
|
5
14
|
|
|
6
15
|
import { getDb, schema } from "../server/db/index.js";
|
|
7
16
|
import { documentDiscoveryFilter } from "../server/lib/documents.js";
|
|
8
17
|
import type { ListContentDatabasesResponse } from "../shared/api.js";
|
|
18
|
+
import { documentDiscoveryPagination } from "./_document-discovery-query.js";
|
|
19
|
+
|
|
20
|
+
const DEFAULT_CONTENT_DATABASE_DISCOVERY_LIMIT = 50;
|
|
9
21
|
|
|
10
22
|
function escapeLike(s: string): string {
|
|
11
23
|
return s.replace(/([\\%_])/g, "\\$1");
|
|
12
24
|
}
|
|
13
25
|
|
|
26
|
+
export class ContentDatabaseResolutionError extends Error {}
|
|
27
|
+
|
|
14
28
|
export default defineAction({
|
|
15
29
|
description:
|
|
16
|
-
"
|
|
30
|
+
"Discover one bounded page of ordinary Content databases the user can access from their live title and user-authored description. Returns stable database, document, and space IDs with explicit pagination; follow nextOffset until hasMore is false. Use exact filters before reading a selected database's schema.",
|
|
17
31
|
schema: z.object({
|
|
32
|
+
spaceId: z
|
|
33
|
+
.string()
|
|
34
|
+
.min(1)
|
|
35
|
+
.optional()
|
|
36
|
+
.describe("Exact Content space ID to search within."),
|
|
37
|
+
databaseId: z
|
|
38
|
+
.string()
|
|
39
|
+
.min(1)
|
|
40
|
+
.optional()
|
|
41
|
+
.describe("Exact Content database ID to resolve."),
|
|
42
|
+
documentId: z
|
|
43
|
+
.string()
|
|
44
|
+
.min(1)
|
|
45
|
+
.optional()
|
|
46
|
+
.describe("Exact Content database document/page ID to resolve."),
|
|
47
|
+
title: z
|
|
48
|
+
.string()
|
|
49
|
+
.trim()
|
|
50
|
+
.min(1)
|
|
51
|
+
.optional()
|
|
52
|
+
.describe("Exact live database title to resolve, case-insensitively."),
|
|
18
53
|
excludeDatabaseId: z
|
|
19
54
|
.string()
|
|
20
55
|
.optional()
|
|
@@ -23,63 +58,43 @@ export default defineAction({
|
|
|
23
58
|
.array(z.string())
|
|
24
59
|
.optional()
|
|
25
60
|
.describe("Database ids to omit from the results."),
|
|
26
|
-
query: z
|
|
61
|
+
query: z
|
|
62
|
+
.string()
|
|
63
|
+
.optional()
|
|
64
|
+
.describe("Optional title or user-authored description search text."),
|
|
27
65
|
limit: z.coerce
|
|
28
66
|
.number()
|
|
29
67
|
.int()
|
|
30
68
|
.min(1)
|
|
31
69
|
.max(50)
|
|
32
|
-
.
|
|
33
|
-
.describe("Maximum number of databases to return."),
|
|
70
|
+
.default(DEFAULT_CONTENT_DATABASE_DISCOVERY_LIMIT)
|
|
71
|
+
.describe("Maximum number of databases to return. Defaults to 50."),
|
|
72
|
+
offset: z.coerce
|
|
73
|
+
.number()
|
|
74
|
+
.int()
|
|
75
|
+
.min(0)
|
|
76
|
+
.default(0)
|
|
77
|
+
.describe("Zero-based continuation offset."),
|
|
34
78
|
}),
|
|
35
79
|
http: { method: "GET" },
|
|
36
80
|
readOnly: true,
|
|
81
|
+
publicAgent: { expose: true, readOnly: true, requiresAuth: true },
|
|
37
82
|
run: async (args): Promise<ListContentDatabasesResponse> => {
|
|
38
83
|
const db = getDb();
|
|
39
84
|
const query = args.query?.trim();
|
|
40
85
|
const pattern = query ? `%${escapeLike(query.toLowerCase())}%` : null;
|
|
86
|
+
const exactTitle = args.title?.toLowerCase();
|
|
87
|
+
const resolvesExactly = !!(
|
|
88
|
+
args.databaseId ||
|
|
89
|
+
args.documentId ||
|
|
90
|
+
exactTitle
|
|
91
|
+
);
|
|
41
92
|
const excludedDatabaseIds = new Set(
|
|
42
93
|
[
|
|
43
94
|
args.excludeDatabaseId?.trim(),
|
|
44
95
|
...(args.excludeDatabaseIds ?? []).map((id) => id.trim()),
|
|
45
96
|
].filter((id): id is string => !!id),
|
|
46
97
|
);
|
|
47
|
-
// The same access + discovery filter the sidebar uses, so the picker shows
|
|
48
|
-
// owned AND shared/org databases and never a trashed/hidden one.
|
|
49
|
-
const queryBuilder = db
|
|
50
|
-
.select({
|
|
51
|
-
id: schema.contentDatabases.id,
|
|
52
|
-
documentId: schema.contentDatabases.documentId,
|
|
53
|
-
title: schema.documents.title,
|
|
54
|
-
})
|
|
55
|
-
.from(schema.contentDatabases)
|
|
56
|
-
.innerJoin(
|
|
57
|
-
schema.documents,
|
|
58
|
-
eq(schema.contentDatabases.documentId, schema.documents.id),
|
|
59
|
-
)
|
|
60
|
-
.where(
|
|
61
|
-
and(
|
|
62
|
-
accessFilter(schema.documents, schema.documentShares),
|
|
63
|
-
isNull(schema.documents.trashedAt),
|
|
64
|
-
documentDiscoveryFilter(),
|
|
65
|
-
isNull(schema.contentDatabases.deletedAt),
|
|
66
|
-
excludedDatabaseIds.size === 1
|
|
67
|
-
? ne(
|
|
68
|
-
schema.contentDatabases.id,
|
|
69
|
-
Array.from(excludedDatabaseIds)[0]!,
|
|
70
|
-
)
|
|
71
|
-
: undefined,
|
|
72
|
-
pattern
|
|
73
|
-
? sql`lower(${schema.documents.title}) LIKE ${pattern} ESCAPE '\\'`
|
|
74
|
-
: undefined,
|
|
75
|
-
),
|
|
76
|
-
)
|
|
77
|
-
.orderBy(asc(schema.documents.position));
|
|
78
|
-
|
|
79
|
-
const rows = args.limit
|
|
80
|
-
? await queryBuilder.limit(args.limit)
|
|
81
|
-
: await queryBuilder;
|
|
82
|
-
|
|
83
98
|
const localTableSources =
|
|
84
99
|
excludedDatabaseIds.size > 0
|
|
85
100
|
? await db
|
|
@@ -90,39 +105,165 @@ export default defineAction({
|
|
|
90
105
|
.from(schema.contentDatabaseSources)
|
|
91
106
|
.where(eq(schema.contentDatabaseSources.sourceType, "local-table"))
|
|
92
107
|
: [];
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
108
|
+
const excludedDatabaseRows =
|
|
109
|
+
excludedDatabaseIds.size > 0
|
|
110
|
+
? await db
|
|
111
|
+
.select({ id: schema.contentDatabases.id })
|
|
112
|
+
.from(schema.contentDatabases)
|
|
113
|
+
.innerJoin(
|
|
114
|
+
schema.documents,
|
|
115
|
+
eq(schema.contentDatabases.documentId, schema.documents.id),
|
|
116
|
+
)
|
|
117
|
+
.where(
|
|
118
|
+
and(
|
|
119
|
+
accessFilter(schema.documents, schema.documentShares),
|
|
120
|
+
or(
|
|
121
|
+
inArray(
|
|
122
|
+
schema.contentDatabases.id,
|
|
123
|
+
Array.from(excludedDatabaseIds),
|
|
124
|
+
),
|
|
125
|
+
inArray(
|
|
126
|
+
schema.contentDatabases.documentId,
|
|
127
|
+
Array.from(excludedDatabaseIds),
|
|
128
|
+
),
|
|
129
|
+
),
|
|
130
|
+
),
|
|
131
|
+
)
|
|
132
|
+
: [];
|
|
133
|
+
const excludedSourceChainDatabaseIds = new Set([
|
|
134
|
+
...excludedDatabaseIds,
|
|
135
|
+
...excludedDatabaseRows.map((row) => row.id),
|
|
136
|
+
]);
|
|
137
|
+
let expandedSourceChain = true;
|
|
138
|
+
while (expandedSourceChain) {
|
|
139
|
+
expandedSourceChain = false;
|
|
140
|
+
for (const source of localTableSources) {
|
|
141
|
+
if (
|
|
142
|
+
excludedSourceChainDatabaseIds.has(source.sourceTable) &&
|
|
143
|
+
!excludedSourceChainDatabaseIds.has(source.databaseId)
|
|
144
|
+
) {
|
|
145
|
+
excludedSourceChainDatabaseIds.add(source.databaseId);
|
|
146
|
+
expandedSourceChain = true;
|
|
147
|
+
}
|
|
106
148
|
}
|
|
107
|
-
|
|
108
|
-
};
|
|
149
|
+
}
|
|
109
150
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
151
|
+
// The same access + discovery filter the sidebar uses, so the picker shows
|
|
152
|
+
// owned AND shared/org databases and never a trashed/hidden one. Resolve
|
|
153
|
+
// source-chain exclusions before limiting so every page is truthfully full.
|
|
154
|
+
const where = and(
|
|
155
|
+
accessFilter(schema.documents, schema.documentShares),
|
|
156
|
+
isNull(schema.documents.trashedAt),
|
|
157
|
+
documentDiscoveryFilter(),
|
|
158
|
+
or(
|
|
159
|
+
eq(schema.documents.hideFromSearch, 0),
|
|
160
|
+
isNull(schema.documents.hideFromSearch),
|
|
161
|
+
),
|
|
162
|
+
isNull(schema.contentDatabases.deletedAt),
|
|
163
|
+
isNull(schema.contentDatabases.systemRole),
|
|
164
|
+
args.spaceId
|
|
165
|
+
? eq(schema.contentDatabases.spaceId, args.spaceId)
|
|
166
|
+
: undefined,
|
|
167
|
+
args.databaseId
|
|
168
|
+
? eq(schema.contentDatabases.id, args.databaseId)
|
|
169
|
+
: undefined,
|
|
170
|
+
args.documentId
|
|
171
|
+
? eq(schema.contentDatabases.documentId, args.documentId)
|
|
172
|
+
: undefined,
|
|
173
|
+
exactTitle
|
|
174
|
+
? sql`lower(${schema.documents.title}) = ${exactTitle}`
|
|
175
|
+
: undefined,
|
|
176
|
+
excludedSourceChainDatabaseIds.size > 0
|
|
177
|
+
? notInArray(
|
|
178
|
+
schema.contentDatabases.id,
|
|
179
|
+
Array.from(excludedSourceChainDatabaseIds),
|
|
180
|
+
)
|
|
181
|
+
: undefined,
|
|
182
|
+
excludedDatabaseIds.size > 0
|
|
183
|
+
? notInArray(
|
|
184
|
+
schema.contentDatabases.documentId,
|
|
185
|
+
Array.from(excludedDatabaseIds),
|
|
186
|
+
)
|
|
187
|
+
: undefined,
|
|
188
|
+
pattern
|
|
189
|
+
? or(
|
|
190
|
+
sql`lower(${schema.documents.title}) LIKE ${pattern} ESCAPE '\\'`,
|
|
191
|
+
sql`lower(${schema.documents.description}) LIKE ${pattern} ESCAPE '\\'`,
|
|
192
|
+
)
|
|
193
|
+
: undefined,
|
|
194
|
+
);
|
|
195
|
+
const baseQuery = () =>
|
|
196
|
+
db
|
|
197
|
+
.select({
|
|
198
|
+
id: schema.contentDatabases.id,
|
|
199
|
+
documentId: schema.contentDatabases.documentId,
|
|
200
|
+
title: schema.documents.title,
|
|
201
|
+
description: schema.documents.description,
|
|
202
|
+
spaceId: schema.contentDatabases.spaceId,
|
|
203
|
+
})
|
|
204
|
+
.from(schema.contentDatabases)
|
|
205
|
+
.innerJoin(
|
|
206
|
+
schema.documents,
|
|
207
|
+
eq(schema.contentDatabases.documentId, schema.documents.id),
|
|
208
|
+
)
|
|
209
|
+
.where(where)
|
|
210
|
+
.orderBy(
|
|
211
|
+
asc(schema.documents.position),
|
|
212
|
+
asc(schema.contentDatabases.id),
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
// Two visible matches are sufficient to reject an exact selector without
|
|
216
|
+
// materializing every duplicate-title row.
|
|
217
|
+
const rows = resolvesExactly
|
|
218
|
+
? await baseQuery().limit(2)
|
|
219
|
+
: await baseQuery().limit(args.limit).offset(args.offset);
|
|
220
|
+
|
|
221
|
+
if (resolvesExactly && rows.length !== 1) {
|
|
222
|
+
const selector = args.databaseId
|
|
223
|
+
? `database ID "${args.databaseId}"`
|
|
224
|
+
: args.documentId
|
|
225
|
+
? `document ID "${args.documentId}"`
|
|
226
|
+
: `title "${args.title?.trim()}"`;
|
|
227
|
+
throw new ContentDatabaseResolutionError(
|
|
228
|
+
rows.length === 0
|
|
229
|
+
? `No accessible Content database matched exact ${selector}.`
|
|
230
|
+
: `Exact ${selector} is ambiguous across multiple accessible Content databases.`,
|
|
231
|
+
);
|
|
232
|
+
}
|
|
125
233
|
|
|
126
|
-
|
|
234
|
+
const databases = rows.map((row) => ({
|
|
235
|
+
databaseId: row.id,
|
|
236
|
+
documentId: row.documentId,
|
|
237
|
+
spaceId: row.spaceId,
|
|
238
|
+
// The document's live title (matches the sidebar) rather than the
|
|
239
|
+
// possibly-stale content_databases.title.
|
|
240
|
+
title: row.title ?? "Untitled database",
|
|
241
|
+
description: row.description,
|
|
242
|
+
}));
|
|
243
|
+
|
|
244
|
+
const totalItems = resolvesExactly
|
|
245
|
+
? databases.length
|
|
246
|
+
: Number(
|
|
247
|
+
(
|
|
248
|
+
await db
|
|
249
|
+
.select({ count: sql<number>`count(*)` })
|
|
250
|
+
.from(schema.contentDatabases)
|
|
251
|
+
.innerJoin(
|
|
252
|
+
schema.documents,
|
|
253
|
+
eq(schema.contentDatabases.documentId, schema.documents.id),
|
|
254
|
+
)
|
|
255
|
+
.where(where)
|
|
256
|
+
)[0]?.count ?? 0,
|
|
257
|
+
);
|
|
258
|
+
|
|
259
|
+
return {
|
|
260
|
+
databases,
|
|
261
|
+
pagination: documentDiscoveryPagination({
|
|
262
|
+
offset: resolvesExactly ? 0 : args.offset,
|
|
263
|
+
limit: args.limit,
|
|
264
|
+
totalItems,
|
|
265
|
+
returnedItems: databases.length,
|
|
266
|
+
}),
|
|
267
|
+
};
|
|
127
268
|
},
|
|
128
269
|
});
|