@agent-native/core 0.120.3 → 0.120.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +8 -0
- package/corpus/core/package.json +1 -1
- package/corpus/templates/content/actions/update-document.ts +145 -91
- package/corpus/templates/content/app/components/editor/CommentComposer.tsx +4 -1
- package/corpus/templates/content/app/components/editor/CommentsSidebar.tsx +366 -164
- package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +51 -28
- package/corpus/templates/content/app/components/editor/database/sidebar.tsx +19 -8
- package/corpus/templates/content/app/i18n-data.ts +15 -0
- package/corpus/templates/content/changelog/2026-07-23-shared-pages-now-keep-durable-content-visible-and-comment-th.md +6 -0
- package/corpus/templates/content/changelog/2026-07-24-comment-drafts-now-stay-open-when-saving-fails-and-long-live.md +6 -0
- package/corpus/templates/design/actions/update-file.ts +85 -18
- package/corpus/templates/design/app/lib/design-save-outbox.ts +39 -0
- package/corpus/toolkit/CHANGELOG.md +6 -0
- package/corpus/toolkit/package.json +1 -1
- package/corpus/toolkit/src/editor/useCollabReconcile.ts +83 -15
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/secrets/routes.d.ts +9 -9
- package/package.json +2 -2
package/corpus/README.md
CHANGED
package/corpus/core/CHANGELOG.md
CHANGED
package/corpus/core/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-native/core",
|
|
3
|
-
"version": "0.120.
|
|
3
|
+
"version": "0.120.4",
|
|
4
4
|
"description": "Framework for agent-native application development — where AI agents and UI share SQL state, actions, and context",
|
|
5
5
|
"homepage": "https://github.com/BuilderIO/agent-native#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -40,6 +40,33 @@ export interface DocumentUpdateConflictResponse {
|
|
|
40
40
|
document: DocumentUpdateResponse;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
const documentAuditOwner = Symbol("documentAuditOwner");
|
|
44
|
+
|
|
45
|
+
type DocumentAuditScopedResult = {
|
|
46
|
+
[documentAuditOwner]?: string;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
function scopeDocumentAudit<T extends object>(result: T, ownerEmail: string) {
|
|
50
|
+
Object.defineProperty(result, documentAuditOwner, { value: ownerEmail });
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function isFavoriteOnlyUpdate(args: {
|
|
55
|
+
isFavorite?: boolean;
|
|
56
|
+
title?: string;
|
|
57
|
+
content?: string;
|
|
58
|
+
description?: string;
|
|
59
|
+
icon?: string | null;
|
|
60
|
+
}) {
|
|
61
|
+
return (
|
|
62
|
+
args.isFavorite !== undefined &&
|
|
63
|
+
args.title === undefined &&
|
|
64
|
+
args.content === undefined &&
|
|
65
|
+
args.description === undefined &&
|
|
66
|
+
args.icon === undefined
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
43
70
|
function nanoid(size = 12): string {
|
|
44
71
|
const chars =
|
|
45
72
|
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
|
@@ -301,6 +328,29 @@ export default defineAction({
|
|
|
301
328
|
.default([])
|
|
302
329
|
.describe("Exact item versions that influenced this agent update."),
|
|
303
330
|
}),
|
|
331
|
+
audit: {
|
|
332
|
+
// Document bodies and personal favorite preferences are both sensitive.
|
|
333
|
+
// Keep actor/target/outcome attribution without copying mutation payloads
|
|
334
|
+
// into an owner-visible audit row.
|
|
335
|
+
recordInputs: false,
|
|
336
|
+
target: (args, result) => {
|
|
337
|
+
const favoriteOnly = isFavoriteOnlyUpdate(args);
|
|
338
|
+
return {
|
|
339
|
+
type: "document",
|
|
340
|
+
id: args.id,
|
|
341
|
+
// Favorites are a private preference owned by the actor, even when
|
|
342
|
+
// the underlying document belongs to somebody else.
|
|
343
|
+
ownerEmail: favoriteOnly
|
|
344
|
+
? undefined
|
|
345
|
+
: (result as DocumentAuditScopedResult | null)?.[documentAuditOwner],
|
|
346
|
+
visibility: "private",
|
|
347
|
+
};
|
|
348
|
+
},
|
|
349
|
+
summary: (args, result) =>
|
|
350
|
+
(result as DocumentUpdateConflictResponse | null)?.conflict
|
|
351
|
+
? `Document update conflicted for ${args.id}`
|
|
352
|
+
: `Updated document ${args.id}`,
|
|
353
|
+
},
|
|
304
354
|
run: async (
|
|
305
355
|
args,
|
|
306
356
|
ctx,
|
|
@@ -315,12 +365,7 @@ export default defineAction({
|
|
|
315
365
|
const isAgentCaller =
|
|
316
366
|
ctx?.caller === "tool" || ctx?.caller === "mcp" || ctx?.caller === "a2a";
|
|
317
367
|
|
|
318
|
-
const favoriteOnly =
|
|
319
|
-
args.isFavorite !== undefined &&
|
|
320
|
-
args.title === undefined &&
|
|
321
|
-
args.content === undefined &&
|
|
322
|
-
args.description === undefined &&
|
|
323
|
-
args.icon === undefined;
|
|
368
|
+
const favoriteOnly = isFavoriteOnlyUpdate(args);
|
|
324
369
|
const access = favoriteOnly
|
|
325
370
|
? await resolveContentDocumentAccess(id)
|
|
326
371
|
: await assertAccess("document", id, "editor");
|
|
@@ -411,6 +456,11 @@ export default defineAction({
|
|
|
411
456
|
args.title !== undefined && args.title !== existing.title;
|
|
412
457
|
const contentChanged =
|
|
413
458
|
content !== undefined && content !== existing.content;
|
|
459
|
+
const clearsNonEmptyContent =
|
|
460
|
+
contentChanged &&
|
|
461
|
+
content !== undefined &&
|
|
462
|
+
isEffectivelyEmptyDocumentContent(content) &&
|
|
463
|
+
!isEffectivelyEmptyDocumentContent(existing.content);
|
|
414
464
|
const iconChanged = args.icon !== undefined && args.icon !== existing.icon;
|
|
415
465
|
const favoriteChanged =
|
|
416
466
|
args.isFavorite !== undefined && args.isFavorite !== currentFavorite;
|
|
@@ -426,40 +476,6 @@ export default defineAction({
|
|
|
426
476
|
favoriteChanged ||
|
|
427
477
|
descriptionChanged;
|
|
428
478
|
|
|
429
|
-
// Snapshot the current state before applying content/title changes.
|
|
430
|
-
// Versions are scoped to the document owner, not the caller — an editor
|
|
431
|
-
// share collaborator shouldn't create a phantom version row under their
|
|
432
|
-
// own email.
|
|
433
|
-
if (titleChanged || contentChanged) {
|
|
434
|
-
const [latestVersion] = await db
|
|
435
|
-
.select({ createdAt: schema.documentVersions.createdAt })
|
|
436
|
-
.from(schema.documentVersions)
|
|
437
|
-
.where(
|
|
438
|
-
and(
|
|
439
|
-
eq(schema.documentVersions.documentId, id),
|
|
440
|
-
eq(schema.documentVersions.ownerEmail, ownerEmail),
|
|
441
|
-
),
|
|
442
|
-
)
|
|
443
|
-
.orderBy(desc(schema.documentVersions.createdAt))
|
|
444
|
-
.limit(1);
|
|
445
|
-
|
|
446
|
-
const shouldSnapshot =
|
|
447
|
-
!latestVersion ||
|
|
448
|
-
Date.now() - new Date(latestVersion.createdAt).getTime() >
|
|
449
|
-
SNAPSHOT_INTERVAL_MS;
|
|
450
|
-
|
|
451
|
-
if (shouldSnapshot) {
|
|
452
|
-
await db.insert(schema.documentVersions).values({
|
|
453
|
-
id: nanoid(),
|
|
454
|
-
ownerEmail,
|
|
455
|
-
documentId: id,
|
|
456
|
-
title: existing.title,
|
|
457
|
-
content: existing.content,
|
|
458
|
-
createdAt: new Date().toISOString(),
|
|
459
|
-
});
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
|
|
463
479
|
let softDeletedDatabaseIds: string[] = [];
|
|
464
480
|
let creativeContext:
|
|
465
481
|
| Awaited<ReturnType<typeof documentMutationCreativeContext>>
|
|
@@ -507,6 +523,36 @@ export default defineAction({
|
|
|
507
523
|
.where(eq(schema.documents.id, id));
|
|
508
524
|
}
|
|
509
525
|
|
|
526
|
+
if (titleChanged || contentChanged) {
|
|
527
|
+
const [latestVersion] = await tx
|
|
528
|
+
.select({ createdAt: schema.documentVersions.createdAt })
|
|
529
|
+
.from(schema.documentVersions)
|
|
530
|
+
.where(
|
|
531
|
+
and(
|
|
532
|
+
eq(schema.documentVersions.documentId, id),
|
|
533
|
+
eq(schema.documentVersions.ownerEmail, ownerEmail),
|
|
534
|
+
),
|
|
535
|
+
)
|
|
536
|
+
.orderBy(desc(schema.documentVersions.createdAt))
|
|
537
|
+
.limit(1);
|
|
538
|
+
const shouldSnapshot =
|
|
539
|
+
clearsNonEmptyContent ||
|
|
540
|
+
!latestVersion ||
|
|
541
|
+
Date.now() - new Date(latestVersion.createdAt).getTime() >
|
|
542
|
+
SNAPSHOT_INTERVAL_MS;
|
|
543
|
+
|
|
544
|
+
if (shouldSnapshot) {
|
|
545
|
+
await tx.insert(schema.documentVersions).values({
|
|
546
|
+
id: nanoid(),
|
|
547
|
+
ownerEmail,
|
|
548
|
+
documentId: id,
|
|
549
|
+
title: existing.title,
|
|
550
|
+
content: existing.content,
|
|
551
|
+
createdAt: new Date().toISOString(),
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
510
556
|
if (favoriteChanged) {
|
|
511
557
|
await setFavoriteMembership({
|
|
512
558
|
db: tx,
|
|
@@ -580,30 +626,35 @@ export default defineAction({
|
|
|
580
626
|
.select()
|
|
581
627
|
.from(schema.documents)
|
|
582
628
|
.where(eq(schema.documents.id, id));
|
|
583
|
-
return
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
629
|
+
return scopeDocumentAudit(
|
|
630
|
+
{
|
|
631
|
+
conflict: true,
|
|
632
|
+
id,
|
|
633
|
+
document: {
|
|
634
|
+
id: current.id,
|
|
635
|
+
urlPath: `/page/${current.id}`,
|
|
636
|
+
parentId: current.parentId,
|
|
637
|
+
title: current.title,
|
|
638
|
+
content: current.content,
|
|
639
|
+
description: current.description,
|
|
640
|
+
icon: current.icon,
|
|
641
|
+
position: current.position,
|
|
642
|
+
isFavorite: currentFavorite,
|
|
643
|
+
hideFromSearch: parseDocumentHideFromSearch(
|
|
644
|
+
current.hideFromSearch,
|
|
645
|
+
),
|
|
646
|
+
visibility: current.visibility,
|
|
647
|
+
accessRole: access.role,
|
|
648
|
+
canEdit: canEditRole(access.role),
|
|
649
|
+
canManage: canManageRole(access.role),
|
|
650
|
+
createdAt: current.createdAt,
|
|
651
|
+
updatedAt: current.updatedAt,
|
|
652
|
+
source: serializeDocumentSource(current),
|
|
653
|
+
softDeletedDatabaseIds: [],
|
|
654
|
+
},
|
|
655
|
+
} satisfies DocumentUpdateConflictResponse,
|
|
656
|
+
ownerEmail,
|
|
657
|
+
);
|
|
607
658
|
}
|
|
608
659
|
|
|
609
660
|
if (contentChanged) {
|
|
@@ -665,32 +716,35 @@ export default defineAction({
|
|
|
665
716
|
|
|
666
717
|
await writeAppState("refresh-signal", { ts: Date.now() });
|
|
667
718
|
|
|
668
|
-
return
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
719
|
+
return scopeDocumentAudit(
|
|
720
|
+
{
|
|
721
|
+
id: doc.id,
|
|
722
|
+
urlPath: `/page/${doc.id}`,
|
|
723
|
+
parentId: doc.parentId,
|
|
724
|
+
title: doc.title,
|
|
725
|
+
content: doc.content,
|
|
726
|
+
description: doc.description,
|
|
727
|
+
icon: doc.icon,
|
|
728
|
+
position: doc.position,
|
|
729
|
+
isFavorite: finalFavorite,
|
|
730
|
+
hideFromSearch: parseDocumentHideFromSearch(doc.hideFromSearch),
|
|
731
|
+
visibility: doc.visibility,
|
|
732
|
+
accessRole: access.role,
|
|
733
|
+
canEdit: canEditRole(access.role),
|
|
734
|
+
canManage: canManageRole(access.role),
|
|
735
|
+
createdAt: doc.createdAt,
|
|
736
|
+
updatedAt: doc.updatedAt,
|
|
737
|
+
source: serializeDocumentSource(doc),
|
|
738
|
+
softDeletedDatabaseIds,
|
|
739
|
+
...(creativeContext
|
|
740
|
+
? {
|
|
741
|
+
contextMode: creativeContext.contextMode,
|
|
742
|
+
contextPackId: creativeContext.contextPackId,
|
|
743
|
+
reuseLabels: creativeContext.reuseLabels,
|
|
744
|
+
}
|
|
745
|
+
: {}),
|
|
746
|
+
} satisfies DocumentUpdateResponse,
|
|
747
|
+
ownerEmail,
|
|
748
|
+
);
|
|
695
749
|
},
|
|
696
750
|
});
|
|
@@ -28,6 +28,7 @@ interface CommentComposerProps {
|
|
|
28
28
|
members: MentionMember[];
|
|
29
29
|
placeholder?: string;
|
|
30
30
|
autoFocus?: boolean;
|
|
31
|
+
disabled?: boolean;
|
|
31
32
|
rows?: number;
|
|
32
33
|
className?: string;
|
|
33
34
|
}
|
|
@@ -52,6 +53,7 @@ export const CommentComposer = forwardRef<
|
|
|
52
53
|
members,
|
|
53
54
|
placeholder,
|
|
54
55
|
autoFocus,
|
|
56
|
+
disabled = false,
|
|
55
57
|
rows = 2,
|
|
56
58
|
className,
|
|
57
59
|
},
|
|
@@ -158,6 +160,7 @@ export const CommentComposer = forwardRef<
|
|
|
158
160
|
<textarea
|
|
159
161
|
ref={setRefs}
|
|
160
162
|
value={value}
|
|
163
|
+
disabled={disabled}
|
|
161
164
|
rows={rows}
|
|
162
165
|
onChange={(e) => {
|
|
163
166
|
onChange(e.target.value);
|
|
@@ -177,7 +180,7 @@ export const CommentComposer = forwardRef<
|
|
|
177
180
|
"w-full resize-none bg-transparent text-sm placeholder:text-muted-foreground focus:outline-none"
|
|
178
181
|
}
|
|
179
182
|
/>
|
|
180
|
-
{menuOpen && (
|
|
183
|
+
{!disabled && menuOpen && (
|
|
181
184
|
<div className="absolute left-0 right-0 top-full z-20 mt-1 overflow-hidden rounded-md border border-border bg-popover py-1 shadow-md">
|
|
182
185
|
{filtered.map((member, i) => (
|
|
183
186
|
<button
|