@agent-native/core 0.114.0 → 0.114.2
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 +2 -2
- package/corpus/core/CHANGELOG.md +15 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/cli/create.ts +23 -0
- package/corpus/core/src/cli/skills.ts +24 -0
- package/corpus/core/src/client/build-compatibility.ts +77 -0
- package/corpus/core/src/client/route-chunk-recovery/index.ts +1 -0
- package/corpus/core/src/client/use-action.ts +32 -1
- package/corpus/core/src/deploy/build.ts +4 -1
- package/corpus/core/src/org/context.ts +56 -30
- package/corpus/core/src/server/action-routes.ts +52 -2
- package/corpus/core/src/server/auth-plugin.ts +9 -4
- package/corpus/core/src/server/better-auth-instance.ts +5 -1
- package/corpus/core/src/shared/mcp-embed-headers.ts +1 -1
- package/corpus/core/src/templates/default/.agents/skills/agent-native-toolkit/SKILL.md +133 -0
- package/corpus/core/src/templates/default/AGENTS.md +4 -1
- package/corpus/core/src/templates/headless/.agents/skills/agent-native-toolkit/SKILL.md +133 -0
- package/corpus/core/src/templates/headless/AGENTS.md +5 -1
- package/corpus/core/src/templates/workspace-core/.agents/skills/agent-native-toolkit/SKILL.md +26 -0
- package/corpus/core/src/templates/workspace-core/AGENTS.md +3 -1
- package/corpus/core/src/templates/workspace-root/AGENTS.md +3 -1
- package/corpus/core/src/vite/client.ts +17 -0
- package/corpus/templates/analytics/.agents/skills/agent-native-toolkit/SKILL.md +133 -0
- package/corpus/templates/analytics/AGENTS.md +5 -0
- package/corpus/templates/assets/.agents/skills/agent-native-toolkit/SKILL.md +133 -0
- package/corpus/templates/assets/AGENTS.md +5 -0
- package/corpus/templates/brain/.agents/skills/agent-native-toolkit/SKILL.md +133 -0
- package/corpus/templates/brain/AGENTS.md +5 -0
- package/corpus/templates/calendar/.agents/skills/agent-native-toolkit/SKILL.md +133 -0
- package/corpus/templates/calendar/AGENTS.md +5 -0
- package/corpus/templates/chat/.agents/skills/agent-native-toolkit/SKILL.md +133 -0
- package/corpus/templates/chat/AGENTS.md +4 -1
- package/corpus/templates/clips/.agents/skills/agent-native-toolkit/SKILL.md +133 -0
- package/corpus/templates/clips/AGENTS.md +5 -0
- package/corpus/templates/content/.agents/skills/agent-native-toolkit/SKILL.md +133 -0
- package/corpus/templates/content/AGENTS.md +7 -0
- package/corpus/templates/content/actions/_content-database-personal-view.ts +49 -5
- package/corpus/templates/content/actions/_content-document-access.ts +21 -0
- package/corpus/templates/content/actions/_content-favorites.ts +91 -0
- package/corpus/templates/content/actions/_content-files.ts +79 -23
- package/corpus/templates/content/actions/_content-sidebar-state.ts +24 -0
- package/corpus/templates/content/actions/_content-space-access.ts +15 -17
- package/corpus/templates/content/actions/_content-space-catalog-guards.ts +20 -0
- package/corpus/templates/content/actions/_content-spaces.ts +364 -167
- package/corpus/templates/content/actions/_database-source-utils.ts +8 -0
- package/corpus/templates/content/actions/_database-utils.ts +249 -15
- package/corpus/templates/content/actions/_delete-content-space.ts +84 -0
- package/corpus/templates/content/actions/_files-system-properties.ts +499 -0
- package/corpus/templates/content/actions/_local-file-documents.ts +0 -1
- package/corpus/templates/content/actions/_property-utils.ts +18 -9
- package/corpus/templates/content/actions/bind-content-database-source-field.ts +3 -0
- package/corpus/templates/content/actions/configure-document-property.ts +3 -0
- package/corpus/templates/content/actions/create-content-database.ts +2 -2
- package/corpus/templates/content/actions/create-content-space.ts +34 -0
- package/corpus/templates/content/actions/create-document.ts +4 -1
- package/corpus/templates/content/actions/delete-content-space.ts +19 -0
- package/corpus/templates/content/actions/delete-database-items.ts +28 -1
- package/corpus/templates/content/actions/delete-document-property.ts +3 -0
- package/corpus/templates/content/actions/delete-document.ts +39 -2
- package/corpus/templates/content/actions/duplicate-database-item.ts +6 -0
- package/corpus/templates/content/actions/duplicate-database-items.ts +6 -0
- package/corpus/templates/content/actions/duplicate-document-property.ts +3 -0
- package/corpus/templates/content/actions/get-content-database.ts +11 -2
- package/corpus/templates/content/actions/get-content-sidebar-state.ts +23 -0
- package/corpus/templates/content/actions/get-document.ts +32 -7
- package/corpus/templates/content/actions/import-content-source.ts +33 -1
- package/corpus/templates/content/actions/list-content-spaces.ts +34 -2
- package/corpus/templates/content/actions/list-documents.ts +39 -7
- package/corpus/templates/content/actions/reorder-document-property.ts +9 -0
- package/corpus/templates/content/actions/set-document-property.ts +41 -23
- package/corpus/templates/content/actions/share-local-file-document.ts +15 -0
- package/corpus/templates/content/actions/submit-content-database-form.ts +7 -0
- package/corpus/templates/content/actions/sync-local-folder-source.ts +14 -0
- package/corpus/templates/content/actions/update-content-database-view.ts +1 -0
- package/corpus/templates/content/actions/update-content-sidebar-state.ts +27 -0
- package/corpus/templates/content/actions/update-document.ts +158 -66
- package/corpus/templates/content/app/components/EmptyState.tsx +4 -57
- package/corpus/templates/content/app/components/editor/CommentsSidebar.tsx +8 -1
- package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +519 -178
- package/corpus/templates/content/app/components/editor/DocumentInfoPanel.tsx +39 -0
- package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +1 -1
- package/corpus/templates/content/app/components/editor/DocumentToolbar.tsx +266 -7
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +430 -152
- package/corpus/templates/content/app/components/editor/database/sidebar.tsx +467 -66
- package/corpus/templates/content/app/components/editor/database/view-config.ts +9 -3
- package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +720 -196
- package/corpus/templates/content/app/components/sidebar/DocumentTreeItem.tsx +173 -1
- package/corpus/templates/content/app/components/sidebar/document-sidebar-sections.ts +1 -1
- package/corpus/templates/content/app/components/sidebar/select-content-space.ts +91 -16
- package/corpus/templates/content/app/entry.client.tsx +5 -1
- package/corpus/templates/content/app/hooks/use-content-database.ts +84 -3
- package/corpus/templates/content/app/hooks/use-content-spaces.ts +60 -2
- package/corpus/templates/content/app/hooks/use-create-page.ts +30 -0
- package/corpus/templates/content/app/hooks/use-documents.ts +270 -19
- package/corpus/templates/content/app/i18n-data.ts +243 -20
- package/corpus/templates/content/app/lib/local-folder-picker-safety.ts +54 -0
- package/corpus/templates/content/app/lib/local-folder-picker-support.ts +18 -0
- package/corpus/templates/content/app/routes/_app.favorites.tsx +30 -0
- package/corpus/templates/content/app/routes/_app.local-files.tsx +18 -6
- package/corpus/templates/content/changelog/2026-07-17-workspace-files-now-stay-in-a-loading-state-until-automatic-.md +6 -0
- package/corpus/templates/content/changelog/2026-07-17-workspace-switching-now-opens-the-selected-files-database-pr.md +6 -0
- package/corpus/templates/content/changelog/2026-07-17-workspaces-now-expand-above-their-own-files-list-without-dup.md +6 -0
- package/corpus/templates/content/changelog/2026-07-18-create-named-workspaces-each-with-its-own-private-files-data.md +6 -0
- package/corpus/templates/content/changelog/2026-07-18-favorites-align-with-workspace-and-file-rows-in-the-sidebar.md +6 -0
- package/corpus/templates/content/changelog/2026-07-18-local-folder-selection-no-longer-invokes-the-unsafe-native-p.md +6 -0
- package/corpus/templates/content/changelog/2026-07-18-made-content-workspaces-independently-expandable-restored-or.md +6 -0
- package/corpus/templates/content/changelog/2026-07-18-page-details-now-live-in-a-shared-info-and-comments-rail-dat.md +6 -0
- package/corpus/templates/content/changelog/2026-07-18-workspace-database-views-now-control-sidebar-workspace-navig.md +6 -0
- package/corpus/templates/content/changelog/2026-07-18-workspace-navigation-now-aligns-favorites-with-workspace-pag.md +6 -0
- package/corpus/templates/content/changelog/2026-07-19-favorites-now-open-as-a-table-and-compact-breadcrumbs-make-w.md +6 -0
- package/corpus/templates/content/changelog/2026-07-19-files-databases-now-start-unfiltered-sidebar-rows-exactly-fo.md +6 -0
- package/corpus/templates/content/changelog/2026-07-19-sidebar-page-trees-now-open-only-when-expanded-remember-thei.md +6 -0
- package/corpus/templates/content/changelog/2026-07-19-workspace-file-databases-now-use-the-workspace-name-open-as-.md +6 -0
- package/corpus/templates/content/changelog/2026-07-19-workspace-navigation-now-stays-stable-on-hover-uses-folder-i.md +6 -0
- package/corpus/templates/content/changelog/2026-07-19-workspace-rows-now-use-folder-icons-and-deleting-a-user-crea.md +6 -0
- package/corpus/templates/content/changelog/2026-07-19-workspaces-can-be-created-without-leaving-the-workspaces-dat.md +6 -0
- package/corpus/templates/content/e2e/global-setup.ts +3 -2
- package/corpus/templates/content/parity/matrix.md +28 -28
- package/corpus/templates/content/parity/matrix.ts +8 -2
- package/corpus/templates/content/server/db/index.ts +1 -0
- package/corpus/templates/content/server/db/schema.ts +10 -0
- package/corpus/templates/content/server/lib/documents.ts +12 -7
- package/corpus/templates/content/server/plugins/db.ts +17 -0
- package/corpus/templates/content/shared/api.ts +9 -0
- package/corpus/templates/content/shared/database-form.ts +11 -2
- package/corpus/templates/content/vite.config.ts +1 -0
- package/corpus/templates/design/.agents/skills/agent-native-toolkit/SKILL.md +133 -0
- package/corpus/templates/design/AGENTS.md +5 -0
- package/corpus/templates/dispatch/.agents/skills/agent-native-toolkit/SKILL.md +133 -0
- package/corpus/templates/dispatch/AGENTS.md +5 -0
- package/corpus/templates/forms/.agents/skills/agent-native-toolkit/SKILL.md +133 -0
- package/corpus/templates/forms/AGENTS.md +5 -0
- package/corpus/templates/macros/.agents/skills/agent-native-toolkit/SKILL.md +133 -0
- package/corpus/templates/macros/AGENTS.md +5 -0
- package/corpus/templates/mail/.agents/skills/agent-native-toolkit/SKILL.md +133 -0
- package/corpus/templates/mail/AGENTS.md +5 -0
- package/corpus/templates/plan/.agents/skills/agent-native-toolkit/SKILL.md +133 -0
- package/corpus/templates/plan/AGENTS.md +5 -0
- package/corpus/templates/slides/.agents/skills/agent-native-toolkit/SKILL.md +133 -0
- package/corpus/templates/slides/AGENTS.md +5 -0
- package/corpus/templates/tasks/.agents/skills/agent-native-toolkit/SKILL.md +133 -0
- package/corpus/templates/tasks/AGENTS.md +5 -0
- package/dist/cli/create.d.ts.map +1 -1
- package/dist/cli/create.js +21 -0
- package/dist/cli/create.js.map +1 -1
- package/dist/cli/skills.d.ts.map +1 -1
- package/dist/cli/skills.js +19 -0
- package/dist/cli/skills.js.map +1 -1
- package/dist/client/build-compatibility.d.ts +11 -0
- package/dist/client/build-compatibility.d.ts.map +1 -0
- package/dist/client/build-compatibility.js +56 -0
- package/dist/client/build-compatibility.js.map +1 -0
- package/dist/client/route-chunk-recovery/index.d.ts +1 -0
- package/dist/client/route-chunk-recovery/index.d.ts.map +1 -1
- package/dist/client/route-chunk-recovery/index.js +1 -0
- package/dist/client/route-chunk-recovery/index.js.map +1 -1
- package/dist/client/use-action.d.ts.map +1 -1
- package/dist/client/use-action.js +19 -1
- package/dist/client/use-action.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/deploy/build.js +2 -1
- package/dist/deploy/build.js.map +1 -1
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/observability/routes.d.ts +3 -3
- package/dist/org/context.d.ts +4 -2
- package/dist/org/context.d.ts.map +1 -1
- package/dist/org/context.js +44 -22
- package/dist/org/context.js.map +1 -1
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +10 -10
- package/dist/provider-api/actions/provider-api.d.ts +12 -12
- package/dist/provider-api/corpus-jobs.d.ts +2 -2
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/server/action-routes.d.ts.map +1 -1
- package/dist/server/action-routes.js +32 -2
- package/dist/server/action-routes.js.map +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/auth-plugin.d.ts.map +1 -1
- package/dist/server/auth-plugin.js +9 -5
- package/dist/server/auth-plugin.js.map +1 -1
- package/dist/server/better-auth-instance.d.ts.map +1 -1
- package/dist/server/better-auth-instance.js +4 -1
- package/dist/server/better-auth-instance.js.map +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/shared/mcp-embed-headers.d.ts +1 -1
- package/dist/shared/mcp-embed-headers.d.ts.map +1 -1
- package/dist/shared/mcp-embed-headers.js +1 -1
- package/dist/shared/mcp-embed-headers.js.map +1 -1
- package/dist/templates/chat/.agents/skills/agent-native-toolkit/SKILL.md +133 -0
- package/dist/templates/chat/AGENTS.md +4 -1
- package/dist/templates/default/.agents/skills/agent-native-toolkit/SKILL.md +133 -0
- package/dist/templates/default/AGENTS.md +4 -1
- package/dist/templates/headless/.agents/skills/agent-native-toolkit/SKILL.md +133 -0
- package/dist/templates/headless/AGENTS.md +5 -1
- package/dist/templates/workspace-core/.agents/skills/agent-native-toolkit/SKILL.md +26 -0
- package/dist/templates/workspace-core/AGENTS.md +3 -1
- package/dist/templates/workspace-root/AGENTS.md +3 -1
- package/dist/vite/client.d.ts +6 -0
- package/dist/vite/client.d.ts.map +1 -1
- package/dist/vite/client.js +8 -0
- package/dist/vite/client.js.map +1 -1
- package/package.json +1 -1
- package/src/cli/create.ts +23 -0
- package/src/cli/skills.ts +24 -0
- package/src/client/build-compatibility.ts +77 -0
- package/src/client/route-chunk-recovery/index.ts +1 -0
- package/src/client/use-action.ts +32 -1
- package/src/deploy/build.ts +4 -1
- package/src/org/context.ts +56 -30
- package/src/server/action-routes.ts +52 -2
- package/src/server/auth-plugin.ts +9 -4
- package/src/server/better-auth-instance.ts +5 -1
- package/src/shared/mcp-embed-headers.ts +1 -1
- package/src/templates/chat/.agents/skills/agent-native-toolkit/SKILL.md +133 -0
- package/src/templates/chat/AGENTS.md +4 -1
- package/src/templates/default/.agents/skills/agent-native-toolkit/SKILL.md +133 -0
- package/src/templates/default/AGENTS.md +4 -1
- package/src/templates/headless/.agents/skills/agent-native-toolkit/SKILL.md +133 -0
- package/src/templates/headless/AGENTS.md +5 -1
- package/src/templates/workspace-core/.agents/skills/agent-native-toolkit/SKILL.md +26 -0
- package/src/templates/workspace-core/AGENTS.md +3 -1
- package/src/templates/workspace-root/AGENTS.md +3 -1
- package/src/vite/client.ts +17 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: agent-native-toolkit
|
|
3
|
+
description: >-
|
|
4
|
+
Inventory and ownership rules for shared Agent Native workspace UI. Use
|
|
5
|
+
before building app chrome, settings, navigation, sharing, collaboration,
|
|
6
|
+
setup, history, comments, chat rails, agent UX, or repeated workspace behavior.
|
|
7
|
+
scope: dev
|
|
8
|
+
metadata:
|
|
9
|
+
internal: true
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Agent-Native Toolkit
|
|
13
|
+
|
|
14
|
+
Use this skill when deciding whether app chrome, settings, collaboration,
|
|
15
|
+
sharing, navigation, organization, setup, history, comments, or agent UX should
|
|
16
|
+
be built app-locally or moved into reusable framework/toolkit pieces.
|
|
17
|
+
|
|
18
|
+
## Core Rule
|
|
19
|
+
|
|
20
|
+
Apps own domain models, domain actions, and product-specific workflows. The
|
|
21
|
+
framework and `@agent-native/toolkit` own repeated workspace behavior users
|
|
22
|
+
expect to work the same everywhere.
|
|
23
|
+
|
|
24
|
+
Move behavior into shared toolkit primitives when it is:
|
|
25
|
+
|
|
26
|
+
- workspace-wide, such as settings, nav, search, org membership, or setup
|
|
27
|
+
- agent-visible, such as context, actions, run progress, or proof-of-done
|
|
28
|
+
- governed, such as secrets, permissions, sharing, audit, or billing
|
|
29
|
+
- repeated by two or more apps
|
|
30
|
+
- not tied to one domain model
|
|
31
|
+
|
|
32
|
+
Keep behavior app-local when the abstraction would hide important domain
|
|
33
|
+
language or make a simple app-specific workflow harder to understand.
|
|
34
|
+
|
|
35
|
+
## Discover Before Building
|
|
36
|
+
|
|
37
|
+
Before creating an app-local version of repeated workspace or agent UI:
|
|
38
|
+
|
|
39
|
+
1. Check the reusable kits below and the installed package documentation.
|
|
40
|
+
2. Search installed public components and source with `docs-search` and
|
|
41
|
+
`source-search`.
|
|
42
|
+
3. Run `agent-native eject --list` to see the version-matched units published
|
|
43
|
+
by the packages installed in this app.
|
|
44
|
+
4. Read `customizing-agent-native` and configure, compose, or eject the
|
|
45
|
+
smallest unit instead of recreating shared behavior from memory.
|
|
46
|
+
|
|
47
|
+
Use public package exports at runtime. Published source and ejection manifests
|
|
48
|
+
are discovery and ownership-transfer mechanisms, not private runtime APIs.
|
|
49
|
+
|
|
50
|
+
## Settings Direction
|
|
51
|
+
|
|
52
|
+
Durable settings belong in the Settings app or a registered settings route. The
|
|
53
|
+
agent sidebar should not become a second settings app. It can show contextual
|
|
54
|
+
quick controls and deep links such as:
|
|
55
|
+
|
|
56
|
+
- `/settings/ai`
|
|
57
|
+
- `/settings/connections`
|
|
58
|
+
- `/settings/secrets`
|
|
59
|
+
- `/settings/usage`
|
|
60
|
+
- `/settings/apps/:appId`
|
|
61
|
+
|
|
62
|
+
When adding a new API key, OAuth grant, provider connection, model selector, app
|
|
63
|
+
preference, notification preference, or usage/billing surface, register it as a
|
|
64
|
+
settings tab or app settings panel first. Only add sidebar UI when it is needed
|
|
65
|
+
in the moment of agent use.
|
|
66
|
+
|
|
67
|
+
## Reusable Kits
|
|
68
|
+
|
|
69
|
+
- **Settings kit**: a searchable settings page with account, workspace, AI
|
|
70
|
+
models, LLM keys, connections, secrets, usage, notifications, changelog, and
|
|
71
|
+
app-specific panels. Search is on by default; register a `SettingsSearchEntry`
|
|
72
|
+
per control so users find settings by name across tabs.
|
|
73
|
+
- **Collaboration kit**: Yjs docs, presence, agent presence, live cursors,
|
|
74
|
+
remote selections, recent edit highlights, real-time sync indicators, and
|
|
75
|
+
undo/redo grouping.
|
|
76
|
+
- **Sharing kit**: private/workspace/org/public-link access, invites, roles,
|
|
77
|
+
expirations, agent-readable links, and resource registration.
|
|
78
|
+
- **Navigation and command kit**: app shell, side nav, breadcrumbs, app switcher,
|
|
79
|
+
command palette entries, recent resources, pinned resources, and global search.
|
|
80
|
+
- **Organization kit**: folders, tags, favorites, archive, trash, ownership,
|
|
81
|
+
membership, and common resource metadata.
|
|
82
|
+
- **Setup and connections kit**: declarative setup requirements, model readiness,
|
|
83
|
+
missing-secret states, OAuth grants, and provider connection health.
|
|
84
|
+
- **Agent UX kit**: sidebar, composer, staged context, mentions, voice, human
|
|
85
|
+
approval, generative UI, progress, and screen-state exposure.
|
|
86
|
+
- **Chat history kit**: presentational chat lists and recent-chat rails belong
|
|
87
|
+
in Toolkit; Core keeps thread persistence, agent execution, transport, and
|
|
88
|
+
page-to-sidebar handoff. Apps inject routing, labels, and domain actions.
|
|
89
|
+
- **Agent page kit**: the full-page `/agent` surface (`AgentTabsPage` from
|
|
90
|
+
`@agent-native/core/client`) with Context, Files, Connections, Jobs, and
|
|
91
|
+
Access tabs plus a Personal/Organization scope toggle. The canonical home
|
|
92
|
+
for context transparency, MCP servers, A2A remote agents, recurring
|
|
93
|
+
jobs/automations, and external-client connect flows. See the `agent-page`
|
|
94
|
+
skill.
|
|
95
|
+
- **History and recovery kit**: audit log, activity feed, version history,
|
|
96
|
+
checkpoints, undo, redo, restore, and proof-of-done.
|
|
97
|
+
- **Comments and review kit**: anchored comments, pins, mentions, review
|
|
98
|
+
requests, resolved threads, agent follow-up tasks, and notifications.
|
|
99
|
+
- **Workflow and observability kit**: notifications, approvals, scheduled work,
|
|
100
|
+
background runs, recurring jobs, traces, evals, feedback, and run timelines.
|
|
101
|
+
|
|
102
|
+
## Implementation Checklist
|
|
103
|
+
|
|
104
|
+
When adding or refactoring one of these areas:
|
|
105
|
+
|
|
106
|
+
1. Search existing framework and template code for duplicated UI or actions.
|
|
107
|
+
2. Decide the shared contract: data shape, action API, React component/hook, and
|
|
108
|
+
app adapter points.
|
|
109
|
+
3. Keep shared data provider-agnostic and scoped by auth/sharing rules.
|
|
110
|
+
4. Expose the same capability to the UI and agent through actions or documented
|
|
111
|
+
client helpers.
|
|
112
|
+
5. Register app-specific labels, routes, resource adapters, and settings panels
|
|
113
|
+
instead of hardcoding app names in core UI.
|
|
114
|
+
6. Update docs and relevant skills so future apps discover the shared path.
|
|
115
|
+
7. Keep the component easy to adopt piecemeal: expose props/slots first and
|
|
116
|
+
ship readable source plus a complete ejection unit so apps can take ownership
|
|
117
|
+
of the smallest feature when needed. See `customizing-agent-native` for the
|
|
118
|
+
configure → compose → eject → propose seam ladder.
|
|
119
|
+
|
|
120
|
+
## Related Skills
|
|
121
|
+
|
|
122
|
+
Read these alongside this skill when the work touches the specific area:
|
|
123
|
+
|
|
124
|
+
- `sharing`
|
|
125
|
+
- `real-time-collab`
|
|
126
|
+
- `real-time-sync`
|
|
127
|
+
- `client-side-routing`
|
|
128
|
+
- `context-awareness`
|
|
129
|
+
- `onboarding`
|
|
130
|
+
- `secrets`
|
|
131
|
+
- `audit-log`
|
|
132
|
+
- `observability`
|
|
133
|
+
- `frontend-design`
|
|
@@ -7,6 +7,11 @@ application state shared with the UI.
|
|
|
7
7
|
Detailed document editing, Notion, storage, and UI rules live in
|
|
8
8
|
`.agents/skills/`.
|
|
9
9
|
|
|
10
|
+
Before building common workspace or agent UI, read `agent-native-toolkit` to
|
|
11
|
+
inventory existing public kits and installed package seams. Use
|
|
12
|
+
`customizing-agent-native` for the configure → compose → eject → propose seam
|
|
13
|
+
ladder.
|
|
14
|
+
|
|
10
15
|
## Core Rules
|
|
11
16
|
|
|
12
17
|
- Store large file/blob payloads in configured file/blob storage, not SQL: no
|
|
@@ -134,6 +139,8 @@ cd templates/content && pnpm action <name> [args]
|
|
|
134
139
|
| `ensure-content-spaces` | | Idempotently provision the signed-in user's personal and current organization Content spaces, system databases, and catalog references |
|
|
135
140
|
| `backfill-content-files` | | Assign legacy pages to Content spaces and reconcile exactly one canonical Files database membership per non-system page |
|
|
136
141
|
| `list-content-spaces` | | List already-provisioned Content spaces authorized by the signed-in user's current ownership or organization memberships |
|
|
142
|
+
| `create-content-space` | `--name <name> --requestId <opaque-id>` | Idempotently create a private named Content workspace with its own canonical Files database |
|
|
143
|
+
| `delete-content-space` | `--spaceId <id>` | Permanently delete a user-created Content workspace and all of its content; Personal and organization workspaces are protected |
|
|
137
144
|
| `create-content-database` | `[--documentId <id>] [--spaceId <id>] [--parentId <id>] [--title <text>] [--description <text>]` | Create a described database page in a Content space or convert an existing page into a database |
|
|
138
145
|
| `create-inline-content-database` | `--hostDocumentId <id> [--title <text>] [--description <text>]` | Create a described database owned by an inline database block in the host document |
|
|
139
146
|
| `get-content-database` | `--databaseId <id>` or `--documentId <id>` | Get a database with its description, property/option schema guidance, item pages, and computed ancestry context |
|
|
@@ -4,8 +4,12 @@ import { and, eq, isNull } from "drizzle-orm";
|
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
|
|
6
6
|
import { getDb, schema } from "../server/db/index.js";
|
|
7
|
+
import { CONTENT_DATABASE_PERSONAL_VIEW_OVERRIDES_VERSION } from "../shared/api.js";
|
|
8
|
+
import { resolveContentSpaceAccess } from "./_content-space-access.js";
|
|
9
|
+
import { filesParentPropertyId } from "./_files-system-properties.js";
|
|
7
10
|
|
|
8
|
-
export const PERSONAL_DATABASE_VIEW_OVERRIDES_VERSION =
|
|
11
|
+
export const PERSONAL_DATABASE_VIEW_OVERRIDES_VERSION =
|
|
12
|
+
CONTENT_DATABASE_PERSONAL_VIEW_OVERRIDES_VERSION;
|
|
9
13
|
|
|
10
14
|
export const personalDatabaseViewSettingKey = (databaseId: string) =>
|
|
11
15
|
`content-database-personal-view:${databaseId}`;
|
|
@@ -38,8 +42,7 @@ export const filterSchema = z.object({
|
|
|
38
42
|
parentFilterGroupId: z.string().optional(),
|
|
39
43
|
});
|
|
40
44
|
|
|
41
|
-
|
|
42
|
-
version: z.literal(PERSONAL_DATABASE_VIEW_OVERRIDES_VERSION),
|
|
45
|
+
const personalViewOverridesFields = {
|
|
43
46
|
activeViewId: z.string().optional(),
|
|
44
47
|
views: z.array(
|
|
45
48
|
z.object({
|
|
@@ -49,6 +52,16 @@ export const personalViewOverridesSchema = z.object({
|
|
|
49
52
|
filterMode: z.enum(["and", "or"]).default("and"),
|
|
50
53
|
}),
|
|
51
54
|
),
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export const personalViewOverridesSchema = z.object({
|
|
58
|
+
version: z.literal(PERSONAL_DATABASE_VIEW_OVERRIDES_VERSION),
|
|
59
|
+
...personalViewOverridesFields,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const legacyPersonalViewOverridesSchema = z.object({
|
|
63
|
+
version: z.literal(1),
|
|
64
|
+
...personalViewOverridesFields,
|
|
52
65
|
});
|
|
53
66
|
|
|
54
67
|
export async function assertContentDatabaseViewerAccess(databaseId: string) {
|
|
@@ -64,7 +77,12 @@ export async function assertContentDatabaseViewerAccess(databaseId: string) {
|
|
|
64
77
|
);
|
|
65
78
|
if (!database) throw new Error(`Database "${databaseId}" not found`);
|
|
66
79
|
|
|
67
|
-
|
|
80
|
+
try {
|
|
81
|
+
await assertAccess("document", database.documentId, "viewer");
|
|
82
|
+
} catch (error) {
|
|
83
|
+
if (database.systemRole !== "files" || !database.spaceId) throw error;
|
|
84
|
+
await resolveContentSpaceAccess(database.spaceId);
|
|
85
|
+
}
|
|
68
86
|
}
|
|
69
87
|
|
|
70
88
|
export async function readPersonalDatabaseViewOverrides(
|
|
@@ -76,5 +94,31 @@ export async function readPersonalDatabaseViewOverrides(
|
|
|
76
94
|
personalDatabaseViewSettingKey(databaseId),
|
|
77
95
|
);
|
|
78
96
|
const parsed = personalViewOverridesSchema.safeParse(stored);
|
|
79
|
-
|
|
97
|
+
if (parsed.success) return parsed.data;
|
|
98
|
+
|
|
99
|
+
const legacy = legacyPersonalViewOverridesSchema.safeParse(stored);
|
|
100
|
+
if (!legacy.success) return null;
|
|
101
|
+
const [database] = await getDb()
|
|
102
|
+
.select({ systemRole: schema.contentDatabases.systemRole })
|
|
103
|
+
.from(schema.contentDatabases)
|
|
104
|
+
.where(eq(schema.contentDatabases.id, databaseId));
|
|
105
|
+
const legacyParentKey = filesParentPropertyId(databaseId);
|
|
106
|
+
return {
|
|
107
|
+
...legacy.data,
|
|
108
|
+
version: PERSONAL_DATABASE_VIEW_OVERRIDES_VERSION,
|
|
109
|
+
views: legacy.data.views.map((view) => ({
|
|
110
|
+
...view,
|
|
111
|
+
filters:
|
|
112
|
+
database?.systemRole === "files"
|
|
113
|
+
? view.filters.filter(
|
|
114
|
+
(filter) =>
|
|
115
|
+
!(
|
|
116
|
+
filter.key === legacyParentKey &&
|
|
117
|
+
filter.operator === "is_empty" &&
|
|
118
|
+
filter.value === ""
|
|
119
|
+
),
|
|
120
|
+
)
|
|
121
|
+
: view.filters,
|
|
122
|
+
})),
|
|
123
|
+
};
|
|
80
124
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { getRequestUserEmail } from "@agent-native/core/server/request-context";
|
|
2
|
+
import { resolveAccess } from "@agent-native/core/sharing";
|
|
3
|
+
|
|
4
|
+
import { listContentOrganizationMemberships } from "./_content-space-access.js";
|
|
5
|
+
|
|
6
|
+
export async function resolveContentDocumentAccess(documentId: string) {
|
|
7
|
+
const current = await resolveAccess("document", documentId);
|
|
8
|
+
if (current) return current;
|
|
9
|
+
|
|
10
|
+
const userEmail = getRequestUserEmail();
|
|
11
|
+
if (!userEmail) return null;
|
|
12
|
+
const memberships = await listContentOrganizationMemberships(userEmail);
|
|
13
|
+
for (const membership of memberships) {
|
|
14
|
+
const access = await resolveAccess("document", documentId, {
|
|
15
|
+
userEmail,
|
|
16
|
+
orgId: membership.orgId,
|
|
17
|
+
});
|
|
18
|
+
if (access) return access;
|
|
19
|
+
}
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
import { and, eq, inArray, sql } from "drizzle-orm";
|
|
4
|
+
|
|
5
|
+
import { schema } from "../server/db/index.js";
|
|
6
|
+
import { normalizeContentSpaceEmail } from "./_content-space-access.js";
|
|
7
|
+
import {
|
|
8
|
+
personalContentSpaceId,
|
|
9
|
+
systemIdsForContentSpace,
|
|
10
|
+
} from "./_content-spaces.js";
|
|
11
|
+
|
|
12
|
+
type Db = any;
|
|
13
|
+
|
|
14
|
+
export function favoritesSystemIds(userEmail: string) {
|
|
15
|
+
return systemIdsForContentSpace(
|
|
16
|
+
personalContentSpaceId(normalizeContentSpaceEmail(userEmail)),
|
|
17
|
+
"favorites",
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function favoriteDocumentIds(
|
|
22
|
+
db: Db,
|
|
23
|
+
userEmail: string,
|
|
24
|
+
documentIds: string[],
|
|
25
|
+
) {
|
|
26
|
+
if (documentIds.length === 0) return new Set<string>();
|
|
27
|
+
const favoritesDatabaseId = favoritesSystemIds(userEmail).databaseId;
|
|
28
|
+
const rows = await db
|
|
29
|
+
.select({ documentId: schema.contentDatabaseItems.documentId })
|
|
30
|
+
.from(schema.contentDatabaseItems)
|
|
31
|
+
.where(
|
|
32
|
+
and(
|
|
33
|
+
eq(schema.contentDatabaseItems.databaseId, favoritesDatabaseId),
|
|
34
|
+
inArray(schema.contentDatabaseItems.documentId, documentIds),
|
|
35
|
+
),
|
|
36
|
+
);
|
|
37
|
+
return new Set(rows.map((row: { documentId: string }) => row.documentId));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function setFavoriteMembership(args: {
|
|
41
|
+
db: Db;
|
|
42
|
+
userEmail: string;
|
|
43
|
+
documentId: string;
|
|
44
|
+
favorite: boolean;
|
|
45
|
+
now: string;
|
|
46
|
+
}) {
|
|
47
|
+
const email = normalizeContentSpaceEmail(args.userEmail);
|
|
48
|
+
const favoritesDatabaseId = favoritesSystemIds(email).databaseId;
|
|
49
|
+
const [existing] = await args.db
|
|
50
|
+
.select({ id: schema.contentDatabaseItems.id })
|
|
51
|
+
.from(schema.contentDatabaseItems)
|
|
52
|
+
.where(
|
|
53
|
+
and(
|
|
54
|
+
eq(schema.contentDatabaseItems.databaseId, favoritesDatabaseId),
|
|
55
|
+
eq(schema.contentDatabaseItems.documentId, args.documentId),
|
|
56
|
+
),
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
if (!args.favorite) {
|
|
60
|
+
if (existing) {
|
|
61
|
+
await args.db
|
|
62
|
+
.delete(schema.contentDatabaseItems)
|
|
63
|
+
.where(eq(schema.contentDatabaseItems.id, existing.id));
|
|
64
|
+
}
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
if (existing) return true;
|
|
68
|
+
|
|
69
|
+
const [position] = await args.db
|
|
70
|
+
.select({ max: sql<number>`COALESCE(MAX(position), -1)` })
|
|
71
|
+
.from(schema.contentDatabaseItems)
|
|
72
|
+
.where(eq(schema.contentDatabaseItems.databaseId, favoritesDatabaseId));
|
|
73
|
+
const id = `content_database_item_${createHash("sha256")
|
|
74
|
+
.update(`${favoritesDatabaseId}:${args.documentId}`)
|
|
75
|
+
.digest("hex")
|
|
76
|
+
.slice(0, 32)}`;
|
|
77
|
+
await args.db
|
|
78
|
+
.insert(schema.contentDatabaseItems)
|
|
79
|
+
.values({
|
|
80
|
+
id,
|
|
81
|
+
ownerEmail: email,
|
|
82
|
+
orgId: null,
|
|
83
|
+
databaseId: favoritesDatabaseId,
|
|
84
|
+
documentId: args.documentId,
|
|
85
|
+
position: Number(position?.max ?? -1) + 1,
|
|
86
|
+
createdAt: args.now,
|
|
87
|
+
updatedAt: args.now,
|
|
88
|
+
})
|
|
89
|
+
.onConflictDoNothing();
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
|
|
3
3
|
import { accessFilter } from "@agent-native/core/sharing";
|
|
4
|
-
import { and, eq, inArray, isNull, or, sql } from "drizzle-orm";
|
|
4
|
+
import { and, eq, inArray, isNotNull, isNull, or, sql } from "drizzle-orm";
|
|
5
5
|
|
|
6
6
|
import { schema } from "../server/db/index.js";
|
|
7
7
|
import {
|
|
@@ -62,9 +62,44 @@ async function reconcileDocuments(args: {
|
|
|
62
62
|
args.filesDatabases.map((database) => [database.spaceId, database]),
|
|
63
63
|
);
|
|
64
64
|
const filesDatabaseIds = args.filesDatabases.map((database) => database.id);
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
65
|
+
const documentIds = args.documents.map((document) => document.id);
|
|
66
|
+
const [systemDatabaseDocuments, workspaceReferenceDocuments] =
|
|
67
|
+
documentIds.length > 0
|
|
68
|
+
? await Promise.all([
|
|
69
|
+
args.db
|
|
70
|
+
.select({ id: schema.contentDatabases.documentId })
|
|
71
|
+
.from(schema.contentDatabases)
|
|
72
|
+
.where(
|
|
73
|
+
and(
|
|
74
|
+
inArray(schema.contentDatabases.documentId, documentIds),
|
|
75
|
+
isNotNull(schema.contentDatabases.systemRole),
|
|
76
|
+
),
|
|
77
|
+
),
|
|
78
|
+
args.db
|
|
79
|
+
.select({ id: schema.contentSpaceCatalogItems.documentId })
|
|
80
|
+
.from(schema.contentSpaceCatalogItems)
|
|
81
|
+
.innerJoin(
|
|
82
|
+
schema.contentDatabases,
|
|
83
|
+
eq(
|
|
84
|
+
schema.contentDatabases.id,
|
|
85
|
+
schema.contentSpaceCatalogItems.catalogDatabaseId,
|
|
86
|
+
),
|
|
87
|
+
)
|
|
88
|
+
.where(
|
|
89
|
+
and(
|
|
90
|
+
inArray(
|
|
91
|
+
schema.contentSpaceCatalogItems.documentId,
|
|
92
|
+
documentIds,
|
|
93
|
+
),
|
|
94
|
+
eq(schema.contentDatabases.systemRole, "workspaces"),
|
|
95
|
+
),
|
|
96
|
+
),
|
|
97
|
+
])
|
|
98
|
+
: [[], []];
|
|
99
|
+
const excludedDocumentIds = new Set([
|
|
100
|
+
...systemDatabaseDocuments.map((document: any) => document.id),
|
|
101
|
+
...workspaceReferenceDocuments.map((document: any) => document.id),
|
|
102
|
+
]);
|
|
68
103
|
const existingItems = filesDatabaseIds.length
|
|
69
104
|
? await args.db
|
|
70
105
|
.select()
|
|
@@ -93,7 +128,7 @@ async function reconcileDocuments(args: {
|
|
|
93
128
|
const inserts: Array<typeof schema.contentDatabaseItems.$inferInsert> = [];
|
|
94
129
|
for (const document of args.documents) {
|
|
95
130
|
const existing = itemsByDocument.get(document.id) ?? [];
|
|
96
|
-
if (
|
|
131
|
+
if (excludedDocumentIds.has(document.id)) {
|
|
97
132
|
for (const item of existing) deleteIds.add(item.id);
|
|
98
133
|
continue;
|
|
99
134
|
}
|
|
@@ -239,24 +274,23 @@ export async function reconcileContentFilesMemberships(
|
|
|
239
274
|
const email = normalizeContentSpaceEmail(userEmail);
|
|
240
275
|
const memberships = await listContentOrganizationMemberships(email);
|
|
241
276
|
const personalSpaceId = personalContentSpaceId(email);
|
|
242
|
-
const
|
|
277
|
+
const orgSpaces = new Map<
|
|
278
|
+
string,
|
|
279
|
+
{ spaceId: string; canReconcilePrivateDocuments: boolean }
|
|
280
|
+
>();
|
|
243
281
|
for (const membership of memberships) {
|
|
244
282
|
const spaceId = organizationContentSpaceId(membership.orgId);
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
error.message ===
|
|
252
|
-
`Editor access is required for Content space "${spaceId}"`
|
|
253
|
-
) {
|
|
254
|
-
continue;
|
|
255
|
-
}
|
|
256
|
-
throw error;
|
|
257
|
-
}
|
|
283
|
+
await resolveContentSpaceAccess(spaceId);
|
|
284
|
+
orgSpaces.set(membership.orgId, {
|
|
285
|
+
spaceId,
|
|
286
|
+
canReconcilePrivateDocuments:
|
|
287
|
+
membership.role === "owner" || membership.role === "admin",
|
|
288
|
+
});
|
|
258
289
|
}
|
|
259
|
-
const accessibleSpaceIds = [
|
|
290
|
+
const accessibleSpaceIds = [
|
|
291
|
+
personalSpaceId,
|
|
292
|
+
...[...orgSpaces.values()].map(({ spaceId }) => spaceId),
|
|
293
|
+
];
|
|
260
294
|
const now = new Date().toISOString();
|
|
261
295
|
const result: ContentFilesReconciliation = {
|
|
262
296
|
assignedSpaces: 0,
|
|
@@ -288,7 +322,7 @@ export async function reconcileContentFilesMemberships(
|
|
|
288
322
|
);
|
|
289
323
|
result.assignedSpaces += personalLegacyDocuments.length;
|
|
290
324
|
}
|
|
291
|
-
for (const [orgId,
|
|
325
|
+
for (const [orgId, orgSpace] of orgSpaces) {
|
|
292
326
|
const legacyDocuments = await tx
|
|
293
327
|
.select({ id: schema.documents.id })
|
|
294
328
|
.from(schema.documents)
|
|
@@ -296,12 +330,19 @@ export async function reconcileContentFilesMemberships(
|
|
|
296
330
|
and(
|
|
297
331
|
eq(schema.documents.orgId, orgId),
|
|
298
332
|
isNull(schema.documents.spaceId),
|
|
333
|
+
orgSpace.canReconcilePrivateDocuments
|
|
334
|
+
? undefined
|
|
335
|
+
: or(
|
|
336
|
+
eq(schema.documents.ownerEmail, email),
|
|
337
|
+
eq(schema.documents.visibility, "org"),
|
|
338
|
+
eq(schema.documents.visibility, "public"),
|
|
339
|
+
),
|
|
299
340
|
),
|
|
300
341
|
);
|
|
301
342
|
if (!legacyDocuments.length) continue;
|
|
302
343
|
await tx
|
|
303
344
|
.update(schema.documents)
|
|
304
|
-
.set({ spaceId, updatedAt: now })
|
|
345
|
+
.set({ spaceId: orgSpace.spaceId, updatedAt: now })
|
|
305
346
|
.where(
|
|
306
347
|
inArray(
|
|
307
348
|
schema.documents.id,
|
|
@@ -311,10 +352,25 @@ export async function reconcileContentFilesMemberships(
|
|
|
311
352
|
result.assignedSpaces += legacyDocuments.length;
|
|
312
353
|
}
|
|
313
354
|
|
|
314
|
-
const
|
|
355
|
+
const accessibleDocuments = await tx
|
|
315
356
|
.select()
|
|
316
357
|
.from(schema.documents)
|
|
317
358
|
.where(inArray(schema.documents.spaceId, accessibleSpaceIds));
|
|
359
|
+
const orgSpaceById = new Map(
|
|
360
|
+
[...orgSpaces.values()].map((space) => [space.spaceId, space]),
|
|
361
|
+
);
|
|
362
|
+
const documents = accessibleDocuments.filter((document: any) => {
|
|
363
|
+
if (document.spaceId === personalSpaceId) {
|
|
364
|
+
return normalizeContentSpaceEmail(document.ownerEmail) === email;
|
|
365
|
+
}
|
|
366
|
+
const orgSpace = orgSpaceById.get(document.spaceId);
|
|
367
|
+
return (
|
|
368
|
+
orgSpace?.canReconcilePrivateDocuments === true ||
|
|
369
|
+
normalizeContentSpaceEmail(document.ownerEmail) === email ||
|
|
370
|
+
document.visibility === "org" ||
|
|
371
|
+
document.visibility === "public"
|
|
372
|
+
);
|
|
373
|
+
});
|
|
318
374
|
const filesDatabases = await tx
|
|
319
375
|
.select()
|
|
320
376
|
.from(schema.contentDatabases)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
export const CONTENT_SIDEBAR_STATE_VERSION = 1;
|
|
4
|
+
export const CONTENT_SIDEBAR_STATE_SETTING_KEY = "content-sidebar-state";
|
|
5
|
+
|
|
6
|
+
const expandedIdSchema = z.string().min(1).max(256);
|
|
7
|
+
|
|
8
|
+
export const contentSidebarStateSchema = z.object({
|
|
9
|
+
version: z.literal(CONTENT_SIDEBAR_STATE_VERSION),
|
|
10
|
+
expandedWorkspaceIds: z.array(expandedIdSchema).max(1_000),
|
|
11
|
+
expandedDocumentIds: z.array(expandedIdSchema).max(5_000),
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
export type ContentSidebarState = z.infer<typeof contentSidebarStateSchema>;
|
|
15
|
+
|
|
16
|
+
export function normalizeContentSidebarState(value: unknown) {
|
|
17
|
+
const parsed = contentSidebarStateSchema.safeParse(value);
|
|
18
|
+
if (!parsed.success) return null;
|
|
19
|
+
return {
|
|
20
|
+
...parsed.data,
|
|
21
|
+
expandedWorkspaceIds: [...new Set(parsed.data.expandedWorkspaceIds)],
|
|
22
|
+
expandedDocumentIds: [...new Set(parsed.data.expandedDocumentIds)],
|
|
23
|
+
};
|
|
24
|
+
}
|
|
@@ -1,25 +1,9 @@
|
|
|
1
1
|
import { getDbExec } from "@agent-native/core/db";
|
|
2
|
-
import { table, text } from "@agent-native/core/db/schema";
|
|
3
2
|
import { getRequestUserEmail } from "@agent-native/core/server/request-context";
|
|
4
3
|
import { and, eq, sql } from "drizzle-orm";
|
|
5
4
|
|
|
6
5
|
import { getDb, schema } from "../server/db/index.js";
|
|
7
6
|
|
|
8
|
-
// Keep these lightweight table references local. Importing the public org
|
|
9
|
-
// barrel also initializes auth's long-lived cleanup timer, which breaks test
|
|
10
|
-
// suites that intentionally drain fake timers.
|
|
11
|
-
const organizations = table("organizations", {
|
|
12
|
-
id: text("id").primaryKey(),
|
|
13
|
-
name: text("name").notNull(),
|
|
14
|
-
createdBy: text("created_by").notNull(),
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
const orgMembers = table("org_members", {
|
|
18
|
-
orgId: text("org_id").notNull(),
|
|
19
|
-
email: text("email").notNull(),
|
|
20
|
-
role: text("role").notNull(),
|
|
21
|
-
});
|
|
22
|
-
|
|
23
7
|
export type ContentSpaceRole = "viewer" | "editor" | "owner";
|
|
24
8
|
|
|
25
9
|
export type ContentSpaceAccess = {
|
|
@@ -40,6 +24,10 @@ export async function getContentOrganizationMembership(
|
|
|
40
24
|
options: { db?: any } = {},
|
|
41
25
|
): Promise<{ role: string; name: string; createdBy: string } | null> {
|
|
42
26
|
if (options.db) {
|
|
27
|
+
// The canonical tables carry the active database dialect. Loading them only
|
|
28
|
+
// for transaction-scoped checks avoids initializing auth timers elsewhere.
|
|
29
|
+
const { organizations, orgMembers } =
|
|
30
|
+
await import("@agent-native/core/org");
|
|
43
31
|
const [row] = await options.db
|
|
44
32
|
.select({
|
|
45
33
|
role: orgMembers.role,
|
|
@@ -130,7 +118,7 @@ export async function listContentOrganizationMemberships(userEmail: string) {
|
|
|
130
118
|
|
|
131
119
|
export async function resolveContentSpaceAccess(
|
|
132
120
|
spaceId: string,
|
|
133
|
-
requiredRole: "viewer" | "editor" = "viewer",
|
|
121
|
+
requiredRole: "viewer" | "contributor" | "editor" = "viewer",
|
|
134
122
|
options: { db?: any } = {},
|
|
135
123
|
): Promise<ContentSpaceAccess> {
|
|
136
124
|
const userEmail = getRequestUserEmail();
|
|
@@ -167,6 +155,16 @@ export async function resolveContentSpaceAccess(
|
|
|
167
155
|
: membership.role === "admin"
|
|
168
156
|
? "editor"
|
|
169
157
|
: "viewer";
|
|
158
|
+
if (
|
|
159
|
+
requiredRole === "contributor" &&
|
|
160
|
+
membership.role !== "owner" &&
|
|
161
|
+
membership.role !== "admin" &&
|
|
162
|
+
membership.role !== "member"
|
|
163
|
+
) {
|
|
164
|
+
throw new Error(
|
|
165
|
+
`Contributor access is required for Content space "${spaceId}"`,
|
|
166
|
+
);
|
|
167
|
+
}
|
|
170
168
|
if (requiredRole === "editor" && role === "viewer") {
|
|
171
169
|
throw new Error(`Editor access is required for Content space "${spaceId}"`);
|
|
172
170
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { inArray } from "drizzle-orm";
|
|
2
|
+
|
|
3
|
+
import { schema } from "../server/db/index.js";
|
|
4
|
+
|
|
5
|
+
type Db = any;
|
|
6
|
+
|
|
7
|
+
export async function assertNotWorkspaceCatalogDocuments(
|
|
8
|
+
db: Db,
|
|
9
|
+
documentIds: string[],
|
|
10
|
+
operation: string,
|
|
11
|
+
) {
|
|
12
|
+
if (documentIds.length === 0) return;
|
|
13
|
+
const [workspaceReference] = await db
|
|
14
|
+
.select({ id: schema.contentSpaceCatalogItems.id })
|
|
15
|
+
.from(schema.contentSpaceCatalogItems)
|
|
16
|
+
.where(inArray(schema.contentSpaceCatalogItems.documentId, documentIds));
|
|
17
|
+
if (workspaceReference) {
|
|
18
|
+
throw new Error(`Workspace references cannot be ${operation} as pages`);
|
|
19
|
+
}
|
|
20
|
+
}
|