@hachej/boring-core 0.1.23 → 0.1.26

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.
@@ -9,7 +9,7 @@ import {
9
9
  registerRoutes,
10
10
  registerSettingsRoutes,
11
11
  registerWorkspaceRoutes
12
- } from "../../chunk-6D7LEQSL.js";
12
+ } from "../../chunk-2JDK4XUZ.js";
13
13
  import {
14
14
  PostgresUserStore,
15
15
  PostgresWorkspaceStore,
@@ -531,7 +531,8 @@ async function createCoreWorkspaceAgentServer(options = {}) {
531
531
  const { app, sql, db, userStore, workspaceStore } = await createCoreRuntime(config);
532
532
  const appRoot = options.appRoot;
533
533
  const serveFrontend = options.serveFrontend ?? (process.env.NODE_ENV !== "development" && Boolean(appRoot));
534
- const workspaceRoot = options.workspaceRoot ?? process.cwd();
534
+ const pluginWorkspaceRoot = process.cwd();
535
+ const workspaceRoot = options.workspaceRoot ?? process.env.BORING_AGENT_WORKSPACE_ROOT ?? process.cwd();
535
536
  const telemetrySource = options.telemetry ? "custom" : process.env.BORING_TELEMETRY_ENABLED === "true" ? "db-env" : "noop-env";
536
537
  const telemetry = options.telemetry ?? createDatabaseTelemetryFromEnv(db, { appId: config.appId }, process.env);
537
538
  app.log.debug({ telemetry: { source: telemetrySource } }, "resolved telemetry sink");
@@ -544,7 +545,7 @@ async function createCoreWorkspaceAgentServer(options = {}) {
544
545
  serveSpaShell: (request, reply) => serveFrontendShell(request, reply, path.resolve(appRoot, "dist/front/index.html"), telemetry)
545
546
  } : void 0);
546
547
  const defaultPluginPackagePaths = resolveDefaultWorkspacePluginPackagePaths({
547
- workspaceRoot,
548
+ workspaceRoot: pluginWorkspaceRoot,
548
549
  appPackageJsonPath: options.appPackageJsonPath,
549
550
  defaultPluginPackages: options.defaultPluginPackages
550
551
  });
@@ -568,7 +569,7 @@ async function createCoreWorkspaceAgentServer(options = {}) {
568
569
  return bridge;
569
570
  };
570
571
  const pluginResolveContext = {
571
- workspaceRoot,
572
+ workspaceRoot: pluginWorkspaceRoot,
572
573
  bridge: createUnavailableCorePluginBridge()
573
574
  };
574
575
  const resolvedPlugins = await Promise.all(
@@ -578,7 +579,7 @@ async function createCoreWorkspaceAgentServer(options = {}) {
578
579
  ))
579
580
  );
580
581
  const pluginCollection = collectWorkspaceAgentServerPlugins({
581
- workspaceRoot,
582
+ workspaceRoot: pluginWorkspaceRoot,
582
583
  systemPromptAppend: staticSystemPromptAppend,
583
584
  pi: mergePiOptions(options.pi, defaultPackagePiOptions),
584
585
  plugins: resolvedPlugins,
@@ -1951,9 +1951,79 @@ var updateWorkspaceBody = z3.object({
1951
1951
  }).strict();
1952
1952
 
1953
1953
  // src/server/routes/workspaces.ts
1954
+ var DEFAULT_WORKSPACE_NAME = "My Workspace";
1954
1955
  var workspaceRoutesPlugin = async (app) => {
1955
1956
  const store = app.workspaceStore;
1956
1957
  const provisioner = app.provisioner;
1958
+ const defaultWorkspaceCreates = /* @__PURE__ */ new Map();
1959
+ async function provisionWorkspace(workspace, ownerId, request) {
1960
+ if (!provisioner) return;
1961
+ await store.putWorkspaceRuntime(workspace.id, { state: "pending" });
1962
+ try {
1963
+ const result = await provisioner.provision({
1964
+ workspaceId: workspace.id,
1965
+ workspaceName: workspace.name,
1966
+ ownerId,
1967
+ appId: app.config.appId
1968
+ });
1969
+ await store.putWorkspaceRuntime(workspace.id, {
1970
+ state: "ready",
1971
+ volumePath: result.volumePath
1972
+ });
1973
+ } catch (err) {
1974
+ const message = err instanceof Error ? err.message : String(err);
1975
+ await store.putWorkspaceRuntime(workspace.id, {
1976
+ state: "error",
1977
+ lastError: message,
1978
+ lastErrorOp: "provision"
1979
+ });
1980
+ request.log.error({ workspaceId: workspace.id, err }, "workspace.provision.failed");
1981
+ throw new HttpError({
1982
+ status: 500,
1983
+ code: ERROR_CODES.PROVISION_FAILED,
1984
+ message: "Workspace provisioning failed",
1985
+ requestId: request.id
1986
+ });
1987
+ }
1988
+ }
1989
+ async function createWorkspaceForUser(userId, name, isDefault, request) {
1990
+ const workspace = await store.create(userId, name, app.config.appId, { isDefault });
1991
+ await provisionWorkspace(workspace, userId, request);
1992
+ return workspace;
1993
+ }
1994
+ async function ensureDefaultWorkspaceProvisioned(workspace, request) {
1995
+ if (!provisioner || !workspace.isDefault) return;
1996
+ const runtime = await store.getWorkspaceRuntime(workspace.id);
1997
+ const needsProvisioning = !runtime || runtime.state === "ready" && !runtime.volumePath;
1998
+ if (needsProvisioning) await provisionWorkspace(workspace, workspace.createdBy, request);
1999
+ }
2000
+ async function listOrCreateDefaultWorkspace(userId, request) {
2001
+ const existing = await store.list(userId, app.config.appId);
2002
+ if (existing.length > 0) {
2003
+ await Promise.all(existing.map((workspace) => ensureDefaultWorkspaceProvisioned(workspace, request)));
2004
+ return existing;
2005
+ }
2006
+ const createKey = `${app.config.appId}:${userId}`;
2007
+ const inFlight = defaultWorkspaceCreates.get(createKey);
2008
+ if (inFlight) return await inFlight;
2009
+ const createPromise = (async () => {
2010
+ try {
2011
+ const created = await createWorkspaceForUser(userId, DEFAULT_WORKSPACE_NAME, true, request);
2012
+ return [created];
2013
+ } catch (error) {
2014
+ if (error instanceof HttpError) throw error;
2015
+ const racedExisting = await store.list(userId, app.config.appId);
2016
+ if (racedExisting.length > 0) return racedExisting;
2017
+ throw error;
2018
+ }
2019
+ })();
2020
+ defaultWorkspaceCreates.set(createKey, createPromise);
2021
+ try {
2022
+ return await createPromise;
2023
+ } finally {
2024
+ if (defaultWorkspaceCreates.get(createKey) === createPromise) defaultWorkspaceCreates.delete(createKey);
2025
+ }
2026
+ }
1957
2027
  app.post("/api/v1/workspaces", async (request, reply) => {
1958
2028
  const parsed = createWorkspaceBody.safeParse(request.body);
1959
2029
  if (!parsed.success) {
@@ -1967,42 +2037,13 @@ var workspaceRoutesPlugin = async (app) => {
1967
2037
  const user = request.user;
1968
2038
  const existing = await store.list(user.id, app.config.appId);
1969
2039
  const isDefault = existing.length === 0;
1970
- const workspace = await store.create(user.id, parsed.data.name, app.config.appId, { isDefault });
1971
- if (provisioner) {
1972
- await store.putWorkspaceRuntime(workspace.id, { state: "pending" });
1973
- try {
1974
- const result = await provisioner.provision({
1975
- workspaceId: workspace.id,
1976
- workspaceName: workspace.name,
1977
- ownerId: user.id,
1978
- appId: app.config.appId
1979
- });
1980
- await store.putWorkspaceRuntime(workspace.id, {
1981
- state: "ready",
1982
- volumePath: result.volumePath
1983
- });
1984
- } catch (err) {
1985
- const message = err instanceof Error ? err.message : String(err);
1986
- await store.putWorkspaceRuntime(workspace.id, {
1987
- state: "error",
1988
- lastError: message,
1989
- lastErrorOp: "provision"
1990
- });
1991
- request.log.error({ workspaceId: workspace.id, err }, "workspace.provision.failed");
1992
- throw new HttpError({
1993
- status: 500,
1994
- code: ERROR_CODES.PROVISION_FAILED,
1995
- message: "Workspace provisioning failed",
1996
- requestId: request.id
1997
- });
1998
- }
1999
- }
2040
+ const workspace = await createWorkspaceForUser(user.id, parsed.data.name, isDefault, request);
2000
2041
  request.log.info({ workspaceId: workspace.id, userId: user.id }, "workspace.create");
2001
2042
  reply.status(201);
2002
2043
  return { workspace, role: "owner" };
2003
2044
  });
2004
2045
  app.get("/api/v1/workspaces", async (request) => {
2005
- const workspaces2 = await store.list(request.user.id, app.config.appId);
2046
+ const workspaces2 = await listOrCreateDefaultWorkspace(request.user.id, request);
2006
2047
  return { workspaces: workspaces2 };
2007
2048
  });
2008
2049
  app.get(
@@ -471,68 +471,12 @@ function useWorkspaceMembers(workspaceId) {
471
471
  }
472
472
 
473
473
  // src/front/WorkspaceAuthProvider.tsx
474
- import { createContext as createContext3, useContext as useContext3 } from "react";
474
+ import { createContext as createContext4, useContext as useContext4 } from "react";
475
475
  import { useQuery as useQuery2, useQueryClient } from "@tanstack/react-query";
476
476
  import { matchPath, useLocation, useParams } from "react-router-dom";
477
- import { jsx as jsx4 } from "react/jsx-runtime";
478
- var WorkspaceContext = createContext3({
479
- workspace: null,
480
- role: null
481
- });
482
- var WORKSPACES_QUERY_KEY = ["workspaces"];
483
- function workspaceQueryKey(workspaceId) {
484
- return ["workspace", workspaceId ?? null];
485
- }
486
- async function fetchWorkspaces() {
487
- const data = await apiFetchJson("/api/v1/workspaces");
488
- return data.workspaces;
489
- }
490
- async function fetchWorkspace(workspaceId) {
491
- return await apiFetchJson(
492
- `/api/v1/workspaces/${encodeURIComponent(workspaceId)}`
493
- );
494
- }
495
- function workspaceIdFromPath(pathname) {
496
- const match = matchPath("/w/:id/*", pathname) ?? matchPath("/w/:id", pathname) ?? matchPath("/workspace/:id/*", pathname) ?? matchPath("/workspace/:id", pathname);
497
- const id = match?.params.id?.trim();
498
- return id ? id : null;
499
- }
500
- function WorkspaceAuthProvider({ children }) {
501
- const { id } = useParams();
502
- const location = useLocation();
503
- const queryClient = useQueryClient();
504
- const routeWorkspaceId = id?.trim() ? id : workspaceIdFromPath(location.pathname);
505
- const workspacesQuery = useQuery2({
506
- queryKey: WORKSPACES_QUERY_KEY,
507
- queryFn: fetchWorkspaces
508
- });
509
- const defaultWorkspace = routeWorkspaceId === null ? workspacesQuery.data?.find((workspace2) => workspace2.isDefault) ?? workspacesQuery.data?.[0] ?? null : null;
510
- const resolvedId = routeWorkspaceId ?? defaultWorkspace?.id ?? null;
511
- const cachedDetail = resolvedId ? queryClient.getQueryData(workspaceQueryKey(resolvedId)) : void 0;
512
- const detailQuery = useQuery2({
513
- queryKey: workspaceQueryKey(resolvedId),
514
- queryFn: () => {
515
- if (!resolvedId) {
516
- throw new Error("Workspace id is required");
517
- }
518
- return fetchWorkspace(resolvedId);
519
- },
520
- enabled: resolvedId !== null
521
- });
522
- const detail = detailQuery.data ?? cachedDetail ?? null;
523
- const workspace = detailQuery.isError ? null : detail?.workspace ?? null;
524
- const role = detailQuery.isError ? null : detail?.role ?? null;
525
- return /* @__PURE__ */ jsx4(WorkspaceContext.Provider, { value: { workspace, role }, children });
526
- }
527
- function useCurrentWorkspace() {
528
- return useContext3(WorkspaceContext).workspace;
529
- }
530
- function useWorkspaceRole() {
531
- return useContext3(WorkspaceContext).role;
532
- }
533
477
 
534
478
  // src/front/auth/AuthProvider.tsx
535
- import { createContext as createContext4, useContext as useContext4, useCallback as useCallback2, useMemo as useMemo2 } from "react";
479
+ import { createContext as createContext3, useContext as useContext3, useCallback as useCallback2, useMemo as useMemo2 } from "react";
536
480
 
537
481
  // src/front/auth/authClient.ts
538
482
  import { createAuthClient } from "better-auth/react";
@@ -555,8 +499,8 @@ function getAuthClient(baseURL) {
555
499
  }
556
500
 
557
501
  // src/front/auth/AuthProvider.tsx
558
- import { jsx as jsx5 } from "react/jsx-runtime";
559
- var AuthContext = createContext4(null);
502
+ import { jsx as jsx4 } from "react/jsx-runtime";
503
+ var AuthContext = createContext3(null);
560
504
  function toISOString(value) {
561
505
  if (!value) return "";
562
506
  if (value instanceof Date) return value.toISOString();
@@ -589,10 +533,10 @@ function AuthProvider({
589
533
  () => ({ client, signOut }),
590
534
  [client, signOut]
591
535
  );
592
- return /* @__PURE__ */ jsx5(AuthContext.Provider, { value, children });
536
+ return /* @__PURE__ */ jsx4(AuthContext.Provider, { value, children });
593
537
  }
594
538
  function useAuthContext() {
595
- const ctx = useContext4(AuthContext);
539
+ const ctx = useContext3(AuthContext);
596
540
  if (!ctx) throw new Error("useSession/signIn/signOut must be used within an AuthProvider");
597
541
  return ctx;
598
542
  }
@@ -620,8 +564,21 @@ function useSignUp() {
620
564
  return client.signUp;
621
565
  }
622
566
  function useForgetPassword() {
623
- const { client } = useAuthContext();
624
- return client.forgetPassword;
567
+ useAuthContext();
568
+ return async (opts) => {
569
+ const endpoint = buildApiUrl("/auth/request-password-reset");
570
+ const response = await fetch(endpoint, {
571
+ method: "POST",
572
+ headers: { "content-type": "application/json" },
573
+ credentials: "include",
574
+ body: JSON.stringify(opts)
575
+ });
576
+ const data = await response.json().catch(() => null);
577
+ if (!response.ok) {
578
+ return { data: null, error: data ?? { status: response.status, message: "Request failed" } };
579
+ }
580
+ return { data, error: null };
581
+ };
625
582
  }
626
583
  function useResetPassword() {
627
584
  const { client } = useAuthContext();
@@ -644,6 +601,107 @@ function useSignOut() {
644
601
  return signOut;
645
602
  }
646
603
 
604
+ // src/front/WorkspaceAuthProvider.tsx
605
+ import { jsx as jsx5 } from "react/jsx-runtime";
606
+ var WorkspaceContext = createContext4({
607
+ workspace: null,
608
+ role: null,
609
+ routeStatus: { status: "idle", workspaceId: null }
610
+ });
611
+ var WORKSPACES_QUERY_KEY = ["workspaces"];
612
+ function workspaceQueryKey(workspaceId) {
613
+ return ["workspace", workspaceId ?? null];
614
+ }
615
+ async function fetchWorkspaces() {
616
+ const data = await apiFetchJson("/api/v1/workspaces");
617
+ return data.workspaces;
618
+ }
619
+ async function fetchWorkspace(workspaceId) {
620
+ return await apiFetchJson(
621
+ `/api/v1/workspaces/${encodeURIComponent(workspaceId)}`
622
+ );
623
+ }
624
+ function routePatterns(route) {
625
+ const normalized = route.endsWith("/*") ? route.slice(0, -2) : route;
626
+ return [`${normalized}/*`, normalized];
627
+ }
628
+ function workspaceIdFromPath(pathname, workspaceRoute = "/workspace/:id", workspaceIdParam = "id") {
629
+ const patterns = [
630
+ ...routePatterns(workspaceRoute),
631
+ "/w/:id/*",
632
+ "/w/:id",
633
+ "/workspace/:id/*",
634
+ "/workspace/:id"
635
+ ];
636
+ for (const pattern of patterns) {
637
+ const match = matchPath(pattern, pathname);
638
+ const id = match?.params[workspaceIdParam]?.trim() ?? match?.params.id?.trim();
639
+ if (id) return id;
640
+ }
641
+ return null;
642
+ }
643
+ function routeStatusFromError(workspaceId, error) {
644
+ const detail = getHttpErrorDetail(error);
645
+ if (detail.status === 404 || detail.code === "not_found") {
646
+ return { status: "not-found", workspaceId, message: detail.message };
647
+ }
648
+ if (detail.status === 403 || detail.code === "forbidden" || detail.code === "not_member") {
649
+ return { status: "forbidden", workspaceId, message: detail.message };
650
+ }
651
+ return { status: "switch-failed", workspaceId, message: detail.message };
652
+ }
653
+ function WorkspaceAuthProvider({
654
+ children,
655
+ workspaceRoute,
656
+ workspaceIdParam
657
+ }) {
658
+ const { id } = useParams();
659
+ const location = useLocation();
660
+ const queryClient = useQueryClient();
661
+ const routeWorkspaceId = id?.trim() ? id : workspaceIdFromPath(location.pathname, workspaceRoute, workspaceIdParam);
662
+ const session = useSession();
663
+ const isAuthenticated = Boolean(session.data?.user);
664
+ const workspacesQuery = useQuery2({
665
+ queryKey: WORKSPACES_QUERY_KEY,
666
+ queryFn: fetchWorkspaces,
667
+ enabled: isAuthenticated
668
+ });
669
+ const defaultWorkspace = routeWorkspaceId === null ? workspacesQuery.data?.find((workspace2) => workspace2.isDefault) ?? workspacesQuery.data?.[0] ?? null : null;
670
+ const resolvedId = routeWorkspaceId ?? defaultWorkspace?.id ?? null;
671
+ const cachedDetail = resolvedId ? queryClient.getQueryData(workspaceQueryKey(resolvedId)) : void 0;
672
+ const detailQuery = useQuery2({
673
+ queryKey: workspaceQueryKey(resolvedId),
674
+ queryFn: () => {
675
+ if (!resolvedId) {
676
+ throw new Error("Workspace id is required");
677
+ }
678
+ return fetchWorkspace(resolvedId);
679
+ },
680
+ enabled: isAuthenticated && resolvedId !== null
681
+ });
682
+ const detail = detailQuery.data ?? cachedDetail ?? null;
683
+ const workspace = detailQuery.isError ? null : detail?.workspace ?? null;
684
+ const role = detailQuery.isError ? null : detail?.role ?? null;
685
+ const routeStatus = (() => {
686
+ if (!isAuthenticated) return { status: "idle", workspaceId: routeWorkspaceId };
687
+ if (routeWorkspaceId === null) return { status: "idle", workspaceId: null };
688
+ if (detailQuery.isError) return routeStatusFromError(routeWorkspaceId, detailQuery.error);
689
+ if (detailQuery.isPending && !detail) return { status: "loading", workspaceId: routeWorkspaceId };
690
+ if (workspace?.id === routeWorkspaceId) return { status: "matched", workspaceId: routeWorkspaceId, workspace };
691
+ return { status: "mismatched", workspaceId: routeWorkspaceId, currentWorkspaceId: workspace?.id ?? null };
692
+ })();
693
+ return /* @__PURE__ */ jsx5(WorkspaceContext.Provider, { value: { workspace, role, routeStatus }, children });
694
+ }
695
+ function useCurrentWorkspace() {
696
+ return useContext4(WorkspaceContext).workspace;
697
+ }
698
+ function useWorkspaceRole() {
699
+ return useContext4(WorkspaceContext).role;
700
+ }
701
+ function useWorkspaceRouteStatus() {
702
+ return useContext4(WorkspaceContext).routeStatus;
703
+ }
704
+
647
705
  // src/front/auth/UserIdentityProvider.tsx
648
706
  import { createContext as createContext5, useContext as useContext5, useEffect as useEffect7, useState as useState6, useRef as useRef3 } from "react";
649
707
  import { jsx as jsx6 } from "react/jsx-runtime";
@@ -984,6 +1042,8 @@ function ForgotPasswordPage() {
984
1042
  const forgetPassword = useForgetPassword();
985
1043
  const [isSubmitting, setIsSubmitting] = useState10(false);
986
1044
  const [submitted, setSubmitted] = useState10(false);
1045
+ const redirect = typeof window === "undefined" ? null : new URLSearchParams(window.location.search).get("redirect");
1046
+ const signinHref = redirect ? `${routes.signin}?redirect=${encodeURIComponent(redirect)}` : routes.signin;
987
1047
  const {
988
1048
  register,
989
1049
  handleSubmit,
@@ -1007,7 +1067,7 @@ function ForgotPasswordPage() {
1007
1067
  /* @__PURE__ */ jsx10(CardTitle3, { children: "Check your inbox" }),
1008
1068
  /* @__PURE__ */ jsx10(CardDescription3, { children: "If an account exists with that email, we sent a password reset link. Check your inbox and follow the instructions." })
1009
1069
  ] }),
1010
- /* @__PURE__ */ jsx10(CardFooter3, { children: /* @__PURE__ */ jsx10("a", { href: routes.signin, className: "text-sm text-muted-foreground hover:underline", children: "Back to sign in" }) })
1070
+ /* @__PURE__ */ jsx10(CardFooter3, { children: /* @__PURE__ */ jsx10("a", { href: signinHref, className: "text-sm text-muted-foreground hover:underline", children: "Back to sign in" }) })
1011
1071
  ] }) });
1012
1072
  }
1013
1073
  return /* @__PURE__ */ jsx10("div", { className: "flex min-h-screen items-center justify-center p-4", children: /* @__PURE__ */ jsxs3(Card3, { className: "w-full max-w-sm", children: [
@@ -1032,7 +1092,7 @@ function ForgotPasswordPage() {
1032
1092
  ] }) }),
1033
1093
  /* @__PURE__ */ jsxs3(CardFooter3, { className: "flex flex-col gap-4", children: [
1034
1094
  /* @__PURE__ */ jsx10(Button5, { type: "submit", className: "w-full", disabled: isSubmitting, children: isSubmitting ? "Sending\u2026" : "Send reset link" }),
1035
- /* @__PURE__ */ jsx10("a", { href: routes.signin, className: "text-sm text-muted-foreground hover:underline", children: "Back to sign in" })
1095
+ /* @__PURE__ */ jsx10("a", { href: signinHref, className: "text-sm text-muted-foreground hover:underline", children: "Back to sign in" })
1036
1096
  ] })
1037
1097
  ] })
1038
1098
  ] }) });
@@ -1816,6 +1876,7 @@ function InviteAcceptPage() {
1816
1876
 
1817
1877
  // src/front/AuthGate.tsx
1818
1878
  import { useEffect as useEffect9, useMemo as useMemo3, useRef as useRef5 } from "react";
1879
+ import { matchPath as matchPath2 } from "react-router-dom";
1819
1880
  import { Fragment as Fragment3, jsx as jsx15 } from "react/jsx-runtime";
1820
1881
  var DEFAULT_GRACE_MS = 3e4;
1821
1882
  var UNSAFE_REDIRECT_RE = /[\0\r\n<>"'`]/;
@@ -1842,7 +1903,12 @@ function normalizePublicPath(path) {
1842
1903
  function isPublicPath(pathname, publicPaths) {
1843
1904
  const normalizedPath = normalizePath(pathname);
1844
1905
  if (normalizedPath === "/auth" || normalizedPath.startsWith("/auth/")) return true;
1845
- return publicPaths.some((candidate) => normalizedPath === candidate || normalizedPath.startsWith(`${candidate}/`));
1906
+ return publicPaths.some((candidate) => {
1907
+ if (candidate.includes(":") || candidate.includes("*")) {
1908
+ return Boolean(matchPath2({ path: candidate, end: !candidate.includes("*") }, normalizedPath));
1909
+ }
1910
+ return normalizedPath === candidate || normalizedPath.startsWith(`${candidate}/`);
1911
+ });
1846
1912
  }
1847
1913
  function readSafeRedirect(search) {
1848
1914
  const redirect = new URLSearchParams(normalizeSearch(search)).get("redirect");
@@ -1938,8 +2004,8 @@ function AuthGate({
1938
2004
  }
1939
2005
 
1940
2006
  // src/front/CoreFront.tsx
1941
- import { Suspense, useMemo as useMemo6 } from "react";
1942
- import { BrowserRouter, Routes, Route } from "react-router-dom";
2007
+ import { Suspense, useCallback as useCallback9, useMemo as useMemo6 } from "react";
2008
+ import { BrowserRouter, Routes, Route, useLocation as useLocation2, useNavigate as useNavigate5 } from "react-router-dom";
1943
2009
  import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
1944
2010
  import { Helmet, HelmetProvider } from "react-helmet-async";
1945
2011
 
@@ -2202,12 +2268,15 @@ function WorkspaceSwitcher({
2202
2268
  headers: { "content-type": "application/json" },
2203
2269
  body: JSON.stringify({ name: parsed.data.name })
2204
2270
  });
2205
- await Promise.all([
2206
- queryClient.invalidateQueries({ queryKey: WORKSPACES_QUERY_KEY }),
2207
- queryClient.invalidateQueries({ queryKey: workspaceQueryKey(data.workspace.id) })
2208
- ]);
2271
+ queryClient.setQueryData(workspaceQueryKey(data.workspace.id), data);
2272
+ queryClient.setQueryData(WORKSPACES_QUERY_KEY, (current = []) => {
2273
+ if (current.some((workspace) => workspace.id === data.workspace.id)) return current;
2274
+ return [...current, data.workspace];
2275
+ });
2209
2276
  onModalChange(false);
2210
2277
  navigate(hrefForWorkspace(workspacePathPrefix, data.workspace.id));
2278
+ void queryClient.invalidateQueries({ queryKey: WORKSPACES_QUERY_KEY });
2279
+ void queryClient.invalidateQueries({ queryKey: workspaceQueryKey(data.workspace.id) });
2211
2280
  } catch (error) {
2212
2281
  const detail = getHttpErrorDetail(error);
2213
2282
  if (typeof detail.status === "number" && detail.status >= 400 && detail.status < 500) {
@@ -3420,7 +3489,30 @@ function createDefaultQueryClient() {
3420
3489
  }
3421
3490
  });
3422
3491
  }
3423
- function CoreFront({ children, authPages, cspNonce }) {
3492
+ function RouterAuthGate({ children, publicPaths }) {
3493
+ const location = useLocation2();
3494
+ const navigate = useNavigate5();
3495
+ const authLocation = useMemo6(
3496
+ () => ({ pathname: location.pathname, search: location.search, hash: location.hash }),
3497
+ [location.hash, location.pathname, location.search]
3498
+ );
3499
+ const navigateWithinRouter = useCallback9(
3500
+ (to, options) => {
3501
+ navigate(to, { replace: options?.replace });
3502
+ },
3503
+ [navigate]
3504
+ );
3505
+ return /* @__PURE__ */ jsx22(
3506
+ AuthGate,
3507
+ {
3508
+ location: authLocation,
3509
+ navigate: navigateWithinRouter,
3510
+ publicPaths,
3511
+ children
3512
+ }
3513
+ );
3514
+ }
3515
+ function CoreFront({ children, authPages, cspNonce, workspaceRoute, workspaceIdParam, publicPaths }) {
3424
3516
  const queryClient = useMemo6(createDefaultQueryClient, []);
3425
3517
  const resolvedCspNonce = useMemo6(
3426
3518
  () => cspNonce ?? readCspNonceFromDom(),
@@ -3433,7 +3525,7 @@ function CoreFront({ children, authPages, cspNonce }) {
3433
3525
  const VerifyEmailPage2 = authPages?.verifyEmail ?? VerifyEmailPage;
3434
3526
  const AuthErrorPage2 = authPages?.authError ?? AuthErrorPage;
3435
3527
  const UserSettingsPage2 = authPages?.userSettings ?? UserSettingsPage;
3436
- return /* @__PURE__ */ jsx22(HelmetProvider, { children: /* @__PURE__ */ jsx22(AppErrorBoundary, { children: /* @__PURE__ */ jsx22(QueryClientProvider, { client: queryClient, children: /* @__PURE__ */ jsx22(ConfigProvider, { children: /* @__PURE__ */ jsx22(ThemeProvider, { children: /* @__PURE__ */ jsx22(AuthProvider, { queryClient, children: /* @__PURE__ */ jsx22(UserIdentityProvider, { children: /* @__PURE__ */ jsx22(BrowserRouter, { children: /* @__PURE__ */ jsx22(WorkspaceAuthProvider, { children: /* @__PURE__ */ jsxs15(TopBarSlotProvider, { slot: /* @__PURE__ */ jsx22(UserMenu, {}), children: [
3528
+ return /* @__PURE__ */ jsx22(HelmetProvider, { children: /* @__PURE__ */ jsx22(AppErrorBoundary, { children: /* @__PURE__ */ jsx22(QueryClientProvider, { client: queryClient, children: /* @__PURE__ */ jsx22(ConfigProvider, { children: /* @__PURE__ */ jsx22(ThemeProvider, { children: /* @__PURE__ */ jsx22(AuthProvider, { queryClient, children: /* @__PURE__ */ jsx22(UserIdentityProvider, { children: /* @__PURE__ */ jsx22(BrowserRouter, { children: /* @__PURE__ */ jsx22(WorkspaceAuthProvider, { workspaceRoute, workspaceIdParam, children: /* @__PURE__ */ jsxs15(TopBarSlotProvider, { slot: /* @__PURE__ */ jsx22(UserMenu, {}), children: [
3437
3529
  /* @__PURE__ */ jsx22(Helmet, { children: resolvedCspNonce ? /* @__PURE__ */ jsxs15(Fragment6, { children: [
3438
3530
  /* @__PURE__ */ jsx22("meta", { name: CSP_NONCE_META_NAME, content: resolvedCspNonce }),
3439
3531
  /* @__PURE__ */ jsx22(
@@ -3446,7 +3538,7 @@ function CoreFront({ children, authPages, cspNonce }) {
3446
3538
  }
3447
3539
  )
3448
3540
  ] }) : null }),
3449
- /* @__PURE__ */ jsx22(AuthGate, { publicPaths: ["/invites"], children: /* @__PURE__ */ jsx22(Suspense, { fallback: null, children: /* @__PURE__ */ jsxs15(Routes, { children: [
3541
+ /* @__PURE__ */ jsx22(RouterAuthGate, { publicPaths: ["/invites", ...publicPaths ?? []], children: /* @__PURE__ */ jsx22(Suspense, { fallback: null, children: /* @__PURE__ */ jsxs15(Routes, { children: [
3450
3542
  /* @__PURE__ */ jsx22(Route, { path: routes.signin, element: /* @__PURE__ */ jsx22(SignInPage2, {}) }),
3451
3543
  /* @__PURE__ */ jsx22(Route, { path: routes.signup, element: /* @__PURE__ */ jsx22(SignUpPage2, {}) }),
3452
3544
  /* @__PURE__ */ jsx22(Route, { path: routes.forgotPassword, element: /* @__PURE__ */ jsx22(ForgotPasswordPage2, {}) }),
@@ -3470,7 +3562,7 @@ function CoreFront({ children, authPages, cspNonce }) {
3470
3562
 
3471
3563
  // src/front/commands/CoreCommandContributions.tsx
3472
3564
  import { useMemo as useMemo7, useState as useState20 } from "react";
3473
- import { useNavigate as useNavigate5 } from "react-router-dom";
3565
+ import { useNavigate as useNavigate6 } from "react-router-dom";
3474
3566
 
3475
3567
  // src/front/workspace/commands.ts
3476
3568
  function getWorkspaceCommands(workspaceId, navigate) {
@@ -3508,7 +3600,7 @@ function toPaletteCommand(command) {
3508
3600
  };
3509
3601
  }
3510
3602
  function useCoreCommands() {
3511
- const navigate = useNavigate5();
3603
+ const navigate = useNavigate6();
3512
3604
  const signOut = useSignOut();
3513
3605
  const workspace = useCurrentWorkspace();
3514
3606
  const [isSigningOut, setIsSigningOut] = useState20(false);
@@ -3632,9 +3724,6 @@ export {
3632
3724
  useBlobUrl,
3633
3725
  useCapabilities,
3634
3726
  useWorkspaceMembers,
3635
- WorkspaceAuthProvider,
3636
- useCurrentWorkspace,
3637
- useWorkspaceRole,
3638
3727
  getAuthClient,
3639
3728
  AuthProvider,
3640
3729
  useSession,
@@ -3644,6 +3733,10 @@ export {
3644
3733
  useSendVerificationEmail,
3645
3734
  useChangePassword,
3646
3735
  useSignOut,
3736
+ WorkspaceAuthProvider,
3737
+ useCurrentWorkspace,
3738
+ useWorkspaceRole,
3739
+ useWorkspaceRouteStatus,
3647
3740
  UserIdentityProvider,
3648
3741
  useUser,
3649
3742
  GoogleAuthButton,
@@ -6,7 +6,7 @@ import * as _tanstack_react_query from '@tanstack/react-query';
6
6
  import * as better_auth_react from 'better-auth/react';
7
7
  import { createAuthClient } from 'better-auth/react';
8
8
  import * as better_auth from 'better-auth';
9
- export { a as CoreFront, C as CoreFrontAuthPagesOverride, b as CoreFrontProps } from '../CoreFront-CgAkiEts.js';
9
+ export { a as CoreFront, C as CoreFrontAuthPagesOverride, b as CoreFrontProps } from '../CoreFront-N0QJSYaM.js';
10
10
  import { NavigateFunction } from 'react-router-dom';
11
11
  export { TopBarSlotProvider, useTopBarSlot } from './top-bar-slot.js';
12
12
 
@@ -70,12 +70,42 @@ type EnrichedMember = WorkspaceMember & {
70
70
  };
71
71
  declare function useWorkspaceMembers(workspaceId: string): _tanstack_react_query.UseQueryResult<EnrichedMember[], Error>;
72
72
 
73
+ type WorkspaceRouteStatus = {
74
+ status: 'idle';
75
+ workspaceId: string | null;
76
+ } | {
77
+ status: 'loading';
78
+ workspaceId: string | null;
79
+ } | {
80
+ status: 'matched';
81
+ workspaceId: string;
82
+ workspace: Workspace;
83
+ } | {
84
+ status: 'mismatched';
85
+ workspaceId: string;
86
+ currentWorkspaceId: string | null;
87
+ } | {
88
+ status: 'not-found';
89
+ workspaceId: string;
90
+ message: string;
91
+ } | {
92
+ status: 'forbidden';
93
+ workspaceId: string;
94
+ message: string;
95
+ } | {
96
+ status: 'switch-failed';
97
+ workspaceId: string;
98
+ message: string;
99
+ };
73
100
  interface WorkspaceAuthProviderProps {
74
101
  children: ReactNode;
102
+ workspaceRoute?: string;
103
+ workspaceIdParam?: string;
75
104
  }
76
- declare function WorkspaceAuthProvider({ children }: WorkspaceAuthProviderProps): react_jsx_runtime.JSX.Element;
105
+ declare function WorkspaceAuthProvider({ children, workspaceRoute, workspaceIdParam, }: WorkspaceAuthProviderProps): react_jsx_runtime.JSX.Element;
77
106
  declare function useCurrentWorkspace(): Workspace | null;
78
107
  declare function useWorkspaceRole(): MemberRole | null;
108
+ declare function useWorkspaceRouteStatus(): WorkspaceRouteStatus;
79
109
 
80
110
  interface AuthProviderProps {
81
111
  children: ReactNode;
@@ -464,4 +494,4 @@ declare function sanitizeToolOutput(input: string): string;
464
494
 
465
495
  declare function debounce<T extends (...args: unknown[]) => unknown>(fn: T, ms: number): T;
466
496
 
467
- export { AppErrorBoundary, type AuthClient, AuthGate, type AuthGateProps, AuthProvider, type AuthProviderProps, type Binding, type Breakpoint, ConfigProvider, type ConfigProviderProps, type CoreCommand, type EnrichedMember, ForgotPasswordPage, GoogleAuthButton, type GoogleAuthButtonProps, InviteAcceptPage, InvitesPage, MembersPage, ResetPasswordPage, type RouteMap, SignInPage, SignUpPage, type ThemeApi, ThemeProvider, type ThemeProviderProps, ThemeToggle, type UserIdentity, UserIdentityProvider, type UserIdentityProviderProps, UserMenu, UserSettingsPage, VerifyEmailPage, WorkspaceAuthProvider, type WorkspaceAuthProviderProps, type WorkspaceCommand, WorkspaceSettingsPage, WorkspaceSwitcher, apiFetch, apiFetchJson, buildApiUrl, buildWsUrl, debounce, getApiBase, getAuthClient, getHttpErrorDetail, getWorkspaceCommands, getWsBase, openWebSocket, routeHref, routes, sanitizeMarkdown, sanitizeToolOutput, setApiBase, useBlobUrl, useCapabilities, useChangePassword, useConfig, useConfigLoaded, useCoreCommands, useCurrentWorkspace, useKeyboardShortcuts, useReducedMotion, useSendVerificationEmail, useSession, useSignIn, useSignOut, useSignUp, useTheme, useUser, useVerifyEmail, useViewportBreakpoint, useWorkspaceMembers, useWorkspaceRole };
497
+ export { AppErrorBoundary, type AuthClient, AuthGate, type AuthGateProps, AuthProvider, type AuthProviderProps, type Binding, type Breakpoint, ConfigProvider, type ConfigProviderProps, type CoreCommand, type EnrichedMember, ForgotPasswordPage, GoogleAuthButton, type GoogleAuthButtonProps, InviteAcceptPage, InvitesPage, MembersPage, ResetPasswordPage, type RouteMap, SignInPage, SignUpPage, type ThemeApi, ThemeProvider, type ThemeProviderProps, ThemeToggle, type UserIdentity, UserIdentityProvider, type UserIdentityProviderProps, UserMenu, UserSettingsPage, VerifyEmailPage, WorkspaceAuthProvider, type WorkspaceAuthProviderProps, type WorkspaceCommand, type WorkspaceRouteStatus, WorkspaceSettingsPage, WorkspaceSwitcher, apiFetch, apiFetchJson, buildApiUrl, buildWsUrl, debounce, getApiBase, getAuthClient, getHttpErrorDetail, getWorkspaceCommands, getWsBase, openWebSocket, routeHref, routes, sanitizeMarkdown, sanitizeToolOutput, setApiBase, useBlobUrl, useCapabilities, useChangePassword, useConfig, useConfigLoaded, useCoreCommands, useCurrentWorkspace, useKeyboardShortcuts, useReducedMotion, useSendVerificationEmail, useSession, useSignIn, useSignOut, useSignUp, useTheme, useUser, useVerifyEmail, useViewportBreakpoint, useWorkspaceMembers, useWorkspaceRole, useWorkspaceRouteStatus };
@@ -56,8 +56,9 @@ import {
56
56
  useVerifyEmail,
57
57
  useViewportBreakpoint,
58
58
  useWorkspaceMembers,
59
- useWorkspaceRole
60
- } from "../chunk-JMCBLJ6W.js";
59
+ useWorkspaceRole,
60
+ useWorkspaceRouteStatus
61
+ } from "../chunk-5R3U6QKD.js";
61
62
  import {
62
63
  TopBarSlotProvider,
63
64
  useTopBarSlot
@@ -124,5 +125,6 @@ export {
124
125
  useVerifyEmail,
125
126
  useViewportBreakpoint,
126
127
  useWorkspaceMembers,
127
- useWorkspaceRole
128
+ useWorkspaceRole,
129
+ useWorkspaceRouteStatus
128
130
  };
@@ -24,7 +24,7 @@ import {
24
24
  requireWorkspaceMember,
25
25
  validateConfig,
26
26
  validatePasswordStrength
27
- } from "../chunk-6D7LEQSL.js";
27
+ } from "../chunk-2JDK4XUZ.js";
28
28
  import {
29
29
  createDatabase,
30
30
  runMigrations