@agent-native/core 0.84.0 → 0.84.1
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/core/CHANGELOG.md +6 -0
- package/corpus/core/docs/content/evals.mdx +34 -12
- package/corpus/core/docs/content/locales/ar-SA/evals.mdx +7 -0
- package/corpus/core/docs/content/locales/de-DE/evals.mdx +8 -0
- package/corpus/core/docs/content/locales/es-ES/evals.mdx +8 -0
- package/corpus/core/docs/content/locales/fr-FR/evals.mdx +8 -0
- package/corpus/core/docs/content/locales/hi-IN/evals.mdx +8 -0
- package/corpus/core/docs/content/locales/ja-JP/evals.mdx +7 -0
- package/corpus/core/docs/content/locales/ko-KR/evals.mdx +7 -0
- package/corpus/core/docs/content/locales/pt-BR/evals.mdx +8 -0
- package/corpus/core/docs/content/locales/zh-CN/evals.mdx +7 -0
- package/corpus/core/docs/content/locales/zh-TW/evals.mdx +7 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/eval/define-eval.ts +4 -1
- package/corpus/core/src/eval/report.ts +23 -8
- package/corpus/core/src/eval/runner.ts +41 -6
- package/corpus/core/src/eval/types.ts +11 -0
- package/corpus/templates/content/AGENTS.md +9 -2
- package/corpus/templates/content/actions/_database-row-batch.ts +230 -0
- package/corpus/templates/content/actions/_database-utils.ts +17 -9
- package/corpus/templates/content/actions/_notion-action-utils.ts +26 -0
- package/corpus/templates/content/actions/connect-notion-status.ts +13 -4
- package/corpus/templates/content/actions/create-and-link-notion-page.ts +23 -0
- package/corpus/templates/content/actions/delete-database-items.ts +55 -0
- package/corpus/templates/content/actions/delete-document.ts +4 -4
- package/corpus/templates/content/actions/disconnect-notion.ts +16 -0
- package/corpus/templates/content/actions/duplicate-database-item.ts +1 -1
- package/corpus/templates/content/actions/duplicate-database-items.ts +191 -0
- package/corpus/templates/content/actions/link-notion-page.ts +10 -7
- package/corpus/templates/content/actions/list-notion-links.ts +3 -4
- package/corpus/templates/content/actions/pull-notion-page.ts +7 -9
- package/corpus/templates/content/actions/push-notion-page.ts +7 -9
- package/corpus/templates/content/actions/refresh-notion-sync-status.ts +24 -0
- package/corpus/templates/content/actions/resolve-notion-sync-conflict.ts +23 -0
- package/corpus/templates/content/actions/search-notion-pages.ts +67 -0
- package/corpus/templates/content/actions/unlink-notion-page.ts +23 -0
- package/corpus/templates/content/app/components/editor/DocumentDatabase.tsx +27 -34
- package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +16 -13
- package/corpus/templates/content/app/components/editor/DocumentToolbar.tsx +10 -10
- package/corpus/templates/content/app/components/editor/NotionConflictBanner.tsx +1 -1
- package/corpus/templates/content/app/components/editor/NotionSyncBar.tsx +18 -9
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +27 -34
- package/corpus/templates/content/app/components/editor/database/navigation-state.ts +10 -4
- package/corpus/templates/content/app/components/sidebar/NotionButton.tsx +32 -17
- package/corpus/templates/content/app/hooks/use-content-database.ts +35 -0
- package/corpus/templates/content/app/hooks/use-notion.ts +108 -99
- package/corpus/templates/content/package.json +3 -0
- package/corpus/templates/content/parity/README.md +69 -0
- package/corpus/templates/content/parity/eval-scenarios.ts +118 -0
- package/corpus/templates/content/parity/exceptions.allowlist.ts +17 -0
- package/corpus/templates/content/parity/matrix.md +28 -0
- package/corpus/templates/content/parity/matrix.ts +606 -0
- package/corpus/templates/content/parity/matrix.types.ts +42 -0
- package/corpus/templates/content/parity/parity-evals.eval.ts +4 -0
- package/corpus/templates/content/parity/render-matrix.ts +90 -0
- package/corpus/templates/content/parity/scenario-to-eval.ts +57 -0
- package/corpus/templates/content/shared/api.ts +11 -0
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/eval/define-eval.d.ts.map +1 -1
- package/dist/eval/define-eval.js +2 -1
- package/dist/eval/define-eval.js.map +1 -1
- package/dist/eval/report.d.ts.map +1 -1
- package/dist/eval/report.js +19 -4
- package/dist/eval/report.js.map +1 -1
- package/dist/eval/runner.d.ts.map +1 -1
- package/dist/eval/runner.js +38 -6
- package/dist/eval/runner.js.map +1 -1
- package/dist/eval/types.d.ts +11 -0
- package/dist/eval/types.d.ts.map +1 -1
- package/dist/eval/types.js.map +1 -1
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/progress/routes.d.ts +1 -1
- package/dist/resources/handlers.d.ts +1 -1
- package/docs/content/evals.mdx +34 -12
- package/docs/content/locales/ar-SA/evals.mdx +7 -0
- package/docs/content/locales/de-DE/evals.mdx +8 -0
- package/docs/content/locales/es-ES/evals.mdx +8 -0
- package/docs/content/locales/fr-FR/evals.mdx +8 -0
- package/docs/content/locales/hi-IN/evals.mdx +8 -0
- package/docs/content/locales/ja-JP/evals.mdx +7 -0
- package/docs/content/locales/ko-KR/evals.mdx +7 -0
- package/docs/content/locales/pt-BR/evals.mdx +8 -0
- package/docs/content/locales/zh-CN/evals.mdx +7 -0
- package/docs/content/locales/zh-TW/evals.mdx +7 -0
- package/package.json +1 -1
- package/corpus/templates/content/server/routes/api/notion/disconnect.post.ts +0 -12
|
@@ -146,8 +146,10 @@ import {
|
|
|
146
146
|
useContentDatabase,
|
|
147
147
|
useContentDatabases,
|
|
148
148
|
contentDatabaseQueryKey,
|
|
149
|
+
useDeleteDatabaseItems,
|
|
149
150
|
useDisconnectContentDatabaseSource,
|
|
150
151
|
useDuplicateDatabaseItem,
|
|
152
|
+
useDuplicateDatabaseItems,
|
|
151
153
|
useExecuteBuilderSourceExecution,
|
|
152
154
|
useMoveDatabaseItem,
|
|
153
155
|
usePrepareBuilderSourceReview,
|
|
@@ -2766,9 +2768,9 @@ function DatabaseTableView({
|
|
|
2766
2768
|
}) {
|
|
2767
2769
|
const queryClient = useQueryClient();
|
|
2768
2770
|
const moveItem = useMoveDatabaseItem(databaseDocumentId);
|
|
2769
|
-
const
|
|
2771
|
+
const duplicateItems = useDuplicateDatabaseItems(databaseDocumentId);
|
|
2770
2772
|
const setProperty = useSetDocumentProperty(databaseDocumentId);
|
|
2771
|
-
const
|
|
2773
|
+
const deleteItems = useDeleteDatabaseItems(databaseDocumentId);
|
|
2772
2774
|
const [draggedItemId, setDraggedItemId] = useState<string | null>(null);
|
|
2773
2775
|
const [dropTargetItemId, setDropTargetItemId] = useState<string | null>(null);
|
|
2774
2776
|
const [draggedPropertyId, setDraggedPropertyId] = useState<string | null>(
|
|
@@ -3012,14 +3014,15 @@ function DatabaseTableView({
|
|
|
3012
3014
|
async function deleteSelectedRows() {
|
|
3013
3015
|
if (selectedItems.length === 0) return;
|
|
3014
3016
|
const selectedSnapshot = selectedItems;
|
|
3015
|
-
onClearSelection();
|
|
3016
3017
|
setConfirmDeleteSelectedOpen(false);
|
|
3017
3018
|
|
|
3018
3019
|
try {
|
|
3020
|
+
await deleteItems.mutateAsync({
|
|
3021
|
+
documentId: databaseDocumentId,
|
|
3022
|
+
itemIds: selectedSnapshot.map((item) => item.id),
|
|
3023
|
+
});
|
|
3024
|
+
onClearSelection();
|
|
3019
3025
|
onDeletedPreviewItems(selectedSnapshot);
|
|
3020
|
-
for (const item of selectedSnapshot) {
|
|
3021
|
-
await deleteDocument.mutateAsync({ id: item.document.id });
|
|
3022
|
-
}
|
|
3023
3026
|
await queryClient.invalidateQueries({
|
|
3024
3027
|
queryKey: [
|
|
3025
3028
|
"action",
|
|
@@ -3043,22 +3046,11 @@ function DatabaseTableView({
|
|
|
3043
3046
|
const selectedSnapshot = selectedItems;
|
|
3044
3047
|
setIsDuplicatingSelected(true);
|
|
3045
3048
|
|
|
3046
|
-
let duplicatedPreviewItem: ContentDatabaseItem | null = null;
|
|
3047
|
-
let duplicatedCount = 0;
|
|
3048
|
-
let failedCount = 0;
|
|
3049
|
-
|
|
3050
3049
|
try {
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
duplicatedPreviewItem =
|
|
3056
|
-
databaseDuplicatedItemFromResponse(response) ??
|
|
3057
|
-
duplicatedPreviewItem;
|
|
3058
|
-
} catch {
|
|
3059
|
-
failedCount += 1;
|
|
3060
|
-
}
|
|
3061
|
-
}
|
|
3050
|
+
const response = await duplicateItems.mutateAsync({
|
|
3051
|
+
documentId: databaseDocumentId,
|
|
3052
|
+
itemIds: selectedSnapshot.map((item) => item.id),
|
|
3053
|
+
});
|
|
3062
3054
|
|
|
3063
3055
|
await queryClient.invalidateQueries({
|
|
3064
3056
|
queryKey: [
|
|
@@ -3071,16 +3063,15 @@ function DatabaseTableView({
|
|
|
3071
3063
|
queryKey: ["action", "list-documents"],
|
|
3072
3064
|
});
|
|
3073
3065
|
|
|
3066
|
+
const duplicatedPreviewItem =
|
|
3067
|
+
databaseDuplicatedItemFromResponse(response);
|
|
3074
3068
|
if (duplicatedPreviewItem) onPreview(duplicatedPreviewItem);
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
: "No rows were duplicated.",
|
|
3082
|
-
});
|
|
3083
|
-
}
|
|
3069
|
+
onClearSelection();
|
|
3070
|
+
} catch (err) {
|
|
3071
|
+
toast.error(dbText("failedToDuplicateEverySelectedRow"), {
|
|
3072
|
+
description:
|
|
3073
|
+
err instanceof Error ? err.message : dbText("somethingWentWrong"),
|
|
3074
|
+
});
|
|
3084
3075
|
} finally {
|
|
3085
3076
|
setIsDuplicatingSelected(false);
|
|
3086
3077
|
}
|
|
@@ -3139,9 +3130,11 @@ function DatabaseTableView({
|
|
|
3139
3130
|
canEdit={canEdit}
|
|
3140
3131
|
properties={bulkEditableProperties}
|
|
3141
3132
|
duplicateDisabled={
|
|
3142
|
-
isDuplicatingSelected ||
|
|
3133
|
+
isDuplicatingSelected ||
|
|
3134
|
+
duplicateItems.isPending ||
|
|
3135
|
+
deleteItems.isPending
|
|
3143
3136
|
}
|
|
3144
|
-
deleteDisabled={
|
|
3137
|
+
deleteDisabled={deleteItems.isPending}
|
|
3145
3138
|
updateDisabled={setProperty.isPending}
|
|
3146
3139
|
onClearSelection={onClearSelection}
|
|
3147
3140
|
onSetPropertyValue={setSelectedPropertyValue}
|
|
@@ -3360,10 +3353,10 @@ function DatabaseTableView({
|
|
|
3360
3353
|
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
|
3361
3354
|
<AlertDialogAction
|
|
3362
3355
|
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
|
3363
|
-
disabled={
|
|
3356
|
+
disabled={deleteItems.isPending}
|
|
3364
3357
|
onClick={() => void deleteSelectedRows()}
|
|
3365
3358
|
>
|
|
3366
|
-
{
|
|
3359
|
+
{deleteItems.isPending ? "Deleting..." : "Delete"}
|
|
3367
3360
|
</AlertDialogAction>
|
|
3368
3361
|
</AlertDialogFooter>
|
|
3369
3362
|
</AlertDialogContent>
|
|
@@ -250,11 +250,17 @@ export function databaseBulkScalarInputState(
|
|
|
250
250
|
|
|
251
251
|
export function databaseDuplicatedItemFromResponse(
|
|
252
252
|
response: Pick<ContentDatabaseResponse, "items"> &
|
|
253
|
-
Pick<
|
|
253
|
+
Pick<
|
|
254
|
+
Partial<ContentDatabaseResponse>,
|
|
255
|
+
"duplicatedItemId" | "duplicatedItemIds"
|
|
256
|
+
>,
|
|
254
257
|
) {
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
+
const duplicatedItemIds = response.duplicatedItemIds ?? [];
|
|
259
|
+
const duplicatedItemId =
|
|
260
|
+
duplicatedItemIds.length > 0
|
|
261
|
+
? duplicatedItemIds[duplicatedItemIds.length - 1]
|
|
262
|
+
: response.duplicatedItemId;
|
|
263
|
+
return response.items.find((item) => item.id === duplicatedItemId) ?? null;
|
|
258
264
|
}
|
|
259
265
|
|
|
260
266
|
export function toggleDatabaseRowSelection(
|
|
@@ -22,7 +22,11 @@ import {
|
|
|
22
22
|
TooltipContent,
|
|
23
23
|
TooltipTrigger,
|
|
24
24
|
} from "@/components/ui/tooltip";
|
|
25
|
-
import {
|
|
25
|
+
import {
|
|
26
|
+
openNotionOAuthUrl,
|
|
27
|
+
useDisconnectNotion,
|
|
28
|
+
useNotionConnection,
|
|
29
|
+
} from "@/hooks/use-notion";
|
|
26
30
|
import { cn } from "@/lib/utils";
|
|
27
31
|
|
|
28
32
|
// ─── Notion SVG icon ────────────────────────────────────────────────────────
|
|
@@ -125,8 +129,8 @@ export function NotionButton() {
|
|
|
125
129
|
};
|
|
126
130
|
}, []);
|
|
127
131
|
|
|
128
|
-
function handleConnect() {
|
|
129
|
-
if (
|
|
132
|
+
async function handleConnect() {
|
|
133
|
+
if (connection?.error === "missing_credentials") {
|
|
130
134
|
if (needsCredentials) {
|
|
131
135
|
setShowWizard(true);
|
|
132
136
|
return;
|
|
@@ -134,7 +138,24 @@ export function NotionButton() {
|
|
|
134
138
|
toast.error(t("sidebar.notionOAuthNotConfigured"));
|
|
135
139
|
return;
|
|
136
140
|
}
|
|
137
|
-
window.open(
|
|
141
|
+
const popup = window.open("about:blank", "_blank");
|
|
142
|
+
if (!popup) {
|
|
143
|
+
toast.error(t("sidebar.notionOAuthNotConfigured"));
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
popup.opener = null;
|
|
147
|
+
|
|
148
|
+
try {
|
|
149
|
+
popup.location.href = await openNotionOAuthUrl();
|
|
150
|
+
} catch (error) {
|
|
151
|
+
popup.close();
|
|
152
|
+
toast.error(
|
|
153
|
+
error instanceof Error
|
|
154
|
+
? error.message
|
|
155
|
+
: t("sidebar.notionOAuthNotConfigured"),
|
|
156
|
+
);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
138
159
|
|
|
139
160
|
// Clear any existing poll before starting a new one
|
|
140
161
|
if (pollRef.current) clearInterval(pollRef.current);
|
|
@@ -142,18 +163,12 @@ export function NotionButton() {
|
|
|
142
163
|
|
|
143
164
|
// Poll for connection
|
|
144
165
|
pollRef.current = setInterval(async () => {
|
|
145
|
-
const
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
clearInterval(pollRef.current);
|
|
152
|
-
pollRef.current = undefined;
|
|
153
|
-
setShowWizard(false);
|
|
154
|
-
setOpen(false);
|
|
155
|
-
refetch();
|
|
156
|
-
}
|
|
166
|
+
const result = await refetch();
|
|
167
|
+
if (result.data?.connected) {
|
|
168
|
+
clearInterval(pollRef.current);
|
|
169
|
+
pollRef.current = undefined;
|
|
170
|
+
setShowWizard(false);
|
|
171
|
+
setOpen(false);
|
|
157
172
|
}
|
|
158
173
|
}, 2000);
|
|
159
174
|
|
|
@@ -166,7 +181,7 @@ export function NotionButton() {
|
|
|
166
181
|
|
|
167
182
|
async function handleDisconnect() {
|
|
168
183
|
try {
|
|
169
|
-
await disconnectNotion.mutateAsync();
|
|
184
|
+
await disconnectNotion.mutateAsync({});
|
|
170
185
|
toast.success(t("sidebar.notionDisconnectedWorkspace"));
|
|
171
186
|
} catch (err) {
|
|
172
187
|
toast.error(
|
|
@@ -13,6 +13,7 @@ import type {
|
|
|
13
13
|
ContentDatabaseSourceFieldPropertyResponse,
|
|
14
14
|
ContentDatabaseSourceStatusResponse,
|
|
15
15
|
CreateDatabaseRequest,
|
|
16
|
+
DatabaseItemsBatchRequest,
|
|
16
17
|
DisconnectContentDatabaseSourceRequest,
|
|
17
18
|
ExecuteBuilderSourceBatchRequest,
|
|
18
19
|
ExecuteBuilderSourceBatchResponse,
|
|
@@ -258,6 +259,40 @@ export function useDuplicateDatabaseItem(documentId: string) {
|
|
|
258
259
|
});
|
|
259
260
|
}
|
|
260
261
|
|
|
262
|
+
export function useDuplicateDatabaseItems(documentId: string) {
|
|
263
|
+
const queryClient = useQueryClient();
|
|
264
|
+
return useActionMutation<ContentDatabaseResponse, DatabaseItemsBatchRequest>(
|
|
265
|
+
"duplicate-database-items",
|
|
266
|
+
{
|
|
267
|
+
onSuccess: () => {
|
|
268
|
+
queryClient.invalidateQueries({
|
|
269
|
+
queryKey: contentDatabaseQueryKey(documentId),
|
|
270
|
+
});
|
|
271
|
+
queryClient.invalidateQueries({
|
|
272
|
+
queryKey: ["action", "list-documents"],
|
|
273
|
+
});
|
|
274
|
+
},
|
|
275
|
+
},
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
export function useDeleteDatabaseItems(documentId: string) {
|
|
280
|
+
const queryClient = useQueryClient();
|
|
281
|
+
return useActionMutation<ContentDatabaseResponse, DatabaseItemsBatchRequest>(
|
|
282
|
+
"delete-database-items",
|
|
283
|
+
{
|
|
284
|
+
onSuccess: () => {
|
|
285
|
+
queryClient.invalidateQueries({
|
|
286
|
+
queryKey: contentDatabaseQueryKey(documentId),
|
|
287
|
+
});
|
|
288
|
+
queryClient.invalidateQueries({
|
|
289
|
+
queryKey: ["action", "list-documents"],
|
|
290
|
+
});
|
|
291
|
+
},
|
|
292
|
+
},
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
|
|
261
296
|
export function useMoveDatabaseItem(documentId: string) {
|
|
262
297
|
const queryClient = useQueryClient();
|
|
263
298
|
return useActionMutation<ContentDatabaseResponse, MoveDatabaseItemRequest>(
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
appApiPath,
|
|
3
|
+
callAction,
|
|
4
|
+
useActionMutation,
|
|
5
|
+
useActionQuery,
|
|
6
|
+
} from "@agent-native/core/client";
|
|
2
7
|
import type {
|
|
3
8
|
CreateNotionPageRequest,
|
|
4
9
|
Document,
|
|
@@ -8,18 +13,20 @@ import type {
|
|
|
8
13
|
NotionSearchResponse,
|
|
9
14
|
ResolveDocumentSyncConflictRequest,
|
|
10
15
|
} from "@shared/api";
|
|
11
|
-
import {
|
|
16
|
+
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
|
12
17
|
import { useEffect, useRef } from "react";
|
|
13
18
|
|
|
14
|
-
async function
|
|
15
|
-
const res = await fetch(appApiPath(url)
|
|
19
|
+
async function fetchNotionAuthUrl(): Promise<string> {
|
|
20
|
+
const res = await fetch(appApiPath("/api/notion/auth-url"));
|
|
16
21
|
if (!res.ok) {
|
|
17
22
|
const body = await res.json().catch(() => null);
|
|
18
23
|
throw new Error(
|
|
19
24
|
body?.error || body?.message || `${res.status} ${res.statusText}`,
|
|
20
25
|
);
|
|
21
26
|
}
|
|
22
|
-
|
|
27
|
+
const body = (await res.json()) as { url?: string };
|
|
28
|
+
if (!body.url) throw new Error("Notion OAuth URL is unavailable");
|
|
29
|
+
return body.url;
|
|
23
30
|
}
|
|
24
31
|
|
|
25
32
|
function invalidateDocumentQueries(
|
|
@@ -27,34 +34,65 @@ function invalidateDocumentQueries(
|
|
|
27
34
|
documentId: string,
|
|
28
35
|
) {
|
|
29
36
|
queryClient.invalidateQueries({ queryKey: ["action"] });
|
|
30
|
-
queryClient.invalidateQueries({
|
|
37
|
+
queryClient.invalidateQueries({
|
|
38
|
+
queryKey: documentSyncStatusQueryKey(documentId),
|
|
39
|
+
});
|
|
40
|
+
queryClient.invalidateQueries({
|
|
41
|
+
queryKey: documentSyncStatusQueryKey(documentId, { autoSync: true }),
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function documentSyncStatusQueryKey(
|
|
46
|
+
documentId: string,
|
|
47
|
+
options?: { autoSync?: boolean },
|
|
48
|
+
) {
|
|
49
|
+
return [
|
|
50
|
+
"action",
|
|
51
|
+
"refresh-notion-sync-status",
|
|
52
|
+
{ documentId, autoSync: !!options?.autoSync },
|
|
53
|
+
] as const;
|
|
31
54
|
}
|
|
32
55
|
|
|
33
56
|
export function useNotionConnection() {
|
|
57
|
+
return useActionQuery<NotionConnectionStatus>(
|
|
58
|
+
"connect-notion-status",
|
|
59
|
+
undefined,
|
|
60
|
+
{
|
|
61
|
+
staleTime: 30_000,
|
|
62
|
+
},
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function useNotionAuthUrl(enabled: boolean) {
|
|
34
67
|
return useQuery({
|
|
35
|
-
queryKey: ["notion-
|
|
36
|
-
queryFn:
|
|
68
|
+
queryKey: ["notion-auth-url"],
|
|
69
|
+
queryFn: fetchNotionAuthUrl,
|
|
70
|
+
enabled,
|
|
37
71
|
staleTime: 30_000,
|
|
38
72
|
});
|
|
39
73
|
}
|
|
40
74
|
|
|
75
|
+
export async function openNotionOAuthUrl() {
|
|
76
|
+
return fetchNotionAuthUrl();
|
|
77
|
+
}
|
|
78
|
+
|
|
41
79
|
export function useDocumentSyncStatus(
|
|
42
80
|
documentId: string | null,
|
|
43
81
|
options?: { autoSync?: boolean },
|
|
44
82
|
) {
|
|
45
83
|
const queryClient = useQueryClient();
|
|
46
84
|
const lastObservedSyncedAtRef = useRef<string | null>(null);
|
|
47
|
-
const query = useQuery({
|
|
48
|
-
queryKey:
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
85
|
+
const query = useQuery<DocumentSyncStatus>({
|
|
86
|
+
queryKey: documentId
|
|
87
|
+
? documentSyncStatusQueryKey(documentId, options)
|
|
88
|
+
: ["action", "refresh-notion-sync-status", null],
|
|
89
|
+
queryFn: () => {
|
|
90
|
+
if (!documentId) throw new Error("documentId is required");
|
|
91
|
+
return callAction<DocumentSyncStatus>("refresh-notion-sync-status", {
|
|
92
|
+
documentId,
|
|
93
|
+
autoSync: !!options?.autoSync,
|
|
94
|
+
});
|
|
95
|
+
},
|
|
58
96
|
enabled: !!documentId,
|
|
59
97
|
// Poll Notion aggressively when auto-sync is on so remote changes appear
|
|
60
98
|
// within ~2s. Server throttles match (see REFRESH_THROTTLE_AUTO_SYNC_MS in
|
|
@@ -97,118 +135,89 @@ export function useDocumentSyncStatus(
|
|
|
97
135
|
|
|
98
136
|
export function useDisconnectNotion() {
|
|
99
137
|
const queryClient = useQueryClient();
|
|
100
|
-
return
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
138
|
+
return useActionMutation<{ success: boolean; deleted: number }>(
|
|
139
|
+
"disconnect-notion",
|
|
140
|
+
{
|
|
141
|
+
onSuccess: () => {
|
|
142
|
+
queryClient.invalidateQueries({
|
|
143
|
+
queryKey: ["action", "connect-notion-status"],
|
|
144
|
+
});
|
|
145
|
+
queryClient.invalidateQueries({
|
|
146
|
+
queryKey: ["action", "refresh-notion-sync-status"],
|
|
147
|
+
});
|
|
148
|
+
},
|
|
108
149
|
},
|
|
109
|
-
|
|
150
|
+
);
|
|
110
151
|
}
|
|
111
152
|
|
|
112
153
|
export function useLinkDocumentToNotion(documentId: string) {
|
|
113
154
|
const queryClient = useQueryClient();
|
|
114
|
-
return
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
{
|
|
119
|
-
method: "POST",
|
|
120
|
-
headers: { "Content-Type": "application/json" },
|
|
121
|
-
body: JSON.stringify(data),
|
|
122
|
-
},
|
|
123
|
-
),
|
|
155
|
+
return useActionMutation<
|
|
156
|
+
DocumentSyncStatus,
|
|
157
|
+
LinkNotionPageRequest & { documentId: string }
|
|
158
|
+
>("link-notion-page", {
|
|
124
159
|
onSuccess: () => invalidateDocumentQueries(queryClient, documentId),
|
|
125
160
|
});
|
|
126
161
|
}
|
|
127
162
|
|
|
128
163
|
export function useUnlinkDocumentFromNotion(documentId: string) {
|
|
129
164
|
const queryClient = useQueryClient();
|
|
130
|
-
return
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
),
|
|
138
|
-
onSuccess: () => invalidateDocumentQueries(queryClient, documentId),
|
|
139
|
-
});
|
|
165
|
+
return useActionMutation<{ success: boolean }, { documentId: string }>(
|
|
166
|
+
"unlink-notion-page",
|
|
167
|
+
{
|
|
168
|
+
method: "DELETE",
|
|
169
|
+
onSuccess: () => invalidateDocumentQueries(queryClient, documentId),
|
|
170
|
+
},
|
|
171
|
+
);
|
|
140
172
|
}
|
|
141
173
|
|
|
142
174
|
export function usePullDocumentFromNotion(documentId: string) {
|
|
143
175
|
const queryClient = useQueryClient();
|
|
144
|
-
return
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
},
|
|
151
|
-
),
|
|
152
|
-
onSuccess: () => invalidateDocumentQueries(queryClient, documentId),
|
|
153
|
-
});
|
|
176
|
+
return useActionMutation<DocumentSyncStatus, { documentId: string }>(
|
|
177
|
+
"pull-notion-page",
|
|
178
|
+
{
|
|
179
|
+
onSuccess: () => invalidateDocumentQueries(queryClient, documentId),
|
|
180
|
+
},
|
|
181
|
+
);
|
|
154
182
|
}
|
|
155
183
|
|
|
156
184
|
export function usePushDocumentToNotion(documentId: string) {
|
|
157
185
|
const queryClient = useQueryClient();
|
|
158
|
-
return
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
},
|
|
165
|
-
),
|
|
166
|
-
onSuccess: () => invalidateDocumentQueries(queryClient, documentId),
|
|
167
|
-
});
|
|
186
|
+
return useActionMutation<DocumentSyncStatus, { documentId: string }>(
|
|
187
|
+
"push-notion-page",
|
|
188
|
+
{
|
|
189
|
+
onSuccess: () => invalidateDocumentQueries(queryClient, documentId),
|
|
190
|
+
},
|
|
191
|
+
);
|
|
168
192
|
}
|
|
169
193
|
|
|
170
194
|
export function useResolveDocumentSyncConflict(documentId: string) {
|
|
171
195
|
const queryClient = useQueryClient();
|
|
172
|
-
return
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
{
|
|
177
|
-
method: "POST",
|
|
178
|
-
headers: { "Content-Type": "application/json" },
|
|
179
|
-
body: JSON.stringify(data),
|
|
180
|
-
},
|
|
181
|
-
),
|
|
196
|
+
return useActionMutation<
|
|
197
|
+
DocumentSyncStatus,
|
|
198
|
+
ResolveDocumentSyncConflictRequest & { documentId: string }
|
|
199
|
+
>("resolve-notion-sync-conflict", {
|
|
182
200
|
onSuccess: () => invalidateDocumentQueries(queryClient, documentId),
|
|
183
201
|
});
|
|
184
202
|
}
|
|
185
203
|
|
|
186
204
|
export function useCreateAndLinkNotionPage(documentId: string) {
|
|
187
205
|
const queryClient = useQueryClient();
|
|
188
|
-
return
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
{
|
|
193
|
-
method: "POST",
|
|
194
|
-
headers: { "Content-Type": "application/json" },
|
|
195
|
-
body: JSON.stringify(data ?? {}),
|
|
196
|
-
},
|
|
197
|
-
),
|
|
206
|
+
return useActionMutation<
|
|
207
|
+
DocumentSyncStatus,
|
|
208
|
+
CreateNotionPageRequest & { documentId: string }
|
|
209
|
+
>("create-and-link-notion-page", {
|
|
198
210
|
onSuccess: () => invalidateDocumentQueries(queryClient, documentId),
|
|
199
211
|
});
|
|
200
212
|
}
|
|
201
213
|
|
|
202
214
|
export function useSearchNotionPages(query: string, enabled: boolean) {
|
|
203
|
-
return
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
enabled,
|
|
212
|
-
staleTime: 10_000,
|
|
213
|
-
});
|
|
215
|
+
return useActionQuery<NotionSearchResponse>(
|
|
216
|
+
"search-notion-pages",
|
|
217
|
+
{ query },
|
|
218
|
+
{
|
|
219
|
+
enabled,
|
|
220
|
+
staleTime: 10_000,
|
|
221
|
+
},
|
|
222
|
+
);
|
|
214
223
|
}
|
|
@@ -9,6 +9,9 @@
|
|
|
9
9
|
"build": "agent-native build",
|
|
10
10
|
"start": "agent-native start",
|
|
11
11
|
"test": "vitest --run",
|
|
12
|
+
"test:parity": "vitest --run parity",
|
|
13
|
+
"test:parity-capabilities": "vitest --run parity actions/content-database-lifecycle.db.test.ts actions/bind-content-database-source-field.db.test.ts actions/_local-file-documents.test.ts actions/builder-source-review-gates.db.test.ts",
|
|
14
|
+
"eval:parity": "agent-native eval parity",
|
|
12
15
|
"format.fix": "oxfmt --write .",
|
|
13
16
|
"typecheck": "agent-native typecheck",
|
|
14
17
|
"action": "agent-native action",
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Content Parity Harness
|
|
2
|
+
|
|
3
|
+
This folder is the living audit for Content's agent/UI action parity. The
|
|
4
|
+
matrix records which visible workflows are action-backed, which have explicit
|
|
5
|
+
exceptions, and which gaps need follow-up.
|
|
6
|
+
|
|
7
|
+
PR 2.2 adds two executable tiers:
|
|
8
|
+
|
|
9
|
+
- Deterministic parity tests run in normal CI. They use fixture data and
|
|
10
|
+
existing action tests, make no model calls, and require no private provider
|
|
11
|
+
credentials.
|
|
12
|
+
- Gated agent evals run through `agent-native eval parity`. They are opt-in via
|
|
13
|
+
`CONTENT_PARITY_EVALS=1`, capped to four initial scenarios, and should be
|
|
14
|
+
reserved for manual or nightly checks.
|
|
15
|
+
|
|
16
|
+
## Deterministic Checks
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
cd templates/content
|
|
20
|
+
./node_modules/.bin/vitest --run parity
|
|
21
|
+
./node_modules/.bin/vitest --run actions/content-database-lifecycle.db.test.ts actions/bind-content-database-source-field.db.test.ts actions/_local-file-documents.test.ts actions/builder-source-review-gates.db.test.ts
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
The matrix rows use `coverageRefs` to point at deterministic files that prove a
|
|
25
|
+
capability. The meta-tests fail with the row id when a priority capability loses
|
|
26
|
+
coverage, so reviewers do not have to map a random test name back to the audit.
|
|
27
|
+
|
|
28
|
+
## Gated Agent Evals
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
cd templates/content
|
|
32
|
+
./node_modules/.bin/agent-native eval parity --json
|
|
33
|
+
CONTENT_PARITY_EVALS=1 ANTHROPIC_API_KEY=... ./node_modules/.bin/agent-native eval parity
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
With `CONTENT_PARITY_EVALS` unset, parity evals return skipped rows and do not
|
|
37
|
+
call the agent runner. The CLI still exits `0`, but both readable and JSON
|
|
38
|
+
reports mark each row with `status: "skipped"` and a `skipReason` such as
|
|
39
|
+
`Skipped because CONTENT_PARITY_EVALS is unset`.
|
|
40
|
+
|
|
41
|
+
With the gate set, the eval files run the four PR 2.2 scenarios:
|
|
42
|
+
|
|
43
|
+
- `database-source-scope`
|
|
44
|
+
- `document-search-edit`
|
|
45
|
+
- `local-file-source-truth`
|
|
46
|
+
- `builder-source-review-readonly`
|
|
47
|
+
|
|
48
|
+
The scenarios use fake or fixture data only. They must not require private
|
|
49
|
+
Builder, Notion, customer, or workspace credentials. The Builder scenario is
|
|
50
|
+
readonly/mocked by default and must not execute a live write.
|
|
51
|
+
|
|
52
|
+
## CI Posture
|
|
53
|
+
|
|
54
|
+
Normal PR CI runs deterministic parity checks through `pnpm test:content-parity`.
|
|
55
|
+
Live or LLM-backed parity evals are reserved for the gated eval workflow, which
|
|
56
|
+
can be triggered manually or by the nightly schedule. Missing provider secrets
|
|
57
|
+
should show as skipped in that workflow summary, not as a scored pass.
|
|
58
|
+
|
|
59
|
+
## Updating The Matrix
|
|
60
|
+
|
|
61
|
+
Edit `matrix.ts`, then regenerate:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
cd templates/content
|
|
65
|
+
./node_modules/.bin/tsx parity/render-matrix.ts --write
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
`parity/__tests__/matrix-render-fresh.test.ts` keeps `matrix.md` synced with
|
|
69
|
+
the typed source.
|