@agent-native/core 0.99.0 → 0.99.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 +1 -1
- package/corpus/core/CHANGELOG.md +23 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/production-agent.ts +33 -38
- package/corpus/core/src/audit/config.ts +0 -1
- package/corpus/core/src/chat-threads/store.ts +57 -41
- package/corpus/core/src/cli/pr-visual-recap-workflow.ts +1 -1
- package/corpus/core/src/client/AssistantChat.tsx +1 -2
- package/corpus/core/src/client/analytics.ts +13 -0
- package/corpus/core/src/client/chat/message-components.tsx +15 -3
- package/corpus/core/src/client/chat/tool-call-display.tsx +18 -1
- package/corpus/core/src/client/extensions/EmbeddedExtension.tsx +5 -9
- package/corpus/core/src/client/extensions/ExtensionViewer.tsx +0 -133
- package/corpus/core/src/client/index.ts +7 -0
- package/corpus/core/src/client/settings/DemoModeSection.tsx +9 -65
- package/corpus/core/src/client/settings/SettingsPanel.tsx +1 -1
- package/corpus/core/src/client/use-db-sync.ts +3 -15
- package/corpus/core/src/client/use-demo-mode-status.ts +16 -30
- package/corpus/core/src/demo/browser-state.ts +47 -0
- package/corpus/core/src/demo/fetch-interceptor.ts +12 -59
- package/corpus/core/src/deploy/workspace-deploy.ts +2 -2
- package/corpus/core/src/server/action-discovery.ts +0 -1
- package/corpus/core/src/server/agent-chat/action-filters-a2a.ts +13 -0
- package/corpus/core/src/server/agent-teams.ts +17 -0
- package/corpus/core/src/server/core-routes-plugin.ts +0 -30
- package/corpus/core/src/server/google-oauth.ts +6 -2
- package/corpus/core/src/templates/workspace-core/.agents/skills/native-navigation/SKILL.md +67 -0
- package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +48 -7
- package/corpus/templates/analytics/AGENTS.md +4 -4
- package/corpus/templates/analytics/actions/compose-dashboard.ts +12 -1
- package/corpus/templates/analytics/actions/dashboard-mutation-api.ts +15 -4
- package/corpus/templates/analytics/actions/mutate-dashboard.ts +2 -1
- package/corpus/templates/analytics/actions/update-dashboard.ts +14 -0
- package/corpus/templates/analytics/app/assets/replay-cursor.svg +17 -0
- package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +4 -3
- package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +21 -27
- package/corpus/templates/analytics/app/global.css +17 -9
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/DashboardFilterBar.tsx +32 -4
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +4 -1
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/interpolate.ts +21 -0
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/types.ts +14 -0
- package/corpus/templates/analytics/app/pages/monitoring/errors/IssueDetail.tsx +1 -1
- package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +77 -23
- package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +4 -6
- package/corpus/templates/analytics/changelog/2026-07-13-all-time-dashboard-filters-no-longer-send-invalid-dates-to-b.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-13-dashboard-charts-now-honor-the-selected-time-range-by-defaul.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-13-error-issue-titles-wrap-instead-of-overflowing.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-13-keep-real-authorized-identities-in-analytics-incident-lookup.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-13-saving-a-new-dashboard-view-no-longer-fails-with-a-server-er.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-13-session-replay-cursors-stay-hidden-until-their-first-recorde.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-13-session-replay-links-now-support-cmd-click-and-middle-click-.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-13-the-analytics-agents-tab-is-available-again-from-the-main-na.md +6 -0
- package/corpus/templates/analytics/seeds/dashboards/agent-native-templates-first-party.json +3 -3
- package/corpus/templates/analytics/server/lib/agent-readable-resource-context.ts +4 -0
- package/corpus/templates/analytics/server/lib/dashboard-time-scope.ts +170 -0
- package/corpus/templates/analytics/server/lib/dashboards-store.ts +43 -4
- package/corpus/templates/analytics/server/lib/error-capture.ts +30 -67
- package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +20 -5
- package/corpus/templates/analytics/server/lib/session-replay-agent-context.ts +1 -1
- package/corpus/templates/analytics/server/lib/session-replay.ts +5 -111
- package/corpus/templates/analytics/server/plugins/agent-chat.ts +6 -0
- package/corpus/templates/assets/app/routes/library.tsx +3 -4
- package/corpus/templates/assets/changelog/2026-07-13-internal-library-links-open-in-new-tabs.md +6 -0
- package/corpus/templates/calendar/app/pages/BookingLinksPage.tsx +43 -40
- package/corpus/templates/calendar/app/pages/ManageBookingPage.tsx +6 -8
- package/corpus/templates/calendar/changelog/2026-07-13-booking-links-and-previews-open-in-new-tabs.md +6 -0
- package/corpus/templates/clips/app/components/library/recording-card.tsx +27 -26
- package/corpus/templates/clips/app/components/library/space-card.tsx +4 -6
- package/corpus/templates/clips/app/components/player/sign-in-prompt-dialog.tsx +4 -7
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +19 -9
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +5 -3
- package/corpus/templates/clips/changelog/2026-07-13-recording-links-open-in-new-tabs.md +6 -0
- package/corpus/templates/content/app/components/editor/ContentReferencePreview.tsx +6 -8
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +5 -3
- package/corpus/templates/content/app/components/editor/database/FormView.tsx +6 -9
- package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +18 -16
- package/corpus/templates/content/changelog/2026-07-13-document-links-open-in-new-tabs.md +6 -0
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +33 -16
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +18 -1
- package/corpus/templates/design/app/pages/DesignSystemSetup.tsx +4 -4
- package/corpus/templates/design/app/pages/DesignSystems.tsx +8 -10
- package/corpus/templates/design/app/pages/Index.tsx +6 -1
- package/corpus/templates/design/app/pages/Present.tsx +12 -6
- package/corpus/templates/design/changelog/2026-07-13-design-navigation-links-open-in-new-tabs.md +6 -0
- package/corpus/templates/design/changelog/2026-07-13-design-session-replays-include-preview-iframes.md +6 -0
- package/corpus/templates/forms/app/pages/FormBuilderPage.tsx +11 -11
- package/corpus/templates/forms/app/pages/FormsListPage.tsx +6 -8
- package/corpus/templates/forms/changelog/2026-07-13-form-navigation-links-open-in-new-tabs.md +6 -0
- package/corpus/templates/mail/app/pages/DraftQueuePage.tsx +3 -8
- package/corpus/templates/mail/changelog/2026-07-13-settings-links-open-in-new-tabs.md +6 -0
- package/dist/agent/production-agent.d.ts +9 -0
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +25 -37
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/audit/config.d.ts.map +1 -1
- package/dist/audit/config.js +0 -1
- package/dist/audit/config.js.map +1 -1
- package/dist/chat-threads/store.d.ts.map +1 -1
- package/dist/chat-threads/store.js +47 -33
- package/dist/chat-threads/store.js.map +1 -1
- package/dist/cli/pr-visual-recap-workflow.d.ts +1 -1
- package/dist/cli/pr-visual-recap-workflow.d.ts.map +1 -1
- package/dist/cli/pr-visual-recap-workflow.js +1 -1
- package/dist/cli/pr-visual-recap-workflow.js.map +1 -1
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +1 -1
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/analytics.d.ts.map +1 -1
- package/dist/client/analytics.js +11 -0
- package/dist/client/analytics.js.map +1 -1
- package/dist/client/chat/message-components.d.ts.map +1 -1
- package/dist/client/chat/message-components.js +9 -3
- package/dist/client/chat/message-components.js.map +1 -1
- package/dist/client/chat/tool-call-display.d.ts +3 -1
- package/dist/client/chat/tool-call-display.d.ts.map +1 -1
- package/dist/client/chat/tool-call-display.js +10 -2
- package/dist/client/chat/tool-call-display.js.map +1 -1
- package/dist/client/extensions/EmbeddedExtension.js +2 -6
- package/dist/client/extensions/EmbeddedExtension.js.map +1 -1
- package/dist/client/extensions/ExtensionViewer.d.ts.map +1 -1
- package/dist/client/extensions/ExtensionViewer.js +2 -59
- package/dist/client/extensions/ExtensionViewer.js.map +1 -1
- package/dist/client/index.d.ts +2 -0
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +2 -0
- package/dist/client/index.js.map +1 -1
- package/dist/client/settings/DemoModeSection.d.ts +1 -13
- package/dist/client/settings/DemoModeSection.d.ts.map +1 -1
- package/dist/client/settings/DemoModeSection.js +5 -52
- package/dist/client/settings/DemoModeSection.js.map +1 -1
- package/dist/client/settings/SettingsPanel.js +1 -1
- package/dist/client/settings/SettingsPanel.js.map +1 -1
- package/dist/client/use-db-sync.d.ts.map +1 -1
- package/dist/client/use-db-sync.js +3 -7
- package/dist/client/use-db-sync.js.map +1 -1
- package/dist/client/use-demo-mode-status.d.ts +3 -4
- package/dist/client/use-demo-mode-status.d.ts.map +1 -1
- package/dist/client/use-demo-mode-status.js +8 -23
- package/dist/client/use-demo-mode-status.js.map +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/demo/browser-state.d.ts +10 -0
- package/dist/demo/browser-state.d.ts.map +1 -0
- package/dist/demo/browser-state.js +49 -0
- package/dist/demo/browser-state.js.map +1 -0
- package/dist/demo/fetch-interceptor.d.ts +2 -7
- package/dist/demo/fetch-interceptor.d.ts.map +1 -1
- package/dist/demo/fetch-interceptor.js +12 -56
- package/dist/demo/fetch-interceptor.js.map +1 -1
- package/dist/deploy/workspace-deploy.js +2 -2
- package/dist/deploy/workspace-deploy.js.map +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/notifications/routes.d.ts +2 -2
- package/dist/observability/routes.d.ts +5 -5
- package/dist/resources/handlers.d.ts +3 -3
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/action-discovery.d.ts.map +1 -1
- package/dist/server/action-discovery.js +0 -1
- package/dist/server/action-discovery.js.map +1 -1
- package/dist/server/agent-chat/action-filters-a2a.d.ts.map +1 -1
- package/dist/server/agent-chat/action-filters-a2a.js +11 -1
- package/dist/server/agent-chat/action-filters-a2a.js.map +1 -1
- package/dist/server/agent-teams.d.ts.map +1 -1
- package/dist/server/agent-teams.js +15 -1
- package/dist/server/agent-teams.js.map +1 -1
- package/dist/server/core-routes-plugin.d.ts.map +1 -1
- package/dist/server/core-routes-plugin.js +0 -27
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/dist/server/google-oauth.d.ts.map +1 -1
- package/dist/server/google-oauth.js +6 -2
- package/dist/server/google-oauth.js.map +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/templates/workspace-core/.agents/skills/native-navigation/SKILL.md +67 -0
- package/package.json +2 -2
- package/src/templates/workspace-core/.agents/skills/native-navigation/SKILL.md +67 -0
- package/corpus/core/src/demo/actions/toggle-demo-mode.ts +0 -23
- package/corpus/core/src/demo/config.ts +0 -57
- package/dist/demo/actions/toggle-demo-mode.d.ts +0 -8
- package/dist/demo/actions/toggle-demo-mode.d.ts.map +0 -1
- package/dist/demo/actions/toggle-demo-mode.js +0 -21
- package/dist/demo/actions/toggle-demo-mode.js.map +0 -1
- package/dist/demo/config.d.ts +0 -9
- package/dist/demo/config.d.ts.map +0 -1
- package/dist/demo/config.js +0 -58
- package/dist/demo/config.js.map +0 -1
|
@@ -416,7 +416,7 @@ function writeCloudflareRoutingManifest(distDir: string, apps: string[]): void {
|
|
|
416
416
|
const dispatch = apps
|
|
417
417
|
.map(
|
|
418
418
|
(a) =>
|
|
419
|
-
` if (pathname === "/${a}" || pathname.startsWith("/${a}/")) return ${moduleIdent(a)}.fetch(requestForMountedApp(request, "/${a}"), env, ctx);`,
|
|
419
|
+
` if (pathname === "/${a}" || pathname === "/${a}.data" || pathname.startsWith("/${a}/")) return ${moduleIdent(a)}.fetch(requestForMountedApp(request, "/${a}"), env, ctx);`,
|
|
420
420
|
)
|
|
421
421
|
.join("\n");
|
|
422
422
|
const dispatchRootFrameworkRoutes = apps.includes("dispatch")
|
|
@@ -880,7 +880,7 @@ function patchNetlifyFunctionEntry(
|
|
|
880
880
|
const pathConfig =
|
|
881
881
|
app === "dispatch"
|
|
882
882
|
? ["/_agent-native/*", "/.well-known/*", `${basePath}/*`]
|
|
883
|
-
: [basePath, `${basePath}/*`];
|
|
883
|
+
: [basePath, `${basePath}.data`, `${basePath}/*`];
|
|
884
884
|
const normalizeBasePathHelper =
|
|
885
885
|
app === "dispatch"
|
|
886
886
|
? ""
|
|
@@ -599,7 +599,6 @@ export async function mergeCoreSharingActions(
|
|
|
599
599
|
"change-appearance",
|
|
600
600
|
() => import("../appearance/actions/change-appearance.js"),
|
|
601
601
|
],
|
|
602
|
-
["toggle-demo-mode", () => import("../demo/actions/toggle-demo-mode.js")],
|
|
603
602
|
// Audit log — read surface (who changed what, when, agent vs human).
|
|
604
603
|
[
|
|
605
604
|
"list-audit-events",
|
|
@@ -6,9 +6,11 @@ import {
|
|
|
6
6
|
type A2AToolResultSummary,
|
|
7
7
|
} from "../../a2a/artifact-response.js";
|
|
8
8
|
import { collectFinalResponseTextFromAgentEvents } from "../../a2a/response-text.js";
|
|
9
|
+
import { resolveMainChatMaxOutputTokens } from "../../agent/engine/output-tokens.js";
|
|
9
10
|
import type { EngineTool } from "../../agent/engine/types.js";
|
|
10
11
|
import {
|
|
11
12
|
filterInitialEngineTools,
|
|
13
|
+
resolveAgentRequestReasoningEffort,
|
|
12
14
|
type ActionEntry,
|
|
13
15
|
} from "../../agent/production-agent.js";
|
|
14
16
|
import { runAgentLoopDirectWithSoftTimeout } from "../../agent/run-loop-with-resume.js";
|
|
@@ -160,6 +162,17 @@ function runDelegatedAgentLoop(
|
|
|
160
162
|
return runner(
|
|
161
163
|
{
|
|
162
164
|
...runOptions,
|
|
165
|
+
// Delegated runs resolve their own model and do not pass through the
|
|
166
|
+
// interactive request handler's output-token setup. Use the same
|
|
167
|
+
// model-aware headroom here so reasoning models (notably GPT-5.x) do
|
|
168
|
+
// not spend the small internal default entirely on reasoning before
|
|
169
|
+
// emitting a tool call or answer. Preserve explicit test/caller values.
|
|
170
|
+
maxOutputTokens:
|
|
171
|
+
runOptions.maxOutputTokens ??
|
|
172
|
+
resolveMainChatMaxOutputTokens(runOptions.model),
|
|
173
|
+
reasoningEffort:
|
|
174
|
+
runOptions.reasoningEffort ??
|
|
175
|
+
resolveAgentRequestReasoningEffort({ model: runOptions.model }),
|
|
163
176
|
finalResponseGuard: pluginOptions.finalResponseGuard,
|
|
164
177
|
},
|
|
165
178
|
pluginOptions.runSoftTimeoutMs,
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
19
19
|
|
|
20
20
|
import { applyAgentTextEventToBuffer } from "../a2a/response-text.js";
|
|
21
|
+
import { resolveMainChatMaxOutputTokens } from "../agent/engine/output-tokens.js";
|
|
21
22
|
import type { AgentEngine, EngineMessage } from "../agent/engine/types.js";
|
|
22
23
|
import type {
|
|
23
24
|
ActionEntry,
|
|
@@ -26,6 +27,7 @@ import type {
|
|
|
26
27
|
import {
|
|
27
28
|
actionsToEngineTools,
|
|
28
29
|
filterInitialEngineTools,
|
|
30
|
+
resolveAgentRequestReasoningEffort,
|
|
29
31
|
} from "../agent/production-agent.js";
|
|
30
32
|
import {
|
|
31
33
|
runAgentLoop,
|
|
@@ -589,6 +591,11 @@ function createTaskMessageFinalGuard(
|
|
|
589
591
|
retryMessage: formatQueuedTaskMessages(queuedMessages),
|
|
590
592
|
fallbackMessage:
|
|
591
593
|
"I received an orchestrator update while finishing, but could not continue from it. Please check the task status and send the update again if needed.",
|
|
594
|
+
// A queued update can introduce a request for an action that was
|
|
595
|
+
// deferred behind tool-search. The retry is already a corrective turn,
|
|
596
|
+
// so expose the full authorized registry rather than making the
|
|
597
|
+
// sub-agent spend that turn rediscovering the same tool.
|
|
598
|
+
expandToolSurface: true,
|
|
592
599
|
};
|
|
593
600
|
};
|
|
594
601
|
}
|
|
@@ -1835,6 +1842,16 @@ export async function processAgentTeamRun(
|
|
|
1835
1842
|
chunkUsage = await runAgentLoop({
|
|
1836
1843
|
engine: config.engine,
|
|
1837
1844
|
model: config.model,
|
|
1845
|
+
// Agent-team runs are delegated turns too. Keep their
|
|
1846
|
+
// first attempt on the same model-aware budget as A2A /
|
|
1847
|
+
// MCP so reasoning models do not spend the old 4K
|
|
1848
|
+
// default before emitting a tool call or answer.
|
|
1849
|
+
maxOutputTokens: resolveMainChatMaxOutputTokens(
|
|
1850
|
+
config.model,
|
|
1851
|
+
),
|
|
1852
|
+
reasoningEffort: resolveAgentRequestReasoningEffort({
|
|
1853
|
+
model: config.model,
|
|
1854
|
+
}),
|
|
1838
1855
|
systemPrompt,
|
|
1839
1856
|
tools,
|
|
1840
1857
|
availableTools,
|
|
@@ -1205,36 +1205,6 @@ export function createCoreRoutesPlugin(
|
|
|
1205
1205
|
}),
|
|
1206
1206
|
);
|
|
1207
1207
|
|
|
1208
|
-
// Demo-mode status — read by the client fetch interceptor and the
|
|
1209
|
-
// Demo mode settings toggle. `forced` reflects the DEMO_MODE env (a
|
|
1210
|
-
// hosted demo deployment); `enabled` ORs that with the per-user
|
|
1211
|
-
// application_state toggle. No request-context dependency: the env case
|
|
1212
|
-
// needs nothing, and the per-user case resolves the session straight
|
|
1213
|
-
// off the request cookie via getSession(event).
|
|
1214
|
-
getH3App(nitroApp).use(
|
|
1215
|
-
`${P}/demo/status`,
|
|
1216
|
-
defineEventHandler(async (event) => {
|
|
1217
|
-
const { isDemoModeForced } = await import("../demo/config.js");
|
|
1218
|
-
const forced = isDemoModeForced();
|
|
1219
|
-
if (forced) return { enabled: true, forced: true };
|
|
1220
|
-
try {
|
|
1221
|
-
const session = await getSession(event);
|
|
1222
|
-
const email = session?.email;
|
|
1223
|
-
if (!email) return { enabled: false, forced: false };
|
|
1224
|
-
const { appStateGet } =
|
|
1225
|
-
await import("../application-state/store.js");
|
|
1226
|
-
const state = await appStateGet(email, "demo-mode");
|
|
1227
|
-
return {
|
|
1228
|
-
enabled:
|
|
1229
|
-
(state as { enabled?: boolean } | null)?.enabled === true,
|
|
1230
|
-
forced: false,
|
|
1231
|
-
};
|
|
1232
|
-
} catch {
|
|
1233
|
-
return { enabled: false, forced: false };
|
|
1234
|
-
}
|
|
1235
|
-
}),
|
|
1236
|
-
);
|
|
1237
|
-
|
|
1238
1208
|
// Polling
|
|
1239
1209
|
getH3App(nitroApp).use(`${P}/poll`, createPollHandler());
|
|
1240
1210
|
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
type H3Event,
|
|
18
18
|
} from "h3";
|
|
19
19
|
|
|
20
|
-
import {
|
|
20
|
+
import { getAppBasePathFromViteEnv } from "./app-base-path.js";
|
|
21
21
|
import { getAppName } from "./app-name.js";
|
|
22
22
|
import { signupAttributionFromCookieHeader } from "./attribution.js";
|
|
23
23
|
import {
|
|
@@ -281,7 +281,11 @@ export function getOrigin(event: H3Event): string {
|
|
|
281
281
|
|
|
282
282
|
/** App mount prefix, if the template is served under APP_BASE_PATH. */
|
|
283
283
|
export function getAppBasePath(): string {
|
|
284
|
-
|
|
284
|
+
// Vite statically replaces VITE_* values in the server bundle during the
|
|
285
|
+
// build, but Netlify/Nitro does not necessarily expose those build vars at
|
|
286
|
+
// runtime. Keep auth and OAuth path matching aligned with the SSR handler by
|
|
287
|
+
// falling back to import.meta.env (including BASE_URL).
|
|
288
|
+
return getAppBasePathFromViteEnv();
|
|
285
289
|
}
|
|
286
290
|
|
|
287
291
|
/** Build an absolute same-origin URL that preserves APP_BASE_PATH. */
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: native-navigation
|
|
3
|
+
description: >-
|
|
4
|
+
Use when adding or reviewing UI navigation so internal routes preserve
|
|
5
|
+
normal SPA behavior plus Cmd/Ctrl-click and middle-click new-tab semantics.
|
|
6
|
+
scope: dev
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Native Navigation
|
|
10
|
+
|
|
11
|
+
## Rule
|
|
12
|
+
|
|
13
|
+
Pure navigation must render a real React Router `<Link>` for same-SPA routes,
|
|
14
|
+
a native `<a href>` for external/public or explicit new-tab destinations, or a
|
|
15
|
+
shadcn `Button asChild` wrapper around one. Do not use a button whose only job
|
|
16
|
+
is to call `navigate()`, `window.open()`, or `window.location`.
|
|
17
|
+
|
|
18
|
+
## Why
|
|
19
|
+
|
|
20
|
+
Browser modifier-click, middle-click, keyboard activation, accessibility, and
|
|
21
|
+
normal client-side routing come from real links. Imperative JavaScript
|
|
22
|
+
navigation hides the destination and blocks those browser semantics.
|
|
23
|
+
|
|
24
|
+
## How
|
|
25
|
+
|
|
26
|
+
```tsx
|
|
27
|
+
import { Link } from "react-router";
|
|
28
|
+
|
|
29
|
+
<Link to={`/items/${item.id}`}>...</Link>
|
|
30
|
+
|
|
31
|
+
<Button asChild>
|
|
32
|
+
<Link to="/settings">Settings</Link>
|
|
33
|
+
</Button>
|
|
34
|
+
|
|
35
|
+
<a href={previewUrl} target="_blank" rel="noopener noreferrer">
|
|
36
|
+
Preview
|
|
37
|
+
</a>
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
- Use `Link` for same-SPA internal routes.
|
|
41
|
+
- Use `<a>` when the URL is external, crosses an app/public boundary, is an
|
|
42
|
+
auth or sign-in destination, or intentionally opens in a new tab.
|
|
43
|
+
- Preserve query strings and hashes in `to` or `href`.
|
|
44
|
+
- For cards with nested actions, link only the non-interactive card region or
|
|
45
|
+
use a transparent Link overlay; never nest links or buttons.
|
|
46
|
+
- If selection mode changes click behavior, prevent default only for an
|
|
47
|
+
unmodified primary click and preserve modified clicks.
|
|
48
|
+
|
|
49
|
+
Keep imperative navigation for post-mutation redirects, OAuth and popup flows,
|
|
50
|
+
downloads, media/blob URLs, playback, keyboard shortcuts, and stateful tabs.
|
|
51
|
+
|
|
52
|
+
## Review
|
|
53
|
+
|
|
54
|
+
Search for `onClick={() => navigate`, `window.open`, `window.location`,
|
|
55
|
+
`onAuxClick`, and buttons whose only purpose is routing. For each candidate,
|
|
56
|
+
confirm whether it is pure navigation or a stateful workflow before changing
|
|
57
|
+
it.
|
|
58
|
+
|
|
59
|
+
Verify the rendered DOM exposes an `href`, then test normal click, Cmd/Ctrl-click,
|
|
60
|
+
middle-click, keyboard Enter, and nested controls where applicable. Run oxfmt
|
|
61
|
+
and focused app tests or typechecks for the touched package.
|
|
62
|
+
|
|
63
|
+
## Related Skills
|
|
64
|
+
|
|
65
|
+
- `client-side-routing`
|
|
66
|
+
- `frontend-design`
|
|
67
|
+
- `shadcn-ui`
|
|
@@ -58,6 +58,23 @@ once — do not switch to db-patch or raw SQL.
|
|
|
58
58
|
|
|
59
59
|
Do not use `app-db` as a dashboard source. For first-party events collected through `/track`, use `source: "first-party"` or the `query-agent-native-analytics` action rather than raw internal `db-query`.
|
|
60
60
|
|
|
61
|
+
AI-generated first-party panels are dashboard-time-bound by default. Set
|
|
62
|
+
`config.timeScope` to `"dashboard"` and include the matching dashboard time
|
|
63
|
+
filter in the SQL. The allowed values are:
|
|
64
|
+
|
|
65
|
+
- `dashboard`: use the dashboard-selected time range; the default for ordinary metrics.
|
|
66
|
+
- `fixed-window`: use an explicit bounded window independent of the dashboard filter.
|
|
67
|
+
- `cohort-history`: use the bounded history of an explicitly defined cohort.
|
|
68
|
+
- `all-time`: scan all available history; use only when the user requests it and
|
|
69
|
+
put `all-time`, `lifetime`, or `historical` in the title or description.
|
|
70
|
+
|
|
71
|
+
`{{timeRange}}` requires an explicit matching `filters` entry with
|
|
72
|
+
`id: "timeRange"` and `type: "select"`. `{{<id>Start}}` and `{{<id>End}}`
|
|
73
|
+
require a matching `filters` entry with that id and `type: "date-range"`.
|
|
74
|
+
Do not rely on undeclared time variables. Server validation rejects unbound
|
|
75
|
+
first-party SQL, so declare the filter or choose an explicit non-dashboard
|
|
76
|
+
scope before saving.
|
|
77
|
+
|
|
61
78
|
## Creating A Dashboard
|
|
62
79
|
|
|
63
80
|
When the user asks for a dashboard:
|
|
@@ -154,7 +171,8 @@ Notes:
|
|
|
154
171
|
"source": "first-party",
|
|
155
172
|
"chartType": "metric",
|
|
156
173
|
"width": 1,
|
|
157
|
-
"
|
|
174
|
+
"config": { "timeScope": "dashboard" },
|
|
175
|
+
"sql": "SELECT COUNT(*) AS value FROM analytics_events WHERE event_name = 'click' AND event_date >= '{{dateStart}}' AND event_date < '{{dateEnd}}'",
|
|
158
176
|
},
|
|
159
177
|
{
|
|
160
178
|
"id": "kpi-signups",
|
|
@@ -162,7 +180,8 @@ Notes:
|
|
|
162
180
|
"source": "first-party",
|
|
163
181
|
"chartType": "metric",
|
|
164
182
|
"width": 1,
|
|
165
|
-
"
|
|
183
|
+
"config": { "timeScope": "dashboard" },
|
|
184
|
+
"sql": "SELECT COUNT(*) AS value FROM analytics_events WHERE event_name = 'signup' AND event_date >= '{{dateStart}}' AND event_date < '{{dateEnd}}'",
|
|
166
185
|
},
|
|
167
186
|
{
|
|
168
187
|
"id": "kpi-active",
|
|
@@ -170,7 +189,8 @@ Notes:
|
|
|
170
189
|
"source": "first-party",
|
|
171
190
|
"chartType": "metric",
|
|
172
191
|
"width": 1,
|
|
173
|
-
"
|
|
192
|
+
"config": { "timeScope": "dashboard" },
|
|
193
|
+
"sql": "SELECT COUNT(DISTINCT user_id) AS value FROM analytics_events WHERE event_date >= '{{dateStart}}' AND event_date < '{{dateEnd}}'",
|
|
174
194
|
},
|
|
175
195
|
// Section header switches the grid to 2 columns for the panels below it.
|
|
176
196
|
{
|
|
@@ -186,7 +206,8 @@ Notes:
|
|
|
186
206
|
"source": "first-party",
|
|
187
207
|
"chartType": "line",
|
|
188
208
|
"width": 2,
|
|
189
|
-
"
|
|
209
|
+
"config": { "timeScope": "dashboard" },
|
|
210
|
+
"sql": "SELECT event_date AS date, COUNT(*) AS value FROM analytics_events WHERE event_date >= '{{dateStart}}' AND event_date < '{{dateEnd}}' GROUP BY 1 ORDER BY 1",
|
|
190
211
|
},
|
|
191
212
|
],
|
|
192
213
|
}
|
|
@@ -196,6 +217,13 @@ Notes:
|
|
|
196
217
|
|
|
197
218
|
`filters[]` defines dashboard-wide controls. Filter values are available in panel SQL through `{{var}}` interpolation. Date ranges emit `{{<id>Start}}` and `{{<id>End}}`.
|
|
198
219
|
|
|
220
|
+
For dashboard-time-bound first-party SQL, use `config.timeScope: "dashboard"`
|
|
221
|
+
and a predicate that consumes the declared filter, such as
|
|
222
|
+
`event_date >= '{{dateStart}}' AND event_date < '{{dateEnd}}'`. A
|
|
223
|
+
`{{timeRange}}` token must have a matching select filter and SQL branches for
|
|
224
|
+
its options; date variables must have a matching date-range filter. The server
|
|
225
|
+
rejects unbound first-party SQL during dashboard validation.
|
|
226
|
+
|
|
199
227
|
**Filter ids must be unique.** Two filters with the same `id` collide on the same URL param, so changing one visibly updates the other in the UI. The dashboard save endpoint rejects duplicates with a 400.
|
|
200
228
|
|
|
201
229
|
**Use `type: "date-range"` for paired start/end dates.** Don't add two `type: "date"` filters labeled "Start" and "End" — even with distinct ids, that ships the wrong UX. A single date-range filter renders as the "from … to …" pair and exposes both halves to SQL via `{{<id>Start}}` / `{{<id>End}}`.
|
|
@@ -244,6 +272,17 @@ type DashboardPatch = {
|
|
|
244
272
|
variables?: Record<string, string>;
|
|
245
273
|
};
|
|
246
274
|
|
|
275
|
+
type PanelTimeScope =
|
|
276
|
+
| "dashboard"
|
|
277
|
+
| "fixed-window"
|
|
278
|
+
| "cohort-history"
|
|
279
|
+
| "all-time";
|
|
280
|
+
|
|
281
|
+
type PanelConfig = Record<string, unknown> & {
|
|
282
|
+
// Use "dashboard" for AI-generated first-party panels by default.
|
|
283
|
+
timeScope?: PanelTimeScope;
|
|
284
|
+
};
|
|
285
|
+
|
|
247
286
|
type PanelPatch = {
|
|
248
287
|
title?: string;
|
|
249
288
|
sql?: string;
|
|
@@ -268,7 +307,7 @@ type PanelPatch = {
|
|
|
268
307
|
width?: number;
|
|
269
308
|
columns?: number;
|
|
270
309
|
tab?: string;
|
|
271
|
-
config?:
|
|
310
|
+
config?: PanelConfig;
|
|
272
311
|
description?: string;
|
|
273
312
|
};
|
|
274
313
|
|
|
@@ -346,7 +385,9 @@ dashboard
|
|
|
346
385
|
source: "first-party",
|
|
347
386
|
chartType: "metric",
|
|
348
387
|
width: 1,
|
|
349
|
-
|
|
388
|
+
config: { timeScope: "dashboard" },
|
|
389
|
+
// Assumes filters includes { id: "date", type: "date-range", default: "30d" }.
|
|
390
|
+
sql: "SELECT COUNT(*) AS value FROM analytics_events WHERE event_date >= '{{dateStart}}' AND event_date < '{{dateEnd}}'",
|
|
350
391
|
})
|
|
351
392
|
.atTop();
|
|
352
393
|
```
|
|
@@ -441,7 +482,7 @@ For a **first-party analytics** dashboard, prefer `compose-dashboard` over hand-
|
|
|
441
482
|
- **Never hand-author large first-party configs panel-by-panel.** Call `compose-dashboard` with the metric keys instead.
|
|
442
483
|
- Unknown metric keys are skipped and reported in `unknownMetrics` (not fatal). Each panel's SQL is validated independently — valid panels save, invalid ones are reported in `invalidMetrics`.
|
|
443
484
|
- By default (no `overwrite`), composing into an existing dashboard APPENDS the new panels and skips ids already present. `overwrite: true` replaces the whole config.
|
|
444
|
-
- Each metric accepts an optional per-metric `window` of `'30d' | '90d' | 'all'` (only affects windowed virality/time metrics) and `title` / `chartType` / `width` overrides.
|
|
485
|
+
- Each metric accepts an optional per-metric `window` of `'30d' | '90d' | 'all'` (only affects windowed virality/time metrics) and `title` / `chartType` / `width` overrides. Request `'all'` only when the user asks for all-time coverage, and describe it as full available history.
|
|
445
486
|
- Returns `{ dashboardId, panelCount, createdMetrics, unknownMetrics, invalidMetrics, skippedExistingIds }` — report `panelCount` as proof-of-done.
|
|
446
487
|
|
|
447
488
|
Available metric keys: `total-signups`, `signups-over-time`, `signups-by-template`, `sessions-by-app`, `sessions-over-time`, `replay-sessions`, `replay-chunks-over-time`, `recent-replay-sessions`, `signed-in-vs-anon`, `total-template-clicks`, `total-demo-clicks`, `total-cli-copies`, `template-interest-over-time`, `clicks-by-template`, `demo-clicks-by-template`, `cli-copies-by-template`, `cli-copies-over-time`, `pageviews-over-time`, `top-referrer-domains`, `referred-signups-30d`, `viral-signup-share-30d`, `clip-share-signups-30d`, `signups-by-referral-source`, `referred-signups-over-time`, `top-referrers`, `share-funnel-30d`, `viral-participation-rate-90d`, `viral-coefficient-90d`, `activated-referrers-90d`.
|
|
@@ -119,11 +119,11 @@ details live in `.agents/skills/`.
|
|
|
119
119
|
without raw replay events or storage references. Do not add replay blob or
|
|
120
120
|
dashboard mutation actions to this catalog without an explicit security
|
|
121
121
|
review.
|
|
122
|
-
-
|
|
123
|
-
|
|
122
|
+
- Demo mode is a browser-local presentation preference. Analytics backend
|
|
123
|
+
actions, agent/MCP reads, session replays, and error issues always return real
|
|
124
|
+
authorized identities, so incident lookups should use the actual
|
|
124
125
|
`userId`/email parameter (e.g. `userId: "user@example.com"`) or recording/
|
|
125
|
-
issue id
|
|
126
|
-
output.
|
|
126
|
+
issue id.
|
|
127
127
|
- Analytics keeps its direct MCP surface explicitly curated, so external
|
|
128
128
|
agents should use `ask_app` for multi-step investigation and changes. The
|
|
129
129
|
six incident reads above are bounded, user/org-scoped fallback tools for
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
} from "@agent-native/core/server";
|
|
12
12
|
import { z } from "zod";
|
|
13
13
|
|
|
14
|
+
import { validateFirstPartyDashboardTimeScope } from "../server/lib/dashboard-time-scope";
|
|
14
15
|
import {
|
|
15
16
|
getDashboard,
|
|
16
17
|
upsertDashboard,
|
|
@@ -138,7 +139,7 @@ export default defineAction({
|
|
|
138
139
|
"If the dashboard already exists and `overwrite` is false (default), the new panels are APPENDED (panels whose id is already present are skipped); with `overwrite: true` the config is replaced. " +
|
|
139
140
|
"Returns { dashboardId, panelCount, createdMetrics, unknownMetrics, invalidMetrics, urlPath, deepLink, message } — use panelCount as proof-of-done. " +
|
|
140
141
|
`Available metric keys: ${METRIC_KEYS.join(", ")}. ` +
|
|
141
|
-
"Each metric accepts an optional per-metric `window` of '30d' | '90d' | 'all' (only affects windowed virality/time metrics).",
|
|
142
|
+
"Each metric accepts an optional per-metric `window` of '30d' | '90d' | 'all' (only affects windowed virality/time metrics). Catalog panels are time-scoped by construction; custom first-party SQL added later must use `{{timeRange}}` or declare `config.timeScope`.",
|
|
142
143
|
schema: z.object({
|
|
143
144
|
dashboardId: z
|
|
144
145
|
.string()
|
|
@@ -200,6 +201,7 @@ export default defineAction({
|
|
|
200
201
|
const unknownMetrics: string[] = [];
|
|
201
202
|
const invalidMetrics: Array<{ metric: string; reason: string }> = [];
|
|
202
203
|
const composedPanels: ComposedPanel[] = [];
|
|
204
|
+
const catalogDashboard = withFirstPartyDashboardFilters({ filters: [] });
|
|
203
205
|
|
|
204
206
|
for (const req of requests) {
|
|
205
207
|
const panel = buildPanel(req.metric, {
|
|
@@ -220,6 +222,15 @@ export default defineAction({
|
|
|
220
222
|
// rest of the dashboard still builds. (Catalog SQL is known-good, so this
|
|
221
223
|
// is a defensive net, e.g. if a future window/override produces bad SQL.)
|
|
222
224
|
try {
|
|
225
|
+
const timeScopeError = validateFirstPartyDashboardTimeScope(
|
|
226
|
+
panel,
|
|
227
|
+
catalogDashboard,
|
|
228
|
+
0,
|
|
229
|
+
);
|
|
230
|
+
if (timeScopeError) {
|
|
231
|
+
invalidMetrics.push({ metric: req.metric, reason: timeScopeError });
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
223
234
|
validateFirstPartyAnalyticsSql(panel.sql);
|
|
224
235
|
} catch (e: any) {
|
|
225
236
|
invalidMetrics.push({
|
|
@@ -32,6 +32,17 @@ type DashboardPatch = {
|
|
|
32
32
|
parentId?: string;
|
|
33
33
|
};
|
|
34
34
|
|
|
35
|
+
type PanelTimeScope =
|
|
36
|
+
| "dashboard"
|
|
37
|
+
| "fixed-window"
|
|
38
|
+
| "cohort-history"
|
|
39
|
+
| "all-time";
|
|
40
|
+
|
|
41
|
+
type PanelConfig = Record<string, unknown> & {
|
|
42
|
+
/** Use "dashboard" for AI-generated first-party panels by default. */
|
|
43
|
+
timeScope?: PanelTimeScope;
|
|
44
|
+
};
|
|
45
|
+
|
|
35
46
|
type PanelPatch = {
|
|
36
47
|
title?: string;
|
|
37
48
|
sql?: string;
|
|
@@ -40,7 +51,7 @@ type PanelPatch = {
|
|
|
40
51
|
width?: number;
|
|
41
52
|
columns?: number;
|
|
42
53
|
tab?: string;
|
|
43
|
-
config?:
|
|
54
|
+
config?: PanelConfig;
|
|
44
55
|
description?: string; // shorthand for config.description
|
|
45
56
|
};
|
|
46
57
|
|
|
@@ -112,9 +123,9 @@ export const DASHBOARD_MUTATION_EXAMPLES = [
|
|
|
112
123
|
'dashboard.panelsMatching({"source":"first-party"}).setWidth(2);',
|
|
113
124
|
'dashboard.panel("retention").setConfigPath("yAxis.format","percent");',
|
|
114
125
|
'dashboard.section("retention-activity-section").append(["repeat-users","retention-over-time"]);',
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
126
|
+
`dashboard.insertPanel({"id":"new-kpi","title":"New KPI","source":"first-party","chartType":"metric","width":1,"config":{"timeScope":"dashboard"},"sql":"SELECT COUNT(*) AS value FROM analytics_events WHERE event_date >= '{{dateStart}}' AND event_date < '{{dateEnd}}'"}).atTop();`,
|
|
127
|
+
`dashboard.insertPanel({"id":"new-chart","title":"New Chart","source":"first-party","chartType":"line","width":1,"config":{"timeScope":"dashboard"},"sql":"SELECT event_date AS date, COUNT(*) AS value FROM analytics_events WHERE event_date >= '{{dateStart}}' AND event_date < '{{dateEnd}}' GROUP BY event_date ORDER BY event_date"}).nextTo("retention-over-time");`,
|
|
128
|
+
`dashboard.insertPanel({"id":"row-chart","title":"Row Chart","source":"first-party","chartType":"bar","width":1,"config":{"timeScope":"dashboard"},"sql":"SELECT event_name AS name, COUNT(*) AS value FROM analytics_events WHERE event_date >= '{{dateStart}}' AND event_date < '{{dateEnd}}' GROUP BY event_name"}).atRow(2);`,
|
|
118
129
|
'dashboard.insertPanel({"id":"pipeline-widget","title":"Pipeline Widget","chartType":"extension","width":3,"config":{"extensionId":"<existing-extension-id>"}}).atBottom();',
|
|
119
130
|
] as const;
|
|
120
131
|
|
|
@@ -122,6 +122,7 @@ const apiHelp =
|
|
|
122
122
|
"Subjects: dashboard.set, dashboard.panel, dashboard.panels, dashboard.panelsMatching, dashboard.section, dashboard.insertPanel. " +
|
|
123
123
|
"Selection methods: moveToTop, moveToBottom, moveBefore, moveAfter, moveToIndex, moveNextTo, moveToRow, remove, set, setTitle, setSql, setWidth, setConfig, setConfigPath, duplicate. " +
|
|
124
124
|
"Inserted panels support atTop, atBottom, before, after, atIndex, nextTo, atRow, atRowStart, and atRowEnd. Use nextTo(panelId) or atRow(rowNumber) for visible row placement. " +
|
|
125
|
+
"AI-generated first-party panels are dashboard-time-bound by default: set config.timeScope to dashboard and include a matching dashboard time filter in SQL. Allowed 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. A {{timeRange}} token requires the timeRange select filter; {{<id>Start}}/{{<id>End}} require a matching date-range filter. Server validation rejects unbound first-party SQL. " +
|
|
125
126
|
`Examples: ${DASHBOARD_MUTATION_EXAMPLES.slice(0, 5).join(" ")}`;
|
|
126
127
|
|
|
127
128
|
function resolveScope() {
|
|
@@ -198,7 +199,7 @@ export default defineAction({
|
|
|
198
199
|
"Apply general SQL dashboard edits through a small typed mutation API in ONE atomic save. " +
|
|
199
200
|
"Prefer this for dashboard layout and panel edits: move panels by id, edit titles/SQL/width/config, remove panels, duplicate panels, insert panels, or patch dashboard fields. " +
|
|
200
201
|
"For user placement requests like 'second row' or 'next to return rates', use row-aware placement such as `dashboard.insertPanel(...).nextTo(\"retention-over-time\")` or `.atRow(2)`, then verify rendered rows from `get-sql-dashboard.layout.groups`. " +
|
|
201
|
-
"This is code-shaped but not arbitrary code execution: the server parses the allowed dashboard methods, validates the resulting config with the same invariants as update-dashboard, saves once, syncs collab, and returns compact proof. " +
|
|
202
|
+
"This is code-shaped but not arbitrary code execution: the server parses the allowed dashboard methods, validates the resulting config with the same invariants as update-dashboard, saves once, syncs collab, and returns compact proof. First-party SQL must be explicitly time-bound as described in the API help; server validation rejects unbound first-party SQL. " +
|
|
202
203
|
"The main code argument is a string, so it avoids brittle JSON-pointer indexes and native-array serialization issues. " +
|
|
203
204
|
`Common example: ${DASHBOARD_MUTATION_EXAMPLES[0]}`,
|
|
204
205
|
schema: z.object({
|
|
@@ -13,6 +13,7 @@ import { z } from "zod";
|
|
|
13
13
|
|
|
14
14
|
import { interpolate } from "../app/pages/adhoc/sql-dashboard/interpolate";
|
|
15
15
|
import { dryRunQuery } from "../server/lib/bigquery";
|
|
16
|
+
import { validateFirstPartyDashboardTimeScope } from "../server/lib/dashboard-time-scope";
|
|
16
17
|
import {
|
|
17
18
|
upsertDashboard,
|
|
18
19
|
upsertDashboardWithRetry,
|
|
@@ -421,8 +422,20 @@ export async function validatePanelSql(
|
|
|
421
422
|
const raw = typeof p.sql === "string" ? p.sql : "";
|
|
422
423
|
if (raw.trim()) {
|
|
423
424
|
try {
|
|
425
|
+
const timeScopeError = validateFirstPartyDashboardTimeScope(
|
|
426
|
+
p,
|
|
427
|
+
config,
|
|
428
|
+
i,
|
|
429
|
+
);
|
|
430
|
+
if (timeScopeError) return timeScopeError;
|
|
424
431
|
validateFirstPartyAnalyticsSql(interpolate(raw, vars));
|
|
425
432
|
} catch (e: any) {
|
|
433
|
+
if (
|
|
434
|
+
typeof e?.message === "string" &&
|
|
435
|
+
e.message.startsWith("panel[")
|
|
436
|
+
) {
|
|
437
|
+
return e.message;
|
|
438
|
+
}
|
|
426
439
|
return `panel[${i}] "${p.title || p.id}" first-party analytics SQL is invalid: ${e?.message ?? e}`;
|
|
427
440
|
}
|
|
428
441
|
}
|
|
@@ -552,6 +565,7 @@ export default defineAction({
|
|
|
552
565
|
"Use this action when creating a brand-new dashboard from a complete config, for the UI full-config save path, or for an explicitly requested low-level JSON-pointer compatibility edit. " +
|
|
553
566
|
"Do not use `ops` or `panelOrder` for ordinary agent edits like moving charts, adding panels to an existing dashboard, changing widths, or updating panel config; call `mutate-dashboard` once with the full edit script. " +
|
|
554
567
|
"When this action is appropriate, provide only one of `ops`, `panelOrder`, or `config`; `config` replaces the whole dashboard config. " +
|
|
568
|
+
"First-party event panels must bind to a declared dashboard time filter with `{{timeRange}}` or date-range variables. Intentional fixed-window, cohort-history, and all-time exceptions must be explicit in `panel.config.timeScope`; unbounded first-party SQL is rejected at save time. " +
|
|
555
569
|
"To add a shipped catalog template's panels to an existing dashboard, prefer `install-dashboard-template` with `mergePanels: true` — it appends the template's panels in one call without you having to author each panel. " +
|
|
556
570
|
"The result is compact by default: `panelCount`, `appliedOps`, `panelOrder`, `firstPanelIds`, and `summary`. Set `returnConfig: true` only when you truly need the full config in the tool result. " +
|
|
557
571
|
"The UI auto-refreshes after this action — do NOT call `refresh-screen`.",
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<svg
|
|
3
|
+
version="1.1"
|
|
4
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
5
|
+
viewBox="-0.8 -0.8 22.8 30.4"
|
|
6
|
+
shape-rendering="geometricPrecision"
|
|
7
|
+
>
|
|
8
|
+
<path
|
|
9
|
+
d="M 2.5 1.8 C 2.2 1.5 1.8 1.7 1.8 2.1 V 21.9 C 1.8 22.5 2.5 22.8 2.9 22.4 L 7.4 17.5 L 11.5 26.2 C 11.7 26.7 12.3 26.9 12.8 26.7 L 15.7 25.3 C 16.2 25.1 16.4 24.5 16.2 24 L 12.4 15.4 H 18.4 C 19.1 15.4 19.4 14.5 18.9 14 Z"
|
|
10
|
+
fill="#000000"
|
|
11
|
+
stroke="#FFFFFF"
|
|
12
|
+
stroke-width="4.35"
|
|
13
|
+
stroke-linecap="round"
|
|
14
|
+
stroke-linejoin="round"
|
|
15
|
+
paint-order="stroke fill"
|
|
16
|
+
/>
|
|
17
|
+
</svg>
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
type ReactNode,
|
|
25
25
|
} from "react";
|
|
26
26
|
import { createPortal } from "react-dom";
|
|
27
|
+
import { Link } from "react-router";
|
|
27
28
|
import {
|
|
28
29
|
Area,
|
|
29
30
|
AreaChart,
|
|
@@ -1579,12 +1580,12 @@ function TableRenderer({
|
|
|
1579
1580
|
{replayHref ? (
|
|
1580
1581
|
<span className="inline-flex flex-col gap-0.5">
|
|
1581
1582
|
<span>{content}</span>
|
|
1582
|
-
<
|
|
1583
|
-
|
|
1583
|
+
<Link
|
|
1584
|
+
to={replayHref}
|
|
1584
1585
|
className="text-xs font-medium text-primary hover:underline"
|
|
1585
1586
|
>
|
|
1586
1587
|
{t("sessions.watchReplay")}
|
|
1587
|
-
</
|
|
1588
|
+
</Link>
|
|
1588
1589
|
</span>
|
|
1589
1590
|
) : (
|
|
1590
1591
|
content
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
IconReportAnalytics,
|
|
15
15
|
IconSearch,
|
|
16
16
|
IconArchive,
|
|
17
|
+
IconActivity,
|
|
17
18
|
IconHeartbeat,
|
|
18
19
|
IconPin,
|
|
19
20
|
IconPlus,
|
|
@@ -26,7 +27,6 @@ import {
|
|
|
26
27
|
IconPlayerPlay,
|
|
27
28
|
IconLayoutSidebarLeftCollapse,
|
|
28
29
|
IconLayoutSidebarLeftExpand,
|
|
29
|
-
IconShieldCheck,
|
|
30
30
|
} from "@tabler/icons-react";
|
|
31
31
|
import {
|
|
32
32
|
useQuery,
|
|
@@ -97,7 +97,7 @@ import {
|
|
|
97
97
|
type ChatThreadSummary,
|
|
98
98
|
} from "@agent-native/core/client";
|
|
99
99
|
import { ExtensionsSidebarSection } from "@agent-native/core/client/extensions";
|
|
100
|
-
import { OrgSwitcher
|
|
100
|
+
import { OrgSwitcher } from "@agent-native/core/client/org";
|
|
101
101
|
|
|
102
102
|
import {
|
|
103
103
|
AlertDialog,
|
|
@@ -1593,7 +1593,6 @@ export function Sidebar({ mobile }: { mobile?: boolean } = {}) {
|
|
|
1593
1593
|
const navigate = useNavigate();
|
|
1594
1594
|
const t = useT();
|
|
1595
1595
|
const queryClient = useQueryClient();
|
|
1596
|
-
const { canManageOrg } = useOrgRole();
|
|
1597
1596
|
const { setTheme } = useTheme();
|
|
1598
1597
|
|
|
1599
1598
|
const isAskRoute = location.pathname === "/ask";
|
|
@@ -2428,16 +2427,12 @@ export function Sidebar({ mobile }: { mobile?: boolean } = {}) {
|
|
|
2428
2427
|
href: "/monitoring",
|
|
2429
2428
|
active: location.pathname.startsWith("/monitoring"),
|
|
2430
2429
|
},
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
active: location.pathname.startsWith("/agents"),
|
|
2438
|
-
},
|
|
2439
|
-
]
|
|
2440
|
-
: []),
|
|
2430
|
+
{
|
|
2431
|
+
icon: IconActivity,
|
|
2432
|
+
label: t("navigation.agents"),
|
|
2433
|
+
href: "/agents",
|
|
2434
|
+
active: location.pathname.startsWith("/agents"),
|
|
2435
|
+
},
|
|
2441
2436
|
{
|
|
2442
2437
|
icon: IconDatabase,
|
|
2443
2438
|
label: t("navigation.dataSources"),
|
|
@@ -2641,20 +2636,19 @@ export function Sidebar({ mobile }: { mobile?: boolean } = {}) {
|
|
|
2641
2636
|
{t("navigation.monitoring")}
|
|
2642
2637
|
</Link>
|
|
2643
2638
|
|
|
2644
|
-
{
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
) : null}
|
|
2639
|
+
{/* Agents link */}
|
|
2640
|
+
<Link
|
|
2641
|
+
to="/agents"
|
|
2642
|
+
className={cn(
|
|
2643
|
+
"order-6 flex items-center gap-3 rounded-lg px-3 py-2 transition-colors hover:text-primary",
|
|
2644
|
+
location.pathname.startsWith("/agents")
|
|
2645
|
+
? "bg-sidebar-accent text-sidebar-accent-foreground"
|
|
2646
|
+
: "text-muted-foreground hover:bg-sidebar-accent/50",
|
|
2647
|
+
)}
|
|
2648
|
+
>
|
|
2649
|
+
<IconActivity className="h-4 w-4" />
|
|
2650
|
+
{t("navigation.agents")}
|
|
2651
|
+
</Link>
|
|
2658
2652
|
|
|
2659
2653
|
{/* Data Sources link */}
|
|
2660
2654
|
<Link
|