@agent-native/core 0.157.1 → 0.157.3
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/calendar/server/handlers/bookings.ts +6 -1
- package/corpus/templates/clips/actions/request-recording-access.ts +47 -14
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/reset-chunks.post.ts +125 -15
- 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/database/DatabaseView.tsx +7 -0
- package/corpus/templates/content/app/components/editor/database/settings.tsx +7 -0
- package/corpus/templates/content/app/hooks/use-content-database.ts +91 -11
- 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/dist/collab/awareness.d.ts +2 -2
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +5 -5
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/docs/content/cloneable-saas.mdx +24 -26
- package/package.json +1 -1
|
@@ -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
|
});
|
|
@@ -28,6 +28,15 @@ import {
|
|
|
28
28
|
|
|
29
29
|
export default defineAction({
|
|
30
30
|
description: "Set a Notion-style property value on a document.",
|
|
31
|
+
publicAgent: {
|
|
32
|
+
expose: true,
|
|
33
|
+
readOnly: false,
|
|
34
|
+
requiresAuth: true,
|
|
35
|
+
isConsequential: true,
|
|
36
|
+
title: "Set Content Document Property",
|
|
37
|
+
description:
|
|
38
|
+
"Delegate one property update on an existing Content database document.",
|
|
39
|
+
},
|
|
31
40
|
schema: z.object({
|
|
32
41
|
documentId: z.string().describe("Document ID (required)"),
|
|
33
42
|
databaseId: z
|
|
@@ -195,6 +195,15 @@ function resolveSubmittedProperties(
|
|
|
195
195
|
export default defineAction({
|
|
196
196
|
description:
|
|
197
197
|
"Submit one row through a Content database form. Validates that form's required questions, resolves option labels safely, writes the title, Blocks, and property values atomically, verifies the saved row, and returns its exact page link.",
|
|
198
|
+
publicAgent: {
|
|
199
|
+
expose: true,
|
|
200
|
+
readOnly: false,
|
|
201
|
+
requiresAuth: true,
|
|
202
|
+
isConsequential: true,
|
|
203
|
+
title: "Submit Content Database Form",
|
|
204
|
+
description:
|
|
205
|
+
"Delegate a validated, atomic submission to an existing Content database form.",
|
|
206
|
+
},
|
|
198
207
|
schema: submitContentDatabaseFormSchema,
|
|
199
208
|
mcpApp: {
|
|
200
209
|
compactCatalog: true,
|
|
@@ -290,6 +290,15 @@ export function isStaleBuilderImageSourceComponentSave(args: {
|
|
|
290
290
|
export default defineAction({
|
|
291
291
|
description:
|
|
292
292
|
"Update an existing document's title, content, icon, or favorite status.",
|
|
293
|
+
publicAgent: {
|
|
294
|
+
expose: true,
|
|
295
|
+
readOnly: false,
|
|
296
|
+
requiresAuth: true,
|
|
297
|
+
isConsequential: true,
|
|
298
|
+
title: "Update Content Document",
|
|
299
|
+
description:
|
|
300
|
+
"Delegate a sparse update to an existing Content document while preserving omitted fields.",
|
|
301
|
+
},
|
|
293
302
|
schema: z.object({
|
|
294
303
|
id: z.string().optional().describe("Document ID (required)"),
|
|
295
304
|
title: z.string().optional().describe("New title"),
|
|
@@ -113,6 +113,7 @@ import {
|
|
|
113
113
|
import { Link, useLocation, useNavigate } from "react-router";
|
|
114
114
|
import { toast } from "sonner";
|
|
115
115
|
|
|
116
|
+
import { QueryErrorState } from "@/components/QueryErrorState";
|
|
116
117
|
import {
|
|
117
118
|
contentSpaceForCatalogItem,
|
|
118
119
|
createContentSpaceSelectionQueue,
|
|
@@ -9265,6 +9266,12 @@ function AddSourceView({
|
|
|
9265
9266
|
<Spinner className="size-3.5" />
|
|
9266
9267
|
{dbText("loadingTables")}
|
|
9267
9268
|
</div>
|
|
9269
|
+
) : query.isError ? (
|
|
9270
|
+
<QueryErrorState
|
|
9271
|
+
compact
|
|
9272
|
+
onRetry={() => void query.refetch()}
|
|
9273
|
+
retrying={query.isFetching}
|
|
9274
|
+
/>
|
|
9268
9275
|
) : tables.length === 0 ? (
|
|
9269
9276
|
<div className="min-w-0 break-words px-2 text-xs text-muted-foreground">
|
|
9270
9277
|
{dbText("noOtherDatabasesAvailableToAdd")}
|
|
@@ -52,6 +52,7 @@ import {
|
|
|
52
52
|
} from "react";
|
|
53
53
|
import { toast } from "sonner";
|
|
54
54
|
|
|
55
|
+
import { QueryErrorState } from "@/components/QueryErrorState";
|
|
55
56
|
import { Button } from "@/components/ui/button";
|
|
56
57
|
import {
|
|
57
58
|
DropdownMenu,
|
|
@@ -1543,6 +1544,12 @@ function AddSourceView({
|
|
|
1543
1544
|
<Spinner className="size-3.5" />
|
|
1544
1545
|
{dbText("loadingTables")}
|
|
1545
1546
|
</div>
|
|
1547
|
+
) : query.isError ? (
|
|
1548
|
+
<QueryErrorState
|
|
1549
|
+
compact
|
|
1550
|
+
onRetry={() => void query.refetch()}
|
|
1551
|
+
retrying={query.isFetching}
|
|
1552
|
+
/>
|
|
1546
1553
|
) : tables.length === 0 ? (
|
|
1547
1554
|
<div className="min-w-0 break-words px-2 text-xs text-muted-foreground">
|
|
1548
1555
|
{dbText("noOtherDatabasesAvailableToAdd")}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
callAction,
|
|
2
3
|
useActionMutation,
|
|
3
4
|
useActionQuery,
|
|
4
5
|
} from "@agent-native/core/client/hooks";
|
|
@@ -56,7 +57,7 @@ import type {
|
|
|
56
57
|
ValidateBuilderSourceExecutionRequest,
|
|
57
58
|
} from "@shared/api";
|
|
58
59
|
import type { Query, QueryClient } from "@tanstack/react-query";
|
|
59
|
-
import { useQueryClient } from "@tanstack/react-query";
|
|
60
|
+
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
|
60
61
|
|
|
61
62
|
import { documentQueryFilter } from "../lib/document-query";
|
|
62
63
|
|
|
@@ -1390,16 +1391,95 @@ export function useContentDatabases(args: {
|
|
|
1390
1391
|
excludeDatabaseIds?: string[];
|
|
1391
1392
|
enabled: boolean;
|
|
1392
1393
|
}) {
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
args.
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
:
|
|
1401
|
-
|
|
1402
|
-
|
|
1394
|
+
const filters = {
|
|
1395
|
+
excludeDatabaseId: args.excludeDatabaseId ?? undefined,
|
|
1396
|
+
excludeDatabaseIds: args.excludeDatabaseIds ?? undefined,
|
|
1397
|
+
};
|
|
1398
|
+
return useQuery({
|
|
1399
|
+
queryKey: ["action", "list-content-databases", filters],
|
|
1400
|
+
queryFn: async ({ signal }) => ({
|
|
1401
|
+
databases: await fetchCompleteContentDatabaseList((offset, limit) =>
|
|
1402
|
+
callAction<ListContentDatabasesResponse>(
|
|
1403
|
+
"list-content-databases",
|
|
1404
|
+
{ ...filters, offset, limit },
|
|
1405
|
+
{ method: "GET", signal },
|
|
1406
|
+
),
|
|
1407
|
+
),
|
|
1408
|
+
}),
|
|
1409
|
+
enabled: args.enabled,
|
|
1410
|
+
retry: false,
|
|
1411
|
+
});
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
const CONTENT_DATABASE_LIST_PAGE_SIZE = 50;
|
|
1415
|
+
|
|
1416
|
+
export async function fetchCompleteContentDatabaseList(
|
|
1417
|
+
fetchPage: (
|
|
1418
|
+
offset: number,
|
|
1419
|
+
limit: number,
|
|
1420
|
+
) => Promise<ListContentDatabasesResponse>,
|
|
1421
|
+
) {
|
|
1422
|
+
const databases: ListContentDatabasesResponse["databases"] = [];
|
|
1423
|
+
const databaseIds = new Set<string>();
|
|
1424
|
+
let offset = 0;
|
|
1425
|
+
let expectedTotal: number | null = null;
|
|
1426
|
+
|
|
1427
|
+
while (true) {
|
|
1428
|
+
const page = await fetchPage(offset, CONTENT_DATABASE_LIST_PAGE_SIZE);
|
|
1429
|
+
const { pagination } = page;
|
|
1430
|
+
if (!pagination) {
|
|
1431
|
+
throw new Error(
|
|
1432
|
+
"list-content-databases returned no pagination boundary; refusing to treat the result as complete.",
|
|
1433
|
+
);
|
|
1434
|
+
}
|
|
1435
|
+
if (
|
|
1436
|
+
pagination.offset !== offset ||
|
|
1437
|
+
pagination.limit !== CONTENT_DATABASE_LIST_PAGE_SIZE ||
|
|
1438
|
+
pagination.returnedItems !== page.databases.length
|
|
1439
|
+
) {
|
|
1440
|
+
throw new Error(
|
|
1441
|
+
"list-content-databases returned inconsistent pagination metadata; retry the complete read.",
|
|
1442
|
+
);
|
|
1443
|
+
}
|
|
1444
|
+
if (expectedTotal === null) expectedTotal = pagination.totalItems;
|
|
1445
|
+
if (pagination.totalItems !== expectedTotal) {
|
|
1446
|
+
throw new Error(
|
|
1447
|
+
"Content databases changed during paginated discovery; retry the complete read.",
|
|
1448
|
+
);
|
|
1449
|
+
}
|
|
1450
|
+
for (const database of page.databases) {
|
|
1451
|
+
if (databaseIds.has(database.databaseId)) {
|
|
1452
|
+
throw new Error(
|
|
1453
|
+
`list-content-databases repeated database "${database.databaseId}" across pages; refusing an ambiguous result.`,
|
|
1454
|
+
);
|
|
1455
|
+
}
|
|
1456
|
+
databaseIds.add(database.databaseId);
|
|
1457
|
+
databases.push(database);
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
const expectedNextOffset = offset + page.databases.length;
|
|
1461
|
+
if (!pagination.hasMore) {
|
|
1462
|
+
if (
|
|
1463
|
+
pagination.nextOffset !== null ||
|
|
1464
|
+
expectedNextOffset !== expectedTotal ||
|
|
1465
|
+
databases.length !== expectedTotal
|
|
1466
|
+
) {
|
|
1467
|
+
throw new Error(
|
|
1468
|
+
"list-content-databases claimed exhaustion before every declared database was returned.",
|
|
1469
|
+
);
|
|
1470
|
+
}
|
|
1471
|
+
return databases;
|
|
1472
|
+
}
|
|
1473
|
+
if (
|
|
1474
|
+
pagination.nextOffset !== expectedNextOffset ||
|
|
1475
|
+
pagination.nextOffset <= offset
|
|
1476
|
+
) {
|
|
1477
|
+
throw new Error(
|
|
1478
|
+
"list-content-databases returned a non-advancing continuation; refusing a clipped result.",
|
|
1479
|
+
);
|
|
1480
|
+
}
|
|
1481
|
+
offset = pagination.nextOffset;
|
|
1482
|
+
}
|
|
1403
1483
|
}
|
|
1404
1484
|
|
|
1405
1485
|
export function useSuggestSourceJoinKey(args: {
|
|
@@ -6,7 +6,7 @@ This generated matrix tracks whether high-value Content UI operations use the sa
|
|
|
6
6
|
| -------------------------------------------------- | ----------- | --------------------------------------------------------------------------------------------------------- | --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------- | -------------- | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------- | ------------------------------ |
|
|
7
7
|
| comments.threads | comments | List, add, reply, resolve, reopen, and delete comment threads | action-backed | `add-comment`, `delete-comment`, `list-comments`, `update-comment` | `app/components/editor/CommentsSidebar.tsx`, `app/hooks/use-comments.ts` | Comment threads, replies, anchors, mentions, resolution state, and deletion are stored through comment actions. | - | - | P0 | seeded | - | - | - |
|
|
8
8
|
| database.form-submissions | database | Submit public database forms as new rows | action-backed | `submit-content-database-form` | `app/components/editor/database/FormView.tsx` | A validated form submission atomically creates a database row document and its editable property values. | - | - | P0 | covered | `actions/submit-content-database-form.db.test.ts` | - | - |
|
|
9
|
-
| database.lifecycle-and-trash | database | Create, soft-delete, restore, list, and inspect content databases | action-backed | `create-content-database`, `create-inline-content-database`, `delete-content-database`, `get-content-database`, `list-content-databases`, `list-trashed-content-databases`, `restore-content-database`
|
|
9
|
+
| database.lifecycle-and-trash | database | Create, soft-delete, restore, list, and inspect content databases | action-backed | `create-content-database`, `create-inline-content-database`, `delete-content-database`, `describe-content-database`, `get-content-database`, `list-content-databases`, `list-trashed-content-databases`, `restore-content-database` | `app/components/editor/SlashCommandMenu.tsx`, `app/hooks/use-content-database.ts`, `app/hooks/use-documents.ts` | Database pages and database records are created, read, soft-deleted, restored, and listed. | - | - | P0 | covered | `actions/content-database-lifecycle.db.test.ts`, `actions/list-content-databases.db.test.ts`, `server/plugins/agent-chat.spec.ts`, `../../packages/core/src/server/agent-chat/content-a2a-capabilities.spec.ts` | `database-source-scope` | - |
|
|
10
10
|
| database.private-preview-drafts | database | Persist and reconcile a user's private database-page preview draft | action-backed | `get-preview-document-draft`, `update-preview-document-draft` | `app/components/editor/database/DatabaseView.tsx`, `app/hooks/use-documents.ts` | A user's private preview draft is read, saved, conflict-checked, or deleted without changing the shared database page until the normal save flow applies it. | These per-user editor-state actions are intentionally hidden from agent tools because preview drafts are a private UI recovery mechanism. | - | P1 | covered | `actions/preview-document-draft.db.test.ts` | - | - |
|
|
11
11
|
| database.properties-and-view-config | database | Configure properties, values, ordering, and saved views | action-backed | `configure-document-property`, `delete-document-property`, `duplicate-document-property`, `get-content-database-personal-view`, `list-document-properties`, `reorder-document-property`, `set-document-property`, `update-content-database-personal-view`, `update-content-database-view` | `app/components/editor/DocumentProperties.tsx`, `app/components/editor/DocumentDatabase.tsx`, `app/components/editor/database/DatabaseView.tsx`, `app/hooks/use-document-properties.ts` | Property schemas, property values, property order, filters, sorts, grouping, hidden columns, view type, and view settings are stored. | - | - | P0 | covered | `actions/bind-content-database-source-field.db.test.ts`, `actions/content-database-source-actions.test.ts`, `actions/resync-content-database-source.db.test.ts` | `database-source-scope` | - |
|
|
12
12
|
| database.rows | database | Add, duplicate, move, open, edit, and remove database rows | action-backed | `add-database-item`, `update-database-item`, `upsert-database-item-by-key`, `remove-database-items`, `duplicate-database-items`, `duplicate-database-item`, `migrate-content-database-rows`, `move-database-item`, `set-document-property` | `app/components/editor/DocumentDatabase.tsx`, `app/components/editor/database/DatabaseView.tsx` | Database row memberships and ordering are created, duplicated, moved, edited, and removed without deleting the backing page; bounded migrations atomically update row bodies and properties through the same canonical data model. | - | - | P0 | covered | `actions/database-row-batch-actions.db.test.ts`, `actions/upsert-database-item-by-key.db.test.ts`, `actions/migrate-content-database-rows.db.test.ts`, `parity/__tests__/database-row-batch-reliability.test.ts` | `database-bulk-row-reliability` | - |
|
|
@@ -275,6 +275,7 @@ export const parityMatrix: ParityRow[] = [
|
|
|
275
275
|
"create-content-database",
|
|
276
276
|
"create-inline-content-database",
|
|
277
277
|
"delete-content-database",
|
|
278
|
+
"describe-content-database",
|
|
278
279
|
"get-content-database",
|
|
279
280
|
"list-content-databases",
|
|
280
281
|
"list-trashed-content-databases",
|
|
@@ -285,7 +286,12 @@ export const parityMatrix: ParityRow[] = [
|
|
|
285
286
|
spinePriority: "P0",
|
|
286
287
|
testCoverage: "covered",
|
|
287
288
|
followUpPR: null,
|
|
288
|
-
coverageRefs: [
|
|
289
|
+
coverageRefs: [
|
|
290
|
+
"actions/content-database-lifecycle.db.test.ts",
|
|
291
|
+
"actions/list-content-databases.db.test.ts",
|
|
292
|
+
"server/plugins/agent-chat.spec.ts",
|
|
293
|
+
"../../packages/core/src/server/agent-chat/content-a2a-capabilities.spec.ts",
|
|
294
|
+
],
|
|
289
295
|
evalScenarioIds: ["database-source-scope"],
|
|
290
296
|
},
|
|
291
297
|
{
|
|
@@ -34,6 +34,9 @@ export default createAgentChatPlugin({
|
|
|
34
34
|
durableBackgroundRuns: true,
|
|
35
35
|
actions: loadActionsFromStaticRegistry(actionsRegistry),
|
|
36
36
|
initialToolNames: INITIAL_TOOL_NAMES,
|
|
37
|
+
mcp: {
|
|
38
|
+
connectorCatalog: ["list-content-databases", "describe-content-database"],
|
|
39
|
+
},
|
|
37
40
|
anonymousOwner: resolvePublicViewerOwner,
|
|
38
41
|
extraContext: publicDocumentExtraContext,
|
|
39
42
|
// Enable sandboxed JavaScript execution so Content agents can fetch,
|
|
@@ -1077,11 +1077,20 @@ export interface ChangeContentDatabaseSourceRoleRequest {
|
|
|
1077
1077
|
export interface ContentDatabaseSummary {
|
|
1078
1078
|
databaseId: string;
|
|
1079
1079
|
documentId: string;
|
|
1080
|
+
spaceId: string | null;
|
|
1080
1081
|
title: string;
|
|
1082
|
+
description: string;
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
export interface ContentDatabaseDescriptionResponse {
|
|
1086
|
+
database: ContentDatabase;
|
|
1087
|
+
contextPath: ContentContextPathEntry[];
|
|
1088
|
+
properties: DocumentProperty[];
|
|
1081
1089
|
}
|
|
1082
1090
|
|
|
1083
1091
|
export interface ListContentDatabasesResponse {
|
|
1084
1092
|
databases: ContentDatabaseSummary[];
|
|
1093
|
+
pagination: DocumentDiscoveryPagination;
|
|
1085
1094
|
}
|
|
1086
1095
|
|
|
1087
1096
|
export interface TrashedContentDatabaseSummary {
|
|
@@ -62,11 +62,11 @@ export declare const postAwareness: import("h3").EventHandlerWithFetch<import("h
|
|
|
62
62
|
error: string;
|
|
63
63
|
states?: undefined;
|
|
64
64
|
} | {
|
|
65
|
+
error?: undefined;
|
|
65
66
|
states: {
|
|
66
67
|
clientId: number;
|
|
67
68
|
state: string;
|
|
68
69
|
}[];
|
|
69
|
-
error?: undefined;
|
|
70
70
|
}>>;
|
|
71
71
|
/**
|
|
72
72
|
* GET /_agent-native/collab/:docId/users
|
|
@@ -77,9 +77,9 @@ export declare const getActiveUsers: import("h3").EventHandlerWithFetch<import("
|
|
|
77
77
|
error: string;
|
|
78
78
|
users?: undefined;
|
|
79
79
|
} | {
|
|
80
|
+
error?: undefined;
|
|
80
81
|
users: {
|
|
81
82
|
clientId: number;
|
|
82
83
|
lastSeen: number;
|
|
83
84
|
}[];
|
|
84
|
-
error?: undefined;
|
|
85
85
|
}>>;
|