@agent-native/core 0.72.4 → 0.74.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/core/CHANGELOG.md +67 -0
- package/corpus/core/docs/content/skills-guide.md +14 -0
- package/corpus/core/docs/content/template-clips.md +7 -1
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/durable-background.ts +35 -12
- package/corpus/core/src/agent/production-agent.ts +55 -8
- package/corpus/core/src/cli/create.ts +1 -1
- package/corpus/core/src/cli/skills.ts +450 -19
- package/corpus/core/src/client/ConnectBuilderCard.tsx +7 -7
- package/corpus/core/src/client/NewWorkspaceAppFlow.tsx +1 -1
- package/corpus/core/src/client/blocks/library/AnnotatedCodeBlock.tsx +33 -18
- package/corpus/core/src/client/blocks/library/annotation-rail.tsx +3 -4
- package/corpus/core/src/client/components/CodeRequiredDialog.tsx +5 -8
- package/corpus/core/src/server/agent-chat-plugin.ts +7 -7
- package/corpus/core/src/server/auth.ts +7 -0
- package/corpus/core/src/server/onboarding-html.ts +95 -9
- package/corpus/core/src/server/self-dispatch.ts +8 -1
- package/corpus/core/src/templates/default/AGENTS.md +4 -0
- package/corpus/core/src/templates/default/package.json +2 -1
- package/corpus/core/src/templates/headless/AGENTS.md +4 -0
- package/corpus/core/src/templates/headless/package.json +1 -0
- package/corpus/core/src/templates/workspace-root/AGENTS.md +4 -0
- package/corpus/core/src/templates/workspace-root/README.md +4 -0
- package/corpus/core/src/templates/workspace-root/package.json +1 -0
- package/corpus/templates/analytics/actions/compose-dashboard.ts +34 -0
- package/corpus/templates/analytics/actions/install-dashboard-template.ts +51 -3
- package/corpus/templates/analytics/actions/update-dashboard.ts +3 -4
- package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +175 -48
- package/corpus/templates/analytics/app/global.css +15 -13
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/PanelEditorDialog.tsx +9 -32
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +0 -26
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +0 -23
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/pivot.ts +65 -2
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/types.ts +3 -4
- package/corpus/templates/analytics/changelog/2026-06-24-dashboard-charts-now-fill-sparse-template-time-series-suppor.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-24-dashboard-filters-now-apply-consistently-to-first-party-traf.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-24-dashboard-rows-now-auto-fit-panels-so-one-two-or-three-chart.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-24-dashboard-traffic-tables-now-show-top-urls-and-shared-clips-.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-24-first-party-signup-and-pageview-panels-now-honor-dashboard-filters.md +5 -0
- package/corpus/templates/analytics/changelog/2026-06-24-first-party-template-dashboards-now-include-repeat-users-ret.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-24-retention-charts-now-break-out-1-day-and-7-day-return-rates-.md +6 -0
- package/corpus/templates/analytics/seeds/dashboards/agent-native-templates-first-party.json +330 -49
- package/corpus/templates/analytics/server/lib/dashboard-catalog.ts +7 -2
- package/corpus/templates/analytics/server/lib/first-party-analytics.ts +35 -8
- package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +337 -45
- package/corpus/templates/calendar/changelog/2026-06-23-added-a-heads-up-explaining-google-s-app-not-verified-screen.md +1 -1
- package/corpus/templates/calendar/server/plugins/auth.ts +12 -0
- package/corpus/templates/clips/.agents/skills/recording/SKILL.md +7 -7
- package/corpus/templates/clips/AGENTS.md +6 -2
- package/corpus/templates/clips/app/components/capture-install-options.tsx +20 -4
- package/corpus/templates/clips/app/components/library/library-layout.tsx +7 -0
- package/corpus/templates/clips/app/components/player/player-controls.tsx +62 -32
- package/corpus/templates/clips/app/lib/capture-install-options.ts +39 -5
- package/corpus/templates/clips/app/routes/_app.settings._index.tsx +786 -323
- package/corpus/templates/clips/app/routes/download.tsx +2 -2
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +1 -1
- package/corpus/templates/clips/changelog/2026-06-24-inline-slack-playback-controls-no-longer-shift-when-adjustin.md +6 -0
- package/corpus/templates/clips/changelog/2026-06-24-settings-now-puts-what-s-new-in-a-compact-sidebar-prioritize.md +6 -0
- package/corpus/templates/clips/changelog/2026-06-24-shared-clips-now-expose-their-full-console-log-stream-all-le.md +6 -0
- package/corpus/templates/clips/changelog/2026-06-24-shared-clips-now-expose-their-full-fetch-xhr-network-request.md +6 -0
- package/corpus/templates/clips/changelog/2026-06-24-the-chrome-extension-option-now-only-appears-on-supported-hosts.md +5 -0
- package/corpus/templates/clips/chrome-extension/STORE_DRAFT.md +13 -8
- package/corpus/templates/clips/chrome-extension/public/manifest.json +1 -1
- package/corpus/templates/clips/chrome-extension/scripts/package.ts +15 -2
- package/corpus/templates/clips/chrome-extension/src/overlay.css +4 -25
- package/corpus/templates/clips/chrome-extension/src/overlay.ts +1 -3
- package/corpus/templates/clips/server/lib/public-agent-context.ts +67 -24
- package/corpus/templates/mail/server/plugins/auth.ts +4 -1
- package/corpus/templates/plan/app/pages/PlansPage.tsx +271 -197
- package/corpus/templates/plan/changelog/2026-06-24-annotated-code-callouts-now-use-numbered-gutter-markers-that.md +6 -0
- package/corpus/templates/plan/server/plan-content.ts +45 -2
- package/dist/agent/durable-background.d.ts +9 -4
- package/dist/agent/durable-background.d.ts.map +1 -1
- package/dist/agent/durable-background.js +34 -11
- package/dist/agent/durable-background.js.map +1 -1
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +52 -8
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/cli/create.js +1 -1
- package/dist/cli/create.js.map +1 -1
- package/dist/cli/skills.d.ts.map +1 -1
- package/dist/cli/skills.js +361 -19
- package/dist/cli/skills.js.map +1 -1
- package/dist/client/ConnectBuilderCard.d.ts.map +1 -1
- package/dist/client/ConnectBuilderCard.js +6 -6
- package/dist/client/ConnectBuilderCard.js.map +1 -1
- package/dist/client/NewWorkspaceAppFlow.js +1 -1
- package/dist/client/NewWorkspaceAppFlow.js.map +1 -1
- package/dist/client/blocks/library/AnnotatedCodeBlock.d.ts.map +1 -1
- package/dist/client/blocks/library/AnnotatedCodeBlock.js +14 -10
- package/dist/client/blocks/library/AnnotatedCodeBlock.js.map +1 -1
- package/dist/client/blocks/library/annotation-rail.d.ts +3 -4
- package/dist/client/blocks/library/annotation-rail.d.ts.map +1 -1
- package/dist/client/blocks/library/annotation-rail.js +3 -4
- package/dist/client/blocks/library/annotation-rail.js.map +1 -1
- package/dist/client/components/CodeRequiredDialog.d.ts.map +1 -1
- package/dist/client/components/CodeRequiredDialog.js +2 -1
- package/dist/client/components/CodeRequiredDialog.js.map +1 -1
- package/dist/server/agent-chat-plugin.js +7 -7
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/auth.d.ts +7 -0
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/auth.js +1 -0
- package/dist/server/auth.js.map +1 -1
- package/dist/server/onboarding-html.d.ts +15 -0
- package/dist/server/onboarding-html.d.ts.map +1 -1
- package/dist/server/onboarding-html.js +73 -9
- package/dist/server/onboarding-html.js.map +1 -1
- package/dist/server/self-dispatch.d.ts.map +1 -1
- package/dist/server/self-dispatch.js +5 -1
- package/dist/server/self-dispatch.js.map +1 -1
- package/dist/templates/default/AGENTS.md +4 -0
- package/dist/templates/default/package.json +2 -1
- package/dist/templates/headless/AGENTS.md +4 -0
- package/dist/templates/headless/package.json +1 -0
- package/dist/templates/workspace-root/AGENTS.md +4 -0
- package/dist/templates/workspace-root/README.md +4 -0
- package/dist/templates/workspace-root/package.json +1 -0
- package/docs/content/skills-guide.md +14 -0
- package/docs/content/template-clips.md +7 -1
- package/package.json +1 -1
- package/src/templates/default/AGENTS.md +4 -0
- package/src/templates/default/package.json +2 -1
- package/src/templates/headless/AGENTS.md +4 -0
- package/src/templates/headless/package.json +1 -0
- package/src/templates/workspace-root/AGENTS.md +4 -0
- package/src/templates/workspace-root/README.md +4 -0
- package/src/templates/workspace-root/package.json +1 -0
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
AnnotationHiddenStack,
|
|
17
17
|
AnnotationHoverCard,
|
|
18
18
|
AnnotationInlineOverlayStack,
|
|
19
|
+
AnnotationGutterMarker,
|
|
19
20
|
anchorFromElements,
|
|
20
21
|
buildLineMarkerMap,
|
|
21
22
|
hasRailAnnotations,
|
|
@@ -42,9 +43,9 @@ import { DevInput, DevLabel, DevTextarea } from "./dev-doc-ui.js";
|
|
|
42
43
|
* pairs, so it reads correctly in BOTH light and dark mode. Code lines render as
|
|
43
44
|
* `<span>`s (never one `<pre>` per line) so they don't pick up document
|
|
44
45
|
* code/pre chrome. Lives in core so any app can register the dev-doc block.
|
|
45
|
-
* Each annotated range also gets a
|
|
46
|
-
*
|
|
47
|
-
* column.
|
|
46
|
+
* Each annotated range also gets a numbered marker in the left gutter on the
|
|
47
|
+
* first line, matching the diff block's annotation affordance without adding a
|
|
48
|
+
* persistent note column.
|
|
48
49
|
*
|
|
49
50
|
* Editing is panel-driven (config-style, like the diff/HTML blocks): a monospace
|
|
50
51
|
* code Textarea, filename/language Inputs, and add/remove-able annotation rows.
|
|
@@ -283,6 +284,7 @@ function AnnotatedCodeRead({
|
|
|
283
284
|
);
|
|
284
285
|
const rangeStartMarkers =
|
|
285
286
|
markers?.filter((item) => item.range?.start === lineNo) ?? [];
|
|
287
|
+
const showMarkerColumn = hasAnnotations;
|
|
286
288
|
|
|
287
289
|
const buildAnchorForItem = (
|
|
288
290
|
item: ResolvedAnnotation<AnnotatedCodeAnnotation>,
|
|
@@ -373,14 +375,13 @@ function AnnotatedCodeRead({
|
|
|
373
375
|
<span className="w-11 shrink-0 select-none px-3 text-right text-[11px] tabular-nums text-plan-muted/60">
|
|
374
376
|
{lineNo}
|
|
375
377
|
</span>
|
|
376
|
-
|
|
377
|
-
{highlightedLines[lineNo - 1]}
|
|
378
|
-
</span>
|
|
379
|
-
{rangeStartMarkers.length > 0 && (
|
|
378
|
+
{showMarkerColumn && (
|
|
380
379
|
<span
|
|
381
380
|
aria-hidden
|
|
382
|
-
className="
|
|
383
|
-
data-annotated-code-marker-stack
|
|
381
|
+
className="flex w-6 shrink-0 select-none items-center justify-center gap-1 py-0"
|
|
382
|
+
data-annotated-code-marker-stack={
|
|
383
|
+
rangeStartMarkers.length > 0 ? "" : undefined
|
|
384
|
+
}
|
|
384
385
|
>
|
|
385
386
|
{rangeStartMarkers.map((item) => (
|
|
386
387
|
<span
|
|
@@ -394,24 +395,35 @@ function AnnotatedCodeRead({
|
|
|
394
395
|
) ?? event.currentTarget;
|
|
395
396
|
openAnnotation(item, row);
|
|
396
397
|
}}
|
|
397
|
-
|
|
398
|
+
onClick={(event) => {
|
|
399
|
+
event.stopPropagation();
|
|
400
|
+
const row =
|
|
401
|
+
event.currentTarget.closest<HTMLElement>(
|
|
402
|
+
"[data-code-line]",
|
|
403
|
+
) ?? event.currentTarget;
|
|
404
|
+
openAnnotation(item, row);
|
|
405
|
+
}}
|
|
406
|
+
className="inline-flex cursor-pointer outline-none transition-transform hover:scale-110"
|
|
398
407
|
>
|
|
399
|
-
<
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
activeIndex === item.index
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
)}
|
|
408
|
+
<AnnotationGutterMarker
|
|
409
|
+
marker={item.marker}
|
|
410
|
+
active={
|
|
411
|
+
activeIndex === item.index ||
|
|
412
|
+
persistentAnnotationIndexes.has(item.index)
|
|
413
|
+
}
|
|
406
414
|
/>
|
|
407
415
|
</span>
|
|
408
416
|
))}
|
|
409
417
|
</span>
|
|
410
418
|
)}
|
|
419
|
+
<span className="flex-1 whitespace-pre pr-4 text-plan-code-text">
|
|
420
|
+
{highlightedLines[lineNo - 1]}
|
|
421
|
+
</span>
|
|
411
422
|
{overlayItems.length > 0 && (
|
|
412
423
|
<AnnotationInlineOverlayStack
|
|
413
424
|
items={overlayItems}
|
|
414
425
|
ctx={ctx}
|
|
426
|
+
showMarker
|
|
415
427
|
containerRef={codeRef}
|
|
416
428
|
mode={showAnnotationOverlays ? "capture" : "margin"}
|
|
417
429
|
side={showAnnotationOverlays ? "right" : annotationMarginSide}
|
|
@@ -503,7 +515,9 @@ function AnnotatedCodeRead({
|
|
|
503
515
|
live in a visually-hidden stack (a11y + tests) and surface ONE at a
|
|
504
516
|
time as an on-hover popover anchored to the right of the code. */}
|
|
505
517
|
{codeSurface}
|
|
506
|
-
{hasAnnotations &&
|
|
518
|
+
{hasAnnotations && (
|
|
519
|
+
<AnnotationHiddenStack items={resolved} ctx={ctx} showMarker />
|
|
520
|
+
)}
|
|
507
521
|
{hasAnnotations &&
|
|
508
522
|
!showAnnotationOverlays &&
|
|
509
523
|
!activeItemIsPersistentlyVisible &&
|
|
@@ -513,6 +527,7 @@ function AnnotatedCodeRead({
|
|
|
513
527
|
item={activeItem}
|
|
514
528
|
anchor={hover.anchor}
|
|
515
529
|
ctx={ctx}
|
|
530
|
+
showMarker
|
|
516
531
|
preferredSide={annotationHoverSide}
|
|
517
532
|
hoverFallbackSide={annotationHoverFallbackSide}
|
|
518
533
|
onMouseEnter={hover.cancelClose}
|
|
@@ -26,11 +26,10 @@ import type { BlockRenderContext } from "../types.js";
|
|
|
26
26
|
* into stable, marker-numbered, range-resolved records and a line→markers map.
|
|
27
27
|
* - `rangeLabel` — the human "Line 8" / "Lines 3–6" label.
|
|
28
28
|
* - `AnnotationGutterMarker` — the numbered amber pip placed on an annotated row
|
|
29
|
-
*
|
|
29
|
+
* by both the diff grid and the annotated-code gutter.
|
|
30
30
|
* - `AnnotationNoteRail` — the responsive list of note cards with two-way hover.
|
|
31
|
-
* `showMarker` opts
|
|
32
|
-
*
|
|
33
|
-
* keep its original card chrome.
|
|
31
|
+
* `showMarker` opts blocks into a leading numbered pip on each card so a note
|
|
32
|
+
* can be matched to its `①`/`②` row marker.
|
|
34
33
|
*
|
|
35
34
|
* `AnnotatedCodeBlock` annotates a single code surface; `DiffBlock` annotates a
|
|
36
35
|
* before/after grid (each annotation also carries a `side`). The shared types
|
|
@@ -12,6 +12,8 @@ import { trackEvent } from "../analytics.js";
|
|
|
12
12
|
import { withBuilderConnectTrackingParams } from "../settings/useBuilderStatus.js";
|
|
13
13
|
|
|
14
14
|
const DESKTOP_DOWNLOAD_URL = "https://www.agent-native.com/download";
|
|
15
|
+
const CODE_CHANGE_FALLBACK_DETAIL =
|
|
16
|
+
"Edit locally or use Builder.io to edit this code in the cloud and continue customizing the app any way you like.";
|
|
15
17
|
|
|
16
18
|
export interface CodeRequiredDialogProps {
|
|
17
19
|
open: boolean;
|
|
@@ -219,15 +221,10 @@ export function CodeRequiredDialog({
|
|
|
219
221
|
<IconExternalLink size={24} />
|
|
220
222
|
</div>
|
|
221
223
|
<div style={s.optionText}>
|
|
222
|
-
<span style={s.optionTitle}>
|
|
223
|
-
|
|
224
|
-
</span>
|
|
225
|
-
<span style={s.optionDesc}>
|
|
226
|
-
You don't have access yet. Use the desktop app or your local
|
|
227
|
-
clone for this code change.
|
|
228
|
-
</span>
|
|
224
|
+
<span style={s.optionTitle}>This requires a code change</span>
|
|
225
|
+
<span style={s.optionDesc}>{CODE_CHANGE_FALLBACK_DETAIL}</span>
|
|
229
226
|
</div>
|
|
230
|
-
<span style={s.badge}>
|
|
227
|
+
<span style={s.badge}>Code change</span>
|
|
231
228
|
</div>
|
|
232
229
|
) : (
|
|
233
230
|
<a
|
|
@@ -2039,7 +2039,7 @@ function createBuilderBrowserTool(deps: {
|
|
|
2039
2039
|
const entries: Record<string, ActionEntry> = {
|
|
2040
2040
|
"connect-builder": {
|
|
2041
2041
|
tool: {
|
|
2042
|
-
description: `Render a Builder.io card inline in the chat. Call this as the first step (no code exploration or planning needed) when the user asks to modify the APP'S OWN SOURCE CODE: add a feature, change the UI chrome, edit a React component, add a route, add an integration, fix a bug in the app itself, or anything else that requires source-file edits while in hosted/production mode. ${extensionRequestGuidance}Do NOT call this for content the app is meant to produce — creating a video, generating a design, drafting an email, building a slide deck, making a dashboard, etc. — those run through the app's own domain actions, not Builder. Do NOT mention 'click Send to Builder' in your response unless this card is already in the conversation. If Builder is connected and Builder Cloud Agents are available, the card shows a 'Send to Builder' button that hands the work off to Builder's cloud agent and returns a branch URL. If \`builderEnabled\` is false, the card shows
|
|
2042
|
+
description: `Render a Builder.io card inline in the chat. Call this as the first step (no code exploration or planning needed) when the user asks to modify the APP'S OWN SOURCE CODE: add a feature, change the UI chrome, edit a React component, add a route, add an integration, fix a bug in the app itself, or anything else that requires source-file edits while in hosted/production mode. ${extensionRequestGuidance}Do NOT call this for content the app is meant to produce — creating a video, generating a design, drafting an email, building a slide deck, making a dashboard, etc. — those run through the app's own domain actions, not Builder. Do NOT mention 'click Send to Builder' in your response unless this card is already in the conversation. The tool result includes \`builderEnabled\`; treat \`true\` as "Builder Cloud Agents can take the code-change handoff" and \`false\` as "this still needs a code change, but no Builder Cloud Agent can run here." If Builder is connected and Builder Cloud Agents are available, the card shows a 'Send to Builder' button that hands the work off to Builder's cloud agent and returns a branch URL. If \`builderEnabled\` is false, the card still renders but shows the code-change fallback: "This requires a code change. Edit locally or use Builder.io to edit this code in the cloud and continue customizing the app any way you like." Never tell the user to enable Builder Cloud Agents in Builder org settings or beta settings, and do not claim the Builder card has everything, is pre-loaded for handoff, or can run the cloud agent when \`builderEnabled\` is false. When you call this for a code-change request, pass the user's request verbatim as the \`prompt\` arg so the card can forward it to Builder unchanged when cloud agents are available.`,
|
|
2043
2043
|
parameters: {
|
|
2044
2044
|
type: "object",
|
|
2045
2045
|
properties: {
|
|
@@ -2920,7 +2920,7 @@ Do NOT add this offer for one-shot work: lookups (find Alice, what's the schema,
|
|
|
2920
2920
|
|
|
2921
2921
|
builder: `### Connecting Builder.io
|
|
2922
2922
|
|
|
2923
|
-
When the user asks to connect Builder.io or you hit a "Builder not configured" error, call the \`connect-builder\` tool. It renders a
|
|
2923
|
+
When the user asks to connect Builder.io or you hit a "Builder not configured" error, call the \`connect-builder\` tool. It renders a Connect/code-change card inline — do NOT write out multi-step setup instructions yourself. Inspect the returned \`builderEnabled\` flag: \`true\` means Builder Cloud Agents can take the code-change handoff, while \`false\` means this requires a code change and the user should edit locally or use Builder.io to edit this code in the cloud and continue customizing the app any way they like. If Builder Cloud Agents are not available for this workspace, never send the user to Builder org settings or beta settings.`,
|
|
2924
2924
|
|
|
2925
2925
|
browser: `### Browser Automation
|
|
2926
2926
|
|
|
@@ -3108,14 +3108,14 @@ ${extensionConnectBuilderGuard}
|
|
|
3108
3108
|
In Act mode, when the user asks you to change the UI, modify code, add a feature, fix a bug in the app itself, change styles, add a hook, create a component, add a route, add an integration, or anything else that requires editing source files — you MUST take exactly these steps, in order:
|
|
3109
3109
|
|
|
3110
3110
|
1. Briefly acknowledge the user's specific request in their own terms — one short clause naming what they asked for (e.g. "Got it — wider subject lines in the email list."). Do NOT restate the request verbatim, do NOT add a generic preamble, and do NOT promise outcomes. Skip this step entirely if the user already knows you're handing off (e.g. they said "send this to Builder").
|
|
3111
|
-
2. Call the \`connect-builder\` tool, passing the user's full request verbatim as the \`prompt\` argument. This renders an inline card. If Builder is connected and \`builderEnabled\` is true, the card hands the prompt off to Builder's cloud agent on one click and returns a branch URL. If Builder is not connected, it shows the Connect Builder flow. If \`builderEnabled\` is false, it shows a
|
|
3112
|
-
3. After the card renders, inspect the tool result and write one sentence that frames the next click around what the user just asked — not as a Builder pitch.
|
|
3111
|
+
2. Call the \`connect-builder\` tool, passing the user's full request verbatim as the \`prompt\` argument. This renders an inline card. The returned \`builderEnabled\` flag is authoritative: \`true\` means Builder Cloud Agents can take the code-change handoff; \`false\` means this still requires a code change, but no Builder Cloud Agent can run from this workspace. If Builder is connected and \`builderEnabled\` is true, the card hands the prompt off to Builder's cloud agent on one click and returns a branch URL. If Builder is not connected and \`builderEnabled\` is true, it shows the Connect Builder flow. If \`builderEnabled\` is false, it still shows the card, but as a code-change fallback instead of a runnable cloud handoff.
|
|
3112
|
+
3. After the card renders, inspect the tool result and write one sentence that frames the next click around what the user just asked — not as a Builder pitch. If \`builderEnabled\` is true and the card can send, say something like "Click Send to Builder and it'll wire that up for you." If the user needs to connect first, say something like "Connect Builder once and it'll handle this and future changes." If \`builderEnabled\` is false, say: "This requires a code change. Edit locally or use Builder.io to edit this code in the cloud and continue customizing the app any way you like." Do NOT say "click Send to Builder", mention a branch URL, say the card is pre-loaded, or imply a Builder Cloud Agent can run when \`builderEnabled\` is false.
|
|
3113
3113
|
|
|
3114
|
-
When \`builderEnabled\` is false, Builder Cloud Agents are not self-serve from Builder org settings. Do NOT tell the user to go to Builder org settings, beta features, "Cloud Agents", or "AI Agents" to enable them. The only allowed guidance is the card's
|
|
3114
|
+
When \`builderEnabled\` is false, Builder Cloud Agents are not self-serve from Builder org settings. Do NOT tell the user to go to Builder org settings, beta features, "Cloud Agents", or "AI Agents" to enable them. The only allowed guidance is the card's code-change fallback: edit locally, use Agent Native Desktop, or use Builder.io to edit this code in the cloud and continue customizing the app any way you like.
|
|
3115
3115
|
|
|
3116
3116
|
**Act-mode handoff — do exactly this:** The moment a code change is requested, call \`connect-builder\` with the user's verbatim request, then write one sentence framing the next click. That is the whole job: Builder plans and edits in its own sandbox, so the planning, file-mapping, and code all happen there, not here. Keep your turn to the call plus that one sentence — skip code exploration, implementation plans, \`resources\` writes of specs, and sub-agents (they have no code-editing tools either). You don't need filesystem access to recommend Builder, so don't reach for it or list tools you lack.
|
|
3117
3117
|
|
|
3118
|
-
Builder Cloud Agents are the recommended path when
|
|
3118
|
+
Builder Cloud Agents are the recommended path when \`builderEnabled\` is true because they are one-click and run in the cloud. Local dev, Agent Native Desktop, and Builder.io cloud editing are the fallback when \`builderEnabled\` is false.
|
|
3119
3119
|
${FRAMEWORK_CORE}`;
|
|
3120
3120
|
|
|
3121
3121
|
const DEV_FRAMEWORK_PROMPT = `## Agent-Native Framework — Development Mode
|
|
@@ -3157,7 +3157,7 @@ ${extensionInstructionsCompact}
|
|
|
3157
3157
|
|
|
3158
3158
|
### Code Changes — Call \`connect-builder\`
|
|
3159
3159
|
|
|
3160
|
-
In Act mode, when a request genuinely needs a source-code edit (per the rules above), do this in order: (1) briefly acknowledge the user's specific ask in their own terms — one short clause, no preamble — then (2) call \`connect-builder\` with the user's request as the \`prompt\`, then (3) write one sentence framing the next click around what they asked for, not a Builder pitch. Do NOT write code, list files, make plans, or spawn sub-agents. Mention \`npx agent-native create <app-name>\`, Agent Native Desktop, or the
|
|
3160
|
+
In Act mode, when a request genuinely needs a source-code edit (per the rules above), do this in order: (1) briefly acknowledge the user's specific ask in their own terms — one short clause, no preamble — then (2) call \`connect-builder\` with the user's request as the \`prompt\`, then (3) inspect \`builderEnabled\` in the result and write one sentence framing the next click around what they asked for, not a Builder pitch. When \`builderEnabled\` is true, Builder Cloud Agents can take the handoff; when \`builderEnabled\` is false, say this requires a code change and they can edit locally or use Builder.io to edit this code in the cloud and continue customizing the app any way they like. Do NOT write code, list files, make plans, or spawn sub-agents. Mention \`npx agent-native create <app-name>\`, Agent Native Desktop, or the code-change fallback when Builder Cloud Agents aren't available. Never tell users to enable Builder Cloud Agents from Builder org settings, beta features, "Cloud Agents", or "AI Agents"; they are not self-serve there.
|
|
3161
3161
|
${FRAMEWORK_CORE_COMPACT}`;
|
|
3162
3162
|
|
|
3163
3163
|
const DEV_FRAMEWORK_PROMPT_COMPACT = `## Agent-Native Framework — Development Mode
|
|
@@ -262,6 +262,12 @@ export interface AuthOptions {
|
|
|
262
262
|
continueLabel?: string;
|
|
263
263
|
cancelLabel?: string;
|
|
264
264
|
};
|
|
265
|
+
/**
|
|
266
|
+
* Optional email signup legal copy for the built-in login page.
|
|
267
|
+
* Leave unset to use Agent Native links only on `*.agent-native.com` hosts,
|
|
268
|
+
* pass false to suppress, or pass URLs for custom/self-hosted policies.
|
|
269
|
+
*/
|
|
270
|
+
signupLegalNotice?: OnboardingHtmlOptions["signupLegalNotice"];
|
|
265
271
|
/**
|
|
266
272
|
* Google sign-in flow: `'popup'`, `'redirect'`, or `'auto'` (default).
|
|
267
273
|
*
|
|
@@ -1013,6 +1019,7 @@ function getOnboardingHtmlOptions(
|
|
|
1013
1019
|
googleOnly: options.googleOnly,
|
|
1014
1020
|
marketing: options.marketing,
|
|
1015
1021
|
googleSignInNotice: options.googleSignInNotice,
|
|
1022
|
+
signupLegalNotice: options.signupLegalNotice,
|
|
1016
1023
|
googleAuthMode: options.googleAuthMode,
|
|
1017
1024
|
requestHost: event ? getRequestHost(event) : undefined,
|
|
1018
1025
|
requestPath: rawPath,
|
|
@@ -55,6 +55,36 @@ function withAppBasePath(path: string): string {
|
|
|
55
55
|
return `${basePath}${cleanPath}`;
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
const AGENT_NATIVE_TERMS_URL = "https://www.agent-native.com/terms";
|
|
59
|
+
const AGENT_NATIVE_PRIVACY_URL = "https://www.agent-native.com/privacy";
|
|
60
|
+
|
|
61
|
+
export interface SignupLegalNoticeOptions {
|
|
62
|
+
termsUrl: string;
|
|
63
|
+
privacyUrl: string;
|
|
64
|
+
termsLabel?: string;
|
|
65
|
+
privacyLabel?: string;
|
|
66
|
+
prefix?: string;
|
|
67
|
+
connector?: string;
|
|
68
|
+
suffix?: string;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function normalizeRequestHostname(host: string | undefined): string {
|
|
72
|
+
const firstHost = host?.split(",")[0]?.trim().toLowerCase() ?? "";
|
|
73
|
+
if (!firstHost) return "";
|
|
74
|
+
if (firstHost.startsWith("[")) {
|
|
75
|
+
const close = firstHost.indexOf("]");
|
|
76
|
+
return close > 0 ? firstHost.slice(1, close) : firstHost;
|
|
77
|
+
}
|
|
78
|
+
return firstHost.replace(/:\d+$/, "");
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function isAgentNativeHostedHost(host: string | undefined): boolean {
|
|
82
|
+
const hostname = normalizeRequestHostname(host);
|
|
83
|
+
return (
|
|
84
|
+
hostname === "agent-native.com" || hostname.endsWith(".agent-native.com")
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
58
88
|
export interface OnboardingHtmlOptions {
|
|
59
89
|
/**
|
|
60
90
|
* Hide email/password forms and show ONLY the Google sign-in button.
|
|
@@ -93,6 +123,12 @@ export interface OnboardingHtmlOptions {
|
|
|
93
123
|
continueLabel?: string;
|
|
94
124
|
cancelLabel?: string;
|
|
95
125
|
};
|
|
126
|
+
/**
|
|
127
|
+
* Optional email signup legal copy. Builder-hosted `*.agent-native.com`
|
|
128
|
+
* deployments get the Agent Native links automatically; self-hosted and
|
|
129
|
+
* custom-domain apps must opt in with their own URLs.
|
|
130
|
+
*/
|
|
131
|
+
signupLegalNotice?: SignupLegalNoticeOptions | false;
|
|
96
132
|
/**
|
|
97
133
|
* Google sign-in flow: `'popup'`, `'redirect'`, or `'auto'` (default).
|
|
98
134
|
* Falls back to `GOOGLE_AUTH_MODE` env var, then `'auto'`. Builder web
|
|
@@ -139,6 +175,21 @@ export function getOnboardingHtml(opts: OnboardingHtmlOptions = {}): string {
|
|
|
139
175
|
.replace(/</g, "<")
|
|
140
176
|
.replace(/>/g, ">")
|
|
141
177
|
.replace(/"/g, """);
|
|
178
|
+
const hostedSignupLegalNotice: SignupLegalNoticeOptions | undefined =
|
|
179
|
+
opts.signupLegalNotice === undefined &&
|
|
180
|
+
isAgentNativeHostedHost(opts.requestHost)
|
|
181
|
+
? {
|
|
182
|
+
termsUrl: AGENT_NATIVE_TERMS_URL,
|
|
183
|
+
privacyUrl: AGENT_NATIVE_PRIVACY_URL,
|
|
184
|
+
}
|
|
185
|
+
: undefined;
|
|
186
|
+
const signupLegalNotice =
|
|
187
|
+
opts.signupLegalNotice === false
|
|
188
|
+
? undefined
|
|
189
|
+
: (opts.signupLegalNotice ?? hostedSignupLegalNotice);
|
|
190
|
+
const signupLegalNoteHtml = signupLegalNotice
|
|
191
|
+
? ` <p class="legal-note">${esc(signupLegalNotice.prefix ?? "By signing up, you accept our")} <a href="${esc(signupLegalNotice.termsUrl)}" target="_blank" rel="noreferrer">${esc(signupLegalNotice.termsLabel ?? "Terms")}</a> ${esc(signupLegalNotice.connector ?? "and")} <a href="${esc(signupLegalNotice.privacyUrl)}" target="_blank" rel="noreferrer">${esc(signupLegalNotice.privacyLabel ?? "Privacy Policy")}</a>${esc(signupLegalNotice.suffix ?? ".")}</p>`
|
|
192
|
+
: "";
|
|
142
193
|
const googleSignInNotice = opts.googleSignInNotice;
|
|
143
194
|
const googleNoticeBodyParts = googleSignInNotice
|
|
144
195
|
? (Array.isArray(googleSignInNotice.body)
|
|
@@ -177,11 +228,16 @@ export function getOnboardingHtml(opts: OnboardingHtmlOptions = {}): string {
|
|
|
177
228
|
aria-describedby="google-preflight-copy"
|
|
178
229
|
tabindex="-1"
|
|
179
230
|
>
|
|
180
|
-
<
|
|
181
|
-
|
|
182
|
-
<
|
|
183
|
-
|
|
231
|
+
<button type="button" class="google-preflight-close" aria-label="Close Google sign-in choices" onclick="__anHideGoogleNotice()">×</button>
|
|
232
|
+
<div class="google-preflight-main">
|
|
233
|
+
<span class="google-preflight-icon" aria-hidden="true">
|
|
234
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.24 3.957l-8.422 14.06a1.989 1.989 0 0 0 1.7 2.983h16.845a1.989 1.989 0 0 0 1.7 -2.983l-8.423 -14.06a1.989 1.989 0 0 0 -3.4 0z"/><path d="M12 9v4"/><path d="M12 16h.01"/></svg>
|
|
235
|
+
</span>
|
|
236
|
+
<div class="google-preflight-text">
|
|
237
|
+
<p class="google-preflight-title" id="google-preflight-title">${esc(googleSignInNotice.title)}</p>
|
|
184
238
|
${googleNoticeBodyHtml}
|
|
239
|
+
</div>
|
|
240
|
+
</div>
|
|
185
241
|
<div class="google-preflight-actions">
|
|
186
242
|
<button type="button" class="btn-primary" id="google-preflight-continue" onclick="__anAcceptGoogleNotice()">${esc(googleSignInNotice.continueLabel ?? "Continue")}</button>
|
|
187
243
|
${googleNoticeRunLocalHtml}
|
|
@@ -707,6 +763,19 @@ ${
|
|
|
707
763
|
cursor: pointer;
|
|
708
764
|
}
|
|
709
765
|
.btn-secondary:hover { color: #bbb; border-color: rgba(255,255,255,0.2); }
|
|
766
|
+
.legal-note {
|
|
767
|
+
margin-top: 0.625rem;
|
|
768
|
+
color: #666;
|
|
769
|
+
font-size: 0.6875rem;
|
|
770
|
+
line-height: 1.45;
|
|
771
|
+
text-align: center;
|
|
772
|
+
}
|
|
773
|
+
.legal-note a {
|
|
774
|
+
color: #777;
|
|
775
|
+
text-decoration: underline;
|
|
776
|
+
text-underline-offset: 2px;
|
|
777
|
+
}
|
|
778
|
+
.legal-note a:hover { color: #aaa; }
|
|
710
779
|
.msg { margin-top: 0.75rem; font-size: 0.8125rem; display: none; }
|
|
711
780
|
.msg.error { color: #f87171; }
|
|
712
781
|
.msg.success { color: #33C4FF; }
|
|
@@ -898,25 +967,40 @@ ${
|
|
|
898
967
|
border-left: 1px solid rgba(255,255,255,0.12);
|
|
899
968
|
border-top: 1px solid rgba(255,255,255,0.12);
|
|
900
969
|
}
|
|
901
|
-
.google-preflight-
|
|
970
|
+
.google-preflight-main {
|
|
902
971
|
display: flex;
|
|
903
972
|
align-items: flex-start;
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
margin-bottom: 0.375rem;
|
|
973
|
+
gap: 0.625rem;
|
|
974
|
+
padding-right: 1.25rem;
|
|
907
975
|
}
|
|
976
|
+
.google-preflight-icon {
|
|
977
|
+
display: inline-flex;
|
|
978
|
+
align-items: center;
|
|
979
|
+
justify-content: center;
|
|
980
|
+
flex: none;
|
|
981
|
+
width: 1.75rem;
|
|
982
|
+
height: 1.75rem;
|
|
983
|
+
border-radius: 7px;
|
|
984
|
+
background: rgba(245,158,11,0.15);
|
|
985
|
+
color: #fcd34d;
|
|
986
|
+
}
|
|
987
|
+
.google-preflight-icon svg { width: 1rem; height: 1rem; }
|
|
988
|
+
.google-preflight-text { min-width: 0; }
|
|
908
989
|
.google-preflight-title {
|
|
909
990
|
color: #fff;
|
|
910
991
|
font-size: 0.8125rem;
|
|
911
992
|
font-weight: 600;
|
|
993
|
+
margin-bottom: 0.25rem;
|
|
912
994
|
}
|
|
913
995
|
.google-preflight-close {
|
|
996
|
+
position: absolute;
|
|
997
|
+
top: 0.5rem;
|
|
998
|
+
right: 0.5rem;
|
|
914
999
|
display: inline-flex;
|
|
915
1000
|
align-items: center;
|
|
916
1001
|
justify-content: center;
|
|
917
1002
|
width: 1.5rem;
|
|
918
1003
|
height: 1.5rem;
|
|
919
|
-
margin: -0.25rem -0.25rem 0 0;
|
|
920
1004
|
background: transparent;
|
|
921
1005
|
border: none;
|
|
922
1006
|
border-radius: 999px;
|
|
@@ -942,6 +1026,7 @@ ${
|
|
|
942
1026
|
flex: 1;
|
|
943
1027
|
width: auto;
|
|
944
1028
|
margin-top: 0;
|
|
1029
|
+
white-space: nowrap;
|
|
945
1030
|
}
|
|
946
1031
|
.google-preflight-command {
|
|
947
1032
|
margin-top: 0.75rem;
|
|
@@ -1039,6 +1124,7 @@ ${
|
|
|
1039
1124
|
<label for="s-pass2">Confirm password</label>
|
|
1040
1125
|
<input id="s-pass2" type="password" autocomplete="new-password" placeholder="Confirm password" required minlength="8" />
|
|
1041
1126
|
<button type="submit">Create account</button>
|
|
1127
|
+
${signupLegalNoteHtml}
|
|
1042
1128
|
<p class="msg" id="s-msg"></p>
|
|
1043
1129
|
</form>
|
|
1044
1130
|
|
|
@@ -150,7 +150,14 @@ export async function fireInternalDispatch(
|
|
|
150
150
|
}
|
|
151
151
|
});
|
|
152
152
|
dispatchPromise.catch((err) => {
|
|
153
|
-
|
|
153
|
+
// Include the resolved base URL: a self-dispatch failure is almost always
|
|
154
|
+
// about *which* host we POST to (custom domain behind an edge/auth wall vs
|
|
155
|
+
// the deploy URL), and that is invisible from the error alone. Keeps prod
|
|
156
|
+
// logs diagnostic without changing the URL resolution order.
|
|
157
|
+
console.error(
|
|
158
|
+
`[self-dispatch] dispatch to ${options.path} (base ${baseUrl}) failed:`,
|
|
159
|
+
err,
|
|
160
|
+
);
|
|
154
161
|
});
|
|
155
162
|
|
|
156
163
|
const settleMs = options.settleMs ?? DEFAULT_DISPATCH_SETTLE_MS;
|
|
@@ -38,6 +38,10 @@ Read these local package docs before implementing advanced Agent Native
|
|
|
38
38
|
features. Prefer this app's own `AGENTS.md` and `.agents/skills/` for
|
|
39
39
|
app-specific rules, then use the corpus for reusable framework/template
|
|
40
40
|
patterns.
|
|
41
|
+
After updating `@agent-native/core`, run `pnpm skills:update` or
|
|
42
|
+
`npx @agent-native/core@latest skills update scaffold --project` from the app
|
|
43
|
+
root to refresh framework-provided `.agents/skills` and repair `CLAUDE.md` /
|
|
44
|
+
`.claude/skills` compatibility links.
|
|
41
45
|
|
|
42
46
|
### Database Code
|
|
43
47
|
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
"start": "agent-native start",
|
|
9
9
|
"typecheck": "agent-native typecheck",
|
|
10
10
|
"action": "agent-native action",
|
|
11
|
-
"script": "agent-native script"
|
|
11
|
+
"script": "agent-native script",
|
|
12
|
+
"skills:update": "agent-native skills update scaffold --project"
|
|
12
13
|
},
|
|
13
14
|
"dependencies": {
|
|
14
15
|
"@agent-native/core": "latest",
|
|
@@ -38,6 +38,10 @@ first-party template patterns ships in `node_modules/@agent-native/core/corpus`.
|
|
|
38
38
|
Read these local package docs before implementing advanced Agent Native
|
|
39
39
|
features. Prefer this app's own `AGENTS.md` for app-specific rules, then use
|
|
40
40
|
the corpus for reusable framework/template patterns.
|
|
41
|
+
After updating `@agent-native/core`, run `pnpm skills:update` or
|
|
42
|
+
`npx @agent-native/core@latest skills update scaffold --project` from the app
|
|
43
|
+
root to refresh framework-provided `.agents/skills` and repair `CLAUDE.md` /
|
|
44
|
+
`.claude/skills` compatibility links.
|
|
41
45
|
|
|
42
46
|
## Actions
|
|
43
47
|
|
|
@@ -27,6 +27,10 @@ first-party template patterns ships in `node_modules/@agent-native/core/corpus`.
|
|
|
27
27
|
Use package docs for framework APIs, the package corpus for reusable
|
|
28
28
|
framework/template patterns, and `packages/shared/AGENTS.md` plus
|
|
29
29
|
`packages/shared/.agents/skills/` for workspace-specific conventions.
|
|
30
|
+
After updating `@agent-native/core`, run `pnpm skills:update` or
|
|
31
|
+
`npx @agent-native/core@latest skills update scaffold --project` from the
|
|
32
|
+
workspace root to refresh framework-provided shared skills and repair
|
|
33
|
+
`CLAUDE.md` / `.claude/skills` compatibility links.
|
|
30
34
|
|
|
31
35
|
## Core Agent Rule
|
|
32
36
|
|
|
@@ -35,6 +35,10 @@ the shared package (`@{{APP_NAME}}/shared`).
|
|
|
35
35
|
|
|
36
36
|
The workspace root also links `.agents/skills` to the shared package so coding
|
|
37
37
|
agents launched from the root can discover the same workspace-wide skills.
|
|
38
|
+
Run `pnpm skills:update` (or
|
|
39
|
+
`npx @agent-native/core@latest skills update scaffold --project`) after updating
|
|
40
|
+
`@agent-native/core` to refresh framework-provided shared skills and repair
|
|
41
|
+
Claude compatibility links.
|
|
38
42
|
|
|
39
43
|
Runtime-editable global resources live in Dispatch, not in `packages/shared`.
|
|
40
44
|
Use Dispatch **Resources** for company context and guardrails that admins should
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
"build": "pnpm -r build",
|
|
8
8
|
"typecheck": "pnpm -r typecheck",
|
|
9
9
|
"repair:workspace-org": "tsx scripts/repair-workspace-org.ts",
|
|
10
|
+
"skills:update": "agent-native skills update scaffold --project",
|
|
10
11
|
"fmt:check": "prettier --check .",
|
|
11
12
|
"lint": "pnpm fmt:check"
|
|
12
13
|
},
|
|
@@ -7,10 +7,12 @@ import {
|
|
|
7
7
|
import { z } from "zod";
|
|
8
8
|
import { getDashboard, upsertDashboard } from "../server/lib/dashboards-store";
|
|
9
9
|
import {
|
|
10
|
+
buildFirstPartyDashboardFilters,
|
|
10
11
|
buildPanel,
|
|
11
12
|
listMetricKeys,
|
|
12
13
|
type ComposedPanel,
|
|
13
14
|
type MetricWindow,
|
|
15
|
+
usesFirstPartyDashboardFilters,
|
|
14
16
|
} from "../server/lib/first-party-metric-catalog";
|
|
15
17
|
import { validateFirstPartyAnalyticsSql } from "../server/lib/first-party-analytics.js";
|
|
16
18
|
import {
|
|
@@ -97,6 +99,32 @@ const metricSchema = z.union([
|
|
|
97
99
|
|
|
98
100
|
const METRIC_KEYS = listMetricKeys();
|
|
99
101
|
|
|
102
|
+
function filterId(filter: unknown): string | null {
|
|
103
|
+
if (!filter || typeof filter !== "object" || Array.isArray(filter)) {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
const id = (filter as { id?: unknown }).id;
|
|
107
|
+
return typeof id === "string" && id.trim() ? id : null;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function withFirstPartyDashboardFilters(
|
|
111
|
+
config: Record<string, unknown>,
|
|
112
|
+
): Record<string, unknown> {
|
|
113
|
+
const filters = Array.isArray(config.filters) ? [...config.filters] : [];
|
|
114
|
+
const existingIds = new Set(
|
|
115
|
+
filters
|
|
116
|
+
.map((filter) => filterId(filter))
|
|
117
|
+
.filter((id): id is string => id !== null),
|
|
118
|
+
);
|
|
119
|
+
for (const filter of buildFirstPartyDashboardFilters()) {
|
|
120
|
+
if (!existingIds.has(filter.id)) {
|
|
121
|
+
filters.push(filter);
|
|
122
|
+
existingIds.add(filter.id);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return { ...config, filters };
|
|
126
|
+
}
|
|
127
|
+
|
|
100
128
|
export default defineAction({
|
|
101
129
|
description:
|
|
102
130
|
"Build a large first-party analytics dashboard in ONE fast call: name the metrics you want and the SERVER generates the validated SQL + chart config for every panel. " +
|
|
@@ -249,6 +277,12 @@ export default defineAction({
|
|
|
249
277
|
};
|
|
250
278
|
}
|
|
251
279
|
|
|
280
|
+
if (
|
|
281
|
+
composedPanels.some((panel) => usesFirstPartyDashboardFilters(panel.sql))
|
|
282
|
+
) {
|
|
283
|
+
finalConfig = withFirstPartyDashboardFilters(finalConfig);
|
|
284
|
+
}
|
|
285
|
+
|
|
252
286
|
const panelCount = Array.isArray(finalConfig.panels)
|
|
253
287
|
? (finalConfig.panels as unknown[]).length
|
|
254
288
|
: 0;
|
|
@@ -41,6 +41,49 @@ function uniqueConstraintMessage(err: unknown): boolean {
|
|
|
41
41
|
return /unique|constraint|primary key/i.test(message);
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
function filterId(filter: unknown): string | null {
|
|
45
|
+
if (!filter || typeof filter !== "object" || Array.isArray(filter)) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
const id = (filter as { id?: unknown }).id;
|
|
49
|
+
return typeof id === "string" && id.trim() ? id : null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function mergeMissingFilters(
|
|
53
|
+
targetConfig: Record<string, unknown>,
|
|
54
|
+
seedConfig: Record<string, unknown>,
|
|
55
|
+
): { config: Record<string, unknown>; addedFilterIds: string[] } {
|
|
56
|
+
const seedFilters = Array.isArray(seedConfig.filters)
|
|
57
|
+
? (seedConfig.filters as unknown[])
|
|
58
|
+
: [];
|
|
59
|
+
if (seedFilters.length === 0) {
|
|
60
|
+
return { config: targetConfig, addedFilterIds: [] };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const targetFilters = Array.isArray(targetConfig.filters)
|
|
64
|
+
? [...targetConfig.filters]
|
|
65
|
+
: [];
|
|
66
|
+
const existingIds = new Set(
|
|
67
|
+
targetFilters
|
|
68
|
+
.map((filter) => filterId(filter))
|
|
69
|
+
.filter((id): id is string => id !== null),
|
|
70
|
+
);
|
|
71
|
+
const addedFilterIds: string[] = [];
|
|
72
|
+
for (const filter of seedFilters) {
|
|
73
|
+
const id = filterId(filter);
|
|
74
|
+
if (id && existingIds.has(id)) continue;
|
|
75
|
+
targetFilters.push(filter);
|
|
76
|
+
if (id) {
|
|
77
|
+
existingIds.add(id);
|
|
78
|
+
addedFilterIds.push(id);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return addedFilterIds.length > 0
|
|
83
|
+
? { config: { ...targetConfig, filters: targetFilters }, addedFilterIds }
|
|
84
|
+
: { config: targetConfig, addedFilterIds };
|
|
85
|
+
}
|
|
86
|
+
|
|
44
87
|
export default defineAction({
|
|
45
88
|
description:
|
|
46
89
|
"Install a dashboard template from the Analytics catalog into the user's SQL-backed dashboards. Use list-dashboard-templates first when choosing a template. " +
|
|
@@ -125,7 +168,10 @@ export default defineAction({
|
|
|
125
168
|
.filter((id): id is string => !!id),
|
|
126
169
|
);
|
|
127
170
|
|
|
128
|
-
const seedConfig = cloneDashboardConfig(entry)
|
|
171
|
+
const seedConfig = cloneDashboardConfig(entry) as unknown as Record<
|
|
172
|
+
string,
|
|
173
|
+
unknown
|
|
174
|
+
>;
|
|
129
175
|
const seedPanels = Array.isArray(seedConfig.panels)
|
|
130
176
|
? (seedConfig.panels as unknown as Array<Record<string, unknown>>)
|
|
131
177
|
: [];
|
|
@@ -146,13 +192,15 @@ export default defineAction({
|
|
|
146
192
|
}
|
|
147
193
|
}
|
|
148
194
|
|
|
149
|
-
|
|
195
|
+
let mergedConfig: Record<string, unknown> = {
|
|
150
196
|
...targetConfig,
|
|
151
197
|
panels: [...existingPanels, ...appended],
|
|
152
198
|
};
|
|
199
|
+
const filterMerge = mergeMissingFilters(mergedConfig, seedConfig);
|
|
200
|
+
mergedConfig = filterMerge.config;
|
|
153
201
|
const panelCount = (mergedConfig.panels as unknown[]).length;
|
|
154
202
|
|
|
155
|
-
if (appended.length > 0) {
|
|
203
|
+
if (appended.length > 0 || filterMerge.addedFilterIds.length > 0) {
|
|
156
204
|
const saved = await upsertDashboard(
|
|
157
205
|
targetId,
|
|
158
206
|
target.kind,
|