@agent-native/core 0.144.0 → 0.144.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/actions/migrate-first-party-analytics-to-bigquery.ts +1 -1
  3. package/corpus/templates/analytics/server/lib/first-party-analytics-backend.ts +36 -14
  4. package/corpus/templates/clips/app/components/player/player-controls.tsx +0 -3
  5. package/corpus/templates/clips/app/components/player/scrubber.tsx +2 -33
  6. package/corpus/templates/clips/app/components/player/video-player.tsx +58 -11
  7. package/corpus/templates/clips/changelog/2026-08-06-trimmed-clips-play-on-a-clean-timeline-without-cut-markers.md +6 -0
  8. package/corpus/templates/dispatch/AGENTS.md +8 -1
  9. package/corpus/templates/dispatch/app/dispatch-extensions.tsx +1 -0
  10. package/corpus/templates/dispatch/app/hooks/use-navigation-state.ts +30 -15
  11. package/corpus/templates/dispatch/app/i18n-data.ts +131 -0
  12. package/corpus/templates/dispatch/app/routes/admin._index.tsx +4 -0
  13. package/corpus/templates/dispatch/app/routes/admin.agents.tsx +1 -0
  14. package/corpus/templates/dispatch/app/routes/admin.approvals.tsx +1 -0
  15. package/corpus/templates/dispatch/app/routes/admin.apps.$appId.tsx +1 -0
  16. package/corpus/templates/dispatch/app/routes/admin.apps.tsx +1 -0
  17. package/corpus/templates/dispatch/app/routes/admin.audit.tsx +1 -0
  18. package/corpus/templates/dispatch/app/routes/admin.automations.tsx +1 -0
  19. package/corpus/templates/dispatch/app/routes/admin.destinations.tsx +4 -0
  20. package/corpus/templates/dispatch/app/routes/admin.dreams.tsx +1 -0
  21. package/corpus/templates/dispatch/app/routes/admin.identities.tsx +1 -0
  22. package/corpus/templates/dispatch/app/routes/admin.integrations.tsx +4 -0
  23. package/corpus/templates/dispatch/app/routes/admin.messaging.tsx +1 -0
  24. package/corpus/templates/dispatch/app/routes/admin.metrics.tsx +1 -0
  25. package/corpus/templates/dispatch/app/routes/admin.new-app.tsx +1 -0
  26. package/corpus/templates/dispatch/app/routes/admin.operations.tsx +1 -0
  27. package/corpus/templates/dispatch/app/routes/admin.thread-debug.tsx +4 -0
  28. package/corpus/templates/dispatch/app/routes/admin.transactional-email.$appId.$id.tsx +4 -0
  29. package/corpus/templates/dispatch/app/routes/admin.transactional-email.tsx +4 -0
  30. package/corpus/templates/dispatch/app/routes/admin.tsx +1 -0
  31. package/corpus/templates/dispatch/app/routes/admin.vault.tsx +1 -0
  32. package/corpus/templates/dispatch/app/routes/admin.workspace.tsx +1 -0
  33. package/corpus/templates/dispatch/changelog/2026-08-06-dispatch-keeps-everyday-navigation-focused-while-management-.md +6 -0
  34. package/corpus/templates/slides/AGENTS.md +10 -12
  35. package/corpus/templates/slides/actions/export-pptx.ts +68 -0
  36. package/corpus/templates/slides/actions/get-deck.ts +12 -0
  37. package/corpus/templates/slides/actions/import-file.ts +130 -55
  38. package/corpus/templates/slides/actions/import-pptx.ts +108 -8
  39. package/corpus/templates/slides/actions/patch-deck.ts +51 -0
  40. package/corpus/templates/slides/actions/update-slide.ts +29 -3
  41. package/corpus/templates/slides/app/components/onboarding/FirstDeckOnboardingFlow.tsx +11 -0
  42. package/corpus/templates/slides/app/lib/create-deck-generation.ts +72 -11
  43. package/corpus/templates/slides/app/lib/import-uploaded-deck.ts +89 -0
  44. package/corpus/templates/slides/app/pages/Index.tsx +85 -18
  45. package/corpus/templates/slides/changelog/2026-08-06-uploaded-decks-keep-their-slides-and-visuals-when-you-improv.md +6 -0
  46. package/corpus/templates/slides/server/handlers/import/html-converter.ts +13 -2
  47. package/corpus/templates/slides/server/lib/chat-attachments.ts +2 -2
  48. package/corpus/templates/slides/server/lib/source-import.ts +167 -0
  49. package/corpus/templates/slides/server/plugins/agent-chat.ts +8 -0
  50. package/dist/collab/routes.d.ts +1 -1
  51. package/dist/jobs/scheduler.js +79 -23
  52. package/dist/notifications/routes.d.ts +1 -1
  53. package/dist/observability/routes.d.ts +3 -3
  54. package/dist/progress/routes.d.ts +1 -1
  55. package/dist/secrets/routes.d.ts +6 -6
  56. package/dist/server/realtime-token.d.ts +1 -1
  57. package/docs/content/creating-templates.mdx +1 -1
  58. package/docs/content/getting-started-actions.mdx +38 -189
  59. package/docs/content/getting-started-database.mdx +80 -164
  60. package/docs/content/getting-started-pages.mdx +44 -155
  61. package/docs/content/getting-started.mdx +26 -106
  62. package/package.json +1 -1
package/corpus/README.md CHANGED
@@ -31,4 +31,4 @@ rg -n "defineAction|useActionQuery" node_modules/@agent-native/core/corpus
31
31
 
32
32
  ## Generated Counts
33
33
 
34
- - template files: 7821
34
+ - template files: 7847
@@ -36,7 +36,7 @@ const migrationSchema = z.object({
36
36
  "Optional BigQuery table reference as dataset.table or project.dataset.table. The default is <BIGQUERY_PROJECT_ID>.analytics.first_party_analytics_events_raw.",
37
37
  ),
38
38
  cursor: z.string().trim().optional(),
39
- limit: z.number().int().min(1).max(500).optional(),
39
+ limit: z.number().int().min(1).max(5_000).optional(),
40
40
  confirm: z.boolean().optional(),
41
41
  });
42
42
 
@@ -73,6 +73,8 @@ const FIRST_PARTY_QUERY_TABLES = [
73
73
  "analytics_user_days",
74
74
  ] as const;
75
75
  const BACKEND_CONFIG_CACHE_TTL_MS = 30_000;
76
+ const MAX_BACKFILL_BATCH_SIZE = 5_000;
77
+ const MAX_INSERT_BATCH_SIZE = 200;
76
78
 
77
79
  const backendConfigCache = new Map<
78
80
  string,
@@ -361,8 +363,16 @@ export async function insertFirstPartyAnalyticsRows(
361
363
  getAccessToken(),
362
364
  ]);
363
365
  const payloadRows = rows.map(firstPartyEventRowToBigQuery);
364
- for (let offset = 0; offset < payloadRows.length; offset += 50) {
365
- await insertBatch(table, token, payloadRows.slice(offset, offset + 50));
366
+ for (
367
+ let offset = 0;
368
+ offset < payloadRows.length;
369
+ offset += MAX_INSERT_BATCH_SIZE
370
+ ) {
371
+ await insertBatch(
372
+ table,
373
+ token,
374
+ payloadRows.slice(offset, offset + MAX_INSERT_BATCH_SIZE),
375
+ );
366
376
  }
367
377
  return payloadRows.length;
368
378
  }
@@ -1041,6 +1051,8 @@ function backfillRowsByIdsSql(ids: string[]): {
1041
1051
  };
1042
1052
  }
1043
1053
 
1054
+ const MAX_SQLITE_BIND_VARIABLES = 900;
1055
+
1044
1056
  function backfillRowCursor(
1045
1057
  row: Record<string, unknown>,
1046
1058
  ): FirstPartyAnalyticsBackfillCursor {
@@ -1075,7 +1087,10 @@ export async function backfillFirstPartyAnalyticsBatch(
1075
1087
  limit: number,
1076
1088
  configuredTable?: string | null,
1077
1089
  ): Promise<FirstPartyAnalyticsBackfillBatch> {
1078
- const boundedLimit = Math.min(Math.max(Math.floor(limit), 1), 500);
1090
+ const boundedLimit = Math.min(
1091
+ Math.max(Math.floor(limit), 1),
1092
+ MAX_BACKFILL_BATCH_SIZE,
1093
+ );
1079
1094
  const db = getDbExec();
1080
1095
  const parsedCursor = parseBackfillCursor(cursor);
1081
1096
  const branches = scope.orgId
@@ -1118,18 +1133,25 @@ export async function backfillFirstPartyAnalyticsBatch(
1118
1133
  }
1119
1134
 
1120
1135
  const selectedIds = selectedRows.map((row) => backfillRowCursor(row).id);
1121
- const hydratedQuery = backfillRowsByIdsSql(selectedIds);
1122
- const hydratedResult = await db.execute({
1123
- sql: hydratedQuery.sql,
1124
- args: hydratedQuery.args,
1125
- timeoutMs: 20_000,
1126
- maxAttempts: 1,
1127
- });
1136
+ const hydratedRows: Record<string, unknown>[] = [];
1137
+ for (
1138
+ let offset = 0;
1139
+ offset < selectedIds.length;
1140
+ offset += MAX_SQLITE_BIND_VARIABLES
1141
+ ) {
1142
+ const hydratedQuery = backfillRowsByIdsSql(
1143
+ selectedIds.slice(offset, offset + MAX_SQLITE_BIND_VARIABLES),
1144
+ );
1145
+ const hydratedResult = await db.execute({
1146
+ sql: hydratedQuery.sql,
1147
+ args: hydratedQuery.args,
1148
+ timeoutMs: 20_000,
1149
+ maxAttempts: 1,
1150
+ });
1151
+ hydratedRows.push(...(hydratedResult.rows as Record<string, unknown>[]));
1152
+ }
1128
1153
  const hydratedById = new Map(
1129
- (hydratedResult.rows as Record<string, unknown>[]).map((row) => [
1130
- backfillRowCursor(row).id,
1131
- row,
1132
- ]),
1154
+ hydratedRows.map((row) => [backfillRowCursor(row).id, row]),
1133
1155
  );
1134
1156
  const selectedEvents = selectedIds
1135
1157
  .map((id) => hydratedById.get(id))
@@ -53,7 +53,6 @@ export interface PlayerControlsProps {
53
53
  comments?: { id: string; videoTimestampMs: number; content: string }[];
54
54
  chapters?: { startMs: number; title: string }[];
55
55
  reactions?: { id: string; emoji: string; videoTimestampMs: number }[];
56
- excludedRanges?: { startMs: number; endMs: number }[];
57
56
  onPlayPause: () => void;
58
57
  onSeek: (ms: number) => void;
59
58
  onSeekRelative: (deltaMs: number) => void;
@@ -84,7 +83,6 @@ export function PlayerControls(props: PlayerControlsProps) {
84
83
  comments,
85
84
  chapters,
86
85
  reactions,
87
- excludedRanges,
88
86
  onPlayPause,
89
87
  onSeek,
90
88
  onSeekRelative,
@@ -116,7 +114,6 @@ export function PlayerControls(props: PlayerControlsProps) {
116
114
  comments={comments}
117
115
  chapters={chapters}
118
116
  reactions={reactions}
119
- excludedRanges={excludedRanges}
120
117
  />
121
118
 
122
119
  <div className="flex min-w-0 items-center gap-1.5 text-white">
@@ -15,19 +15,11 @@ export interface ScrubberProps {
15
15
  comments?: { id: string; videoTimestampMs: number; content: string }[];
16
16
  chapters?: { startMs: number; title: string }[];
17
17
  reactions?: { id: string; emoji: string; videoTimestampMs: number }[];
18
- excludedRanges?: { startMs: number; endMs: number }[];
19
18
  }
20
19
 
21
20
  export function Scrubber(props: ScrubberProps) {
22
- const {
23
- currentMs,
24
- durationMs,
25
- onSeek,
26
- comments,
27
- chapters,
28
- reactions,
29
- excludedRanges,
30
- } = props;
21
+ const { currentMs, durationMs, onSeek, comments, chapters, reactions } =
22
+ props;
31
23
  const barRef = useRef<HTMLDivElement | null>(null);
32
24
  const activePointerIdRef = useRef<number | null>(null);
33
25
  const [hoverMs, setHoverMs] = useState<number | null>(null);
@@ -165,29 +157,6 @@ export function Scrubber(props: ScrubberProps) {
165
157
  style={{ width: pct + "%" }}
166
158
  />
167
159
 
168
- {/* Cut ranges */}
169
- {excludedRanges?.map((range, i) => {
170
- const startPct =
171
- (Math.max(0, range.startMs) / Math.max(1, durationMs)) * 100;
172
- const endPct =
173
- (Math.min(durationMs, range.endMs) / Math.max(1, durationMs)) * 100;
174
- return (
175
- <div
176
- key={`${range.startMs}-${range.endMs}-${i}`}
177
- className="absolute inset-y-0 rounded-sm bg-black/70"
178
- style={{
179
- left: `${startPct}%`,
180
- width: `${Math.max(0.5, endPct - startPct)}%`,
181
- backgroundImage:
182
- "repeating-linear-gradient(45deg, rgba(255,255,255,0.2) 0 3px, transparent 3px 7px)",
183
- }}
184
- title={`Cut: ${msToClock(range.startMs)}-${msToClock(
185
- range.endMs,
186
- )}`}
187
- />
188
- );
189
- })}
190
-
191
160
  {/* Chapter notches */}
192
161
  {chapters?.map((ch, i) => (
193
162
  <button
@@ -40,7 +40,11 @@ import {
40
40
  uploadRecordingThumbnail,
41
41
  } from "@/lib/thumbnail-capture";
42
42
  import {
43
+ editedToOriginal,
44
+ effectiveDuration,
43
45
  getExcludedRanges,
46
+ isExcluded,
47
+ originalToEdited,
44
48
  parseEdits,
45
49
  type TrimRange,
46
50
  } from "@/lib/timestamp-mapping";
@@ -418,6 +422,49 @@ export const VideoPlayer = forwardRef<VideoPlayerHandle, VideoPlayerProps>(
418
422
  const [shouldRefreshAutoThumbnail, setShouldRefreshAutoThumbnail] =
419
423
  useState(false);
420
424
  const excludedRanges = useMemo(() => getExcludedRanges(edits), [edits]);
425
+ const scrubberTimeline = useMemo(() => {
426
+ const mapMarker = (originalMs: number): number | null => {
427
+ if (!Number.isFinite(originalMs) || isExcluded(originalMs, edits)) {
428
+ return null;
429
+ }
430
+ return originalToEdited(originalMs, edits);
431
+ };
432
+
433
+ return {
434
+ durationMs: effectiveDuration(resolvedDurationMs, edits),
435
+ currentMs: originalToEdited(currentMs, edits),
436
+ comments: (comments ?? []).flatMap((comment) => {
437
+ const editedMs = mapMarker(comment.videoTimestampMs);
438
+ return editedMs === null
439
+ ? []
440
+ : [
441
+ {
442
+ id: comment.id,
443
+ content: comment.content,
444
+ videoTimestampMs: editedMs,
445
+ },
446
+ ];
447
+ }),
448
+ chapters: (chapters ?? []).flatMap((chapter) => {
449
+ const editedMs = mapMarker(chapter.startMs);
450
+ return editedMs === null
451
+ ? []
452
+ : [{ startMs: editedMs, title: chapter.title }];
453
+ }),
454
+ reactions: (reactions ?? []).flatMap((reaction) => {
455
+ const editedMs = mapMarker(reaction.videoTimestampMs);
456
+ return editedMs === null
457
+ ? []
458
+ : [
459
+ {
460
+ id: reaction.id,
461
+ emoji: reaction.emoji,
462
+ videoTimestampMs: editedMs,
463
+ },
464
+ ];
465
+ }),
466
+ };
467
+ }, [chapters, comments, currentMs, edits, reactions, resolvedDurationMs]);
421
468
  const activeVideoSourceIdentity = useMemo(
422
469
  () => videoSourceIdentity(activeVideoSrc),
423
470
  [activeVideoSrc],
@@ -954,16 +1001,17 @@ export const VideoPlayer = forwardRef<VideoPlayerHandle, VideoPlayerProps>(
954
1001
  const seekByMs = useCallback(
955
1002
  (deltaMs: number) => {
956
1003
  const v = videoRef.current;
957
- const liveMs =
1004
+ const liveOriginalMs =
958
1005
  v &&
959
1006
  Number.isFinite(v.currentTime) &&
960
1007
  v.currentTime >= 0 &&
961
1008
  v.currentTime < 1e7
962
1009
  ? Math.floor(v.currentTime * 1000)
963
1010
  : currentMs;
964
- seekToVisibleMs(liveMs + deltaMs);
1011
+ const liveEditedMs = originalToEdited(liveOriginalMs, edits);
1012
+ seekToVisibleMs(editedToOriginal(liveEditedMs + deltaMs, edits));
965
1013
  },
966
- [currentMs, seekToVisibleMs],
1014
+ [currentMs, edits, seekToVisibleMs],
967
1015
  );
968
1016
 
969
1017
  // Imperative handle for parent
@@ -1780,8 +1828,8 @@ export const VideoPlayer = forwardRef<VideoPlayerHandle, VideoPlayerProps>(
1780
1828
  >
1781
1829
  <PlayerControls
1782
1830
  isPlaying={isPlaying}
1783
- durationMs={resolvedDurationMs}
1784
- currentMs={currentMs}
1831
+ durationMs={scrubberTimeline.durationMs}
1832
+ currentMs={scrubberTimeline.currentMs}
1785
1833
  volume={volume}
1786
1834
  muted={muted}
1787
1835
  speed={speed}
@@ -1789,16 +1837,15 @@ export const VideoPlayer = forwardRef<VideoPlayerHandle, VideoPlayerProps>(
1789
1837
  isFullscreen={isFullscreen}
1790
1838
  isPip={isPip}
1791
1839
  theaterMode={!!theaterMode}
1792
- comments={comments}
1793
- chapters={chapters}
1794
- reactions={reactions}
1795
- excludedRanges={excludedRanges}
1840
+ comments={scrubberTimeline.comments}
1841
+ chapters={scrubberTimeline.chapters}
1842
+ reactions={scrubberTimeline.reactions}
1796
1843
  hasCaptions={!!transcriptSegments?.length}
1797
1844
  onPlayPause={() => {
1798
1845
  togglePlayback();
1799
1846
  }}
1800
- onSeek={(ms) => {
1801
- seekToVisibleMs(ms);
1847
+ onSeek={(editedMs) => {
1848
+ seekToVisibleMs(editedToOriginal(editedMs, edits));
1802
1849
  }}
1803
1850
  onSeekRelative={seekByMs}
1804
1851
  onVolumeChange={(vol) => {
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-08-06
4
+ ---
5
+
6
+ Trimmed clips play on a clean timeline without cut markers
@@ -34,6 +34,13 @@ ladder.
34
34
  fire-and-forget promises after a serverless response.
35
35
  - Use `view-screen` when the current integration, resource, approval, route, or
36
36
  setup item is unclear.
37
+ - Dispatch keeps its primary navigation focused on Overview, Chat, Apps, and
38
+ the workspace app rail. Workspace management and operator tools live under
39
+ the top-level `/admin` control plane, which uses grouped navigation for
40
+ `/admin/operations`, `/admin/metrics`, `/admin/integrations`, `/admin/vault`,
41
+ `/admin/automations`, `/admin/approvals`, `/admin/destinations`,
42
+ `/admin/agents`, `/admin/workspace`, `/admin/messaging`, `/admin/identities`,
43
+ `/admin/audit`, `/admin/dreams`, and `/admin/thread-debug`.
37
44
  - Keep approval and routing behavior explicit. Never silently widen access to
38
45
  secrets, apps, integrations, or workspace resources.
39
46
  - Curated workspace templates are private app sources. Use
@@ -41,7 +48,7 @@ ladder.
41
48
  `remix-workspace-template` to create an independent app. A new app may use
42
49
  empty or synthetic data only; never copy source-app records, credentials,
43
50
  secrets, or private configuration.
44
- - `/operations` is the focused operator console. Its Monitoring tab reuses the
51
+ - `/admin/operations` is the focused operator console. Its Monitoring tab reuses the
45
52
  shared observability dashboard for traces, conversations, evaluations,
46
53
  experiments, and feedback; its Database tab reuses the Code-mode database
47
54
  admin. Use `navigate --view operations|monitoring|observability|database` and
@@ -16,6 +16,7 @@ import type { DispatchExtensionConfig } from "@agent-native/dispatch/components"
16
16
  * {
17
17
  * id: "reports",
18
18
  * to: "/reports",
19
+ * adminTo: "/admin/reports",
19
20
  * label: "Reports",
20
21
  * icon: IconChartBar,
21
22
  * section: "operations",
@@ -148,7 +148,8 @@ function resolveExtensionPath(
148
148
  extensions?: DispatchExtensionConfig,
149
149
  ): string | undefined {
150
150
  if (!view) return undefined;
151
- return extensions?.navItems?.find((item) => item.id === view)?.to;
151
+ const item = extensions?.navItems?.find((candidate) => candidate.id === view);
152
+ return item?.adminTo ?? item?.to;
152
153
  }
153
154
 
154
155
  function resolveView(
@@ -160,6 +161,11 @@ function resolveView(
160
161
  if (pathname === "/extensions" || pathname.startsWith("/extensions/")) {
161
162
  return "extensions";
162
163
  }
164
+ if (pathname === "/admin") return "admin";
165
+ if (pathname.startsWith("/admin/")) {
166
+ const adminView = resolveView(pathname.slice("/admin".length), extensions);
167
+ return adminView === "overview" ? "admin" : adminView;
168
+ }
163
169
  if (pathname.startsWith("/chat")) return "chat";
164
170
  if (pathname.startsWith("/apps")) return "apps";
165
171
  if (pathname.startsWith("/operations")) return "operations";
@@ -173,6 +179,9 @@ function resolveView(
173
179
  if (pathname.startsWith("/destinations")) return "destinations";
174
180
  if (pathname.startsWith("/identities")) return "identities";
175
181
  if (pathname.startsWith("/approvals")) return "approvals";
182
+ if (pathname.startsWith("/transactional-email")) {
183
+ return "transactional-email";
184
+ }
176
185
  if (pathname.startsWith("/audit")) return "audit";
177
186
  if (pathname.startsWith("/dreams")) return "dreams";
178
187
  if (pathname.startsWith("/thread-debug")) return "thread-debug";
@@ -186,6 +195,8 @@ function resolvePath(
186
195
  command?: Pick<NavigationState, "extensionId">,
187
196
  ): string | undefined {
188
197
  switch (view) {
198
+ case "admin":
199
+ return "/admin";
189
200
  case "chat":
190
201
  case "ask":
191
202
  return "/chat";
@@ -197,39 +208,43 @@ function resolvePath(
197
208
  case "monitoring":
198
209
  case "observability":
199
210
  case "database":
200
- return view === "database" ? "/operations?view=database" : "/operations";
211
+ return view === "database"
212
+ ? "/admin/operations?view=database"
213
+ : "/admin/operations";
201
214
  case "metrics":
202
215
  case "usage":
203
- return "/metrics";
216
+ return "/admin/metrics";
204
217
  case "new-app":
205
218
  case "create-app":
206
- return "/new-app";
219
+ return "/admin/new-app";
207
220
  case "vault":
208
221
  case "secrets":
209
- return "/vault";
222
+ return "/admin/vault";
210
223
  case "integrations":
211
- return "/integrations";
224
+ return "/admin/integrations";
212
225
  case "workspace":
213
226
  case "resources":
214
- return "/workspace";
227
+ return "/admin/workspace";
215
228
  case "agents":
216
- return "/agents";
229
+ return "/admin/agents";
217
230
  case "messaging":
218
- return "/messaging";
231
+ return "/admin/messaging";
219
232
  case "destinations":
220
233
  case "routes":
221
- return "/destinations";
234
+ return "/admin/destinations";
222
235
  case "identities":
223
- return "/identities";
236
+ return "/admin/identities";
224
237
  case "approvals":
225
- return "/approvals";
238
+ return "/admin/approvals";
226
239
  case "audit":
227
- return "/audit";
240
+ return "/admin/audit";
241
+ case "transactional-email":
242
+ return "/admin/transactional-email";
228
243
  case "dreams":
229
- return "/dreams";
244
+ return "/admin/dreams";
230
245
  case "thread-debug":
231
246
  case "threads":
232
- return "/thread-debug";
247
+ return "/admin/thread-debug";
233
248
  case "team":
234
249
  return "/settings/organization";
235
250
  case "extensions":