@agent-native/core 0.107.0 → 0.107.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +10 -0
- package/corpus/core/docs/content/integrations.mdx +27 -27
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/production-agent.ts +19 -0
- package/corpus/core/src/agent/run-store.ts +98 -2
- package/corpus/core/src/client/AssistantChat.tsx +13 -0
- package/corpus/core/src/client/CommandMenu.tsx +5 -0
- package/corpus/core/src/client/active-run-state.ts +34 -0
- package/corpus/core/src/client/agent-chat-adapter.ts +4 -0
- package/corpus/core/src/client/resources/McpConnectionSuggestion.tsx +41 -11
- package/corpus/core/src/client/resources/McpIntegrationDialog.tsx +27 -9
- package/corpus/core/src/client/resources/mcp-integration-catalog.ts +45 -0
- package/corpus/core/src/client/sharing/ShareButton.tsx +30 -9
- package/corpus/core/src/client/use-agent-chat-home-handoff.ts +2 -2
- package/corpus/core/src/localization/default-messages.ts +4 -1
- package/corpus/core/src/secrets/register-framework-secrets.ts +28 -0
- package/corpus/core/src/server/agent-chat-plugin.ts +29 -1
- package/corpus/core/src/vite/client.ts +24 -0
- package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +29 -21
- package/corpus/templates/analytics/AGENTS.md +8 -0
- package/corpus/templates/analytics/actions/dashboard-mutation-api.ts +1 -1
- package/corpus/templates/analytics/actions/update-dashboard.ts +6 -2
- package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +35 -7
- package/corpus/templates/analytics/app/components/layout/Layout.tsx +1 -3
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +11 -1
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/extension-slot.ts +11 -0
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +27 -0
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/types.ts +6 -0
- package/corpus/templates/analytics/changelog/2026-07-17-dashboard-extension-boxes.md +6 -0
- package/corpus/templates/analytics/server/lib/agent-readable-resource-context.ts +8 -0
- package/corpus/templates/analytics/server/plugins/agent-chat.ts +1 -0
- package/corpus/templates/assets/changelog/2026-07-17-the-agent-chat-sidebar-stays-closed-until-you-open-it-or-sta.md +6 -0
- package/corpus/templates/brain/app/hooks/use-navigation-state.ts +6 -1
- package/corpus/templates/brain/changelog/2026-07-17-the-agent-chat-sidebar-stays-closed-until-you-open-it-or-sta.md +6 -0
- package/corpus/templates/calendar/app/components/layout/AppLayout.tsx +1 -1
- package/corpus/templates/calendar/changelog/2026-07-17-the-agent-chat-sidebar-stays-closed-until-you-open-it-or-sta.md +6 -0
- package/corpus/templates/chat/app/hooks/use-navigation-state.ts +4 -1
- package/corpus/templates/chat/changelog/2026-07-17-the-agent-chat-sidebar-stays-closed-until-you-open-it-or-sta.md +6 -0
- package/corpus/templates/clips/app/components/library/library-layout.tsx +1 -1
- package/corpus/templates/clips/changelog/2026-07-17-the-agent-chat-sidebar-stays-closed-until-you-open-it-or-sta.md +6 -0
- package/corpus/templates/content/actions/_content-space-access.ts +45 -2
- package/corpus/templates/content/actions/create-content-database.ts +41 -7
- package/corpus/templates/content/app/components/layout/Layout.tsx +1 -1
- package/corpus/templates/content/app/root.tsx +1 -10
- package/corpus/templates/content/changelog/2026-07-17-creating-a-database-now-completes-reliably-in-hosted-workspa.md +6 -0
- package/corpus/templates/content/changelog/2026-07-17-the-agent-chat-sidebar-stays-closed-until-you-open-it-or-sta.md +6 -0
- package/corpus/templates/dispatch/app/hooks/use-navigation-state.ts +4 -1
- package/corpus/templates/dispatch/changelog/2026-07-17-the-agent-chat-sidebar-stays-closed-until-you-open-it-or-sta.md +6 -0
- package/corpus/templates/forms/app/components/layout/Layout.tsx +2 -4
- package/corpus/templates/forms/app/components/ui/popover.tsx +25 -1
- package/corpus/templates/forms/app/hooks/use-navigation-state.ts +6 -1
- package/corpus/templates/forms/app/pages/FormBuilderPage.tsx +13 -10
- package/corpus/templates/forms/app/root.tsx +6 -1
- package/corpus/templates/forms/changelog/2026-07-17-form-builder-popovers-stay-within-the-viewport.md +6 -0
- package/corpus/templates/forms/changelog/2026-07-17-form-editor-loading-matches-current-sidebar-free-layout.md +6 -0
- package/corpus/templates/forms/changelog/2026-07-17-the-agent-chat-sidebar-stays-closed-until-you-open-it-or-sta.md +6 -0
- package/corpus/templates/mail/app/components/layout/AppLayout.tsx +1 -1
- package/corpus/templates/mail/changelog/2026-07-17-the-agent-chat-sidebar-stays-closed-until-you-open-it-or-sta.md +6 -0
- package/corpus/templates/plan/app/components/layout/Sidebar.tsx +14 -3
- package/corpus/templates/plan/app/hooks/use-navigation-state.ts +8 -1
- package/corpus/templates/plan/app/root.tsx +0 -2
- package/corpus/templates/plan/changelog/2026-07-17-the-agent-chat-sidebar-stays-closed-until-you-open-it-or-sta.md +6 -0
- package/corpus/templates/slides/app/components/layout/Layout.tsx +1 -1
- package/corpus/templates/slides/app/context/DeckContext.tsx +23 -23
- package/corpus/templates/slides/changelog/2026-07-17-duplicated-slides-now-stay-in-the-deck.md +6 -0
- package/corpus/templates/slides/changelog/2026-07-17-the-agent-chat-sidebar-stays-closed-until-you-open-it-or-sta.md +6 -0
- package/corpus/templates/tasks/app/components/layout/Layout.tsx +2 -13
- package/corpus/templates/tasks/changelog/2026-07-17-the-agent-chat-sidebar-stays-closed-until-you-open-it.md +6 -0
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +13 -1
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/run-store.d.ts +17 -0
- package/dist/agent/run-store.d.ts.map +1 -1
- package/dist/agent/run-store.js +84 -2
- package/dist/agent/run-store.js.map +1 -1
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +10 -1
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/CommandMenu.d.ts.map +1 -1
- package/dist/client/CommandMenu.js +4 -0
- package/dist/client/CommandMenu.js.map +1 -1
- package/dist/client/active-run-state.d.ts +7 -0
- package/dist/client/active-run-state.d.ts.map +1 -1
- package/dist/client/active-run-state.js +28 -0
- package/dist/client/active-run-state.js.map +1 -1
- package/dist/client/agent-chat-adapter.d.ts.map +1 -1
- package/dist/client/agent-chat-adapter.js +4 -1
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/resources/McpConnectionSuggestion.d.ts.map +1 -1
- package/dist/client/resources/McpConnectionSuggestion.js +27 -8
- package/dist/client/resources/McpConnectionSuggestion.js.map +1 -1
- package/dist/client/resources/McpIntegrationDialog.d.ts.map +1 -1
- package/dist/client/resources/McpIntegrationDialog.js +4 -2
- package/dist/client/resources/McpIntegrationDialog.js.map +1 -1
- package/dist/client/resources/mcp-integration-catalog.d.ts +6 -0
- package/dist/client/resources/mcp-integration-catalog.d.ts.map +1 -1
- package/dist/client/resources/mcp-integration-catalog.js +33 -0
- package/dist/client/resources/mcp-integration-catalog.js.map +1 -1
- package/dist/client/sharing/ShareButton.d.ts.map +1 -1
- package/dist/client/sharing/ShareButton.js +15 -12
- package/dist/client/sharing/ShareButton.js.map +1 -1
- package/dist/client/use-agent-chat-home-handoff.d.ts +1 -1
- package/dist/client/use-agent-chat-home-handoff.d.ts.map +1 -1
- package/dist/client/use-agent-chat-home-handoff.js +1 -1
- package/dist/client/use-agent-chat-home-handoff.js.map +1 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/localization/default-messages.d.ts +2 -0
- package/dist/localization/default-messages.d.ts.map +1 -1
- package/dist/localization/default-messages.js +3 -1
- package/dist/localization/default-messages.js.map +1 -1
- package/dist/observability/routes.d.ts +5 -5
- package/dist/secrets/register-framework-secrets.d.ts.map +1 -1
- package/dist/secrets/register-framework-secrets.js +25 -0
- package/dist/secrets/register-framework-secrets.js.map +1 -1
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +23 -1
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/templates/chat/app/hooks/use-navigation-state.ts +4 -1
- package/dist/templates/chat/changelog/2026-07-17-the-agent-chat-sidebar-stays-closed-until-you-open-it-or-sta.md +6 -0
- package/dist/vite/client.d.ts.map +1 -1
- package/dist/vite/client.js +20 -0
- package/dist/vite/client.js.map +1 -1
- package/docs/content/integrations.mdx +27 -27
- package/package.json +1 -1
- package/src/templates/chat/app/hooks/use-navigation-state.ts +4 -1
- package/src/templates/chat/changelog/2026-07-17-the-agent-chat-sidebar-stays-closed-until-you-open-it-or-sta.md +6 -0
|
@@ -67,6 +67,7 @@ interface SqlChartCardProps {
|
|
|
67
67
|
isDragSource?: boolean;
|
|
68
68
|
selectedForChat?: boolean;
|
|
69
69
|
onSelectForChat?: (options?: SelectDashboardPanelOptions) => void;
|
|
70
|
+
extensionContext?: Record<string, unknown> | null;
|
|
70
71
|
}
|
|
71
72
|
|
|
72
73
|
const PanelDragHandle = memo(function PanelDragHandle({
|
|
@@ -122,6 +123,7 @@ export function SqlChartCard({
|
|
|
122
123
|
isDragSource = false,
|
|
123
124
|
selectedForChat = false,
|
|
124
125
|
onSelectForChat,
|
|
126
|
+
extensionContext,
|
|
125
127
|
}: SqlChartCardProps) {
|
|
126
128
|
const t = useT();
|
|
127
129
|
const queryClient = useQueryClient();
|
|
@@ -346,6 +348,7 @@ export function SqlChartCard({
|
|
|
346
348
|
panel={panel}
|
|
347
349
|
resolvedSql={resolvedSql}
|
|
348
350
|
loadData
|
|
351
|
+
extensionContext={extensionContext}
|
|
349
352
|
/>
|
|
350
353
|
)}
|
|
351
354
|
<div className="absolute right-1 top-1 flex items-center gap-1 opacity-0 group-hover:opacity-100">
|
|
@@ -435,6 +438,7 @@ export function SqlChartCard({
|
|
|
435
438
|
panel={panel}
|
|
436
439
|
resolvedSql={resolvedSql}
|
|
437
440
|
loadData
|
|
441
|
+
extensionContext={extensionContext}
|
|
438
442
|
/>
|
|
439
443
|
</ChartFillHeight>
|
|
440
444
|
</div>
|
|
@@ -616,6 +620,7 @@ export function SqlChartCard({
|
|
|
616
620
|
resolvedSql={resolvedSql}
|
|
617
621
|
loadData={shouldLoadData}
|
|
618
622
|
onExportCsvChange={handleExportCsvChange}
|
|
623
|
+
extensionContext={extensionContext}
|
|
619
624
|
/>
|
|
620
625
|
</CardContent>
|
|
621
626
|
</Card>
|
|
@@ -627,7 +632,12 @@ export function SqlChartCard({
|
|
|
627
632
|
</DialogHeader>
|
|
628
633
|
<div className="flex min-h-0 flex-1 flex-col overflow-auto">
|
|
629
634
|
<ChartFillHeight>
|
|
630
|
-
<SqlChart
|
|
635
|
+
<SqlChart
|
|
636
|
+
panel={panel}
|
|
637
|
+
resolvedSql={resolvedSql}
|
|
638
|
+
loadData
|
|
639
|
+
extensionContext={extensionContext}
|
|
640
|
+
/>
|
|
631
641
|
</ChartFillHeight>
|
|
632
642
|
</div>
|
|
633
643
|
</DialogContent>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stable extension-point name for a dashboard box. The dashboard and panel ids
|
|
3
|
+
* are encoded independently so arbitrary saved ids cannot collapse into one
|
|
4
|
+
* slot or add path separators to the slots API.
|
|
5
|
+
*/
|
|
6
|
+
export function dashboardExtensionSlotId(
|
|
7
|
+
dashboardId: string,
|
|
8
|
+
panelId: string,
|
|
9
|
+
): string {
|
|
10
|
+
return `analytics.dashboard.${encodeURIComponent(dashboardId)}.panel.${encodeURIComponent(panelId)}`;
|
|
11
|
+
}
|
|
@@ -142,6 +142,7 @@ import {
|
|
|
142
142
|
resolveFilterVars,
|
|
143
143
|
} from "./DashboardFilterBar";
|
|
144
144
|
import { EmailReportDialog } from "./EmailReportDialog";
|
|
145
|
+
import { dashboardExtensionSlotId } from "./extension-slot";
|
|
145
146
|
import { interpolate } from "./interpolate";
|
|
146
147
|
import { serializePanelSql } from "./panel-sql";
|
|
147
148
|
import { AddPanelPopover, PanelEditorDialog } from "./PanelEditorDialog";
|
|
@@ -271,6 +272,7 @@ const PanelCell = memo(function PanelCell({
|
|
|
271
272
|
onRemovePanel,
|
|
272
273
|
onEditPanel,
|
|
273
274
|
onSavePanel,
|
|
275
|
+
dashboardExtensionContext,
|
|
274
276
|
}: {
|
|
275
277
|
panel: SqlPanel;
|
|
276
278
|
vars: Record<string, string>;
|
|
@@ -286,6 +288,7 @@ const PanelCell = memo(function PanelCell({
|
|
|
286
288
|
onRemovePanel: (panelId: string) => void;
|
|
287
289
|
onEditPanel: (panel: SqlPanel) => void;
|
|
288
290
|
onSavePanel: (panel: SqlPanel) => Promise<void>;
|
|
291
|
+
dashboardExtensionContext: Record<string, unknown>;
|
|
289
292
|
}) {
|
|
290
293
|
const resolved = useMemo(
|
|
291
294
|
() =>
|
|
@@ -366,6 +369,23 @@ const PanelCell = memo(function PanelCell({
|
|
|
366
369
|
isDragSource={isDragSource}
|
|
367
370
|
selectedForChat={selectedForChat}
|
|
368
371
|
onSelectForChat={handleSelectForChat}
|
|
372
|
+
extensionContext={
|
|
373
|
+
panel.chartType === "extension"
|
|
374
|
+
? {
|
|
375
|
+
...dashboardExtensionContext,
|
|
376
|
+
panel: {
|
|
377
|
+
id: panel.id,
|
|
378
|
+
title: panel.title,
|
|
379
|
+
slotId:
|
|
380
|
+
panel.config?.extensionSlotId ??
|
|
381
|
+
dashboardExtensionSlotId(
|
|
382
|
+
String(dashboardExtensionContext.dashboardId),
|
|
383
|
+
panel.id,
|
|
384
|
+
),
|
|
385
|
+
},
|
|
386
|
+
}
|
|
387
|
+
: undefined
|
|
388
|
+
}
|
|
369
389
|
/>
|
|
370
390
|
</div>
|
|
371
391
|
);
|
|
@@ -1966,6 +1986,13 @@ export default function SqlDashboardPage() {
|
|
|
1966
1986
|
onRemovePanel={removePanel}
|
|
1967
1987
|
onEditPanel={openEditPanel}
|
|
1968
1988
|
onSavePanel={handleSavePanel}
|
|
1989
|
+
dashboardExtensionContext={{
|
|
1990
|
+
dashboardId,
|
|
1991
|
+
dashboardName: dashboard.name,
|
|
1992
|
+
dashboardDescription:
|
|
1993
|
+
dashboard.description ?? null,
|
|
1994
|
+
filters: vars,
|
|
1995
|
+
}}
|
|
1969
1996
|
/>
|
|
1970
1997
|
<DashboardDropLine
|
|
1971
1998
|
slot={{
|
|
@@ -99,8 +99,14 @@ export interface SqlPanelConfig {
|
|
|
99
99
|
/**
|
|
100
100
|
* Extension panels only (`chartType: "extension"`): id of the extension to
|
|
101
101
|
* render inline as a sandboxed iframe instead of running the SQL pipeline.
|
|
102
|
+
* Kept for dashboards saved before slot-backed dashboard boxes.
|
|
102
103
|
*/
|
|
103
104
|
extensionId?: string;
|
|
105
|
+
/**
|
|
106
|
+
* Extension panels only: a named extension-point slot. The dashboard renders
|
|
107
|
+
* its installed extensions as this panel's box and supplies dashboard context.
|
|
108
|
+
*/
|
|
109
|
+
extensionSlotId?: string;
|
|
104
110
|
}
|
|
105
111
|
|
|
106
112
|
export interface SqlPanel {
|
|
@@ -64,6 +64,14 @@ function panelSummaries(config: Record<string, unknown>) {
|
|
|
64
64
|
typeof panelConfig.description === "string"
|
|
65
65
|
? panelConfig.description
|
|
66
66
|
: undefined,
|
|
67
|
+
extensionId:
|
|
68
|
+
typeof panelConfig.extensionId === "string"
|
|
69
|
+
? panelConfig.extensionId
|
|
70
|
+
: undefined,
|
|
71
|
+
extensionSlotId:
|
|
72
|
+
typeof panelConfig.extensionSlotId === "string"
|
|
73
|
+
? panelConfig.extensionSlotId
|
|
74
|
+
: undefined,
|
|
67
75
|
};
|
|
68
76
|
});
|
|
69
77
|
}
|
|
@@ -704,6 +704,7 @@ export default createAgentChatPlugin({
|
|
|
704
704
|
analyticsSourceGuidanceOpening() +
|
|
705
705
|
"DASHBOARD CREATION RULE — You may create dashboards, analyses, SQL panels, or other resources only when the user explicitly asks you to (e.g. 'build me a dashboard for...', 'create a new analysis', 'add a chart for...'). Never create any resource proactively during research, trend analysis, or answering questions. If you think a dashboard would be useful, suggest it and wait for explicit confirmation before creating anything. Never add new items to the sidebar or modify existing dashboards without an explicit user directive. " +
|
|
706
706
|
"DASHBOARD MUTATION RULE — For dashboard edits, default to `mutate-dashboard` with the typed `dashboard.*` script API so the main payload is a string and avoids native-array serialization traps. It can move panels by id, edit titles/SQL/config, insert, duplicate, remove, and patch dashboard fields in one atomic save. The script API is constrained: no variables/imports/loops/functions, only JSON-compatible arguments on documented dashboard methods. Do not count shifting `/panels/<index>` positions for ordinary dashboard edits unless the user specifically asks for low-level JSON-pointer operations. " +
|
|
707
|
+
'DASHBOARD EXTENSION BOX RULE — Analytics dashboards support extension boxes. Insert `chartType: "extension"` panels with `config.extensionSlotId` set to `analytics.dashboard.<dashboard-id>.panel.<panel-id>`, then call `add-extension-slot-target` and `install-extension` with that same id. The install is per-user; the extension box stays on the shared dashboard and receives dashboard/panel/current-filter context. Use `get-sql-dashboard` panel summaries to inspect an existing box; `config.extensionId` is only the legacy direct-embed format. ' +
|
|
707
708
|
"EXTENSION DATA-REPAIR RULE — When fixing data in an existing extension-backed dashboard or migrated surface such as Risk Meeting, inspect the current dashboard and extension first, then use `update-extension` `patches`/`edits` that change only the data-loading seam. Preserve the existing layout, CSS, copy, and interactions; never reconstruct the full HTML body for a data-only fix. Full-body replacement is blocked unless `allowFullReplacement=true`, which is reserved for an explicit visual rewrite or a complete replacement body supplied by the user. If a focused edit fails, change the target instead of retrying identical arguments. " +
|
|
708
709
|
'FIRST-PARTY DASHBOARD TIME RULE — AI-generated `source: "first-party"` panels are dashboard-time-bound by default: set `config.timeScope` to `dashboard` and include a matching dashboard time predicate. `{{timeRange}}` requires a matching `filters` entry with `id: "timeRange"` and `type: "select"`; `{{<id>Start}}`/`{{<id>End}}` require a matching `type: "date-range"` filter with that id. Allowed `timeScope` values are `dashboard`, `fixed-window`, `cohort-history`, and `all-time`; use `all-time` only when the user requests full available history and put all-time, lifetime, or historical in the title or description. Server validation rejects unbound first-party SQL. ' +
|
|
709
710
|
"DASHBOARD READ RULE — `get-sql-dashboard` is compact by default: use its `panels` summaries plus `layout.panelOrder`, `layout.firstPanelIds`, and `layout.groups[].rows[].rowNumber/panelIds` for orientation and verification. Pass `includeConfig: true` only when you truly need full panel SQL/config. " +
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
appBasePath,
|
|
3
3
|
appPath,
|
|
4
|
+
isAgentChatHomeHandoffActive,
|
|
4
5
|
markAgentChatHomeHandoff,
|
|
5
6
|
useAgentRouteState,
|
|
6
7
|
} from "@agent-native/core/client";
|
|
@@ -106,7 +107,11 @@ export function useNavigationState() {
|
|
|
106
107
|
return `${path}${params.size ? `?${params.toString()}` : ""}`;
|
|
107
108
|
},
|
|
108
109
|
onNavigate: (_command, path) => {
|
|
109
|
-
if (
|
|
110
|
+
if (
|
|
111
|
+
location.pathname === "/" &&
|
|
112
|
+
pathnameFromPath(path) !== "/" &&
|
|
113
|
+
isAgentChatHomeHandoffActive("brain")
|
|
114
|
+
) {
|
|
110
115
|
markAgentChatHomeHandoff("brain");
|
|
111
116
|
}
|
|
112
117
|
},
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
appBasePath,
|
|
3
|
+
isAgentChatHomeHandoffActive,
|
|
3
4
|
appPath,
|
|
4
5
|
markAgentChatHomeHandoff,
|
|
5
6
|
useAgentRouteState,
|
|
@@ -34,7 +35,9 @@ export function useNavigationState() {
|
|
|
34
35
|
isChatPath(location.pathname) &&
|
|
35
36
|
!isChatPath(pathnameFromPath(path))
|
|
36
37
|
) {
|
|
37
|
-
|
|
38
|
+
if (isAgentChatHomeHandoffActive("chat")) {
|
|
39
|
+
markAgentChatHomeHandoff("chat");
|
|
40
|
+
}
|
|
38
41
|
}
|
|
39
42
|
},
|
|
40
43
|
});
|
|
@@ -559,7 +559,7 @@ export function LibraryLayout({ children }: LibraryLayoutProps) {
|
|
|
559
559
|
|
|
560
560
|
<AgentSidebar
|
|
561
561
|
position="right"
|
|
562
|
-
defaultOpen={
|
|
562
|
+
defaultOpen={false}
|
|
563
563
|
emptyStateText={t("navigation.agentEmptyState")}
|
|
564
564
|
suggestions={[
|
|
565
565
|
t("navigation.agentSuggestionSummary"),
|
|
@@ -1,9 +1,25 @@
|
|
|
1
1
|
import { getDbExec } from "@agent-native/core/db";
|
|
2
|
+
import { table, text } from "@agent-native/core/db/schema";
|
|
2
3
|
import { getRequestUserEmail } from "@agent-native/core/server/request-context";
|
|
3
|
-
import { eq } from "drizzle-orm";
|
|
4
|
+
import { and, eq, sql } from "drizzle-orm";
|
|
4
5
|
|
|
5
6
|
import { getDb, schema } from "../server/db/index.js";
|
|
6
7
|
|
|
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
|
+
|
|
7
23
|
export type ContentSpaceRole = "viewer" | "editor" | "owner";
|
|
8
24
|
|
|
9
25
|
export type ContentSpaceAccess = {
|
|
@@ -21,7 +37,32 @@ export function normalizeContentSpaceEmail(email: string): string {
|
|
|
21
37
|
export async function getContentOrganizationMembership(
|
|
22
38
|
orgId: string,
|
|
23
39
|
userEmail: string,
|
|
40
|
+
options: { db?: any } = {},
|
|
24
41
|
): Promise<{ role: string; name: string; createdBy: string } | null> {
|
|
42
|
+
if (options.db) {
|
|
43
|
+
const [row] = await options.db
|
|
44
|
+
.select({
|
|
45
|
+
role: orgMembers.role,
|
|
46
|
+
name: organizations.name,
|
|
47
|
+
createdBy: organizations.createdBy,
|
|
48
|
+
})
|
|
49
|
+
.from(orgMembers)
|
|
50
|
+
.innerJoin(organizations, eq(organizations.id, orgMembers.orgId))
|
|
51
|
+
.where(
|
|
52
|
+
and(
|
|
53
|
+
eq(orgMembers.orgId, orgId),
|
|
54
|
+
sql`LOWER(${orgMembers.email}) = ${normalizeContentSpaceEmail(userEmail)}`,
|
|
55
|
+
),
|
|
56
|
+
)
|
|
57
|
+
.limit(1);
|
|
58
|
+
return row
|
|
59
|
+
? {
|
|
60
|
+
role: String(row.role ?? "member").toLowerCase(),
|
|
61
|
+
name: row.name,
|
|
62
|
+
createdBy: row.createdBy,
|
|
63
|
+
}
|
|
64
|
+
: null;
|
|
65
|
+
}
|
|
25
66
|
const result = await getDbExec().execute({
|
|
26
67
|
sql: `SELECT m.role AS role, o.name AS name, o.created_by AS "createdBy"
|
|
27
68
|
FROM org_members m
|
|
@@ -90,11 +131,12 @@ export async function listContentOrganizationMemberships(userEmail: string) {
|
|
|
90
131
|
export async function resolveContentSpaceAccess(
|
|
91
132
|
spaceId: string,
|
|
92
133
|
requiredRole: "viewer" | "editor" = "viewer",
|
|
134
|
+
options: { db?: any } = {},
|
|
93
135
|
): Promise<ContentSpaceAccess> {
|
|
94
136
|
const userEmail = getRequestUserEmail();
|
|
95
137
|
if (!userEmail) throw new Error("no authenticated user");
|
|
96
138
|
const normalizedUserEmail = normalizeContentSpaceEmail(userEmail);
|
|
97
|
-
const [space] = await getDb()
|
|
139
|
+
const [space] = await (options.db ?? getDb())
|
|
98
140
|
.select()
|
|
99
141
|
.from(schema.contentSpaces)
|
|
100
142
|
.where(eq(schema.contentSpaces.id, spaceId));
|
|
@@ -115,6 +157,7 @@ export async function resolveContentSpaceAccess(
|
|
|
115
157
|
const membership = await getContentOrganizationMembership(
|
|
116
158
|
space.orgId,
|
|
117
159
|
normalizedUserEmail,
|
|
160
|
+
options,
|
|
118
161
|
);
|
|
119
162
|
if (!membership)
|
|
120
163
|
throw new Error(`Not authorized for Content space "${spaceId}"`);
|
|
@@ -5,7 +5,13 @@ import {
|
|
|
5
5
|
getRequestOrgId,
|
|
6
6
|
getRequestUserEmail,
|
|
7
7
|
} from "@agent-native/core/server/request-context";
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
accessFilter,
|
|
10
|
+
assertAccess,
|
|
11
|
+
currentAccess,
|
|
12
|
+
ForbiddenError,
|
|
13
|
+
type ShareRole,
|
|
14
|
+
} from "@agent-native/core/sharing";
|
|
9
15
|
import { and, eq, sql } from "drizzle-orm";
|
|
10
16
|
import { z } from "zod";
|
|
11
17
|
|
|
@@ -158,9 +164,34 @@ export async function resolveContentDatabaseSpace(
|
|
|
158
164
|
return spaceId;
|
|
159
165
|
}
|
|
160
166
|
|
|
167
|
+
async function assertDocumentEditorAccess(db: any, documentId: string) {
|
|
168
|
+
const [document] = await db
|
|
169
|
+
.select()
|
|
170
|
+
.from(schema.documents)
|
|
171
|
+
.where(
|
|
172
|
+
and(
|
|
173
|
+
eq(schema.documents.id, documentId),
|
|
174
|
+
accessFilter(
|
|
175
|
+
schema.documents,
|
|
176
|
+
schema.documentShares,
|
|
177
|
+
currentAccess(),
|
|
178
|
+
"editor",
|
|
179
|
+
),
|
|
180
|
+
),
|
|
181
|
+
);
|
|
182
|
+
if (!document) {
|
|
183
|
+
throw new ForbiddenError(`No editor access to document ${documentId}`);
|
|
184
|
+
}
|
|
185
|
+
return document;
|
|
186
|
+
}
|
|
187
|
+
|
|
161
188
|
export async function createContentDatabaseRecord(
|
|
162
189
|
args: CreateDatabaseRequest,
|
|
163
|
-
options: {
|
|
190
|
+
options: {
|
|
191
|
+
db?: any;
|
|
192
|
+
spaceId?: string;
|
|
193
|
+
resolveSpaceAccess?: typeof resolveContentSpaceAccess;
|
|
194
|
+
} = {},
|
|
164
195
|
): Promise<string> {
|
|
165
196
|
const db = options.db ?? getDb();
|
|
166
197
|
const now = new Date().toISOString();
|
|
@@ -178,8 +209,7 @@ export async function createContentDatabaseRecord(
|
|
|
178
209
|
}> = [];
|
|
179
210
|
|
|
180
211
|
if (documentId) {
|
|
181
|
-
const
|
|
182
|
-
const document = access.resource;
|
|
212
|
+
const document = await assertDocumentEditorAccess(db, documentId);
|
|
183
213
|
ownerEmail = document.ownerEmail as string;
|
|
184
214
|
orgId = (document.orgId as string | null) ?? null;
|
|
185
215
|
spaceId = (document.spaceId as string | null) ?? spaceId;
|
|
@@ -230,8 +260,7 @@ export async function createContentDatabaseRecord(
|
|
|
230
260
|
let hideFromSearch = 0;
|
|
231
261
|
|
|
232
262
|
if (parentId) {
|
|
233
|
-
const
|
|
234
|
-
const parent = parentAccess.resource;
|
|
263
|
+
const parent = await assertDocumentEditorAccess(db, parentId);
|
|
235
264
|
ownerEmail = parent.ownerEmail as string;
|
|
236
265
|
orgId = (parent.orgId as string | null) ?? null;
|
|
237
266
|
spaceId = (parent.spaceId as string | null) ?? spaceId;
|
|
@@ -257,7 +286,12 @@ export async function createContentDatabaseRecord(
|
|
|
257
286
|
"A top-level database requires a resolved Content space",
|
|
258
287
|
);
|
|
259
288
|
}
|
|
260
|
-
const spaceAccess = await
|
|
289
|
+
const spaceAccess = await (
|
|
290
|
+
options.resolveSpaceAccess ?? resolveContentSpaceAccess
|
|
291
|
+
)(spaceId, "editor", { db });
|
|
292
|
+
if (spaceAccess.space.id !== spaceId) {
|
|
293
|
+
throw new Error("Resolved Content space does not match the request");
|
|
294
|
+
}
|
|
261
295
|
ownerEmail = getRequestUserEmail() ?? ownerEmail;
|
|
262
296
|
orgId = spaceAccess.space.orgId;
|
|
263
297
|
visibility = orgId ? "org" : "private";
|
|
@@ -189,7 +189,7 @@ export function Layout({ children }: LayoutProps) {
|
|
|
189
189
|
)}
|
|
190
190
|
<AgentSidebar
|
|
191
191
|
position="right"
|
|
192
|
-
defaultOpen={
|
|
192
|
+
defaultOpen={false}
|
|
193
193
|
agentPageHref="/agent"
|
|
194
194
|
emptyStateText={t("chat.emptyState")}
|
|
195
195
|
suggestions={[
|
|
@@ -479,15 +479,6 @@ function PublicAgentShell({ children }: { children: React.ReactNode }) {
|
|
|
479
479
|
|
|
480
480
|
useEffect(() => setMounted(true), []);
|
|
481
481
|
|
|
482
|
-
useEffect(() => {
|
|
483
|
-
if (!mounted) return;
|
|
484
|
-
if (!window.matchMedia("(min-width: 768px)").matches) return;
|
|
485
|
-
const id = window.setTimeout(() => {
|
|
486
|
-
window.dispatchEvent(new Event("agent-panel:open"));
|
|
487
|
-
}, 0);
|
|
488
|
-
return () => window.clearTimeout(id);
|
|
489
|
-
}, [mounted]);
|
|
490
|
-
|
|
491
482
|
const content = <>{children}</>;
|
|
492
483
|
|
|
493
484
|
if (!mounted) {
|
|
@@ -503,7 +494,7 @@ function PublicAgentShell({ children }: { children: React.ReactNode }) {
|
|
|
503
494
|
return (
|
|
504
495
|
<AgentSidebar
|
|
505
496
|
position="right"
|
|
506
|
-
defaultOpen
|
|
497
|
+
defaultOpen={false}
|
|
507
498
|
defaultSidebarWidth={420}
|
|
508
499
|
emptyStateText={t("chat.publicEmptyState")}
|
|
509
500
|
suggestions={[
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
appBasePath,
|
|
3
3
|
appPath,
|
|
4
|
+
isAgentChatHomeHandoffActive,
|
|
4
5
|
markAgentChatHomeHandoff,
|
|
5
6
|
useAgentRouteState,
|
|
6
7
|
} from "@agent-native/core/client";
|
|
@@ -54,7 +55,9 @@ export function useNavigationState(extensions?: DispatchExtensionConfig) {
|
|
|
54
55
|
routerPath(location.pathname) === "/chat" &&
|
|
55
56
|
pathnameFromPath(path) !== "/chat"
|
|
56
57
|
) {
|
|
57
|
-
|
|
58
|
+
if (isAgentChatHomeHandoffActive("dispatch")) {
|
|
59
|
+
markAgentChatHomeHandoff("dispatch");
|
|
60
|
+
}
|
|
58
61
|
}
|
|
59
62
|
},
|
|
60
63
|
});
|
|
@@ -51,8 +51,6 @@ export function Layout({ children }: LayoutProps) {
|
|
|
51
51
|
if (!formId) return null;
|
|
52
52
|
return { type: "form" as const, id: formId };
|
|
53
53
|
}, [location.pathname]);
|
|
54
|
-
const sidebarScope = chatHomeHandoffActive ? null : formScope;
|
|
55
|
-
|
|
56
54
|
if (BARE_ROUTES.has(location.pathname)) {
|
|
57
55
|
return <>{children}</>;
|
|
58
56
|
}
|
|
@@ -89,7 +87,7 @@ export function Layout({ children }: LayoutProps) {
|
|
|
89
87
|
<AgentSidebar
|
|
90
88
|
position="right"
|
|
91
89
|
agentPageHref="/agent"
|
|
92
|
-
defaultOpen
|
|
90
|
+
defaultOpen={false}
|
|
93
91
|
chatViewTransition
|
|
94
92
|
storageKey="forms"
|
|
95
93
|
browserTabId={TAB_ID}
|
|
@@ -101,7 +99,7 @@ export function Layout({ children }: LayoutProps) {
|
|
|
101
99
|
t("agent.suggestionSubmissions"),
|
|
102
100
|
t("agent.suggestionExport"),
|
|
103
101
|
]}
|
|
104
|
-
scope={
|
|
102
|
+
scope={formScope}
|
|
105
103
|
>
|
|
106
104
|
<div className="flex h-full flex-1 flex-col overflow-hidden">
|
|
107
105
|
{showHeader ? <Header /> : null}
|
|
@@ -1 +1,25 @@
|
|
|
1
|
-
|
|
1
|
+
import {
|
|
2
|
+
Popover,
|
|
3
|
+
PopoverAnchor,
|
|
4
|
+
PopoverContent as ToolkitPopoverContent,
|
|
5
|
+
PopoverTrigger,
|
|
6
|
+
} from "@agent-native/toolkit/ui/popover";
|
|
7
|
+
import * as React from "react";
|
|
8
|
+
|
|
9
|
+
import { cn } from "@/lib/utils";
|
|
10
|
+
|
|
11
|
+
const PopoverContent = React.forwardRef<
|
|
12
|
+
React.ElementRef<typeof ToolkitPopoverContent>,
|
|
13
|
+
React.ComponentPropsWithoutRef<typeof ToolkitPopoverContent>
|
|
14
|
+
>(({ className, collisionPadding = 16, ...props }, ref) => (
|
|
15
|
+
<ToolkitPopoverContent
|
|
16
|
+
ref={ref}
|
|
17
|
+
collisionPadding={collisionPadding}
|
|
18
|
+
className={cn("max-w-[calc(100vw-2rem)]", className)}
|
|
19
|
+
{...props}
|
|
20
|
+
/>
|
|
21
|
+
));
|
|
22
|
+
|
|
23
|
+
PopoverContent.displayName = ToolkitPopoverContent.displayName;
|
|
24
|
+
|
|
25
|
+
export { Popover, PopoverAnchor, PopoverContent, PopoverTrigger };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
appBasePath,
|
|
3
|
+
isAgentChatHomeHandoffActive,
|
|
3
4
|
markAgentChatHomeHandoff,
|
|
4
5
|
useAgentRouteState,
|
|
5
6
|
} from "@agent-native/core/client";
|
|
@@ -131,7 +132,11 @@ export function useNavigationState() {
|
|
|
131
132
|
navigateOptions: { flushSync: true, replace: true },
|
|
132
133
|
onNavigate: (_command, path) => {
|
|
133
134
|
void prewarmFormsRoutePath(path);
|
|
134
|
-
if (
|
|
135
|
+
if (
|
|
136
|
+
location.pathname === "/ask" &&
|
|
137
|
+
path !== "/ask" &&
|
|
138
|
+
isAgentChatHomeHandoffActive("forms")
|
|
139
|
+
) {
|
|
135
140
|
markAgentChatHomeHandoff("forms");
|
|
136
141
|
}
|
|
137
142
|
},
|
|
@@ -359,10 +359,19 @@ export function FormBuilderPage() {
|
|
|
359
359
|
<Skeleton className="h-8 w-20 rounded-md" />
|
|
360
360
|
</div>
|
|
361
361
|
</div>
|
|
362
|
-
{/*
|
|
363
|
-
<div className="
|
|
364
|
-
<div className="flex-1
|
|
365
|
-
<
|
|
362
|
+
{/* Tabs */}
|
|
363
|
+
<div className="border-b border-border px-2 sm:px-4 py-2 shrink-0 overflow-hidden">
|
|
364
|
+
<div className="flex w-max items-center gap-1 rounded-lg bg-muted/40 p-1">
|
|
365
|
+
<Skeleton className="h-8 w-12 rounded-md" />
|
|
366
|
+
<Skeleton className="h-8 w-16 rounded-md" />
|
|
367
|
+
<Skeleton className="h-8 w-16 rounded-md" />
|
|
368
|
+
<Skeleton className="h-8 w-24 rounded-md" />
|
|
369
|
+
</div>
|
|
370
|
+
</div>
|
|
371
|
+
{/* Body: builder */}
|
|
372
|
+
<div className="flex flex-1 overflow-hidden relative">
|
|
373
|
+
<div className="flex-1 overflow-auto bg-muted/30">
|
|
374
|
+
<div className="max-w-2xl mx-auto py-4 sm:py-8 px-3 sm:px-4 space-y-4">
|
|
366
375
|
<Skeleton className="h-8 w-2/3" />
|
|
367
376
|
<Skeleton className="h-4 w-1/2" />
|
|
368
377
|
<div className="space-y-3 pt-4">
|
|
@@ -378,12 +387,6 @@ export function FormBuilderPage() {
|
|
|
378
387
|
</div>
|
|
379
388
|
</div>
|
|
380
389
|
</div>
|
|
381
|
-
<div className="hidden lg:block w-72 border-s border-border p-4 space-y-4">
|
|
382
|
-
<Skeleton className="h-4 w-32" />
|
|
383
|
-
<Skeleton className="h-9 w-full" />
|
|
384
|
-
<Skeleton className="h-9 w-full" />
|
|
385
|
-
<Skeleton className="h-9 w-full" />
|
|
386
|
-
</div>
|
|
387
390
|
</div>
|
|
388
391
|
</div>
|
|
389
392
|
);
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
useCommandMenuShortcut,
|
|
9
9
|
getThemeInitScript,
|
|
10
10
|
configureTracking,
|
|
11
|
+
isAgentChatHomeHandoffActive,
|
|
11
12
|
markAgentChatHomeHandoff,
|
|
12
13
|
navigateWithAgentChatViewTransition,
|
|
13
14
|
setClientAppState,
|
|
@@ -199,7 +200,11 @@ function OpenLinkInterceptor() {
|
|
|
199
200
|
if (!path) return;
|
|
200
201
|
|
|
201
202
|
event.preventDefault();
|
|
202
|
-
if (
|
|
203
|
+
if (
|
|
204
|
+
location.pathname === "/ask" &&
|
|
205
|
+
path !== "/ask" &&
|
|
206
|
+
isAgentChatHomeHandoffActive("forms")
|
|
207
|
+
) {
|
|
203
208
|
markAgentChatHomeHandoff("forms");
|
|
204
209
|
}
|
|
205
210
|
navigateWithAgentChatViewTransition(navigate, path);
|