@agent-native/core 0.153.0 → 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.
Files changed (38) hide show
  1. package/corpus/templates/analytics/.agents/skills/adhoc-analysis/SKILL.md +21 -19
  2. package/corpus/templates/calendar/app/components/calendar/DayView.tsx +3 -2
  3. package/corpus/templates/calendar/app/components/calendar/WeekView.tsx +3 -2
  4. package/corpus/templates/calendar/app/lib/event-layout.ts +16 -35
  5. package/corpus/templates/clips/app/components/meetings/share-meeting-dialog.tsx +27 -55
  6. package/corpus/templates/clips/app/components/player/share-dialog.tsx +104 -66
  7. package/corpus/templates/clips/app/components/sharing/share-ui.tsx +45 -43
  8. package/corpus/templates/clips/app/routes/_app.meetings.$meetingId.tsx +0 -1
  9. package/corpus/templates/clips/desktop/src/app.tsx +818 -442
  10. package/corpus/templates/clips/desktop/src/styles.css +244 -2
  11. package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +2 -2
  12. package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +1 -0
  13. package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +584 -1
  14. package/corpus/templates/clips/desktop/src-tauri/src/rewind_clip.rs +25 -0
  15. package/corpus/templates/clips/desktop/src-tauri/src/util.rs +1 -1
  16. package/corpus/templates/factory/app/routes/factory.tsx +0 -3
  17. package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +1 -1
  18. package/corpus/templates/slides/app/components/editor/ShareDialog.tsx +28 -25
  19. package/corpus/templates/slides/app/components/editor/SlideContextToolbar.tsx +50 -0
  20. package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +12 -60
  21. package/dist/agent/production-agent.d.ts +8 -0
  22. package/dist/agent/production-agent.js +25 -2
  23. package/dist/agent/run-manager.js +13 -3
  24. package/dist/client/AgentPanel.js +6 -4
  25. package/dist/client/org/WorkspaceNotice.js +7 -4
  26. package/dist/client/sharing/ShareButton.d.ts +2 -2
  27. package/dist/client/sharing/ShareButton.js +18 -13
  28. package/dist/client/sharing/ShareDialog.d.ts +1 -1
  29. package/dist/client/sharing/ShareDialog.js +16 -11
  30. package/dist/collab/awareness.d.ts +2 -2
  31. package/dist/collab/struct-routes.d.ts +1 -1
  32. package/dist/resources/handlers.d.ts +1 -1
  33. package/dist/server/auth.js +18 -4
  34. package/dist/server/better-auth-instance.js +8 -1
  35. package/dist/server/magic-link-attribution.d.ts +21 -0
  36. package/dist/server/magic-link-attribution.js +115 -0
  37. package/dist/server/realtime-token.d.ts +1 -1
  38. package/package.json +1 -1
@@ -136,6 +136,8 @@ pub(crate) fn rewind_clip_prepare(
136
136
  let sources = screen_memory::rewind_clip_sources(&app);
137
137
  let output = artifact_path(&app, &artifact_label)?;
138
138
  #[cfg(target_os = "macos")]
139
+ let recovery_intent = (server_url.clone(), recording_id.clone());
140
+ #[cfg(target_os = "macos")]
139
141
  let shared_sink = match screen_memory::prepare_shared_clip_sink(
140
142
  &app,
141
143
  output,
@@ -168,6 +170,29 @@ pub(crate) fn rewind_clip_prepare(
168
170
  release_temporary_audio(&app, temporary_audio);
169
171
  return Err(not_compatible("shared Rewind Clip sinks require macOS"));
170
172
  }
173
+ #[cfg(target_os = "macos")]
174
+ if let (Some(server_url), Some(recording_id)) = recovery_intent {
175
+ if !server_url.trim().is_empty() && !recording_id.trim().is_empty() {
176
+ let (width, height) = shared_sink.dimensions();
177
+ if let Err(error) = native_screen::persist_recording_intent(
178
+ shared_sink.path(),
179
+ &recording_id,
180
+ &server_url,
181
+ native_screen::MP4_RECORDING_MIME_TYPE,
182
+ Some(width),
183
+ Some(height),
184
+ include_mic || include_system_audio,
185
+ include_mic,
186
+ include_system_audio,
187
+ has_camera,
188
+ true,
189
+ ) {
190
+ shared_sink.cancel();
191
+ release_temporary_audio(&app, temporary_audio);
192
+ return Err(error);
193
+ }
194
+ }
195
+ }
171
196
  let response_sources = sources.clone();
172
197
  let mut active = state.0.lock().map_err(|error| error.to_string())?;
173
198
  if active.is_some() {
@@ -7,7 +7,7 @@ use crate::state::{
7
7
  };
8
8
 
9
9
  const POPOVER_SHADOW_GUTTER_LOGICAL: f64 = 24.0;
10
- const POPOVER_DEFAULT_WIDTH_LOGICAL: f64 = 360.0;
10
+ const POPOVER_DEFAULT_WIDTH_LOGICAL: f64 = 320.0;
11
11
  const POPOVER_DEFAULT_HEIGHT_LOGICAL: f64 = 520.0;
12
12
 
13
13
  // ---------------------------------------------------------------------------
@@ -1516,9 +1516,6 @@ function RulesView({ t }: { t: ReturnType<typeof useT> }) {
1516
1516
  <CardTitle className="text-base">
1517
1517
  {t("factoryRoute.rulesTitle")}
1518
1518
  </CardTitle>
1519
- <p className="text-sm text-muted-foreground">
1520
- {t("factoryRoute.rulesDescription")}
1521
- </p>
1522
1519
  </CardHeader>
1523
1520
  <CardContent className="space-y-2">
1524
1521
  <Button
@@ -636,7 +636,7 @@ export default function EditorToolbar({
636
636
  />
637
637
 
638
638
  {/* Consolidated editor menu */}
639
- <div className="flex-shrink-0">
639
+ <div className="ml-auto flex-shrink-0">
640
640
  <DropdownMenu>
641
641
  <Tooltip>
642
642
  <TooltipTrigger asChild>
@@ -6,6 +6,7 @@ import {
6
6
  IconCheck,
7
7
  IconLoader2,
8
8
  IconExternalLink,
9
+ IconChevronDown,
9
10
  } from "@tabler/icons-react";
10
11
  import { useState, type ReactNode } from "react";
11
12
 
@@ -86,7 +87,7 @@ export default function ShareDialog({ deck, children }: ShareDialogProps) {
86
87
  <PopoverTrigger asChild>{children}</PopoverTrigger>
87
88
  <PopoverContent
88
89
  align="end"
89
- className="w-[420px] bg-card border-border p-4"
90
+ className="w-[min(420px,calc(100vw-2rem))] bg-card border-border p-4"
90
91
  >
91
92
  {showCloudUpgrade || isLocal ? (
92
93
  <CloudUpgrade
@@ -104,35 +105,37 @@ export default function ShareDialog({ deck, children }: ShareDialogProps) {
104
105
  <IconShare2 className="w-4 h-4 text-[#609FF8]" />
105
106
  {t("share.title")}
106
107
  </div>
107
- <div className="text-muted-foreground text-xs mt-0.5">
108
- {t("share.description", { title: deck.title })}
109
- </div>
110
108
  </div>
111
109
 
112
110
  <div className="space-y-4">
113
111
  {!shareUrl ? (
114
112
  <>
115
- <div className="bg-muted/50 rounded-lg p-3 border border-border">
116
- <h4 className="text-sm font-medium text-foreground/90 mb-2">
113
+ <details className="group rounded-md border border-border bg-muted/30">
114
+ <summary className="flex cursor-pointer list-none items-center justify-between gap-3 px-3 py-2.5 text-sm font-medium [&::-webkit-details-marker]:hidden">
117
115
  {t("share.whatGetsShared")}
118
- </h4>
119
- <ul className="text-xs text-muted-foreground space-y-1">
120
- <li>
121
- {t("share.slideContent", {
122
- count: deck.slides.length,
123
- })}
124
- </li>
125
- <li>{t("share.presentationView")}</li>
126
- </ul>
127
- <h4 className="text-sm font-medium text-foreground/90 mt-3 mb-2">
128
- {t("share.whatStaysPrivate")}
129
- </h4>
130
- <ul className="text-xs text-muted-foreground space-y-1">
131
- <li>{t("share.speakerNotes")}</li>
132
- <li>{t("share.otherPresentations")}</li>
133
- <li>{t("share.editingAccess")}</li>
134
- </ul>
135
- </div>
116
+ <IconChevronDown className="size-4 shrink-0 text-muted-foreground transition-transform group-open:rotate-180" />
117
+ </summary>
118
+ <div className="space-y-3 border-t border-border px-3 py-3 text-xs text-muted-foreground">
119
+ <ul className="space-y-1">
120
+ <li>
121
+ {t("share.slideContent", {
122
+ count: deck.slides.length,
123
+ })}
124
+ </li>
125
+ <li>{t("share.presentationView")}</li>
126
+ </ul>
127
+ <div>
128
+ <h4 className="mb-1 font-medium text-foreground/90">
129
+ {t("share.whatStaysPrivate")}
130
+ </h4>
131
+ <ul className="space-y-1">
132
+ <li>{t("share.speakerNotes")}</li>
133
+ <li>{t("share.otherPresentations")}</li>
134
+ <li>{t("share.editingAccess")}</li>
135
+ </ul>
136
+ </div>
137
+ </div>
138
+ </details>
136
139
 
137
140
  {error && (
138
141
  <p className="text-xs text-red-400 bg-red-400/10 px-3 py-2 rounded-lg">
@@ -194,7 +197,7 @@ export default function ShareDialog({ deck, children }: ShareDialogProps) {
194
197
  {t("share.openSharedLink")}
195
198
  </a>
196
199
 
197
- <p className="text-[11px] text-muted-foreground/70 text-center">
200
+ <p className="sr-only text-[11px] text-muted-foreground/70 text-center">
198
201
  {t("share.anyoneWithLink")}
199
202
  </p>
200
203
  </>
@@ -36,6 +36,8 @@ import {
36
36
  IconStackBack,
37
37
  IconStackFront,
38
38
  IconUnderline,
39
+ IconZoomIn,
40
+ IconZoomOut,
39
41
  } from "@tabler/icons-react";
40
42
  import type { ReactNode } from "react";
41
43
 
@@ -150,6 +152,7 @@ export function SlideContextToolbar({
150
152
  objectSelectionCount = 0,
151
153
  onAlignObjects,
152
154
  onDistributeObjects,
155
+ zoomControls,
153
156
  }: {
154
157
  snapshot: SlideStyleSnapshot | null;
155
158
  background: string | undefined;
@@ -164,6 +167,13 @@ export function SlideContextToolbar({
164
167
  objectSelectionCount?: number;
165
168
  onAlignObjects?: (alignment: SlideObjectAlignment) => void;
166
169
  onDistributeObjects?: (distribution: SlideObjectDistribution) => void;
170
+ zoomControls?: {
171
+ value: number;
172
+ onZoomOut: () => void;
173
+ onZoomIn: () => void;
174
+ canZoomOut: boolean;
175
+ canZoomIn: boolean;
176
+ };
167
177
  }) {
168
178
  const t = useT();
169
179
  const documentColors = tokenPalette(designSystem, t).map(
@@ -914,6 +924,46 @@ export function SlideContextToolbar({
914
924
  </Popover>
915
925
  </>
916
926
  )}
927
+ {zoomControls && (
928
+ <>
929
+ <div className={TOOLBAR_DIVIDER} />
930
+ <Tooltip>
931
+ <TooltipTrigger asChild>
932
+ <Button
933
+ type="button"
934
+ variant="ghost"
935
+ size="icon"
936
+ className={MENU_BUTTON_CLASS}
937
+ onClick={zoomControls.onZoomOut}
938
+ disabled={!zoomControls.canZoomOut}
939
+ aria-label={t("raw.zoomOut")}
940
+ >
941
+ <IconZoomOut className="size-3.5" />
942
+ </Button>
943
+ </TooltipTrigger>
944
+ <TooltipContent>{t("raw.zoomOut")}</TooltipContent>
945
+ </Tooltip>
946
+ <span className="w-11 shrink-0 text-center text-xs tabular-nums text-muted-foreground">
947
+ {zoomControls.value}%
948
+ </span>
949
+ <Tooltip>
950
+ <TooltipTrigger asChild>
951
+ <Button
952
+ type="button"
953
+ variant="ghost"
954
+ size="icon"
955
+ className={MENU_BUTTON_CLASS}
956
+ onClick={zoomControls.onZoomIn}
957
+ disabled={!zoomControls.canZoomIn}
958
+ aria-label={t("raw.zoomIn")}
959
+ >
960
+ <IconZoomIn className="size-3.5" />
961
+ </Button>
962
+ </TooltipTrigger>
963
+ <TooltipContent>{t("raw.zoomIn")}</TooltipContent>
964
+ </Tooltip>
965
+ </>
966
+ )}
917
967
  </div>
918
968
  );
919
969
  }
@@ -21,13 +21,10 @@ import {
21
21
  IconBrush,
22
22
  IconClipboard,
23
23
  IconCopy,
24
- IconMaximize,
25
24
  IconStackBack,
26
25
  IconStackFront,
27
26
  IconTrash,
28
27
  IconX,
29
- IconZoomIn,
30
- IconZoomOut,
31
28
  } from "@tabler/icons-react";
32
29
  import {
33
30
  useState,
@@ -1252,10 +1249,6 @@ export default function SlideEditor({
1252
1249
  .find((preset) => preset < canvasZoom);
1253
1250
  setManualCanvasZoom(previous ?? CANVAS_ZOOM_PRESETS[0]);
1254
1251
  }, [canvasZoom, setManualCanvasZoom]);
1255
- const fitCanvasToScreen = useCallback(() => {
1256
- userSetCanvasZoomRef.current = false;
1257
- setCanvasZoom(fitCanvasZoom);
1258
- }, [fitCanvasZoom]);
1259
1252
 
1260
1253
  usePinchZoom({
1261
1254
  containerRef: scrollContainerRef,
@@ -5010,6 +5003,16 @@ export default function SlideEditor({
5010
5003
  const objectOperationSelection = getObjectOperationSelection();
5011
5004
  const hasObjectSelection = objectOperationSelection !== null;
5012
5005
  const objectSelectionCount = objectOperationSelection?.elements.length ?? 0;
5006
+ const zoomControls = slide.excalidrawData
5007
+ ? undefined
5008
+ : {
5009
+ value: canvasZoom,
5010
+ onZoomOut: canvasZoomOut,
5011
+ onZoomIn: canvasZoomIn,
5012
+ canZoomOut: canvasZoom > MIN_CANVAS_ZOOM,
5013
+ canZoomIn:
5014
+ canvasZoom < CANVAS_ZOOM_PRESETS[CANVAS_ZOOM_PRESETS.length - 1],
5015
+ };
5013
5016
 
5014
5017
  // Excalidraw slides have no selectable slide content, so the row collapses
5015
5018
  // to its slide-level state — but that state owns the background picker, and
@@ -5038,6 +5041,7 @@ export default function SlideEditor({
5038
5041
  objectSelectionCount={objectSelectionCount}
5039
5042
  onAlignObjects={handleAlignSelectedObjects}
5040
5043
  onDistributeObjects={handleDistributeSelectedObjects}
5044
+ zoomControls={zoomControls}
5041
5045
  />
5042
5046
  </div>
5043
5047
  ) : null;
@@ -5061,6 +5065,7 @@ export default function SlideEditor({
5061
5065
  objectSelectionCount={objectSelectionCount}
5062
5066
  onAlignObjects={handleAlignSelectedObjects}
5063
5067
  onDistributeObjects={handleDistributeSelectedObjects}
5068
+ zoomControls={zoomControls}
5064
5069
  className="slide-context-toolbar--top-row"
5065
5070
  />
5066
5071
  </div>
@@ -5102,59 +5107,6 @@ export default function SlideEditor({
5102
5107
  </div>
5103
5108
  ) : (
5104
5109
  <div className="relative h-full bg-[var(--slides-editor-surface)]">
5105
- <div className="absolute right-3 top-3 z-20 flex h-8 items-center gap-0.5 rounded-md bg-popover/95 px-1">
5106
- <Tooltip>
5107
- <TooltipTrigger asChild>
5108
- <Button
5109
- variant="ghost"
5110
- size="icon"
5111
- className="h-6 w-6 cursor-pointer"
5112
- onClick={canvasZoomOut}
5113
- disabled={canvasZoom <= MIN_CANVAS_ZOOM}
5114
- aria-label={t("raw.zoomOut")}
5115
- >
5116
- <IconZoomOut className="h-3.5 w-3.5" />
5117
- </Button>
5118
- </TooltipTrigger>
5119
- <TooltipContent>{t("raw.zoomOut")}</TooltipContent>
5120
- </Tooltip>
5121
- <span className="w-11 text-center text-xs tabular-nums text-muted-foreground">
5122
- {canvasZoom}%
5123
- </span>
5124
- <Tooltip>
5125
- <TooltipTrigger asChild>
5126
- <Button
5127
- variant="ghost"
5128
- size="icon"
5129
- className="h-6 w-6 cursor-pointer"
5130
- onClick={canvasZoomIn}
5131
- disabled={
5132
- canvasZoom >=
5133
- CANVAS_ZOOM_PRESETS[CANVAS_ZOOM_PRESETS.length - 1]
5134
- }
5135
- aria-label={t("raw.zoomIn")}
5136
- >
5137
- <IconZoomIn className="h-3.5 w-3.5" />
5138
- </Button>
5139
- </TooltipTrigger>
5140
- <TooltipContent>{t("raw.zoomIn")}</TooltipContent>
5141
- </Tooltip>
5142
- <div className="mx-0.5 h-4 w-px bg-border" />
5143
- <Tooltip>
5144
- <TooltipTrigger asChild>
5145
- <Button
5146
- variant="ghost"
5147
- size="icon"
5148
- className="h-6 w-6 cursor-pointer"
5149
- onClick={fitCanvasToScreen}
5150
- aria-label={t("raw.fitSlideToScreen")}
5151
- >
5152
- <IconMaximize className="h-3.5 w-3.5" />
5153
- </Button>
5154
- </TooltipTrigger>
5155
- <TooltipContent>{t("raw.fitToScreen")}</TooltipContent>
5156
- </Tooltip>
5157
- </div>
5158
5110
  <div
5159
5111
  ref={scrollContainerRef}
5160
5112
  className={`h-full overflow-auto ${
@@ -373,6 +373,14 @@ export interface ProductionAgentOptions {
373
373
  };
374
374
  }
375
375
  export declare function resolveAgentOwnerEmail(options: Pick<ProductionAgentOptions, "resolveOwnerEmail">, event: any): Promise<string | null>;
376
+ /**
377
+ * A transient database failure in the completion callback used to prevent a
378
+ * background continuation from ever being handed off. Keep the retry bounded
379
+ * and database-only so a permanent persistence error still fails loudly.
380
+ */
381
+ export declare function runCompletionCallbackWithDatabaseRetry(callback: () => void | Promise<void>, options?: {
382
+ sleep?: (ms: number) => Promise<void>;
383
+ }): Promise<void>;
376
384
  declare function generateRunId(): string;
377
385
  /** Check if an error is transient and should be retried
378
386
  * @internal exported for unit tests only
@@ -5,7 +5,7 @@ import { parseA2AAgentActivityPart } from "../a2a/activity.js";
5
5
  import { describeToolParameterSignature, isAgentActionStopError, } from "../action.js";
6
6
  import { readAppState } from "../application-state/script-helpers.js";
7
7
  import { isReadOnlyShellCommand } from "../coding-tools/index.js";
8
- import { getDbExec } from "../db/client.js";
8
+ import { getDbExec, isTransientDatabaseError } from "../db/client.js";
9
9
  import { extensionIdFromPathname } from "../extensions/path.js";
10
10
  import { preUploadAttachments } from "../file-upload/pre-upload-attachments.js";
11
11
  import { isMcpActionResult } from "../mcp-client/app-result.js";
@@ -620,6 +620,29 @@ export async function resolveAgentOwnerEmail(options, event) {
620
620
  return ownerEmail ?? getRequestUserEmail() ?? null;
621
621
  }
622
622
  const MAX_RETRIES = 3;
623
+ const COMPLETION_DATABASE_RETRY_DELAYS_MS = [250, 750, 1_500];
624
+ /**
625
+ * A transient database failure in the completion callback used to prevent a
626
+ * background continuation from ever being handed off. Keep the retry bounded
627
+ * and database-only so a permanent persistence error still fails loudly.
628
+ */
629
+ export async function runCompletionCallbackWithDatabaseRetry(callback, options) {
630
+ const sleep = options?.sleep ??
631
+ ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
632
+ for (let attempt = 0;; attempt += 1) {
633
+ try {
634
+ await callback();
635
+ return;
636
+ }
637
+ catch (error) {
638
+ const retryDelay = COMPLETION_DATABASE_RETRY_DELAYS_MS[attempt];
639
+ if (!isTransientDatabaseError(error) || retryDelay === undefined) {
640
+ throw error;
641
+ }
642
+ await sleep(retryDelay);
643
+ }
644
+ }
645
+ }
623
646
  /**
624
647
  * Retry budget override for `builder_gateway_error` — the no-detail Builder
625
648
  * gateway fallback. Production data shows this code is almost never
@@ -6981,7 +7004,7 @@ export function createProductionAgentHandler(options) {
6981
7004
  const baseHandleRunComplete = options.onRunComplete || trackedProgressRunId
6982
7005
  ? async (run) => {
6983
7006
  try {
6984
- await options.onRunComplete?.(run, threadId);
7007
+ await runCompletionCallbackWithDatabaseRetry(() => options.onRunComplete?.(run, threadId));
6985
7008
  }
6986
7009
  catch (err) {
6987
7010
  await completeTrackedProgressRun(run, err);
@@ -448,6 +448,11 @@ function terminalEventForcesErroredStatus(event) {
448
448
  return event?.type === "error" || event?.type === "missing_api_key";
449
449
  }
450
450
  function terminalReasonForRun(finalStatus, terminalEvent, abortReason, completionError) {
451
+ if (finalStatus !== "aborted" &&
452
+ completionError &&
453
+ terminalEvent?.type === "auto_continue") {
454
+ return "completion_error";
455
+ }
451
456
  if (terminalEvent?.type === "auto_continue") {
452
457
  return terminalEvent.reason || "auto_continue";
453
458
  }
@@ -1295,13 +1300,18 @@ export function startRun(runId, threadId, runFn, onComplete, options) {
1295
1300
  : terminalEventForCompletion?.event.type === "error" ||
1296
1301
  terminalEventForCompletion?.event.type === "missing_api_key"
1297
1302
  ? terminalEventForCompletion.event
1298
- : terminalEventForCompletion?.event.type === "auto_continue"
1303
+ : terminalEventForCompletion?.event.type === "auto_continue" &&
1304
+ run.continuationTerminalEvent
1299
1305
  ? // The run was checkpointed at a soft-timeout/loop boundary and
1300
1306
  // is recoverable: the partial turn is in agent_run_events and
1301
- // the continuation run will re-attempt the thread_data save.
1307
+ // the handed-off continuation run will re-attempt the
1308
+ // thread_data save.
1302
1309
  // Even though the completion save failed (finalStatus stays
1303
1310
  // "errored" for SQL/diagnostics), re-emit the auto_continue so
1304
- // the client resumes instead of seeing a dead chat.
1311
+ // the client resumes instead of seeing a dead chat. A
1312
+ // pending auto_continue without this handoff marker is not
1313
+ // recoverable: advertising it makes the client poll a dead
1314
+ // run until it reports background_run_lost.
1305
1315
  terminalEventForCompletion.event
1306
1316
  : {
1307
1317
  type: "error",
@@ -22,7 +22,7 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
22
22
  * <AgentChatSurface mode="page" className="h-screen" />
23
23
  */
24
24
  import { Tooltip as DesignSystemTooltip } from "@agent-native/toolkit/design-system";
25
- import { IconMessageCircle, IconMessageDots, IconTerminal2, IconSettings, IconLayoutSidebarRightCollapse, IconLayoutGrid, IconCheck, IconPlus, IconX, IconDotsVertical, IconHistory, IconArrowsHorizontal, IconArrowsMaximize, IconExternalLink, } from "@tabler/icons-react";
25
+ import { IconMessageCircle, IconMessageDots, IconTerminal2, IconSettings, IconLayoutSidebarRightCollapse, IconLayoutGrid, IconCheck, IconPlus, IconX, IconDotsVertical, IconHistory, IconArrowsHorizontal, IconArrowsMaximize, IconExternalLink, IconShare3, } from "@tabler/icons-react";
26
26
  import React, { useState, useEffect, useRef, useCallback, useMemo, lazy, Suspense, startTransition, } from "react";
27
27
  import { flushSync } from "react-dom";
28
28
  import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuTrigger, } from "./components/ui/dropdown-menu.js";
@@ -666,6 +666,7 @@ function AgentPanelInner({ defaultMode = "chat", className, style, apiUrl, empty
666
666
  : "text-muted-foreground hover:bg-accent/50 hover:text-foreground"), style: AGENT_PANEL_CONTROL_STYLE, children: [_jsx(IconLayoutGrid, { size: 14 }), t("agentPanel.workspace")] }), content: t("agentPanel.workspaceMode"), delayMs: 200 })] })), [codeAccessEnabled, codeUnavailableDescription, showCliMode, t]);
667
667
  const [headerMenuOpen, setHeaderMenuOpen] = useState(false);
668
668
  const [feedbackOpen, setFeedbackOpen] = useState(false);
669
+ const [shareFromMenuOpen, setShareFromMenuOpen] = useState(false);
669
670
  const getChatThreadShareUrl = useCallback((threadId) => {
670
671
  if (typeof window === "undefined")
671
672
  return undefined;
@@ -700,7 +701,7 @@ function AgentPanelInner({ defaultMode = "chat", className, style, apiUrl, empty
700
701
  document.addEventListener("keydown", handleKeyDown);
701
702
  return () => document.removeEventListener("keydown", handleKeyDown);
702
703
  }, [mode, onCollapse, wideDrawerAction]);
703
- const renderHeaderActions = useCallback(({ activeChatSessionId, activeTabId, activeTabMessageCount, addTab, clearActiveTab, closeAllTabs, closeOtherTabs, closeTab, showHistory, tabs, toggleHistory, }) => (_jsxs("div", { className: "relative flex shrink-0 items-center gap-0.5", children: [!onCollapse && SHOW_ONBOARDING && (_jsx(Suspense, { fallback: null, children: _jsx(SetupButton, {}) })), !onCollapse &&
704
+ const renderHeaderActions = useCallback(({ activeChatSessionId, activeTabId, activeTabMessageCount, addTab, clearActiveTab, closeAllTabs, closeOtherTabs, closeTab, showHistory, tabs, toggleHistory, }) => (_jsxs("div", { className: "relative flex shrink-0 items-center gap-0.5", children: [!onCollapse && SHOW_ONBOARDING && (_jsx(Suspense, { fallback: null, children: _jsx(SetupButton, {}) })), (!onCollapse || shareFromMenuOpen) &&
704
705
  (() => {
705
706
  const activeTab = mode === "chat" && activeChatSessionId
706
707
  ? tabs.find((tab) => tab.id === activeChatSessionId)
@@ -709,7 +710,7 @@ function AgentPanelInner({ defaultMode = "chat", className, style, apiUrl, empty
709
710
  (activeTabMessageCount <= 0 && activeTab.status === "idle")) {
710
711
  return null;
711
712
  }
712
- return (_jsx(ShareButton, { resourceType: "chat_thread", resourceId: activeTab.id, allowedRoles: ["viewer", "editor", "admin"], resourceTitle: activeTab.label || "Chat", shareUrl: getChatThreadShareUrl(activeTab.id), trigger: "icon", triggerClassName: "h-7 w-7" }));
713
+ return (_jsx(ShareButton, { resourceType: "chat_thread", resourceId: activeTab.id, allowedRoles: ["viewer", "editor", "admin"], resourceTitle: activeTab.label || "Chat", shareUrl: getChatThreadShareUrl(activeTab.id), trigger: "icon", triggerClassName: "h-7 w-7", defaultOpen: onCollapse && shareFromMenuOpen, onOpenChange: onCollapse ? setShareFromMenuOpen : undefined }));
713
714
  })(), feedbackEnabled ? (_jsx(FeedbackButton, { variant: "icon", side: "bottom", align: "end", chatSessionId: activeChatSessionId, chatStorageKey: storageKey, open: feedbackOpen, onOpenChange: setFeedbackOpen, trigger: _jsx("button", { type: "button", tabIndex: -1, "aria-hidden": "true", className: "pointer-events-none absolute end-0 top-full h-px w-px opacity-0" }) })) : null, mode === "chat" && (_jsx(IconTooltip, { content: t("agentPanel.newChat"), children: _jsx("button", { onClick: addTab, "aria-label": t("agentPanel.newChat"), className: "flex h-6 w-6 items-center justify-center rounded text-muted-foreground hover:text-foreground hover:bg-accent/50", children: _jsx(IconPlus, { size: 14 }) }) })), !onCollapse && mode === "cli" && canUseCodeTools && (_jsx(IconTooltip, { content: t("agentPanel.newTerminal"), children: _jsx("button", { onClick: addCliTab, "aria-label": t("agentPanel.newTerminal"), className: "flex h-6 w-6 items-center justify-center rounded text-muted-foreground hover:text-foreground hover:bg-accent/50", children: _jsx(IconPlus, { size: 14 }) }) })), _jsxs(DropdownMenu, { open: headerMenuOpen, onOpenChange: setHeaderMenuOpen, children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx("button", { className: cn("flex h-6 w-6 items-center justify-center rounded text-muted-foreground hover:text-foreground hover:bg-accent/50", (headerMenuOpen || mode === "settings") &&
714
715
  "bg-accent text-foreground"), "aria-label": t("agentPanel.panelOptions"), children: _jsx(IconDotsVertical, { size: 14 }) }) }), _jsxs(DropdownMenuContent, { align: "end", sideOffset: 6, className: "w-48", children: [onCollapse && (_jsxs(_Fragment, { children: [_jsxs(DropdownMenuItem, { onSelect: onCollapse, children: [_jsx(IconLayoutSidebarRightCollapse, { size: 14, className: "shrink-0" }), t("agentPanel.collapseSidebar"), _jsx(DropdownMenuShortcut, { children: toggleSidebarHint })] }), _jsx(DropdownMenuSeparator, {})] })), onCollapse && mode === "chat" && wideDrawerAction ? (_jsxs(DropdownMenuItem, { onSelect: wideDrawerAction, children: [_jsx(IconArrowsHorizontal, { size: 14, className: "shrink-0" }), wideDrawerLabel, _jsx(DropdownMenuShortcut, { children: widenChatHint })] })) : null, fullViewAction?.kind === "callback" && onFullViewRequest ? (_jsxs(DropdownMenuItem, { onSelect: onFullViewRequest, "aria-label": t("agentPanel.openFullView"), children: [_jsx(IconArrowsMaximize, { size: 14, className: "shrink-0" }), t("agentPanel.openFullView")] })) : fullViewAction?.kind === "link" ? (_jsx(DropdownMenuItem, { asChild: true, children: _jsxs(Link, { to: fullViewAction.href, "aria-label": t("agentPanel.openFullView"), children: [_jsx(IconArrowsMaximize, { size: 14, className: "shrink-0" }), t("agentPanel.openFullView")] }) })) : null, (onCollapse && mode === "chat" && wideDrawerAction) ||
715
716
  fullViewAction ? (_jsx(DropdownMenuSeparator, {})) : null, onCollapse && mode === "chat" && (_jsxs(_Fragment, { children: [_jsxs(DropdownMenuItem, { onSelect: addTab, children: [_jsx(IconPlus, { size: 14, className: "shrink-0" }), t("agentPanel.newChat")] }), (() => {
@@ -720,7 +721,7 @@ function AgentPanelInner({ defaultMode = "chat", className, style, apiUrl, empty
720
721
  (activeTabMessageCount <= 0 && activeTab.status === "idle")) {
721
722
  return null;
722
723
  }
723
- return (_jsx("div", { className: "p-1", children: _jsx(ShareButton, { resourceType: "chat_thread", resourceId: activeTab.id, allowedRoles: ["viewer", "editor", "admin"], resourceTitle: activeTab.label || "Chat", shareUrl: getChatThreadShareUrl(activeTab.id), trigger: "label-icon", triggerClassName: "w-full justify-start" }) }));
724
+ return (_jsxs(DropdownMenuItem, { onSelect: (event) => deferAgentPanelOverlayOpen(event, () => setHeaderMenuOpen(false), () => setShareFromMenuOpen(true)), children: [_jsx(IconShare3, { size: 14, className: "shrink-0" }), "Share"] }));
724
725
  })()] })), mode === "chat" && toggleHistory && (_jsxs(DropdownMenuItem, { onSelect: (event) => deferAgentPanelOverlayOpen(event, () => setHeaderMenuOpen(false), toggleHistory), children: [_jsx(IconHistory, { size: 14, className: "shrink-0" }), showHistory
725
726
  ? t("agentPanel.hideChats")
726
727
  : t("agentPanel.allChats")] })), mode === "chat" && (_jsx(RunsTrayMenuItem, { pollMs: 0, limit: 12, showRecent: true, onOpenThread: openRunThread })), mode === "chat" && _jsx(DropdownMenuSeparator, {}), mode === "cli" && availableClis.length > 0 && (_jsxs(_Fragment, { children: [availableClis.map((cli) => (_jsxs(DropdownMenuItem, { onSelect: () => selectCli(cli.command), className: cn(cli.command === selectedCli
@@ -752,6 +753,7 @@ function AgentPanelInner({ defaultMode = "chat", className, style, apiUrl, empty
752
753
  openRunThread,
753
754
  selectCli,
754
755
  selectedCli,
756
+ shareFromMenuOpen,
755
757
  storageKey,
756
758
  switchMode,
757
759
  t,
@@ -1,8 +1,9 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { IconExternalLink } from "@tabler/icons-react";
2
+ import { IconAlertTriangle, IconExternalLink } from "@tabler/icons-react";
3
3
  import { useState } from "react";
4
4
  import { isLocalDevelopmentOrigin, shouldOfferWorkspace, } from "../../org/workspace-url.js";
5
5
  import { useOrg } from "./hooks.js";
6
+ import { isWorkspaceAppEnvironment } from "./workspace-app-links.js";
6
7
  const DISMISS_KEY_PREFIX = "agent-native:workspace-notice-dismissed:";
7
8
  function dismissKey(workspaceUrl) {
8
9
  return `${DISMISS_KEY_PREFIX}${workspaceUrl}`;
@@ -38,14 +39,16 @@ export function WorkspaceNotice({ className }) {
38
39
  const workspaceUrl = org?.workspaceUrl ?? null;
39
40
  if (typeof window === "undefined")
40
41
  return null;
41
- if (isLocalDevelopmentOrigin(window.location.href))
42
+ if (isLocalDevelopmentOrigin(window.location.href) ||
43
+ isWorkspaceAppEnvironment()) {
42
44
  return null;
45
+ }
43
46
  if (!workspaceUrl || dismissedNow || readDismissed(workspaceUrl))
44
47
  return null;
45
48
  if (!shouldOfferWorkspace(window.location.href, workspaceUrl))
46
49
  return null;
47
50
  const workspaceHost = new URL(workspaceUrl).host;
48
- return (_jsx("div", { className: `border-b border-border bg-blue-50 dark:bg-blue-950/30 px-3 py-2.5 sm:px-4 ${className ?? ""}`, children: _jsxs("div", { className: "flex items-center justify-between gap-4 text-sm", children: [_jsxs("span", { className: "min-w-0 flex-1 text-foreground", children: [_jsx("span", { className: "font-medium", children: org?.orgName }), " has its own workspace at ", _jsx("span", { className: "font-medium", children: workspaceHost }), ". Your team's apps live there, not here."] }), _jsxs("div", { className: "flex shrink-0 items-center gap-2", children: [_jsxs("a", { href: workspaceUrl, className: "inline-flex items-center gap-1.5 rounded-md bg-green-600 px-3 py-1.5 text-xs font-medium text-white no-underline hover:bg-green-700", children: [_jsx(IconExternalLink, { className: "h-3 w-3" }), "Go there"] }), _jsx("button", { type: "button", onClick: () => {
51
+ return (_jsx("div", { role: "status", "aria-live": "polite", className: `border-b border-amber-500/30 bg-amber-500/10 px-3 py-3 text-amber-900 dark:text-amber-200 sm:px-4 ${className ?? ""}`, children: _jsxs("div", { className: "flex flex-wrap items-start gap-x-3 gap-y-2 text-sm", children: [_jsx(IconAlertTriangle, { "aria-hidden": "true", className: "mt-0.5 size-4 shrink-0 text-amber-600 dark:text-amber-400" }), _jsxs("span", { className: "min-w-0 flex-1 leading-5", children: [_jsx("span", { className: "font-medium", children: org?.orgName }), " has its own workspace at ", _jsx("span", { className: "font-medium", children: workspaceHost }), ". Your team's apps live there, not here."] }), _jsxs("div", { className: "flex shrink-0 items-center gap-2", children: [_jsxs("a", { href: workspaceUrl, className: "inline-flex items-center gap-1.5 rounded-md border border-amber-600 bg-amber-600 px-3 py-1.5 text-xs font-medium text-primary-foreground no-underline hover:bg-amber-700 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2", children: [_jsx(IconExternalLink, { className: "h-3 w-3" }), "Go there"] }), _jsx("button", { type: "button", onClick: () => {
49
52
  try {
50
53
  localStorage.setItem(dismissKey(workspaceUrl), "1");
51
54
  }
@@ -53,5 +56,5 @@ export function WorkspaceNotice({ className }) {
53
56
  // Private-mode storage failure only costs a repeat banner.
54
57
  }
55
58
  setDismissedNow(true);
56
- }, className: "rounded-md border border-border px-3 py-1.5 text-xs font-medium text-muted-foreground hover:text-foreground cursor-pointer", children: "Stay here" })] })] }) }));
59
+ }, className: "cursor-pointer rounded-md border border-amber-500/40 bg-background/60 px-3 py-1.5 text-xs font-medium text-amber-900 hover:bg-amber-500/10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 dark:border-amber-800 dark:bg-amber-950/20 dark:text-amber-100 dark:hover:bg-amber-900/40", children: "Stay here" })] })] }) }));
57
60
  }
@@ -29,8 +29,8 @@ export interface ShareButtonProps {
29
29
  shareUrlLabel?: string;
30
30
  /** Optional helper text for the primary copyable link section. */
31
31
  shareUrlDescription?: ReactNode;
32
- /** Where to render share links in the popover. Defaults to the bottom,
33
- * matching the historical Google-Docs-style share dialog. */
32
+ /** Where to render share links in the popover. Defaults to the top,
33
+ * keeping the copyable link as the primary share action. */
34
34
  shareUrlPlacement?: "top" | "bottom";
35
35
  /** Whether to render copyable share URL fields. Defaults to true. */
36
36
  showShareLinks?: boolean;