@agent-native/core 0.102.2 → 0.103.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 +12 -0
- package/corpus/core/docs/content/local-file-mode.mdx +99 -489
- package/corpus/core/docs/content/locales/ar-SA/local-file-mode.mdx +35 -490
- package/corpus/core/docs/content/locales/ar-SA/template-content.mdx +10 -24
- package/corpus/core/docs/content/locales/de-DE/local-file-mode.mdx +37 -491
- package/corpus/core/docs/content/locales/de-DE/template-content.mdx +10 -24
- package/corpus/core/docs/content/locales/es-ES/local-file-mode.mdx +35 -493
- package/corpus/core/docs/content/locales/es-ES/template-content.mdx +10 -24
- package/corpus/core/docs/content/locales/fr-FR/local-file-mode.mdx +38 -491
- package/corpus/core/docs/content/locales/fr-FR/template-content.mdx +10 -24
- package/corpus/core/docs/content/locales/hi-IN/local-file-mode.mdx +33 -492
- package/corpus/core/docs/content/locales/hi-IN/template-content.mdx +10 -24
- package/corpus/core/docs/content/locales/ja-JP/local-file-mode.mdx +36 -492
- package/corpus/core/docs/content/locales/ja-JP/template-content.mdx +10 -24
- package/corpus/core/docs/content/locales/ko-KR/local-file-mode.mdx +35 -490
- package/corpus/core/docs/content/locales/ko-KR/template-content.mdx +10 -24
- package/corpus/core/docs/content/locales/pt-BR/local-file-mode.mdx +35 -493
- package/corpus/core/docs/content/locales/pt-BR/template-content.mdx +10 -24
- package/corpus/core/docs/content/locales/zh-CN/local-file-mode.mdx +31 -489
- package/corpus/core/docs/content/locales/zh-CN/template-content.mdx +9 -24
- package/corpus/core/docs/content/locales/zh-TW/local-file-mode.mdx +31 -489
- package/corpus/core/docs/content/locales/zh-TW/template-content.mdx +9 -24
- package/corpus/core/docs/content/template-content.mdx +26 -42
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/cli/content-local.ts +86 -18
- package/corpus/core/src/cli/index.ts +2 -2
- package/corpus/core/src/cli/skills-content/content-skill.ts +36 -25
- package/corpus/core/src/cli/skills.ts +47 -13
- package/corpus/core/src/local-artifacts/index.ts +65 -0
- package/corpus/core/src/vite/client.ts +2 -0
- package/corpus/templates/assets/app/components/library/GenerationPresetsPanel.tsx +325 -0
- package/corpus/templates/assets/app/hooks/use-navigation-state.ts +12 -0
- package/corpus/templates/assets/app/i18n/zh-TW.ts +21 -0
- package/corpus/templates/assets/app/i18n-data.ts +215 -0
- package/corpus/templates/assets/app/routes/brand-kits.$id.tsx +19 -479
- package/corpus/templates/assets/app/routes/brand-kits.$id_.presets.$presetId.tsx +1 -1
- package/corpus/templates/assets/app/routes/brand-kits.$id_.settings.tsx +446 -0
- package/corpus/templates/clips/app/components/editor/editor-layout.tsx +12 -2
- package/corpus/templates/clips/app/components/player/settings-panel.tsx +30 -12
- package/corpus/templates/clips/app/components/player/video-player.tsx +5 -1
- package/corpus/templates/clips/app/components/recorder/microphone-visualizer.tsx +22 -3
- package/corpus/templates/clips/app/i18n/ar-SA.ts +3 -2
- package/corpus/templates/clips/app/i18n/de-DE.ts +4 -2
- package/corpus/templates/clips/app/i18n/en-US.ts +3 -2
- package/corpus/templates/clips/app/i18n/es-ES.ts +4 -2
- package/corpus/templates/clips/app/i18n/fr-FR.ts +3 -2
- package/corpus/templates/clips/app/i18n/hi-IN.ts +3 -2
- package/corpus/templates/clips/app/i18n/ja-JP.ts +4 -1
- package/corpus/templates/clips/app/i18n/ko-KR.ts +3 -2
- package/corpus/templates/clips/app/i18n/pt-BR.ts +4 -2
- package/corpus/templates/clips/app/i18n/zh-CN.ts +3 -2
- package/corpus/templates/clips/app/i18n/zh-TW.ts +3 -2
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +27 -1
- package/corpus/templates/clips/server/lib/share-password.ts +6 -3
- package/corpus/templates/content/.agents/skills/content/SKILL.md +26 -39
- package/corpus/templates/content/AGENTS.md +25 -30
- package/corpus/templates/content/actions/_builder-docs-client.ts +23 -0
- package/corpus/templates/content/actions/_content-database-source-adapters.ts +21 -3
- package/corpus/templates/content/actions/_content-files.ts +338 -0
- package/corpus/templates/content/actions/_content-space-access.ts +135 -0
- package/corpus/templates/content/actions/_content-spaces.ts +640 -0
- package/corpus/templates/content/actions/_database-source-utils.ts +90 -7
- package/corpus/templates/content/actions/_database-utils.ts +7 -1
- package/corpus/templates/content/actions/_local-file-documents.ts +0 -9
- package/corpus/templates/content/actions/_local-folder-source.ts +53 -0
- package/corpus/templates/content/actions/_property-utils.ts +9 -4
- package/corpus/templates/content/actions/add-database-item.ts +108 -65
- package/corpus/templates/content/actions/backfill-content-files.ts +22 -0
- package/corpus/templates/content/actions/connect-local-folder-source.ts +221 -0
- package/corpus/templates/content/actions/create-content-database.ts +129 -3
- package/corpus/templates/content/actions/create-document.ts +65 -55
- package/corpus/templates/content/actions/create-inline-content-database.ts +6 -1
- package/corpus/templates/content/actions/delete-content-database.ts +3 -0
- package/corpus/templates/content/actions/delete-document.ts +7 -11
- package/corpus/templates/content/actions/disconnect-local-folder-source.ts +175 -0
- package/corpus/templates/content/actions/duplicate-database-item.ts +10 -0
- package/corpus/templates/content/actions/duplicate-database-items.ts +13 -0
- package/corpus/templates/content/actions/edit-document.ts +2 -28
- package/corpus/templates/content/actions/ensure-content-spaces.ts +26 -0
- package/corpus/templates/content/actions/export-content-source.ts +47 -9
- package/corpus/templates/content/actions/get-document.ts +0 -14
- package/corpus/templates/content/actions/import-content-source.ts +43 -3
- package/corpus/templates/content/actions/list-content-spaces.ts +103 -0
- package/corpus/templates/content/actions/list-documents.ts +12 -9
- package/corpus/templates/content/actions/move-document.ts +3 -14
- package/corpus/templates/content/actions/pull-document.ts +0 -23
- package/corpus/templates/content/actions/remove-local-file-source.ts +50 -29
- package/corpus/templates/content/actions/resolve-local-folder-conflict.ts +442 -0
- package/corpus/templates/content/actions/reveal-local-source-file.ts +6 -17
- package/corpus/templates/content/actions/search-documents.ts +0 -34
- package/corpus/templates/content/actions/share-local-file-document.ts +25 -1
- package/corpus/templates/content/actions/submit-content-database-form.ts +12 -0
- package/corpus/templates/content/actions/sync-local-folder-source.ts +731 -0
- package/corpus/templates/content/actions/sync-manifest-local-folder-source.ts +123 -0
- package/corpus/templates/content/actions/update-content-database-view.ts +10 -1
- package/corpus/templates/content/actions/update-document.ts +0 -15
- package/corpus/templates/content/actions/view-screen.ts +2 -50
- package/corpus/templates/content/app/components/editor/SlashCommandMenu.tsx +76 -62
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +70 -22
- package/corpus/templates/content/app/components/editor/database/grouping.ts +2 -1
- package/corpus/templates/content/app/components/editor/database/settings.tsx +23 -20
- package/corpus/templates/content/app/components/editor/database/shared.tsx +2 -0
- package/corpus/templates/content/app/components/editor/database/sidebar.tsx +330 -0
- package/corpus/templates/content/app/components/editor/database/types.ts +1 -0
- package/corpus/templates/content/app/components/editor/database/view-config.ts +3 -1
- package/corpus/templates/content/app/components/layout/Layout.tsx +25 -1
- package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +209 -113
- package/corpus/templates/content/app/components/sidebar/select-content-space.ts +31 -0
- package/corpus/templates/content/app/hooks/use-content-database.ts +16 -0
- package/corpus/templates/content/app/hooks/use-content-spaces.ts +39 -0
- package/corpus/templates/content/app/i18n/zh-TW.ts +11 -0
- package/corpus/templates/content/app/i18n-data.ts +75 -0
- package/corpus/templates/content/app/routes/_app.local-files.tsx +174 -65
- package/corpus/templates/content/changelog/2026-07-16-content-callouts-can-now-be-reviewed-and-published-to-builder.md +6 -0
- package/corpus/templates/content/changelog/2026-07-16-heading-5-and-heading-6-are-available-from-the-editor-slash-.md +6 -0
- package/corpus/templates/content/changelog/2026-07-16-organize-every-workspace-through-a-customizable-files-databa.md +6 -0
- package/corpus/templates/content/parity/matrix.md +2 -1
- package/corpus/templates/content/parity/matrix.ts +37 -1
- package/corpus/templates/content/scripts/dev-database.mjs +7 -5
- package/corpus/templates/content/server/db/schema.ts +113 -40
- package/corpus/templates/content/server/lib/document-context.ts +15 -1
- package/corpus/templates/content/server/lib/notion-sync.ts +6 -0
- package/corpus/templates/content/server/plugins/db.ts +153 -0
- package/corpus/templates/content/shared/api.ts +17 -3
- package/corpus/templates/content/shared/builder-mdx.ts +163 -16
- package/corpus/templates/design/.agents/skills/design-templates/SKILL.md +59 -0
- package/corpus/templates/design/AGENTS.md +6 -1
- package/corpus/templates/design/actions/create-design-from-template.ts +19 -7
- package/corpus/templates/design/actions/list-design-templates.ts +69 -21
- package/corpus/templates/design/actions/navigate.ts +2 -2
- package/corpus/templates/design/actions/view-screen.ts +15 -2
- package/corpus/templates/design/agent-native.app-skill.json +5 -0
- package/corpus/templates/design/app/components/editor/PromptDialog.tsx +281 -56
- package/corpus/templates/design/app/components/templates/TemplatePreview.tsx +14 -2
- package/corpus/templates/design/app/hooks/use-navigation-state.ts +3 -2
- package/corpus/templates/design/app/i18n/zh-TW.ts +3 -3
- package/corpus/templates/design/app/i18n-data.ts +21 -2
- package/corpus/templates/design/app/i18n-template-feature.ts +218 -10
- package/corpus/templates/design/app/pages/Index.tsx +195 -24
- package/corpus/templates/design/app/pages/Templates.tsx +144 -55
- package/corpus/templates/design/app/pages/design-editor/generation-prompt-directives.ts +12 -1
- package/corpus/templates/design/changelog/2026-07-14-templates-now-appear-in-the-new-design-picker-with-previews-.md +6 -0
- package/corpus/templates/design/changelog/2026-07-15-templates-now-open-immediately-after-copying-even-while-the-.md +6 -0
- package/corpus/templates/design/server/plugins/agent-chat.ts +1 -1
- package/corpus/templates/mail/AGENTS.md +23 -18
- package/corpus/templates/mail/actions/bulk-archive.ts +32 -28
- package/corpus/templates/mail/actions/mark-read.ts +131 -20
- package/corpus/templates/mail/actions/mark-thread-read.ts +1 -1
- package/corpus/templates/mail/actions/move-email.ts +30 -24
- package/corpus/templates/mail/actions/send-email.ts +73 -68
- package/corpus/templates/mail/changelog/2026-07-15-unread-inbox-cleanup-now-completes-reliably.md +6 -0
- package/corpus/templates/mail/server/handlers/emails.ts +218 -209
- package/corpus/templates/mail/server/lib/bulk-mark-read.ts +30 -0
- package/corpus/templates/mail/server/lib/email-state.ts +198 -113
- package/corpus/templates/mail/server/lib/google-auth.ts +218 -25
- package/corpus/templates/mail/server/lib/jobs.ts +69 -74
- package/corpus/templates/mail/server/lib/local-email-store.ts +265 -0
- package/corpus/templates/mail/server/plugins/agent-chat.ts +6 -0
- package/dist/cli/content-local.d.ts +1 -0
- package/dist/cli/content-local.d.ts.map +1 -1
- package/dist/cli/content-local.js +75 -17
- package/dist/cli/content-local.js.map +1 -1
- package/dist/cli/index.js +2 -2
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/skills-content/content-skill.d.ts +1 -1
- package/dist/cli/skills-content/content-skill.d.ts.map +1 -1
- package/dist/cli/skills-content/content-skill.js +36 -25
- package/dist/cli/skills-content/content-skill.js.map +1 -1
- package/dist/cli/skills.d.ts.map +1 -1
- package/dist/cli/skills.js +47 -14
- package/dist/cli/skills.js.map +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/local-artifacts/index.d.ts +10 -0
- package/dist/local-artifacts/index.d.ts.map +1 -1
- package/dist/local-artifacts/index.js +39 -0
- package/dist/local-artifacts/index.js.map +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/vite/client.d.ts.map +1 -1
- package/dist/vite/client.js +2 -0
- package/dist/vite/client.js.map +1 -1
- package/docs/content/local-file-mode.mdx +99 -489
- package/docs/content/locales/ar-SA/local-file-mode.mdx +35 -490
- package/docs/content/locales/ar-SA/template-content.mdx +10 -24
- package/docs/content/locales/de-DE/local-file-mode.mdx +37 -491
- package/docs/content/locales/de-DE/template-content.mdx +10 -24
- package/docs/content/locales/es-ES/local-file-mode.mdx +35 -493
- package/docs/content/locales/es-ES/template-content.mdx +10 -24
- package/docs/content/locales/fr-FR/local-file-mode.mdx +38 -491
- package/docs/content/locales/fr-FR/template-content.mdx +10 -24
- package/docs/content/locales/hi-IN/local-file-mode.mdx +33 -492
- package/docs/content/locales/hi-IN/template-content.mdx +10 -24
- package/docs/content/locales/ja-JP/local-file-mode.mdx +36 -492
- package/docs/content/locales/ja-JP/template-content.mdx +10 -24
- package/docs/content/locales/ko-KR/local-file-mode.mdx +35 -490
- package/docs/content/locales/ko-KR/template-content.mdx +10 -24
- package/docs/content/locales/pt-BR/local-file-mode.mdx +35 -493
- package/docs/content/locales/pt-BR/template-content.mdx +10 -24
- package/docs/content/locales/zh-CN/local-file-mode.mdx +31 -489
- package/docs/content/locales/zh-CN/template-content.mdx +9 -24
- package/docs/content/locales/zh-TW/local-file-mode.mdx +31 -489
- package/docs/content/locales/zh-TW/template-content.mdx +9 -24
- package/docs/content/template-content.mdx +26 -42
- package/package.json +1 -1
- package/corpus/templates/assets/app/components/library/EditLibraryDialog.tsx +0 -126
|
@@ -19,10 +19,6 @@ import {
|
|
|
19
19
|
} from "../server/lib/documents.js";
|
|
20
20
|
import { serializeDatabaseMembership } from "./_database-utils.js";
|
|
21
21
|
import { serializeDocumentSource } from "./_document-source.js";
|
|
22
|
-
import {
|
|
23
|
-
isContentLocalFileMode,
|
|
24
|
-
listLocalFileDocuments,
|
|
25
|
-
} from "./_local-file-documents.js";
|
|
26
22
|
import { parseDatabaseViewConfig } from "./_property-utils.js";
|
|
27
23
|
|
|
28
24
|
function contentPreview(content: string, maxLength = 180) {
|
|
@@ -52,9 +48,6 @@ export default defineAction({
|
|
|
52
48
|
schema: z.object({}),
|
|
53
49
|
http: { method: "GET" },
|
|
54
50
|
run: async () => {
|
|
55
|
-
const localFileMode = await isContentLocalFileMode();
|
|
56
|
-
const localDocuments = localFileMode ? await listLocalFileDocuments() : [];
|
|
57
|
-
|
|
58
51
|
const db = getDb();
|
|
59
52
|
const userEmail = getRequestUserEmail();
|
|
60
53
|
const orgId = getRequestOrgId();
|
|
@@ -174,6 +167,10 @@ export default defineAction({
|
|
|
174
167
|
inArray(schema.contentDatabases.documentId, visibleDocumentIds),
|
|
175
168
|
isNull(schema.contentDatabases.deletedAt),
|
|
176
169
|
),
|
|
170
|
+
)
|
|
171
|
+
.orderBy(
|
|
172
|
+
sql`CASE WHEN ${schema.contentDatabases.systemRole} IS NULL THEN 0 ELSE 1 END`,
|
|
173
|
+
asc(schema.contentDatabases.id),
|
|
177
174
|
),
|
|
178
175
|
db
|
|
179
176
|
.select({
|
|
@@ -196,6 +193,10 @@ export default defineAction({
|
|
|
196
193
|
),
|
|
197
194
|
isNull(schema.contentDatabases.deletedAt),
|
|
198
195
|
),
|
|
196
|
+
)
|
|
197
|
+
.orderBy(
|
|
198
|
+
sql`CASE WHEN ${schema.contentDatabases.systemRole} IS NULL THEN 0 ELSE 1 END`,
|
|
199
|
+
asc(schema.contentDatabases.id),
|
|
199
200
|
),
|
|
200
201
|
db
|
|
201
202
|
.select({
|
|
@@ -230,7 +231,9 @@ export default defineAction({
|
|
|
230
231
|
}
|
|
231
232
|
|
|
232
233
|
for (const row of databaseMemberships) {
|
|
233
|
-
databaseMembershipByDocumentId.
|
|
234
|
+
if (!databaseMembershipByDocumentId.has(row.item.documentId)) {
|
|
235
|
+
databaseMembershipByDocumentId.set(row.item.documentId, row);
|
|
236
|
+
}
|
|
234
237
|
}
|
|
235
238
|
|
|
236
239
|
for (const database of softDeletedDatabases) {
|
|
@@ -318,6 +321,6 @@ export default defineAction({
|
|
|
318
321
|
};
|
|
319
322
|
});
|
|
320
323
|
|
|
321
|
-
return { documents:
|
|
324
|
+
return { documents: mapped };
|
|
322
325
|
},
|
|
323
326
|
});
|
|
@@ -9,11 +9,6 @@ import {
|
|
|
9
9
|
parseDocumentFavorite,
|
|
10
10
|
parseDocumentHideFromSearch,
|
|
11
11
|
} from "../server/lib/documents.js";
|
|
12
|
-
import {
|
|
13
|
-
isLocalDocumentId,
|
|
14
|
-
isContentLocalFileMode,
|
|
15
|
-
moveLocalFileDocument,
|
|
16
|
-
} from "./_local-file-documents.js";
|
|
17
12
|
import { documentsPositionScope, withPositionLock } from "./_position-utils.js";
|
|
18
13
|
|
|
19
14
|
async function assertParentIsNotDescendant({
|
|
@@ -214,15 +209,6 @@ export default defineAction({
|
|
|
214
209
|
throw new Error("A document cannot be moved under itself");
|
|
215
210
|
}
|
|
216
211
|
|
|
217
|
-
if ((await isContentLocalFileMode()) && isLocalDocumentId(id)) {
|
|
218
|
-
const doc = await moveLocalFileDocument(id, args);
|
|
219
|
-
await writeAppState("refresh-signal", { ts: Date.now() });
|
|
220
|
-
return {
|
|
221
|
-
...doc,
|
|
222
|
-
urlPath: `/page/${doc.id}`,
|
|
223
|
-
};
|
|
224
|
-
}
|
|
225
|
-
|
|
226
212
|
const access = await assertAccess("document", id, "editor");
|
|
227
213
|
const existing = access.resource;
|
|
228
214
|
const ownerEmail = existing.ownerEmail as string;
|
|
@@ -243,6 +229,9 @@ export default defineAction({
|
|
|
243
229
|
if (parentAccess.resource.ownerEmail !== ownerEmail) {
|
|
244
230
|
throw new Error("Parent document must belong to the same owner");
|
|
245
231
|
}
|
|
232
|
+
if (parentAccess.resource.spaceId !== existing.spaceId) {
|
|
233
|
+
throw new Error("Parent document must be in the same Content space");
|
|
234
|
+
}
|
|
246
235
|
if (!sameRootSection(parentAccess.resource, existing)) {
|
|
247
236
|
throw new Error("Parent document must be in the same section");
|
|
248
237
|
}
|
|
@@ -5,10 +5,6 @@ import { z } from "zod";
|
|
|
5
5
|
|
|
6
6
|
import "../server/db/index.js";
|
|
7
7
|
import { flushOpenDocumentEditorToSql } from "./_document-flush.js";
|
|
8
|
-
import {
|
|
9
|
-
getLocalFileDocument,
|
|
10
|
-
isContentLocalFileMode,
|
|
11
|
-
} from "./_local-file-documents.js";
|
|
12
8
|
|
|
13
9
|
/**
|
|
14
10
|
* Collab-aware "ingest the final" read for external agents.
|
|
@@ -65,25 +61,6 @@ export default defineAction({
|
|
|
65
61
|
readOnly: true,
|
|
66
62
|
publicAgent: { expose: true, readOnly: true, requiresAuth: true },
|
|
67
63
|
run: async ({ id, format }) => {
|
|
68
|
-
if (await isContentLocalFileMode()) {
|
|
69
|
-
const doc = await getLocalFileDocument(id);
|
|
70
|
-
if (doc.source?.kind === "folder") {
|
|
71
|
-
throw new Error("Folders cannot be pulled as markdown documents");
|
|
72
|
-
}
|
|
73
|
-
return {
|
|
74
|
-
id: doc.id,
|
|
75
|
-
title: doc.title,
|
|
76
|
-
description: doc.description ?? "",
|
|
77
|
-
content: formatDocumentContent(doc.content ?? "", format),
|
|
78
|
-
format,
|
|
79
|
-
deepLink: buildDeepLink({
|
|
80
|
-
app: "content",
|
|
81
|
-
view: "editor",
|
|
82
|
-
params: { documentId: doc.id },
|
|
83
|
-
}),
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
|
|
87
64
|
const access = await resolveAccess("document", id);
|
|
88
65
|
if (!access) throw new Error(`Document "${id}" not found`);
|
|
89
66
|
|
|
@@ -1,18 +1,11 @@
|
|
|
1
1
|
import { defineAction } from "@agent-native/core";
|
|
2
2
|
import { writeAppState } from "@agent-native/core/application-state";
|
|
3
3
|
import { accessFilter, assertAccess } from "@agent-native/core/sharing";
|
|
4
|
-
import { and, eq } from "drizzle-orm";
|
|
4
|
+
import { and, eq, inArray } from "drizzle-orm";
|
|
5
5
|
import { z } from "zod";
|
|
6
6
|
|
|
7
7
|
import { getDb, schema } from "../server/db/index.js";
|
|
8
|
-
|
|
9
|
-
invalidateLocalFileDocumentsCache,
|
|
10
|
-
isContentLocalFileMode,
|
|
11
|
-
removeContentLocalFileRoots,
|
|
12
|
-
} from "./_local-file-documents.js";
|
|
13
|
-
import { deleteDocumentRecursive } from "./delete-document.js";
|
|
14
|
-
|
|
15
|
-
async function removeImportedLocalSourceDocuments(
|
|
8
|
+
async function unlinkImportedLocalSourceDocuments(
|
|
16
9
|
sourceRootPath?: string | null,
|
|
17
10
|
) {
|
|
18
11
|
const db = getDb();
|
|
@@ -33,29 +26,60 @@ async function removeImportedLocalSourceDocuments(
|
|
|
33
26
|
.where(
|
|
34
27
|
and(...clauses, accessFilter(schema.documents, schema.documentShares)),
|
|
35
28
|
);
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
29
|
+
const trackedRows = candidates.length
|
|
30
|
+
? await db
|
|
31
|
+
.select({ documentId: schema.contentDatabaseSourceRows.documentId })
|
|
32
|
+
.from(schema.contentDatabaseSourceRows)
|
|
33
|
+
.innerJoin(
|
|
34
|
+
schema.contentDatabaseSources,
|
|
35
|
+
eq(
|
|
36
|
+
schema.contentDatabaseSources.id,
|
|
37
|
+
schema.contentDatabaseSourceRows.sourceId,
|
|
38
|
+
),
|
|
39
|
+
)
|
|
40
|
+
.where(
|
|
41
|
+
and(
|
|
42
|
+
inArray(
|
|
43
|
+
schema.contentDatabaseSourceRows.documentId,
|
|
44
|
+
candidates.map((document) => document.id),
|
|
45
|
+
),
|
|
46
|
+
eq(schema.contentDatabaseSources.sourceType, "local-folder"),
|
|
47
|
+
),
|
|
48
|
+
)
|
|
49
|
+
: [];
|
|
50
|
+
const trackedDocumentIds = new Set(trackedRows.map((row) => row.documentId));
|
|
51
|
+
const documentIds: string[] = [];
|
|
52
|
+
for (const document of candidates) {
|
|
53
|
+
if (trackedDocumentIds.has(document.id)) continue;
|
|
54
|
+
await assertAccess("document", document.id, "admin");
|
|
55
|
+
documentIds.push(document.id);
|
|
56
|
+
}
|
|
41
57
|
|
|
42
|
-
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
58
|
+
if (documentIds.length > 0) {
|
|
59
|
+
const now = new Date().toISOString();
|
|
60
|
+
await db
|
|
61
|
+
.update(schema.documents)
|
|
62
|
+
.set({
|
|
63
|
+
sourceMode: "database",
|
|
64
|
+
sourceKind: null,
|
|
65
|
+
sourcePath: null,
|
|
66
|
+
sourceRootPath: null,
|
|
67
|
+
sourceUpdatedAt: now,
|
|
68
|
+
updatedAt: now,
|
|
69
|
+
})
|
|
70
|
+
.where(inArray(schema.documents.id, documentIds));
|
|
51
71
|
}
|
|
52
72
|
|
|
53
|
-
return {
|
|
73
|
+
return {
|
|
74
|
+
removed: documentIds.length,
|
|
75
|
+
roots: [] as string[],
|
|
76
|
+
manifestPath: null,
|
|
77
|
+
};
|
|
54
78
|
}
|
|
55
79
|
|
|
56
80
|
export default defineAction({
|
|
57
81
|
description:
|
|
58
|
-
"
|
|
82
|
+
"Unlink legacy local-file source metadata from SQL-backed Content pages without deleting either the pages or files on disk. Use disconnect-local-folder-source for connected folder adapters.",
|
|
59
83
|
schema: z.object({
|
|
60
84
|
sourceRootPath: z
|
|
61
85
|
.string()
|
|
@@ -75,9 +99,7 @@ export default defineAction({
|
|
|
75
99
|
"Unlink local-file entries from Content without deleting local Markdown or MDX files.",
|
|
76
100
|
},
|
|
77
101
|
run: async ({ sourceRootPath }) => {
|
|
78
|
-
const result =
|
|
79
|
-
? await removeContentLocalFileRoots(sourceRootPath)
|
|
80
|
-
: await removeImportedLocalSourceDocuments(sourceRootPath);
|
|
102
|
+
const result = await unlinkImportedLocalSourceDocuments(sourceRootPath);
|
|
81
103
|
|
|
82
104
|
if (result.removed === 0 && result.roots.length === 0) {
|
|
83
105
|
throw new Error(
|
|
@@ -87,7 +109,6 @@ export default defineAction({
|
|
|
87
109
|
);
|
|
88
110
|
}
|
|
89
111
|
|
|
90
|
-
invalidateLocalFileDocumentsCache();
|
|
91
112
|
await writeAppState("refresh-signal", { ts: Date.now() });
|
|
92
113
|
|
|
93
114
|
return {
|
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
import { defineAction } from "@agent-native/core";
|
|
4
|
+
import { writeAppState } from "@agent-native/core/application-state";
|
|
5
|
+
import { assertAccess } from "@agent-native/core/sharing";
|
|
6
|
+
import { and, eq, ne } from "drizzle-orm";
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
|
|
9
|
+
import { getDb, schema } from "../server/db/index.js";
|
|
10
|
+
import { resolveContentSpaceAccess } from "./_content-space-access.js";
|
|
11
|
+
import { LOCAL_FOLDER_SOURCE_TYPE } from "./_local-folder-source.js";
|
|
12
|
+
|
|
13
|
+
const MAX_SOURCE_FILE_BYTES = 2 * 1024 * 1024;
|
|
14
|
+
|
|
15
|
+
function hash(content: string) {
|
|
16
|
+
return createHash("sha256").update(content).digest("hex");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function metadataHash(value: {
|
|
20
|
+
title: string;
|
|
21
|
+
description?: string | null;
|
|
22
|
+
icon?: string | null;
|
|
23
|
+
}) {
|
|
24
|
+
return hash(
|
|
25
|
+
JSON.stringify({
|
|
26
|
+
title: value.title,
|
|
27
|
+
description: value.description ?? "",
|
|
28
|
+
icon: value.icon ?? null,
|
|
29
|
+
}),
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function parseObject(value: string | null | undefined) {
|
|
34
|
+
if (!value) return {} as Record<string, unknown>;
|
|
35
|
+
try {
|
|
36
|
+
const parsed = JSON.parse(value) as unknown;
|
|
37
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed)
|
|
38
|
+
? (parsed as Record<string, unknown>)
|
|
39
|
+
: {};
|
|
40
|
+
} catch {
|
|
41
|
+
return {};
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function proposedMetadata(value: string | null | undefined) {
|
|
46
|
+
if (!value) return {} as Record<string, string | null>;
|
|
47
|
+
try {
|
|
48
|
+
const parsed = JSON.parse(value) as unknown;
|
|
49
|
+
if (!Array.isArray(parsed)) return {};
|
|
50
|
+
return Object.fromEntries(
|
|
51
|
+
parsed.flatMap((change) => {
|
|
52
|
+
if (
|
|
53
|
+
!change ||
|
|
54
|
+
typeof change !== "object" ||
|
|
55
|
+
!("field" in change) ||
|
|
56
|
+
!("proposedValue" in change) ||
|
|
57
|
+
!["title", "description", "icon"].includes(String(change.field))
|
|
58
|
+
) {
|
|
59
|
+
return [];
|
|
60
|
+
}
|
|
61
|
+
const proposed = change.proposedValue;
|
|
62
|
+
return typeof proposed === "string" || proposed === null
|
|
63
|
+
? [[String(change.field), proposed] as const]
|
|
64
|
+
: [];
|
|
65
|
+
}),
|
|
66
|
+
);
|
|
67
|
+
} catch {
|
|
68
|
+
return {};
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export default defineAction({
|
|
73
|
+
description:
|
|
74
|
+
"Resolve an incoming local-folder conflict by keeping Content or accepting the exact reviewed folder revision supplied by the trusted bridge.",
|
|
75
|
+
schema: z.object({
|
|
76
|
+
changeSetId: z.string().min(1),
|
|
77
|
+
decision: z.enum(["keep_content", "accept_source"]),
|
|
78
|
+
sourceContent: z.string().max(MAX_SOURCE_FILE_BYTES).optional(),
|
|
79
|
+
}),
|
|
80
|
+
run: async ({ changeSetId, decision, sourceContent }) => {
|
|
81
|
+
const db = getDb();
|
|
82
|
+
const [target] = await db
|
|
83
|
+
.select({
|
|
84
|
+
changeSet: schema.contentDatabaseSourceChangeSets,
|
|
85
|
+
source: schema.contentDatabaseSources,
|
|
86
|
+
database: schema.contentDatabases,
|
|
87
|
+
document: schema.documents,
|
|
88
|
+
})
|
|
89
|
+
.from(schema.contentDatabaseSourceChangeSets)
|
|
90
|
+
.innerJoin(
|
|
91
|
+
schema.contentDatabaseSources,
|
|
92
|
+
eq(
|
|
93
|
+
schema.contentDatabaseSources.id,
|
|
94
|
+
schema.contentDatabaseSourceChangeSets.sourceId,
|
|
95
|
+
),
|
|
96
|
+
)
|
|
97
|
+
.innerJoin(
|
|
98
|
+
schema.contentDatabases,
|
|
99
|
+
eq(
|
|
100
|
+
schema.contentDatabases.id,
|
|
101
|
+
schema.contentDatabaseSources.databaseId,
|
|
102
|
+
),
|
|
103
|
+
)
|
|
104
|
+
.innerJoin(
|
|
105
|
+
schema.documents,
|
|
106
|
+
eq(
|
|
107
|
+
schema.documents.id,
|
|
108
|
+
schema.contentDatabaseSourceChangeSets.documentId,
|
|
109
|
+
),
|
|
110
|
+
)
|
|
111
|
+
.where(eq(schema.contentDatabaseSourceChangeSets.id, changeSetId));
|
|
112
|
+
if (
|
|
113
|
+
!target ||
|
|
114
|
+
target.source.sourceType !== LOCAL_FOLDER_SOURCE_TYPE ||
|
|
115
|
+
target.changeSet.direction !== "incoming" ||
|
|
116
|
+
target.changeSet.state !== "proposed" ||
|
|
117
|
+
!target.database.spaceId ||
|
|
118
|
+
target.database.systemRole !== "files" ||
|
|
119
|
+
target.database.deletedAt ||
|
|
120
|
+
target.document.spaceId !== target.database.spaceId
|
|
121
|
+
) {
|
|
122
|
+
throw new Error(`Open local-folder conflict "${changeSetId}" not found`);
|
|
123
|
+
}
|
|
124
|
+
const targetSpaceId = target.database.spaceId;
|
|
125
|
+
await resolveContentSpaceAccess(targetSpaceId, "editor");
|
|
126
|
+
await assertAccess("document", target.document.id, "editor");
|
|
127
|
+
const bodyChange = parseObject(target.changeSet.bodyChangeJson);
|
|
128
|
+
const sourceDeletion = bodyChange.operation === "source_delete";
|
|
129
|
+
const proposedHash =
|
|
130
|
+
typeof bodyChange.proposedHash === "string"
|
|
131
|
+
? bodyChange.proposedHash
|
|
132
|
+
: null;
|
|
133
|
+
const reviewedContentHash =
|
|
134
|
+
typeof bodyChange.currentHash === "string"
|
|
135
|
+
? bodyChange.currentHash
|
|
136
|
+
: null;
|
|
137
|
+
const reviewedMetadataHash =
|
|
138
|
+
typeof bodyChange.currentMetadataHash === "string"
|
|
139
|
+
? bodyChange.currentMetadataHash
|
|
140
|
+
: null;
|
|
141
|
+
if (!sourceDeletion && !proposedHash)
|
|
142
|
+
throw new Error("Conflict is missing its source hash");
|
|
143
|
+
if (
|
|
144
|
+
decision === "accept_source" &&
|
|
145
|
+
!sourceDeletion &&
|
|
146
|
+
sourceContent === undefined
|
|
147
|
+
) {
|
|
148
|
+
throw new Error(
|
|
149
|
+
"sourceContent is required when accepting the folder revision",
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
if (
|
|
153
|
+
decision === "accept_source" &&
|
|
154
|
+
!sourceDeletion &&
|
|
155
|
+
hash(sourceContent!) !== proposedHash
|
|
156
|
+
) {
|
|
157
|
+
throw new Error(
|
|
158
|
+
"The supplied folder revision changed after review; refresh before resolving",
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
if (
|
|
162
|
+
decision === "accept_source" &&
|
|
163
|
+
!sourceDeletion &&
|
|
164
|
+
(!reviewedContentHash || !reviewedMetadataHash)
|
|
165
|
+
) {
|
|
166
|
+
throw new Error(
|
|
167
|
+
"Conflict is missing its reviewed Content revision; refresh before resolving",
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const now = new Date().toISOString();
|
|
172
|
+
await db.transaction(async (tx: any) => {
|
|
173
|
+
const claimedChangeSets = await tx
|
|
174
|
+
.update(schema.contentDatabaseSourceChangeSets)
|
|
175
|
+
.set({
|
|
176
|
+
state: decision === "accept_source" ? "applied" : "rejected",
|
|
177
|
+
updatedAt: now,
|
|
178
|
+
})
|
|
179
|
+
.where(
|
|
180
|
+
and(
|
|
181
|
+
eq(schema.contentDatabaseSourceChangeSets.id, changeSetId),
|
|
182
|
+
eq(
|
|
183
|
+
schema.contentDatabaseSourceChangeSets.sourceId,
|
|
184
|
+
target.source.id,
|
|
185
|
+
),
|
|
186
|
+
eq(schema.contentDatabaseSourceChangeSets.state, "proposed"),
|
|
187
|
+
),
|
|
188
|
+
)
|
|
189
|
+
.returning({ id: schema.contentDatabaseSourceChangeSets.id });
|
|
190
|
+
if (claimedChangeSets.length !== 1) {
|
|
191
|
+
throw new Error(
|
|
192
|
+
`Open local-folder conflict "${changeSetId}" changed before resolution`,
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
const claimedSources = await tx
|
|
196
|
+
.update(schema.contentDatabaseSources)
|
|
197
|
+
.set({ updatedAt: now })
|
|
198
|
+
.where(
|
|
199
|
+
and(
|
|
200
|
+
eq(schema.contentDatabaseSources.id, target.source.id),
|
|
201
|
+
eq(schema.contentDatabaseSources.databaseId, target.database.id),
|
|
202
|
+
eq(
|
|
203
|
+
schema.contentDatabaseSources.sourceType,
|
|
204
|
+
LOCAL_FOLDER_SOURCE_TYPE,
|
|
205
|
+
),
|
|
206
|
+
),
|
|
207
|
+
)
|
|
208
|
+
.returning({ id: schema.contentDatabaseSources.id });
|
|
209
|
+
if (claimedSources.length !== 1) {
|
|
210
|
+
throw new Error(
|
|
211
|
+
`Local folder source "${target.source.id}" was disconnected before resolution`,
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
if (decision === "accept_source" && sourceDeletion) {
|
|
215
|
+
await tx
|
|
216
|
+
.delete(schema.contentDatabaseSourceRows)
|
|
217
|
+
.where(
|
|
218
|
+
and(
|
|
219
|
+
eq(schema.contentDatabaseSourceRows.sourceId, target.source.id),
|
|
220
|
+
eq(
|
|
221
|
+
schema.contentDatabaseSourceRows.documentId,
|
|
222
|
+
target.document.id,
|
|
223
|
+
),
|
|
224
|
+
),
|
|
225
|
+
);
|
|
226
|
+
const [remainingLocalRow] = await tx
|
|
227
|
+
.select({
|
|
228
|
+
sourceDisplayKey: schema.contentDatabaseSourceRows.sourceDisplayKey,
|
|
229
|
+
sourceValuesJson: schema.contentDatabaseSourceRows.sourceValuesJson,
|
|
230
|
+
sourceName: schema.contentDatabaseSources.sourceName,
|
|
231
|
+
})
|
|
232
|
+
.from(schema.contentDatabaseSourceRows)
|
|
233
|
+
.innerJoin(
|
|
234
|
+
schema.contentDatabaseSources,
|
|
235
|
+
eq(
|
|
236
|
+
schema.contentDatabaseSources.id,
|
|
237
|
+
schema.contentDatabaseSourceRows.sourceId,
|
|
238
|
+
),
|
|
239
|
+
)
|
|
240
|
+
.where(
|
|
241
|
+
and(
|
|
242
|
+
eq(
|
|
243
|
+
schema.contentDatabaseSourceRows.documentId,
|
|
244
|
+
target.document.id,
|
|
245
|
+
),
|
|
246
|
+
ne(schema.contentDatabaseSourceRows.sourceId, target.source.id),
|
|
247
|
+
eq(
|
|
248
|
+
schema.contentDatabaseSources.sourceType,
|
|
249
|
+
LOCAL_FOLDER_SOURCE_TYPE,
|
|
250
|
+
),
|
|
251
|
+
),
|
|
252
|
+
)
|
|
253
|
+
.orderBy(schema.contentDatabaseSources.id)
|
|
254
|
+
.limit(1);
|
|
255
|
+
const remainingValues = parseObject(
|
|
256
|
+
remainingLocalRow?.sourceValuesJson,
|
|
257
|
+
);
|
|
258
|
+
const remainingPath =
|
|
259
|
+
typeof remainingValues.relativePath === "string"
|
|
260
|
+
? remainingValues.relativePath
|
|
261
|
+
: remainingLocalRow?.sourceDisplayKey;
|
|
262
|
+
await tx
|
|
263
|
+
.update(schema.documents)
|
|
264
|
+
.set(
|
|
265
|
+
remainingLocalRow
|
|
266
|
+
? {
|
|
267
|
+
sourceMode: "local-files",
|
|
268
|
+
sourceKind: "file",
|
|
269
|
+
sourcePath: remainingPath ?? null,
|
|
270
|
+
sourceRootPath: remainingLocalRow.sourceName,
|
|
271
|
+
sourceUpdatedAt: now,
|
|
272
|
+
updatedAt: now,
|
|
273
|
+
}
|
|
274
|
+
: {
|
|
275
|
+
sourceMode: null,
|
|
276
|
+
sourceKind: null,
|
|
277
|
+
sourcePath: null,
|
|
278
|
+
sourceRootPath: null,
|
|
279
|
+
sourceUpdatedAt: null,
|
|
280
|
+
updatedAt: now,
|
|
281
|
+
},
|
|
282
|
+
)
|
|
283
|
+
.where(
|
|
284
|
+
and(
|
|
285
|
+
eq(schema.documents.id, target.document.id),
|
|
286
|
+
eq(schema.documents.spaceId, targetSpaceId),
|
|
287
|
+
),
|
|
288
|
+
);
|
|
289
|
+
} else if (decision === "accept_source") {
|
|
290
|
+
const [currentDocument] = await tx
|
|
291
|
+
.select()
|
|
292
|
+
.from(schema.documents)
|
|
293
|
+
.where(
|
|
294
|
+
and(
|
|
295
|
+
eq(schema.documents.id, target.document.id),
|
|
296
|
+
eq(schema.documents.spaceId, targetSpaceId),
|
|
297
|
+
),
|
|
298
|
+
);
|
|
299
|
+
if (
|
|
300
|
+
!currentDocument ||
|
|
301
|
+
hash(currentDocument.content) !== reviewedContentHash ||
|
|
302
|
+
metadataHash(currentDocument) !== reviewedMetadataHash
|
|
303
|
+
) {
|
|
304
|
+
throw new Error(
|
|
305
|
+
"Content changed after this conflict was reviewed; refresh before resolving",
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
const metadata = proposedMetadata(target.changeSet.fieldChangesJson);
|
|
309
|
+
const resolvedTitle = Object.prototype.hasOwnProperty.call(
|
|
310
|
+
metadata,
|
|
311
|
+
"title",
|
|
312
|
+
)
|
|
313
|
+
? (metadata.title ?? "")
|
|
314
|
+
: currentDocument.title;
|
|
315
|
+
const resolvedDescription = Object.prototype.hasOwnProperty.call(
|
|
316
|
+
metadata,
|
|
317
|
+
"description",
|
|
318
|
+
)
|
|
319
|
+
? (metadata.description ?? "")
|
|
320
|
+
: (currentDocument.description ?? "");
|
|
321
|
+
const resolvedIcon = Object.prototype.hasOwnProperty.call(
|
|
322
|
+
metadata,
|
|
323
|
+
"icon",
|
|
324
|
+
)
|
|
325
|
+
? (metadata.icon ?? null)
|
|
326
|
+
: (currentDocument.icon ?? null);
|
|
327
|
+
await tx
|
|
328
|
+
.insert(schema.documentVersions)
|
|
329
|
+
.values({
|
|
330
|
+
id: `content_document_version_${createHash("sha256")
|
|
331
|
+
.update(
|
|
332
|
+
`${currentDocument.id}:${currentDocument.updatedAt}:${proposedHash}`,
|
|
333
|
+
)
|
|
334
|
+
.digest("hex")
|
|
335
|
+
.slice(0, 32)}`,
|
|
336
|
+
ownerEmail: currentDocument.ownerEmail,
|
|
337
|
+
documentId: currentDocument.id,
|
|
338
|
+
title: currentDocument.title,
|
|
339
|
+
content: currentDocument.content,
|
|
340
|
+
createdAt: now,
|
|
341
|
+
})
|
|
342
|
+
.onConflictDoNothing();
|
|
343
|
+
await tx
|
|
344
|
+
.update(schema.documents)
|
|
345
|
+
.set({
|
|
346
|
+
content: sourceContent!,
|
|
347
|
+
...(Object.prototype.hasOwnProperty.call(metadata, "title")
|
|
348
|
+
? { title: metadata.title ?? "" }
|
|
349
|
+
: {}),
|
|
350
|
+
...(Object.prototype.hasOwnProperty.call(metadata, "description")
|
|
351
|
+
? { description: metadata.description ?? "" }
|
|
352
|
+
: {}),
|
|
353
|
+
...(Object.prototype.hasOwnProperty.call(metadata, "icon")
|
|
354
|
+
? { icon: metadata.icon }
|
|
355
|
+
: {}),
|
|
356
|
+
sourceUpdatedAt: now,
|
|
357
|
+
updatedAt: now,
|
|
358
|
+
})
|
|
359
|
+
.where(eq(schema.documents.id, target.document.id));
|
|
360
|
+
const [sourceRow] = await tx
|
|
361
|
+
.select()
|
|
362
|
+
.from(schema.contentDatabaseSourceRows)
|
|
363
|
+
.where(
|
|
364
|
+
and(
|
|
365
|
+
eq(schema.contentDatabaseSourceRows.sourceId, target.source.id),
|
|
366
|
+
eq(
|
|
367
|
+
schema.contentDatabaseSourceRows.documentId,
|
|
368
|
+
target.document.id,
|
|
369
|
+
),
|
|
370
|
+
),
|
|
371
|
+
);
|
|
372
|
+
if (sourceRow) {
|
|
373
|
+
const values = parseObject(sourceRow.sourceValuesJson);
|
|
374
|
+
await tx
|
|
375
|
+
.update(schema.contentDatabaseSourceRows)
|
|
376
|
+
.set({
|
|
377
|
+
sourceValuesJson: JSON.stringify({
|
|
378
|
+
...values,
|
|
379
|
+
contentHash: proposedHash,
|
|
380
|
+
metadataHash: hash(
|
|
381
|
+
JSON.stringify({
|
|
382
|
+
title: resolvedTitle,
|
|
383
|
+
description: resolvedDescription,
|
|
384
|
+
icon: resolvedIcon,
|
|
385
|
+
}),
|
|
386
|
+
),
|
|
387
|
+
}),
|
|
388
|
+
syncState: "linked",
|
|
389
|
+
freshness: "fresh",
|
|
390
|
+
lastSyncedAt: now,
|
|
391
|
+
lastSourceUpdatedAt: now,
|
|
392
|
+
updatedAt: now,
|
|
393
|
+
})
|
|
394
|
+
.where(eq(schema.contentDatabaseSourceRows.id, sourceRow.id));
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
const remaining = await tx
|
|
398
|
+
.select({ id: schema.contentDatabaseSourceChangeSets.id })
|
|
399
|
+
.from(schema.contentDatabaseSourceChangeSets)
|
|
400
|
+
.where(
|
|
401
|
+
and(
|
|
402
|
+
eq(
|
|
403
|
+
schema.contentDatabaseSourceChangeSets.sourceId,
|
|
404
|
+
target.source.id,
|
|
405
|
+
),
|
|
406
|
+
eq(schema.contentDatabaseSourceChangeSets.direction, "incoming"),
|
|
407
|
+
eq(schema.contentDatabaseSourceChangeSets.state, "proposed"),
|
|
408
|
+
),
|
|
409
|
+
);
|
|
410
|
+
const refreshedSources = await tx
|
|
411
|
+
.update(schema.contentDatabaseSources)
|
|
412
|
+
.set({
|
|
413
|
+
syncState: remaining.length ? "error" : "linked",
|
|
414
|
+
freshness:
|
|
415
|
+
remaining.length || decision === "keep_content" ? "stale" : "fresh",
|
|
416
|
+
lastError: remaining.length
|
|
417
|
+
? `${remaining.length} file conflict${remaining.length === 1 ? "" : "s"} require review.`
|
|
418
|
+
: decision === "accept_source"
|
|
419
|
+
? null
|
|
420
|
+
: "Content was kept; push the retained revision to the folder when ready.",
|
|
421
|
+
updatedAt: now,
|
|
422
|
+
})
|
|
423
|
+
.where(eq(schema.contentDatabaseSources.id, target.source.id))
|
|
424
|
+
.returning({ id: schema.contentDatabaseSources.id });
|
|
425
|
+
if (refreshedSources.length !== 1) {
|
|
426
|
+
throw new Error(
|
|
427
|
+
`Local folder source "${target.source.id}" was disconnected during resolution`,
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
});
|
|
431
|
+
await writeAppState("refresh-signal", { ts: Date.now() });
|
|
432
|
+
return {
|
|
433
|
+
success: true,
|
|
434
|
+
changeSetId,
|
|
435
|
+
decision,
|
|
436
|
+
documentId: target.document.id,
|
|
437
|
+
sourceId: target.source.id,
|
|
438
|
+
sourceDeleted: sourceDeletion,
|
|
439
|
+
state: decision === "accept_source" ? "applied" : "rejected",
|
|
440
|
+
};
|
|
441
|
+
},
|
|
442
|
+
});
|