@agent-native/core 0.153.1 → 0.153.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.
@@ -101,25 +101,27 @@ Don't just dump raw data. Synthesize findings:
101
101
 
102
102
  ### Step 4: Generate Charts (when useful)
103
103
 
104
- When the analysis benefits from a visual — trends over time, distributions, comparisons between categories — call `generate-chart` before formatting the report. The action returns a `url` you embed directly in the markdown.
105
-
106
- ```
107
- generate-chart
108
- --title "Closed-lost deals by month"
109
- --type bar
110
- --labels '["Jan","Feb","Mar"]'
111
- --data '[18, 22, 14]'
112
- ```
113
-
114
- Embed the returned URL in `resultMarkdown` using standard markdown image syntax:
115
-
116
- ```markdown
117
- ![Closed-lost deals by month](/api/media/closed-lost-deals-by-month-1234567890.png?v=1234567890)
118
- ```
119
-
120
- You can include multiple charts in one analysis. Reach for a chart when it communicates the finding faster than a table — don't force visuals on every analysis.
121
-
122
- Include the re-generation step in your saved `instructions` so re-runs produce fresh charts.
104
+ When the analysis benefits from a visual — trends over time, distributions, or
105
+ comparisons between categories — query the data first, then use the live
106
+ `/chart` embed described in `data-querying` for an in-chat answer. Do not call
107
+ `generate-chart` for a one-off chat result: it produces a static image for
108
+ saved artifacts, requires pre-stringified JSON, and does not count as a real
109
+ data query for the final response guard.
110
+
111
+ For a stacked multi-series bar chart, keep the query in long form and emit a
112
+ panel with `chartType: "bar"`, `config.pivot` containing `xKey`, `seriesKey`,
113
+ and `valueKey`, plus `config.stacked: true`. The live chart route pivots the
114
+ rows and renders one stack per x-axis category. See `data-querying`'s
115
+ "Inline Charts In Chat" section for the exact `embed` fence and encoding.
116
+
117
+ Only use `generate-chart` when a saved analysis artifact explicitly needs a
118
+ static image. If it returns an error while answering in chat, switch to the
119
+ live embed instead of retrying reformatted `labels` or `data` parameters.
120
+
121
+ You can include multiple live charts in one analysis. Reach for a chart when it
122
+ communicates the finding faster than a table - don't force visuals on every
123
+ analysis. Include the query and embed configuration in saved `instructions` so
124
+ re-runs produce fresh charts.
123
125
 
124
126
  ### Step 5: Format Results as Markdown
125
127
 
@@ -12,7 +12,6 @@ import {
12
12
  CopyField,
13
13
  GeneralAccessSelect,
14
14
  MakePublicCard,
15
- ShareCardHeader,
16
15
  SharePeopleTab,
17
16
  copyToClipboard,
18
17
  useResourceVisibilityMutation,
@@ -30,7 +29,6 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
30
29
 
31
30
  export interface ShareMeetingPopoverProps {
32
31
  meetingId: string;
33
- meetingTitle?: string;
34
32
  shareTranscript: boolean;
35
33
  transcriptReady: boolean;
36
34
  children: ReactNode;
@@ -38,7 +36,6 @@ export interface ShareMeetingPopoverProps {
38
36
 
39
37
  export function ShareMeetingPopover({
40
38
  meetingId,
41
- meetingTitle,
42
39
  shareTranscript,
43
40
  transcriptReady,
44
41
  children,
@@ -52,7 +49,6 @@ export function ShareMeetingPopover({
52
49
  >
53
50
  <ShareMeetingContent
54
51
  meetingId={meetingId}
55
- meetingTitle={meetingTitle}
56
52
  shareTranscript={shareTranscript}
57
53
  transcriptReady={transcriptReady}
58
54
  />
@@ -63,12 +59,10 @@ export function ShareMeetingPopover({
63
59
 
64
60
  function ShareMeetingContent({
65
61
  meetingId,
66
- meetingTitle,
67
62
  shareTranscript,
68
63
  transcriptReady,
69
64
  }: {
70
65
  meetingId: string;
71
- meetingTitle?: string;
72
66
  shareTranscript: boolean;
73
67
  transcriptReady: boolean;
74
68
  }) {
@@ -85,14 +79,9 @@ function ShareMeetingContent({
85
79
 
86
80
  const data = sharesQuery.data;
87
81
  const canManage = data?.role === "owner" || data?.role === "admin";
88
- const titleText = meetingTitle
89
- ? t("clipsFinalRaw.shareNamedMeeting", { title: meetingTitle })
90
- : t("clipsFinalRaw.shareMeeting");
91
82
 
92
83
  return (
93
84
  <>
94
- <ShareCardHeader title={titleText} ownerEmail={data?.ownerEmail} />
95
-
96
85
  <Tabs defaultValue="link" className="min-w-0 px-4 py-3">
97
86
  <TabsList className="grid w-full grid-cols-2">
98
87
  <TabsTrigger value="link" className="gap-1.5">
@@ -27,7 +27,6 @@ import {
27
27
  CopyField,
28
28
  GeneralAccessSelect,
29
29
  MakePublicCard,
30
- ShareCardHeader,
31
30
  SharePeopleTab,
32
31
  copyToClipboard,
33
32
  useResourceVisibilityMutation,
@@ -51,6 +50,7 @@ import {
51
50
  } from "@/components/ui/popover";
52
51
  import { Switch } from "@/components/ui/switch";
53
52
  import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs";
53
+ import { cn } from "@/lib/utils";
54
54
 
55
55
  import { buildEmailPreviewMarkup } from "../../../shared/email-preview";
56
56
  import { isLoomEmbedUrl } from "../../../shared/loom";
@@ -228,19 +228,12 @@ function ShareRecordingContent({
228
228
  absoluteAppUrl(`/share/${recordingId}`),
229
229
  ownerViaId,
230
230
  );
231
- const titleText = recordingTitle
232
- ? t("shareDialog.shareTitle", { title: recordingTitle })
233
- : t("shareDialog.shareRecording");
234
-
235
231
  return (
236
232
  <>
237
- <ShareCardHeader
238
- title={titleText}
239
- ownerEmail={data?.ownerEmail}
240
- reserveCloseButton={reserveCloseButton}
241
- />
242
-
243
- <Tabs defaultValue="link" className="min-w-0 px-4 py-3">
233
+ <Tabs
234
+ defaultValue="link"
235
+ className={cn("min-w-0 px-4 py-3", reserveCloseButton && "pe-12")}
236
+ >
244
237
  <TabsList className="grid w-full grid-cols-3">
245
238
  <TabsTrigger value="link" className="gap-1.5">
246
239
  <IconLink size={14} />
@@ -441,6 +434,25 @@ function LinkTab({
441
434
  ? t("shareDialog.agentPrompt", { agentContextUrl: agentLink })
442
435
  : "";
443
436
  const [agentDetailsOpen, setAgentDetailsOpen] = useState(false);
437
+ const showMakePublic = sharesLoaded && !isPublic && canManage;
438
+ const downloadButton = videoUrl ? (
439
+ <Button
440
+ variant="outline"
441
+ size="sm"
442
+ className={isLoomRecording ? "gap-1.5" : undefined}
443
+ onClick={() => window.open(videoUrl, "_blank", "noopener,noreferrer")}
444
+ >
445
+ {isLoomRecording ? (
446
+ <>
447
+ <IconExternalLink className="h-4 w-4" />
448
+ {t("shareDialog.openPlayer")}
449
+ </>
450
+ ) : (
451
+ t("recordRoute.downloadRecording")
452
+ )}
453
+ </Button>
454
+ ) : null;
455
+ const moveDownloadIntoMakePublicRow = showMakePublic && Boolean(videoUrl);
444
456
 
445
457
  useEffect(() => {
446
458
  if (isPublic) setAgentDetailsOpen(false);
@@ -540,9 +552,12 @@ function LinkTab({
540
552
  </Collapsible>
541
553
  ) : null}
542
554
 
543
- {sharesLoaded && !isPublic && canManage ? (
555
+ {showMakePublic ? (
544
556
  <MakePublicCard
545
557
  isPending={isPending}
558
+ secondaryAction={
559
+ moveDownloadIntoMakePublicRow ? downloadButton : undefined
560
+ }
546
561
  onMakePublic={() =>
547
562
  setResourceVisibility("public", {
548
563
  onSuccess: () => copyToClipboard(shareUrl),
@@ -551,7 +566,9 @@ function LinkTab({
551
566
  />
552
567
  ) : null}
553
568
 
554
- {emailPreviewThumbnailUrl || videoUrl || animatedThumbnailUrl ? (
569
+ {emailPreviewThumbnailUrl ||
570
+ animatedThumbnailUrl ||
571
+ (videoUrl && !moveDownloadIntoMakePublicRow) ? (
555
572
  <div className="flex flex-wrap gap-2">
556
573
  {emailPreviewThumbnailUrl ? (
557
574
  <Button
@@ -571,25 +588,7 @@ function LinkTab({
571
588
  {t("shareDialog.gifPreview")}
572
589
  </Button>
573
590
  ) : null}
574
- {videoUrl ? (
575
- <Button
576
- variant="outline"
577
- size="sm"
578
- className={isLoomRecording ? "gap-1.5" : undefined}
579
- onClick={() =>
580
- window.open(videoUrl, "_blank", "noopener,noreferrer")
581
- }
582
- >
583
- {isLoomRecording ? (
584
- <>
585
- <IconExternalLink className="h-4 w-4" />
586
- {t("shareDialog.openPlayer")}
587
- </>
588
- ) : (
589
- t("recordRoute.downloadRecording")
590
- )}
591
- </Button>
592
- ) : null}
591
+ {!moveDownloadIntoMakePublicRow ? downloadButton : null}
593
592
  </div>
594
593
  ) : null}
595
594
  </div>
@@ -14,7 +14,7 @@ import {
14
14
  IconWorld,
15
15
  } from "@tabler/icons-react";
16
16
  import { useQueryClient } from "@tanstack/react-query";
17
- import { useMemo, useState } from "react";
17
+ import { useMemo, useState, type ReactNode } from "react";
18
18
 
19
19
  import {
20
20
  Avatar as UserAvatar,
@@ -136,34 +136,19 @@ export function useResourceVisibilityMutation(
136
136
  }
137
137
 
138
138
  // ---------------------------------------------------------------------------
139
- // Header (title + owner)
139
+ // Compact section labels
140
140
  // ---------------------------------------------------------------------------
141
141
 
142
- export function ShareCardHeader({
143
- title,
144
- ownerEmail,
145
- reserveCloseButton = false,
142
+ export function ShareSectionLabel({
143
+ children,
144
+ className,
146
145
  }: {
147
- title: string;
148
- ownerEmail?: string | null;
149
- reserveCloseButton?: boolean;
146
+ children: ReactNode;
147
+ className?: string;
150
148
  }) {
151
- const t = useT();
152
149
  return (
153
- <div
154
- className={cn(
155
- "min-w-0 border-b border-border px-4 pb-3 pt-3",
156
- reserveCloseButton && "pe-12",
157
- )}
158
- >
159
- <div className="min-w-0 truncate text-sm font-semibold" title={title}>
160
- {title}
161
- </div>
162
- {ownerEmail ? (
163
- <div className="mt-0.5 truncate text-xs text-muted-foreground">
164
- {t("shareUi.owner", { email: ownerEmail })}
165
- </div>
166
- ) : null}
150
+ <div className={cn("text-xs font-medium text-muted-foreground", className)}>
151
+ {children}
167
152
  </div>
168
153
  );
169
154
  }
@@ -197,9 +182,9 @@ export function GeneralAccessSelect({
197
182
 
198
183
  return (
199
184
  <div>
200
- <div className="mb-2 text-xs font-semibold">
185
+ <ShareSectionLabel className="mb-2">
201
186
  {t("shareUi.generalAccess")}
202
- </div>
187
+ </ShareSectionLabel>
203
188
  <div className="flex items-center gap-3">
204
189
  <span
205
190
  aria-hidden
@@ -245,9 +230,11 @@ export function GeneralAccessSelect({
245
230
  export function MakePublicCard({
246
231
  isPending,
247
232
  onMakePublic,
233
+ secondaryAction,
248
234
  }: {
249
235
  isPending: boolean;
250
236
  onMakePublic: () => void;
237
+ secondaryAction?: ReactNode;
251
238
  }) {
252
239
  const t = useT();
253
240
  return (
@@ -259,16 +246,21 @@ export function MakePublicCard({
259
246
  strokeWidth={1.8}
260
247
  className="text-muted-foreground"
261
248
  />
262
- <Button
263
- type="button"
264
- variant="outline"
265
- size="sm"
266
- className="h-7"
267
- onClick={onMakePublic}
268
- disabled={isPending}
269
- >
270
- {isPending ? t("shareUi.makingPublic") : t("shareUi.makePublicAndCopy")}
271
- </Button>
249
+ <div className="flex items-center gap-2">
250
+ {secondaryAction}
251
+ <Button
252
+ type="button"
253
+ variant="outline"
254
+ size="sm"
255
+ className="h-7"
256
+ onClick={onMakePublic}
257
+ disabled={isPending}
258
+ >
259
+ {isPending
260
+ ? t("shareUi.makingPublic")
261
+ : t("shareUi.makePublicAndCopy")}
262
+ </Button>
263
+ </div>
272
264
  </div>
273
265
  );
274
266
  }
@@ -297,9 +289,7 @@ export function CopyField({
297
289
  return (
298
290
  <div>
299
291
  {label ? (
300
- <div className="mb-1 text-xs font-medium text-muted-foreground">
301
- {label}
302
- </div>
292
+ <ShareSectionLabel className="mb-1">{label}</ShareSectionLabel>
303
293
  ) : null}
304
294
  <div className="flex items-stretch gap-2">
305
295
  <Input
@@ -704,7 +704,6 @@ export default function MeetingDetailRoute() {
704
704
  ) : null}
705
705
  <ShareMeetingPopover
706
706
  meetingId={meeting.id}
707
- meetingTitle={meeting.title}
708
707
  shareTranscript={meeting.shareTranscript === true}
709
708
  transcriptReady={
710
709
  meeting.transcriptStatus === "ready" &&