@agent-native/core 0.161.23 → 0.163.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +1 -1
- package/corpus/templates/analytics/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/corpus/templates/analytics/.agents/skills/sharing/SKILL.md +27 -2
- package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +18 -2
- package/corpus/templates/assets/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/corpus/templates/assets/.agents/skills/sharing/SKILL.md +27 -2
- package/corpus/templates/brain/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/corpus/templates/brain/.agents/skills/sharing/SKILL.md +27 -2
- package/corpus/templates/calendar/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/corpus/templates/calendar/.agents/skills/sharing/SKILL.md +27 -2
- package/corpus/templates/calendar/server/handlers/google-auth.ts +1 -8
- package/corpus/templates/chat/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/corpus/templates/chat/.agents/skills/sharing/SKILL.md +27 -2
- package/corpus/templates/clips/app/components/recorder/pre-record-panel.tsx +88 -35
- package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +36 -1
- package/corpus/templates/clips/app/i18n/en-US.ts +4 -3
- package/corpus/templates/clips/app/lib/camera-composite.ts +15 -1
- package/corpus/templates/clips/changelog/2026-08-18-clips-no-longer-crashes-when-a-camera-bubble-starts-at-a-tin.md +6 -0
- package/corpus/templates/clips/desktop/src/app.tsx +30 -40
- package/corpus/templates/clips/desktop/src/components/MicOffConfirmation.tsx +47 -0
- package/corpus/templates/clips/desktop/src/styles.css +110 -0
- package/corpus/templates/clips/desktop/src-tauri/src/whisper_speech.rs +114 -7
- package/corpus/templates/clips/shared/recording-capture.ts +18 -13
- package/corpus/templates/content/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/corpus/templates/content/.agents/skills/sharing/SKILL.md +27 -2
- package/corpus/templates/content/actions/create-content-database.ts +1 -1
- package/corpus/templates/content/actions/create-document.ts +1 -1
- package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +3 -5
- package/corpus/templates/content/app/components/editor/DocumentToolbar.tsx +0 -3
- package/corpus/templates/content/app/components/editor/NotionConflictBanner.tsx +0 -4
- package/corpus/templates/content/app/components/editor/NotionSyncBar.tsx +4 -1
- package/corpus/templates/content/app/hooks/use-notion.ts +17 -15
- package/corpus/templates/crm/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/corpus/templates/crm/.agents/skills/sharing/SKILL.md +27 -2
- package/corpus/templates/design/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/corpus/templates/design/.agents/skills/sharing/SKILL.md +27 -2
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +6 -1
- package/corpus/templates/dispatch/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/corpus/templates/dispatch/.agents/skills/sharing/SKILL.md +27 -2
- package/corpus/templates/factory/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/corpus/templates/factory/.agents/skills/sharing/SKILL.md +27 -2
- package/corpus/templates/forms/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/corpus/templates/forms/.agents/skills/sharing/SKILL.md +27 -2
- package/corpus/templates/macros/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/corpus/templates/macros/.agents/skills/sharing/SKILL.md +27 -2
- package/corpus/templates/mail/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/corpus/templates/mail/.agents/skills/sharing/SKILL.md +27 -2
- package/corpus/templates/mail/app/components/layout/AppLayout.tsx +18 -3
- package/corpus/templates/mail/server/handlers/google-auth.ts +1 -8
- package/corpus/templates/plan/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/corpus/templates/plan/.agents/skills/sharing/SKILL.md +27 -2
- package/corpus/templates/slides/.agents/skills/create-deck/SKILL.md +4 -0
- package/corpus/templates/slides/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/corpus/templates/slides/.agents/skills/sharing/SKILL.md +27 -2
- package/corpus/templates/slides/actions/_await-fit-check.ts +30 -5
- package/corpus/templates/slides/actions/add-slide.ts +6 -2
- package/corpus/templates/slides/actions/create-deck.ts +2 -1
- package/corpus/templates/slides/app/components/editor/GoogleDriveConnectionCta.tsx +3 -3
- package/corpus/templates/slides/app/components/editor/GoogleSlidesReferenceImport.tsx +3 -3
- package/corpus/templates/slides/app/components/editor/PromptDialog.tsx +3 -1
- package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +1 -1
- package/corpus/templates/slides/app/components/editor/slide-object-interactions.ts +1 -1
- package/corpus/templates/slides/app/lib/create-deck-generation.ts +1 -0
- package/corpus/templates/slides/app/pages/DeckEditor.tsx +35 -2
- package/corpus/templates/slides/app/routes/p.$id.tsx +3 -1
- package/corpus/templates/slides/changelog/2026-08-18-generated-slides-preserve-speaker-notes.md +6 -0
- package/corpus/templates/slides/server/handlers/assets.ts +23 -12
- package/corpus/templates/slides/server/handlers/design-system-decode-job-status.ts +15 -6
- package/corpus/templates/slides/server/handlers/design-system-upload-start.ts +15 -6
- package/corpus/templates/slides/server/handlers/index-design-system-sources.ts +34 -19
- package/corpus/templates/slides/server/handlers/request-auth-context.ts +1 -0
- package/corpus/templates/slides/server/handlers/share.ts +1 -1
- package/corpus/templates/slides/server/plugins/agent-chat.ts +1 -1
- package/corpus/templates/slides/server/routes/api/deck-agent-context.json.get.ts +1 -1
- package/corpus/templates/slides/server/routes/api/exports/[filename].get.ts +9 -3
- package/corpus/templates/slides/server/routes/api/exports/google-slides.post.ts +12 -5
- package/corpus/templates/slides/server/routes/api/exports/html.post.ts +9 -7
- package/corpus/templates/slides/server/routes/api/exports/pptx.post.ts +9 -7
- package/corpus/templates/slides/shared/api.ts +7 -1
- package/corpus/templates/tasks/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/corpus/templates/tasks/.agents/skills/sharing/SKILL.md +27 -2
- package/dist/agent/engine/translate-anthropic.js +27 -2
- package/dist/agent/production-agent.d.ts +2 -2
- package/dist/agent/production-agent.js +16 -8
- package/dist/agent/thread-data-builder.d.ts +1 -0
- package/dist/agent/thread-data-builder.js +6 -4
- package/dist/agent/tool-approval-store.d.ts +10 -1
- package/dist/agent/tool-approval-store.js +19 -2
- package/dist/agent/types.d.ts +9 -0
- package/dist/app-config/schema.d.ts +1 -0
- package/dist/app-config/workspace.d.ts +1 -0
- package/dist/app-config/workspace.js +8 -0
- package/dist/chat-threads/schema.d.ts +2 -2
- package/dist/client/AssistantChat.js +14 -6
- package/dist/client/EnvironmentBadge.d.ts +15 -0
- package/dist/client/EnvironmentBadge.js +79 -0
- package/dist/client/MultiTabAssistantChat.js +73 -17
- package/dist/client/agent-chat-adapter.js +11 -1
- package/dist/client/api-path.js +14 -1
- package/dist/client/app-providers.js +2 -1
- package/dist/client/chat/tool-call-display.d.ts +12 -2
- package/dist/client/chat/tool-call-display.js +17 -7
- package/dist/client/chat/widgets/WorkspaceFileWidget.js +1 -1
- package/dist/client/chat/widgets/builtin-tool-renderers.d.ts +6 -0
- package/dist/client/chat/widgets/builtin-tool-renderers.js +19 -0
- package/dist/client/onboarding/FirstRunOnboarding.js +46 -32
- package/dist/client/onboarding/use-onboarding.d.ts +8 -1
- package/dist/client/onboarding/use-onboarding.js +22 -7
- package/dist/client/org/TeamPage.js +15 -2
- package/dist/client/org/hooks.d.ts +2 -0
- package/dist/client/org/hooks.js +12 -0
- package/dist/client/org/index.d.ts +2 -2
- package/dist/client/org/index.js +1 -1
- package/dist/client/session-replay.js +58 -15
- package/dist/client/settings/SettingsTabsPage.js +19 -6
- package/dist/client/sharing/ShareButton.js +43 -18
- package/dist/client/sharing/useShareButtonController.d.ts +12 -2
- package/dist/client/sharing/useShareButtonController.js +38 -8
- package/dist/client/sse-event-processor.d.ts +7 -0
- package/dist/client/sse-event-processor.js +4 -1
- package/dist/client/ui/index.d.ts +1 -0
- package/dist/client/ui/index.js +1 -0
- package/dist/client/use-action.js +26 -0
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/config.d.ts +15 -0
- package/dist/config.js +53 -0
- package/dist/dashboard-storage/schema.d.ts +2 -2
- package/dist/data-programs/schema.d.ts +2 -2
- package/dist/extensions/fetch-tool.js +8 -2
- package/dist/extensions/schema.d.ts +2 -2
- package/dist/extensions/store.js +3 -0
- package/dist/localization/core-messages/ar-SA.js +1 -0
- package/dist/localization/core-messages/de-DE.js +1 -0
- package/dist/localization/core-messages/en-US.d.ts +1 -0
- package/dist/localization/core-messages/en-US.js +1 -0
- package/dist/localization/core-messages/es-ES.js +1 -0
- package/dist/localization/core-messages/fr-FR.js +1 -0
- package/dist/localization/core-messages/hi-IN.js +1 -0
- package/dist/localization/core-messages/ja-JP.js +1 -0
- package/dist/localization/core-messages/ko-KR.js +1 -0
- package/dist/localization/core-messages/pt-BR.js +1 -0
- package/dist/localization/core-messages/zh-CN.js +1 -0
- package/dist/localization/core-messages/zh-TW.js +1 -0
- package/dist/localization/core-messages.d.ts +1 -0
- package/dist/localization/default-messages.d.ts +4 -0
- package/dist/localization/default-messages.js +4 -0
- package/dist/mcp/embed-app.js +1 -1
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/org/handlers.d.ts +6 -1
- package/dist/org/handlers.js +43 -0
- package/dist/org/index.d.ts +3 -3
- package/dist/org/index.js +2 -2
- package/dist/org/migrations.js +33 -0
- package/dist/org/plugin.js +10 -1
- package/dist/org/schema.d.ts +315 -0
- package/dist/org/schema.js +20 -1
- package/dist/org/types.d.ts +3 -0
- package/dist/org/workspace-app-access.d.ts +10 -0
- package/dist/org/workspace-app-access.js +179 -0
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +7 -7
- package/dist/provider-api/actions/provider-api.d.ts +7 -7
- package/dist/provider-api/index.d.ts +10 -0
- package/dist/provider-api/index.js +45 -9
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/auth.js +24 -4
- package/dist/server/poll.js +13 -3
- package/dist/server/sentry.d.ts +1 -1
- package/dist/sharing/access.d.ts +2 -2
- package/dist/sharing/access.js +56 -3
- package/dist/sharing/actions/list-resource-shares.d.ts +2 -0
- package/dist/sharing/actions/list-resource-shares.js +26 -1
- package/dist/sharing/actions/share-resource.d.ts +1 -1
- package/dist/sharing/actions/share-resource.js +23 -4
- package/dist/sharing/actions/unshare-resource.d.ts +1 -1
- package/dist/sharing/actions/unshare-resource.js +1 -1
- package/dist/sharing/registry.d.ts +12 -0
- package/dist/sharing/schema.d.ts +4 -4
- package/dist/sharing/schema.js +2 -2
- package/dist/templates/chat/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/dist/templates/chat/.agents/skills/sharing/SKILL.md +27 -2
- package/dist/templates/default/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/dist/templates/default/.agents/skills/sharing/SKILL.md +27 -2
- package/dist/templates/workspace-core/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/dist/templates/workspace-core/.agents/skills/sharing/SKILL.md +27 -2
- package/dist/vite/client.d.ts +5 -0
- package/dist/vite/client.js +64 -2
- package/dist/workspace-connections/actions/list-workspace-user-groups.d.ts +9 -1
- package/dist/workspace-connections/actions/list-workspace-user-groups.js +9 -4
- package/dist/workspace-connections/groups.d.ts +2 -0
- package/dist/workspace-connections/groups.js +16 -5
- package/dist/workspace-files/actions.d.ts +1 -8
- package/dist/workspace-files/actions.js +5 -21
- package/dist/workspace-files/index.d.ts +1 -1
- package/dist/workspace-files/index.js +1 -1
- package/dist/workspace-files/store.d.ts +18 -0
- package/dist/workspace-files/store.js +24 -3
- package/package.json +3 -3
- package/src/templates/chat/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/src/templates/chat/.agents/skills/sharing/SKILL.md +27 -2
- package/src/templates/default/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/src/templates/default/.agents/skills/sharing/SKILL.md +27 -2
- package/src/templates/workspace-core/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/src/templates/workspace-core/.agents/skills/sharing/SKILL.md +27 -2
package/dist/sharing/schema.js
CHANGED
|
@@ -49,7 +49,7 @@ export function ownableColumns() {
|
|
|
49
49
|
* Columns:
|
|
50
50
|
* - `id` — share row id (generated by the share action)
|
|
51
51
|
* - `resourceId` — id of the parent resource
|
|
52
|
-
* - `principalType` — `'user' | 'org'`
|
|
52
|
+
* - `principalType` — `'user' | 'group' | 'org'`
|
|
53
53
|
* - `principalId` — email (for user) or org id (for org)
|
|
54
54
|
* - `role` — `'viewer' | 'commenter' | 'editor' | 'admin'`
|
|
55
55
|
* - `createdBy` — email of the user who created the share
|
|
@@ -65,7 +65,7 @@ export function createSharesTable(tableName) {
|
|
|
65
65
|
id: text("id").primaryKey(),
|
|
66
66
|
resourceId: text("resource_id").notNull(),
|
|
67
67
|
principalType: text("principal_type", {
|
|
68
|
-
enum: ["user", "org"],
|
|
68
|
+
enum: ["user", "group", "org"],
|
|
69
69
|
}).notNull(),
|
|
70
70
|
principalId: text("principal_id").notNull(),
|
|
71
71
|
role: text("role", {
|
|
@@ -50,7 +50,7 @@ The agent modifies data in SQL, but the UI runs in the browser. SSE bridges same
|
|
|
50
50
|
|
|
51
51
|
For list/sidebar queries, use the same pattern — pass the counter into the queryKey of every list query you want to keep fresh.
|
|
52
52
|
|
|
53
|
-
4. **Fallback** polling calls `/_agent-native/poll?since=N`. It
|
|
53
|
+
4. **Fallback** polling calls `/_agent-native/poll?since=N`. It polls every 2 seconds only while an agent run is in flight; otherwise it idles at 60 seconds whether or not SSE is connected (`SSE_FALLBACK_INTERVAL_MS` and `IDLE_POLL_INTERVAL_MS`, both 60 s). A hidden tab polls no faster than every 10 seconds (`HIDDEN_POLL_INTERVAL_MS`). Polling is the universal serverless fallback: new framework writes are read from the durable `sync_events` log, while the older DB timestamp scan remains as a slower safety net for direct SQL writes and older processes.
|
|
54
54
|
|
|
55
55
|
5. When the agent writes to the database, the version increments, SSE/polling detects it, and React Query refetches the affected queries.
|
|
56
56
|
|
|
@@ -18,6 +18,11 @@ Any resource a user **creates** (dashboards, documents, forms, decks, compositio
|
|
|
18
18
|
|
|
19
19
|
This is the framework-level primitive. Every ownable resource gets it for free — same API, same UI, same skill.
|
|
20
20
|
|
|
21
|
+
Workspace apps are the intentional organization-scoped exception: their default
|
|
22
|
+
visibility is `org`, configurable by an organization owner/admin. The creator is
|
|
23
|
+
stored as the app owner, and the creator or an organization owner/admin can
|
|
24
|
+
change the app's visibility or manage its share grants.
|
|
25
|
+
|
|
21
26
|
## Concepts
|
|
22
27
|
|
|
23
28
|
### Three visibility levels
|
|
@@ -26,7 +31,8 @@ This is the framework-level primitive. Every ownable resource gets it for free
|
|
|
26
31
|
- **`org`** — owner + explicit grants + anyone in the same org (read-only).
|
|
27
32
|
- **`public`** — owner + explicit grants + **anyone with the link** (read-only). Public docs do NOT appear in other users' list/sidebar/search results — `accessFilter` omits them by default. They're reachable by id (`resolveAccess` admits them) so direct links and SSR routes like `/p/:id` keep working. If a list endpoint legitimately needs cross-user public discovery (a template gallery, etc.), pass `accessFilter(table, shares, ctx, minRole, { includePublic: true })`.
|
|
28
33
|
|
|
29
|
-
Visibility is coarse. Explicit share grants are fine-grained (per user
|
|
34
|
+
Visibility is coarse. Explicit share grants are fine-grained (per user, reusable
|
|
35
|
+
organization group, or per org).
|
|
30
36
|
|
|
31
37
|
### Roles on a share grant
|
|
32
38
|
|
|
@@ -101,6 +107,7 @@ registerShareableResource({
|
|
|
101
107
|
// ...
|
|
102
108
|
allowPublic: false, // hides "Public" in the share dialog and rejects it server-side
|
|
103
109
|
requireOrgMemberForUserShares: true, // user shares must target an org member or pending invitee
|
|
110
|
+
supportsGroupShares: true, // enable reusable organization groups for this resource
|
|
104
111
|
});
|
|
105
112
|
```
|
|
106
113
|
|
|
@@ -111,6 +118,21 @@ Use both for resources that execute code or expose privileged data with the *vie
|
|
|
111
118
|
|
|
112
119
|
Defaults match historical behaviour: `allowPublic: true`, `requireOrgMemberForUserShares: false`. Resources that don't set the flags work as before.
|
|
113
120
|
|
|
121
|
+
### Reusable organization groups
|
|
122
|
+
|
|
123
|
+
The workspace organization model stores reusable groups in
|
|
124
|
+
`workspace_user_groups`. Organization owners/admins manage the groups and their
|
|
125
|
+
members through the existing group actions. Set `supportsGroupShares: true` on a
|
|
126
|
+
resource registration to enable `principalType: "group"` in the generic share
|
|
127
|
+
actions and the shared popover's autocomplete. Group ids are always checked
|
|
128
|
+
against the resource's organization, and membership is evaluated at access time
|
|
129
|
+
so group changes apply to existing shares.
|
|
130
|
+
|
|
131
|
+
Workspace apps opt into this flag and use the organization default visibility
|
|
132
|
+
setting. The app creator is the durable `owner_email`, while organization
|
|
133
|
+
owners/admins receive admin access through the registration's
|
|
134
|
+
`canManageAccess` callback.
|
|
135
|
+
|
|
114
136
|
## Filter list/read queries
|
|
115
137
|
|
|
116
138
|
```ts
|
|
@@ -197,6 +219,9 @@ All app share popovers should use the same compact surface contract:
|
|
|
197
219
|
- Add `ShareAgentsSection` only when the resource has a real agent-readable
|
|
198
220
|
link or prompt. Keep it collapsed by default and supply domain-specific
|
|
199
221
|
content through the shared section shell.
|
|
222
|
+
- When `supportsGroupShares` is enabled, keep the existing people input as the
|
|
223
|
+
only entry point and include organization groups in its autocomplete. Do not
|
|
224
|
+
add a separate group-management surface to each resource.
|
|
200
225
|
- App-specific tabs or controls may remain when they represent a real domain
|
|
201
226
|
action (for example, an embed-code editor), but they should retain the same
|
|
202
227
|
trigger, copy-row, access, and spacing language.
|
|
@@ -211,7 +236,7 @@ The framework auto-mounts these actions in every template — no per-template bo
|
|
|
211
236
|
|
|
212
237
|
| Action | Args | Purpose |
|
|
213
238
|
| -------------------------- | ------------------------------------------------------------------------------ | ----------------------------------------- |
|
|
214
|
-
| `share-resource` | `resourceType, resourceId, principalType, principalId, role, notify?, resourceUrl?, message?` | Grant a user or org access. `notify` defaults to true for individual user shares; `resourceUrl` can provide the direct app link and `message` an optional short note for the notification email. |
|
|
239
|
+
| `share-resource` | `resourceType, resourceId, principalType, principalId, role, notify?, resourceUrl?, message?` | Grant a user, group, or org access. `notify` defaults to true for individual user shares; `resourceUrl` can provide the direct app link and `message` an optional short note for the notification email. |
|
|
215
240
|
| `unshare-resource` | `resourceType, resourceId, principalType, principalId` | Revoke access. |
|
|
216
241
|
| `list-resource-shares` | `resourceType, resourceId` | Current visibility + all share grants. |
|
|
217
242
|
| `set-resource-visibility` | `resourceType, resourceId, visibility` | Change to `private` / `org` / `public`. |
|
|
@@ -50,7 +50,7 @@ The agent modifies data in SQL, but the UI runs in the browser. SSE bridges same
|
|
|
50
50
|
|
|
51
51
|
For list/sidebar queries, use the same pattern — pass the counter into the queryKey of every list query you want to keep fresh.
|
|
52
52
|
|
|
53
|
-
4. **Fallback** polling calls `/_agent-native/poll?since=N`. It
|
|
53
|
+
4. **Fallback** polling calls `/_agent-native/poll?since=N`. It polls every 2 seconds only while an agent run is in flight; otherwise it idles at 60 seconds whether or not SSE is connected (`SSE_FALLBACK_INTERVAL_MS` and `IDLE_POLL_INTERVAL_MS`, both 60 s). A hidden tab polls no faster than every 10 seconds (`HIDDEN_POLL_INTERVAL_MS`). Polling is the universal serverless fallback: new framework writes are read from the durable `sync_events` log, while the older DB timestamp scan remains as a slower safety net for direct SQL writes and older processes.
|
|
54
54
|
|
|
55
55
|
5. When the agent writes to the database, the version increments, SSE/polling detects it, and React Query refetches the affected queries.
|
|
56
56
|
|
|
@@ -18,6 +18,11 @@ Any resource a user **creates** (dashboards, documents, forms, decks, compositio
|
|
|
18
18
|
|
|
19
19
|
This is the framework-level primitive. Every ownable resource gets it for free — same API, same UI, same skill.
|
|
20
20
|
|
|
21
|
+
Workspace apps are the intentional organization-scoped exception: their default
|
|
22
|
+
visibility is `org`, configurable by an organization owner/admin. The creator is
|
|
23
|
+
stored as the app owner, and the creator or an organization owner/admin can
|
|
24
|
+
change the app's visibility or manage its share grants.
|
|
25
|
+
|
|
21
26
|
## Concepts
|
|
22
27
|
|
|
23
28
|
### Three visibility levels
|
|
@@ -26,7 +31,8 @@ This is the framework-level primitive. Every ownable resource gets it for free
|
|
|
26
31
|
- **`org`** — owner + explicit grants + anyone in the same org (read-only).
|
|
27
32
|
- **`public`** — owner + explicit grants + **anyone with the link** (read-only). Public docs do NOT appear in other users' list/sidebar/search results — `accessFilter` omits them by default. They're reachable by id (`resolveAccess` admits them) so direct links and SSR routes like `/p/:id` keep working. If a list endpoint legitimately needs cross-user public discovery (a template gallery, etc.), pass `accessFilter(table, shares, ctx, minRole, { includePublic: true })`.
|
|
28
33
|
|
|
29
|
-
Visibility is coarse. Explicit share grants are fine-grained (per user
|
|
34
|
+
Visibility is coarse. Explicit share grants are fine-grained (per user, reusable
|
|
35
|
+
organization group, or per org).
|
|
30
36
|
|
|
31
37
|
### Roles on a share grant
|
|
32
38
|
|
|
@@ -101,6 +107,7 @@ registerShareableResource({
|
|
|
101
107
|
// ...
|
|
102
108
|
allowPublic: false, // hides "Public" in the share dialog and rejects it server-side
|
|
103
109
|
requireOrgMemberForUserShares: true, // user shares must target an org member or pending invitee
|
|
110
|
+
supportsGroupShares: true, // enable reusable organization groups for this resource
|
|
104
111
|
});
|
|
105
112
|
```
|
|
106
113
|
|
|
@@ -111,6 +118,21 @@ Use both for resources that execute code or expose privileged data with the *vie
|
|
|
111
118
|
|
|
112
119
|
Defaults match historical behaviour: `allowPublic: true`, `requireOrgMemberForUserShares: false`. Resources that don't set the flags work as before.
|
|
113
120
|
|
|
121
|
+
### Reusable organization groups
|
|
122
|
+
|
|
123
|
+
The workspace organization model stores reusable groups in
|
|
124
|
+
`workspace_user_groups`. Organization owners/admins manage the groups and their
|
|
125
|
+
members through the existing group actions. Set `supportsGroupShares: true` on a
|
|
126
|
+
resource registration to enable `principalType: "group"` in the generic share
|
|
127
|
+
actions and the shared popover's autocomplete. Group ids are always checked
|
|
128
|
+
against the resource's organization, and membership is evaluated at access time
|
|
129
|
+
so group changes apply to existing shares.
|
|
130
|
+
|
|
131
|
+
Workspace apps opt into this flag and use the organization default visibility
|
|
132
|
+
setting. The app creator is the durable `owner_email`, while organization
|
|
133
|
+
owners/admins receive admin access through the registration's
|
|
134
|
+
`canManageAccess` callback.
|
|
135
|
+
|
|
114
136
|
## Filter list/read queries
|
|
115
137
|
|
|
116
138
|
```ts
|
|
@@ -197,6 +219,9 @@ All app share popovers should use the same compact surface contract:
|
|
|
197
219
|
- Add `ShareAgentsSection` only when the resource has a real agent-readable
|
|
198
220
|
link or prompt. Keep it collapsed by default and supply domain-specific
|
|
199
221
|
content through the shared section shell.
|
|
222
|
+
- When `supportsGroupShares` is enabled, keep the existing people input as the
|
|
223
|
+
only entry point and include organization groups in its autocomplete. Do not
|
|
224
|
+
add a separate group-management surface to each resource.
|
|
200
225
|
- App-specific tabs or controls may remain when they represent a real domain
|
|
201
226
|
action (for example, an embed-code editor), but they should retain the same
|
|
202
227
|
trigger, copy-row, access, and spacing language.
|
|
@@ -211,7 +236,7 @@ The framework auto-mounts these actions in every template — no per-template bo
|
|
|
211
236
|
|
|
212
237
|
| Action | Args | Purpose |
|
|
213
238
|
| -------------------------- | ------------------------------------------------------------------------------ | ----------------------------------------- |
|
|
214
|
-
| `share-resource` | `resourceType, resourceId, principalType, principalId, role, notify?, resourceUrl?, message?` | Grant a user or org access. `notify` defaults to true for individual user shares; `resourceUrl` can provide the direct app link and `message` an optional short note for the notification email. |
|
|
239
|
+
| `share-resource` | `resourceType, resourceId, principalType, principalId, role, notify?, resourceUrl?, message?` | Grant a user, group, or org access. `notify` defaults to true for individual user shares; `resourceUrl` can provide the direct app link and `message` an optional short note for the notification email. |
|
|
215
240
|
| `unshare-resource` | `resourceType, resourceId, principalType, principalId` | Revoke access. |
|
|
216
241
|
| `list-resource-shares` | `resourceType, resourceId` | Current visibility + all share grants. |
|
|
217
242
|
| `set-resource-visibility` | `resourceType, resourceId, visibility` | Change to `private` / `org` / `public`. |
|
|
@@ -50,7 +50,7 @@ The agent modifies data in SQL, but the UI runs in the browser. SSE bridges same
|
|
|
50
50
|
|
|
51
51
|
For list/sidebar queries, use the same pattern — pass the counter into the queryKey of every list query you want to keep fresh.
|
|
52
52
|
|
|
53
|
-
4. **Fallback** polling calls `/_agent-native/poll?since=N`. It
|
|
53
|
+
4. **Fallback** polling calls `/_agent-native/poll?since=N`. It polls every 2 seconds only while an agent run is in flight; otherwise it idles at 60 seconds whether or not SSE is connected (`SSE_FALLBACK_INTERVAL_MS` and `IDLE_POLL_INTERVAL_MS`, both 60 s). A hidden tab polls no faster than every 10 seconds (`HIDDEN_POLL_INTERVAL_MS`). Polling is the universal serverless fallback: new framework writes are read from the durable `sync_events` log, while the older DB timestamp scan remains as a slower safety net for direct SQL writes and older processes.
|
|
54
54
|
|
|
55
55
|
5. When the agent writes to the database, the version increments, SSE/polling detects it, and React Query refetches the affected queries.
|
|
56
56
|
|
|
@@ -18,6 +18,11 @@ Any resource a user **creates** (dashboards, documents, forms, decks, compositio
|
|
|
18
18
|
|
|
19
19
|
This is the framework-level primitive. Every ownable resource gets it for free — same API, same UI, same skill.
|
|
20
20
|
|
|
21
|
+
Workspace apps are the intentional organization-scoped exception: their default
|
|
22
|
+
visibility is `org`, configurable by an organization owner/admin. The creator is
|
|
23
|
+
stored as the app owner, and the creator or an organization owner/admin can
|
|
24
|
+
change the app's visibility or manage its share grants.
|
|
25
|
+
|
|
21
26
|
## Concepts
|
|
22
27
|
|
|
23
28
|
### Three visibility levels
|
|
@@ -26,7 +31,8 @@ This is the framework-level primitive. Every ownable resource gets it for free
|
|
|
26
31
|
- **`org`** — owner + explicit grants + anyone in the same org (read-only).
|
|
27
32
|
- **`public`** — owner + explicit grants + **anyone with the link** (read-only). Public docs do NOT appear in other users' list/sidebar/search results — `accessFilter` omits them by default. They're reachable by id (`resolveAccess` admits them) so direct links and SSR routes like `/p/:id` keep working. If a list endpoint legitimately needs cross-user public discovery (a template gallery, etc.), pass `accessFilter(table, shares, ctx, minRole, { includePublic: true })`.
|
|
28
33
|
|
|
29
|
-
Visibility is coarse. Explicit share grants are fine-grained (per user
|
|
34
|
+
Visibility is coarse. Explicit share grants are fine-grained (per user, reusable
|
|
35
|
+
organization group, or per org).
|
|
30
36
|
|
|
31
37
|
### Roles on a share grant
|
|
32
38
|
|
|
@@ -101,6 +107,7 @@ registerShareableResource({
|
|
|
101
107
|
// ...
|
|
102
108
|
allowPublic: false, // hides "Public" in the share dialog and rejects it server-side
|
|
103
109
|
requireOrgMemberForUserShares: true, // user shares must target an org member or pending invitee
|
|
110
|
+
supportsGroupShares: true, // enable reusable organization groups for this resource
|
|
104
111
|
});
|
|
105
112
|
```
|
|
106
113
|
|
|
@@ -111,6 +118,21 @@ Use both for resources that execute code or expose privileged data with the *vie
|
|
|
111
118
|
|
|
112
119
|
Defaults match historical behaviour: `allowPublic: true`, `requireOrgMemberForUserShares: false`. Resources that don't set the flags work as before.
|
|
113
120
|
|
|
121
|
+
### Reusable organization groups
|
|
122
|
+
|
|
123
|
+
The workspace organization model stores reusable groups in
|
|
124
|
+
`workspace_user_groups`. Organization owners/admins manage the groups and their
|
|
125
|
+
members through the existing group actions. Set `supportsGroupShares: true` on a
|
|
126
|
+
resource registration to enable `principalType: "group"` in the generic share
|
|
127
|
+
actions and the shared popover's autocomplete. Group ids are always checked
|
|
128
|
+
against the resource's organization, and membership is evaluated at access time
|
|
129
|
+
so group changes apply to existing shares.
|
|
130
|
+
|
|
131
|
+
Workspace apps opt into this flag and use the organization default visibility
|
|
132
|
+
setting. The app creator is the durable `owner_email`, while organization
|
|
133
|
+
owners/admins receive admin access through the registration's
|
|
134
|
+
`canManageAccess` callback.
|
|
135
|
+
|
|
114
136
|
## Filter list/read queries
|
|
115
137
|
|
|
116
138
|
```ts
|
|
@@ -197,6 +219,9 @@ All app share popovers should use the same compact surface contract:
|
|
|
197
219
|
- Add `ShareAgentsSection` only when the resource has a real agent-readable
|
|
198
220
|
link or prompt. Keep it collapsed by default and supply domain-specific
|
|
199
221
|
content through the shared section shell.
|
|
222
|
+
- When `supportsGroupShares` is enabled, keep the existing people input as the
|
|
223
|
+
only entry point and include organization groups in its autocomplete. Do not
|
|
224
|
+
add a separate group-management surface to each resource.
|
|
200
225
|
- App-specific tabs or controls may remain when they represent a real domain
|
|
201
226
|
action (for example, an embed-code editor), but they should retain the same
|
|
202
227
|
trigger, copy-row, access, and spacing language.
|
|
@@ -211,7 +236,7 @@ The framework auto-mounts these actions in every template — no per-template bo
|
|
|
211
236
|
|
|
212
237
|
| Action | Args | Purpose |
|
|
213
238
|
| -------------------------- | ------------------------------------------------------------------------------ | ----------------------------------------- |
|
|
214
|
-
| `share-resource` | `resourceType, resourceId, principalType, principalId, role, notify?, resourceUrl?, message?` | Grant a user or org access. `notify` defaults to true for individual user shares; `resourceUrl` can provide the direct app link and `message` an optional short note for the notification email. |
|
|
239
|
+
| `share-resource` | `resourceType, resourceId, principalType, principalId, role, notify?, resourceUrl?, message?` | Grant a user, group, or org access. `notify` defaults to true for individual user shares; `resourceUrl` can provide the direct app link and `message` an optional short note for the notification email. |
|
|
215
240
|
| `unshare-resource` | `resourceType, resourceId, principalType, principalId` | Revoke access. |
|
|
216
241
|
| `list-resource-shares` | `resourceType, resourceId` | Current visibility + all share grants. |
|
|
217
242
|
| `set-resource-visibility` | `resourceType, resourceId, visibility` | Change to `private` / `org` / `public`. |
|
package/dist/vite/client.d.ts
CHANGED
|
@@ -193,6 +193,11 @@ export interface AgentNativeVitePluginOptions extends Omit<ClientConfigOptions,
|
|
|
193
193
|
}
|
|
194
194
|
export declare function stripMountedDevApiPath(reqUrl: string | undefined, base: string | undefined): string | undefined;
|
|
195
195
|
export declare function isFrameworkDevPath(reqUrl: string, base: string | undefined): boolean;
|
|
196
|
+
/**
|
|
197
|
+
* Framework-owned dynamic dev paths whose responses are served by the h3 app:
|
|
198
|
+
* the `/_agent-native/*` API surface plus framework `/.well-known/*` documents.
|
|
199
|
+
*/
|
|
200
|
+
export declare function isFrameworkDynamicDevPath(reqUrl: string, base: string | undefined): boolean;
|
|
196
201
|
declare function nitroModuleGraphSignature(environment: unknown): string | null;
|
|
197
202
|
declare function nitroStartupGate(options?: {
|
|
198
203
|
now?: () => number;
|
package/dist/vite/client.js
CHANGED
|
@@ -7,7 +7,7 @@ import { renderDesignSystemThemeCss, } from "@agent-native/toolkit/design-system
|
|
|
7
7
|
import { loadEnv, } from "vite";
|
|
8
8
|
import { mergePendingChangelog, parsePendingEntry, } from "../changelog/parse.js";
|
|
9
9
|
import { getViteDevRecoveryScript } from "../client/vite-dev-recovery-script.js";
|
|
10
|
-
import { mergeAgentNativeConfigs, resolveAgentNativeConfig, } from "../config.js";
|
|
10
|
+
import { inferAgentNativeDeploymentEnvironment, mergeAgentNativeConfigs, resolveAgentNativeConfig, } from "../config.js";
|
|
11
11
|
import { writeAgentNativeNitroPresetMarker } from "../deploy/nitro-preset.js";
|
|
12
12
|
import { findWorkspaceRoot } from "../scripts/utils.js";
|
|
13
13
|
import { verifyEmbedSessionToken } from "../server/embed-session.js";
|
|
@@ -1432,6 +1432,41 @@ function baseRedirectGuard() {
|
|
|
1432
1432
|
},
|
|
1433
1433
|
};
|
|
1434
1434
|
}
|
|
1435
|
+
/**
|
|
1436
|
+
* Force Nitro's dev middleware to treat extension-bearing framework endpoints
|
|
1437
|
+
* as dynamic so they reach the h3 server instead of Vite's static pipeline.
|
|
1438
|
+
*
|
|
1439
|
+
* Nitro's Vite dev middleware routes paths with asset-like extensions through
|
|
1440
|
+
* Vite unless a Nitro route matches them. Framework endpoints are registered
|
|
1441
|
+
* as h3 middleware, so mounted `/_agent-native/*` and `/.well-known/*` paths
|
|
1442
|
+
* can otherwise become dev-only 404s before reaching the framework handler.
|
|
1443
|
+
*/
|
|
1444
|
+
function frameworkDevDynamicForwarder() {
|
|
1445
|
+
return {
|
|
1446
|
+
name: "agent-native-framework-dev-dynamic-forwarder",
|
|
1447
|
+
apply: "serve",
|
|
1448
|
+
configureServer(server) {
|
|
1449
|
+
server.middlewares.use((req, _res, next) => {
|
|
1450
|
+
const url = req.url;
|
|
1451
|
+
if (url && isFrameworkDynamicDevPath(url, server.config.base)) {
|
|
1452
|
+
const accept = req.headers["accept"];
|
|
1453
|
+
if (typeof accept !== "string" || !/\btext\/html\b/.test(accept)) {
|
|
1454
|
+
req.headers["accept"] = accept
|
|
1455
|
+
? `text/html,${accept}`
|
|
1456
|
+
: "text/html";
|
|
1457
|
+
}
|
|
1458
|
+
// Embed-start uses document/iframe to select its transplant response.
|
|
1459
|
+
// Only supply the classifier hint when the browser did not provide a
|
|
1460
|
+
// destination; never overwrite the request's original intent.
|
|
1461
|
+
if (req.headers["sec-fetch-dest"] === undefined) {
|
|
1462
|
+
req.headers["sec-fetch-dest"] = "empty";
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
next();
|
|
1466
|
+
});
|
|
1467
|
+
},
|
|
1468
|
+
};
|
|
1469
|
+
}
|
|
1435
1470
|
const VITE_RUNTIME_PATH_PREFIXES = [
|
|
1436
1471
|
"/@fs/",
|
|
1437
1472
|
"/@id/",
|
|
@@ -1872,6 +1907,23 @@ export function isFrameworkDevPath(reqUrl, base) {
|
|
|
1872
1907
|
return (pathname === `${normalizedBase}/_agent-native` ||
|
|
1873
1908
|
pathname.startsWith(`${normalizedBase}/_agent-native/`));
|
|
1874
1909
|
}
|
|
1910
|
+
/**
|
|
1911
|
+
* Framework-owned dynamic dev paths whose responses are served by the h3 app:
|
|
1912
|
+
* the `/_agent-native/*` API surface plus framework `/.well-known/*` documents.
|
|
1913
|
+
*/
|
|
1914
|
+
export function isFrameworkDynamicDevPath(reqUrl, base) {
|
|
1915
|
+
if (isFrameworkDevPath(reqUrl, base))
|
|
1916
|
+
return true;
|
|
1917
|
+
const pathname = devPathname(reqUrl);
|
|
1918
|
+
if (pathname.startsWith("/.well-known/"))
|
|
1919
|
+
return true;
|
|
1920
|
+
if (base && base !== "/") {
|
|
1921
|
+
const normalizedBase = base.endsWith("/") ? base.slice(0, -1) : base;
|
|
1922
|
+
if (pathname.startsWith(`${normalizedBase}/.well-known/`))
|
|
1923
|
+
return true;
|
|
1924
|
+
}
|
|
1925
|
+
return false;
|
|
1926
|
+
}
|
|
1875
1927
|
/**
|
|
1876
1928
|
* Work around a Rolldown bug where Nitro passes service entries as objects
|
|
1877
1929
|
* ({index: "path"}) but Rolldown expects strings. This plugin normalizes
|
|
@@ -2679,6 +2731,7 @@ function createAgentNativePlugins(options, { command, includeReactTransform, use
|
|
|
2679
2731
|
fullReloadOnOptimizeDep504(),
|
|
2680
2732
|
embedDevFrameHeaders(),
|
|
2681
2733
|
baseRedirectGuard(),
|
|
2734
|
+
frameworkDevDynamicForwarder(),
|
|
2682
2735
|
portExposer(),
|
|
2683
2736
|
nitroStartupGate(),
|
|
2684
2737
|
reactRouterVirtualInvalidationMirrorPlugin(),
|
|
@@ -2755,6 +2808,15 @@ function createAgentNativeConfig(options = {}, command, userConfig = {}, mode =
|
|
|
2755
2808
|
const appConfig = resolveAgentNativeConfig(mergeAgentNativeConfigs(readAgentNativeJsonConfig(cwd), projectConfigInput
|
|
2756
2809
|
? resolveAgentNativeConfig(projectConfigInput, configContext)
|
|
2757
2810
|
: {}), configContext);
|
|
2811
|
+
const inferredDeploymentEnvironment = appConfig.deployment?.environment ??
|
|
2812
|
+
inferAgentNativeDeploymentEnvironment(process.env, mode);
|
|
2813
|
+
const resolvedAppConfig = appConfig.deployment?.environment === undefined &&
|
|
2814
|
+
inferredDeploymentEnvironment !== undefined
|
|
2815
|
+
? {
|
|
2816
|
+
...appConfig,
|
|
2817
|
+
deployment: { environment: inferredDeploymentEnvironment },
|
|
2818
|
+
}
|
|
2819
|
+
: appConfig;
|
|
2758
2820
|
const buildId = process.env.DEPLOY_ID?.trim() ||
|
|
2759
2821
|
process.env.COMMIT_REF?.trim() ||
|
|
2760
2822
|
process.env.VERCEL_GIT_COMMIT_SHA?.trim() ||
|
|
@@ -2844,7 +2906,7 @@ function createAgentNativeConfig(options = {}, command, userConfig = {}, mode =
|
|
|
2844
2906
|
...(options.define ?? {}),
|
|
2845
2907
|
__AGENT_NATIVE_BUILD_ID__: JSON.stringify(buildId),
|
|
2846
2908
|
__AGENT_NATIVE_CLIENT_COMPATIBILITY_VERSION__: JSON.stringify(options.clientCompatibilityVersion?.trim() || ""),
|
|
2847
|
-
__AGENT_NATIVE_APP_CONFIG__: JSON.stringify(
|
|
2909
|
+
__AGENT_NATIVE_APP_CONFIG__: JSON.stringify(resolvedAppConfig),
|
|
2848
2910
|
__AGENT_NATIVE_BUILD_GA_MEASUREMENT_ID__: JSON.stringify(process.env.GA_MEASUREMENT_ID?.trim() || ""),
|
|
2849
2911
|
"process.env.AGENT_NATIVE_BUILD_GA_MEASUREMENT_ID": JSON.stringify(process.env.GA_MEASUREMENT_ID?.trim() || ""),
|
|
2850
2912
|
// The release migration owner is configured at build time. Netlify's
|
|
@@ -1,2 +1,10 @@
|
|
|
1
|
-
declare const _default: import("../../action.js").ActionDefinition<Record<string, never>,
|
|
1
|
+
declare const _default: import("../../action.js").ActionDefinition<Record<string, never>, {
|
|
2
|
+
id: string;
|
|
3
|
+
orgId: string;
|
|
4
|
+
name: string;
|
|
5
|
+
createdByEmail: string;
|
|
6
|
+
createdAt: string;
|
|
7
|
+
updatedAt: string;
|
|
8
|
+
memberEmails: any[];
|
|
9
|
+
}[]>;
|
|
2
10
|
export default _default;
|
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { defineAction } from "../../action.js";
|
|
3
|
-
import {
|
|
3
|
+
import { listWorkspaceUserGroupsForOrg, workspaceUserGroupRole, } from "../groups.js";
|
|
4
4
|
export default defineAction({
|
|
5
|
-
description: "List reusable workspace user groups.
|
|
5
|
+
description: "List reusable workspace user groups. Workspace members can see group names for sharing; only owners and admins receive member details and can manage groups.",
|
|
6
6
|
schema: z.object({}),
|
|
7
7
|
http: { method: "GET" },
|
|
8
8
|
readOnly: true,
|
|
9
9
|
run: async (_args, ctx) => {
|
|
10
|
-
await
|
|
11
|
-
|
|
10
|
+
const role = await workspaceUserGroupRole(ctx?.orgId, ctx?.userEmail);
|
|
11
|
+
if (!role)
|
|
12
|
+
throw new Error("Workspace membership is required.");
|
|
13
|
+
const groups = await listWorkspaceUserGroupsForOrg(ctx?.orgId ?? "");
|
|
14
|
+
if (role === "owner" || role === "admin")
|
|
15
|
+
return groups;
|
|
16
|
+
return groups.map((group) => ({ ...group, memberEmails: [] }));
|
|
12
17
|
},
|
|
13
18
|
});
|
|
@@ -20,12 +20,14 @@ export interface UpdateWorkspaceUserGroupMembersInput {
|
|
|
20
20
|
operation: "add" | "remove";
|
|
21
21
|
orgId?: string | null;
|
|
22
22
|
}
|
|
23
|
+
export declare function workspaceUserGroupsTable(): string;
|
|
23
24
|
export declare function normalizeWorkspaceUserGroupIds(value: unknown): string[];
|
|
24
25
|
export declare function ensureWorkspaceUserGroupsTable(): Promise<void>;
|
|
25
26
|
export declare function listWorkspaceUserGroupsForOrg(orgId: string, groupIds?: string[]): Promise<WorkspaceUserGroup[]>;
|
|
26
27
|
export declare function listWorkspaceUserGroups(): Promise<WorkspaceUserGroup[]>;
|
|
27
28
|
export declare function assertWorkspaceUserGroupIds(groupIds: string[] | undefined, orgId: string | null | undefined): Promise<string[] | undefined>;
|
|
28
29
|
export declare function assertWorkspaceUserGroupManager(orgId: string | null | undefined, userEmail: string | undefined): Promise<void>;
|
|
30
|
+
export declare function workspaceUserGroupRole(orgId: string | null | undefined, userEmail: string | undefined): Promise<"owner" | "admin" | "member" | null>;
|
|
29
31
|
export declare function upsertWorkspaceUserGroup(input: UpsertWorkspaceUserGroupInput): Promise<WorkspaceUserGroup>;
|
|
30
32
|
export declare function updateWorkspaceUserGroupMembers(input: UpdateWorkspaceUserGroupMembersInput): Promise<WorkspaceUserGroup>;
|
|
31
33
|
export declare function deleteWorkspaceUserGroup(id: string, orgId?: string | null): Promise<boolean>;
|
|
@@ -3,7 +3,7 @@ import { getDbExec, intType, isPostgres, retryOnDdlRace, safeJsonParse, } from "
|
|
|
3
3
|
import { ensureIndexExists, ensureTableExists } from "../db/ddl-guard.js";
|
|
4
4
|
import { isOrgMember } from "../org/membership.js";
|
|
5
5
|
import { getRequestOrgId, getRequestUserEmail, } from "../server/request-context.js";
|
|
6
|
-
function workspaceUserGroupsTable() {
|
|
6
|
+
export function workspaceUserGroupsTable() {
|
|
7
7
|
return isPostgres()
|
|
8
8
|
? "public.workspace_user_groups"
|
|
9
9
|
: "workspace_user_groups";
|
|
@@ -171,19 +171,25 @@ export async function assertWorkspaceUserGroupIds(groupIds, orgId) {
|
|
|
171
171
|
return normalized;
|
|
172
172
|
}
|
|
173
173
|
export async function assertWorkspaceUserGroupManager(orgId, userEmail) {
|
|
174
|
+
const role = await workspaceUserGroupRole(orgId, userEmail);
|
|
175
|
+
if (role === "owner" || role === "admin")
|
|
176
|
+
return;
|
|
177
|
+
throw new Error("Only workspace admins can manage user groups.");
|
|
178
|
+
}
|
|
179
|
+
export async function workspaceUserGroupRole(orgId, userEmail) {
|
|
174
180
|
const normalizedOrgId = orgId?.trim();
|
|
175
181
|
const normalizedEmail = userEmail?.trim().toLowerCase();
|
|
176
182
|
if (!normalizedOrgId || !normalizedEmail) {
|
|
177
|
-
|
|
183
|
+
return null;
|
|
178
184
|
}
|
|
179
185
|
const { rows } = await getDbExec().execute({
|
|
180
186
|
sql: `SELECT role FROM org_members WHERE org_id = ? AND LOWER(email) = ? LIMIT 1`,
|
|
181
187
|
args: [normalizedOrgId, normalizedEmail],
|
|
182
188
|
});
|
|
183
189
|
const role = String(rows[0]?.role ?? "");
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
190
|
+
return role === "owner" || role === "admin" || role === "member"
|
|
191
|
+
? role
|
|
192
|
+
: null;
|
|
187
193
|
}
|
|
188
194
|
export async function upsertWorkspaceUserGroup(input) {
|
|
189
195
|
const requestScope = requireWorkspaceUserGroupScope();
|
|
@@ -283,6 +289,11 @@ export async function workspaceUserGroupsIncludeUser(orgId, groupIds, userEmail)
|
|
|
283
289
|
if (!normalizedOrgId || !normalizedEmail || normalizedIds.length === 0) {
|
|
284
290
|
return false;
|
|
285
291
|
}
|
|
292
|
+
// Group rows can outlive roster membership. A stale email in the JSON
|
|
293
|
+
// member list must not keep granting access after that person leaves the
|
|
294
|
+
// organization.
|
|
295
|
+
if (!(await isOrgMember(normalizedOrgId, normalizedEmail)))
|
|
296
|
+
return false;
|
|
286
297
|
const groups = await listWorkspaceUserGroupsForOrg(normalizedOrgId, normalizedIds);
|
|
287
298
|
return groups.some((group) => group.memberEmails.includes(normalizedEmail));
|
|
288
299
|
}
|
|
@@ -14,14 +14,7 @@ export type WorkspaceFileActionResult = z.infer<typeof workspaceFileResultSchema
|
|
|
14
14
|
export declare const showWorkspaceFileAction: import("../action.js").ActionDefinition<{
|
|
15
15
|
path: string;
|
|
16
16
|
}, {
|
|
17
|
-
file:
|
|
18
|
-
resourceId: string;
|
|
19
|
-
path: string;
|
|
20
|
-
name: string;
|
|
21
|
-
contentType: string;
|
|
22
|
-
sizeBytes: number;
|
|
23
|
-
updatedAt: string;
|
|
24
|
-
};
|
|
17
|
+
file: import("./store.js").WorkspaceFileCard;
|
|
25
18
|
}>;
|
|
26
19
|
export declare function createWorkspaceFileActionEntries(): Record<string, ActionEntry>;
|
|
27
20
|
export {};
|
|
@@ -2,7 +2,7 @@ import { z } from "zod";
|
|
|
2
2
|
import { ACTION_CHAT_UI_WORKSPACE_FILE_RENDERER } from "../action-ui.js";
|
|
3
3
|
import { defineAction } from "../action.js";
|
|
4
4
|
import { getRequestOrgId, getRequestUserEmail, } from "../server/request-context.js";
|
|
5
|
-
import { getWorkspaceFileMeta } from "./store.js";
|
|
5
|
+
import { getWorkspaceFileMeta, toWorkspaceFileCard, } from "./store.js";
|
|
6
6
|
const workspaceFileResultSchema = z.object({
|
|
7
7
|
file: z.object({
|
|
8
8
|
resourceId: z.string().min(1),
|
|
@@ -22,13 +22,6 @@ function currentWorkspaceScope() {
|
|
|
22
22
|
return { scope: "user", scopeId: userEmail };
|
|
23
23
|
throw new Error("show-workspace-file requires an authenticated request context.");
|
|
24
24
|
}
|
|
25
|
-
function fileName(path) {
|
|
26
|
-
return path.split("/").at(-1) || path;
|
|
27
|
-
}
|
|
28
|
-
function isTextWorkspaceFile(contentType) {
|
|
29
|
-
const mimeType = contentType.split(";")[0].trim().toLowerCase();
|
|
30
|
-
return mimeType.startsWith("text/") || mimeType === "application/json";
|
|
31
|
-
}
|
|
32
25
|
export const showWorkspaceFileAction = defineAction({
|
|
33
26
|
description: "Render a workspace file as a downloadable card in chat. Call this after writing a user-requested export or other durable file with workspaceWrite; a file export is not complete until this action succeeds and the download card appears. The path must exactly match an existing file in the current organization or personal workspace.",
|
|
34
27
|
schema: z.object({
|
|
@@ -52,19 +45,10 @@ export const showWorkspaceFileAction = defineAction({
|
|
|
52
45
|
if (!file) {
|
|
53
46
|
throw new Error(`Workspace file not found: "${path}"`);
|
|
54
47
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
return {
|
|
59
|
-
file: {
|
|
60
|
-
resourceId: file.id,
|
|
61
|
-
path: file.path,
|
|
62
|
-
name: fileName(file.path),
|
|
63
|
-
contentType: file.contentType,
|
|
64
|
-
sizeBytes: file.sizeBytes,
|
|
65
|
-
updatedAt: file.updatedAt,
|
|
66
|
-
},
|
|
67
|
-
};
|
|
48
|
+
// The card only ever renders name/size/type + a download link — it never
|
|
49
|
+
// inlines file content — so every content type, binary included, is safe
|
|
50
|
+
// to show here.
|
|
51
|
+
return { file: toWorkspaceFileCard(file) };
|
|
68
52
|
},
|
|
69
53
|
});
|
|
70
54
|
export function createWorkspaceFileActionEntries() {
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { writeWorkspaceFile, appendWorkspaceFile, readWorkspaceFile, getWorkspaceFileMeta, listWorkspaceFiles, deleteWorkspaceFile, grepWorkspaceFiles, validatePath, MAX_FILE_BYTES, MAX_SCOPE_BYTES, SAVE_TO_FILE_MAX_BYTES, type WorkspaceFilesScope, type WorkspaceFile, type WorkspaceFileMeta, } from "./store.js";
|
|
1
|
+
export { writeWorkspaceFile, appendWorkspaceFile, readWorkspaceFile, getWorkspaceFileMeta, listWorkspaceFiles, deleteWorkspaceFile, grepWorkspaceFiles, validatePath, isScratchWorkspacePath, fileNameFromPath, toWorkspaceFileCard, MAX_FILE_BYTES, MAX_SCOPE_BYTES, SAVE_TO_FILE_MAX_BYTES, type WorkspaceFilesScope, type WorkspaceFile, type WorkspaceFileMeta, type WorkspaceFileCard, } from "./store.js";
|
|
2
2
|
export { createWorkspaceFilesTool } from "./tool.js";
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { writeWorkspaceFile, appendWorkspaceFile, readWorkspaceFile, getWorkspaceFileMeta, listWorkspaceFiles, deleteWorkspaceFile, grepWorkspaceFiles, validatePath, MAX_FILE_BYTES, MAX_SCOPE_BYTES, SAVE_TO_FILE_MAX_BYTES, } from "./store.js";
|
|
1
|
+
export { writeWorkspaceFile, appendWorkspaceFile, readWorkspaceFile, getWorkspaceFileMeta, listWorkspaceFiles, deleteWorkspaceFile, grepWorkspaceFiles, validatePath, isScratchWorkspacePath, fileNameFromPath, toWorkspaceFileCard, MAX_FILE_BYTES, MAX_SCOPE_BYTES, SAVE_TO_FILE_MAX_BYTES, } from "./store.js";
|
|
2
2
|
export { createWorkspaceFilesTool } from "./tool.js";
|
|
@@ -20,6 +20,8 @@ export interface WorkspaceFilesScope {
|
|
|
20
20
|
scope: "user" | "org";
|
|
21
21
|
scopeId: string;
|
|
22
22
|
}
|
|
23
|
+
/** True for `scratch/...` paths — hidden agent staging, never durable. */
|
|
24
|
+
export declare function isScratchWorkspacePath(path: string): boolean;
|
|
23
25
|
/**
|
|
24
26
|
* Validate a workspace file path.
|
|
25
27
|
* - Non-empty, no leading slash, no ".." components, no null bytes.
|
|
@@ -44,6 +46,22 @@ export interface WorkspaceFileMeta {
|
|
|
44
46
|
createdAt: string;
|
|
45
47
|
updatedAt: string;
|
|
46
48
|
}
|
|
49
|
+
/** Display name for a workspace path — the last path segment. */
|
|
50
|
+
export declare function fileNameFromPath(path: string): string;
|
|
51
|
+
export interface WorkspaceFileCard {
|
|
52
|
+
resourceId: string;
|
|
53
|
+
path: string;
|
|
54
|
+
name: string;
|
|
55
|
+
contentType: string;
|
|
56
|
+
sizeBytes: number;
|
|
57
|
+
updatedAt: string;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Shape any workspace-file creator can spread into its own tool result as
|
|
61
|
+
* `{ file: toWorkspaceFileCard(meta) }` to get a download card in chat —
|
|
62
|
+
* the chat renderer matches on this shape alone, no per-caller wiring needed.
|
|
63
|
+
*/
|
|
64
|
+
export declare function toWorkspaceFileCard(meta: WorkspaceFileMeta): WorkspaceFileCard;
|
|
47
65
|
/**
|
|
48
66
|
* Write (create or overwrite) a workspace file.
|
|
49
67
|
* Enforces per-file limits for the compatibility API; persistence is handled by
|