@agent-native/dispatch 0.15.21 → 0.15.23

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 (31) hide show
  1. package/dist/actions/provider-api-register.d.ts +11 -11
  2. package/dist/actions/sync-vault-to-app.d.ts +4 -4
  3. package/dist/components/layout/Layout.d.ts.map +1 -1
  4. package/dist/components/layout/Layout.js +1 -5
  5. package/dist/components/layout/Layout.js.map +1 -1
  6. package/dist/routes/pages/_index.js +1 -1
  7. package/dist/routes/pages/_index.js.map +1 -1
  8. package/dist/routes/pages/overview.js +1 -1
  9. package/dist/routes/pages/overview.js.map +1 -1
  10. package/dist/server/lib/app-creation-store.js +1 -1
  11. package/dist/server/lib/app-creation-store.js.map +1 -1
  12. package/dist/server/lib/vault-store.d.ts +4 -4
  13. package/dist/server/lib/vault-store.d.ts.map +1 -1
  14. package/dist/server/lib/vault-store.js +53 -28
  15. package/dist/server/lib/vault-store.js.map +1 -1
  16. package/dist/server/plugins/auth.js +1 -1
  17. package/dist/server/plugins/auth.js.map +1 -1
  18. package/package.json +3 -5
  19. package/src/components/layout/Layout.spec.tsx +33 -0
  20. package/src/components/layout/Layout.tsx +2 -19
  21. package/src/routes/pages/_index.tsx +1 -1
  22. package/src/routes/pages/overview.tsx +1 -1
  23. package/src/server/lib/app-creation-store.ts +1 -1
  24. package/src/server/lib/vault-store.spec.ts +159 -0
  25. package/src/server/lib/vault-store.ts +61 -28
  26. package/src/server/plugins/auth.ts +1 -1
  27. package/dist/server/lib/pre-auth-routing.d.ts +0 -2
  28. package/dist/server/lib/pre-auth-routing.d.ts.map +0 -1
  29. package/dist/server/lib/pre-auth-routing.js +0 -136
  30. package/dist/server/lib/pre-auth-routing.js.map +0 -1
  31. package/src/server/lib/pre-auth-routing.ts +0 -159
@@ -170,6 +170,39 @@ describe("Dispatch NavContent", () => {
170
170
  expect(lists[0].querySelector("a")?.className).toContain("h-8 w-8");
171
171
  });
172
172
 
173
+ it("keeps Dispatch branding and anchors Settings above the organization picker", async () => {
174
+ await act(async () => {
175
+ root.render(
176
+ <MemoryRouter initialEntries={["/overview"]}>
177
+ <TooltipProvider>
178
+ <NavContent />
179
+ </TooltipProvider>
180
+ </MemoryRouter>,
181
+ );
182
+ });
183
+
184
+ expect(container.textContent).toContain("Dispatch");
185
+ expect(container.textContent).not.toContain("Agent-Native");
186
+
187
+ const settingsLink = container.querySelector('a[href="/settings"]');
188
+ const organization = [...container.querySelectorAll("div")].find(
189
+ (element) => element.textContent?.trim() === "Organization",
190
+ );
191
+ const footerActions = container.querySelector(
192
+ "[data-sidebar-footer-actions]",
193
+ );
194
+
195
+ expect(settingsLink).not.toBeNull();
196
+ expect(organization).toBeDefined();
197
+ expect(footerActions).not.toBeNull();
198
+ expect(settingsLink!.compareDocumentPosition(organization!)).toBe(
199
+ Node.DOCUMENT_POSITION_FOLLOWING,
200
+ );
201
+ expect(organization!.compareDocumentPosition(footerActions!)).toBe(
202
+ Node.DOCUMENT_POSITION_FOLLOWING,
203
+ );
204
+ });
205
+
173
206
  it("uses the shared chat history rail and retains thread actions", async () => {
174
207
  await act(async () => {
175
208
  root.render(
@@ -8,7 +8,6 @@ import {
8
8
  type ChatThreadSummary,
9
9
  } from "@agent-native/core/client/agent-chat";
10
10
  import { appBasePath, appPath } from "@agent-native/core/client/api-path";
11
- import { useActionQuery } from "@agent-native/core/client/hooks";
12
11
  import { LanguagePicker, useT } from "@agent-native/core/client/i18n";
13
12
  import { openCommandMenu } from "@agent-native/core/client/navigation";
14
13
  import { InvitationBanner, OrgSwitcher } from "@agent-native/core/client/org";
@@ -243,12 +242,6 @@ function pageOwnsToolbar(pathname: string): boolean {
243
242
  return false;
244
243
  }
245
244
 
246
- interface WorkspaceInfo {
247
- name: string | null;
248
- displayName: string | null;
249
- appCount: number;
250
- }
251
-
252
245
  function sectionFor(item: DispatchNavItem): DispatchNavSection {
253
246
  return item.section ?? "operations";
254
247
  }
@@ -478,13 +471,6 @@ export function NavContent({
478
471
  const t = useT();
479
472
  const location = useLocation();
480
473
  const navigate = useNavigate();
481
- const { data: workspace } = useActionQuery(
482
- "get-workspace-info",
483
- {},
484
- { staleTime: 60_000 },
485
- );
486
- const ws = workspace as WorkspaceInfo | undefined;
487
- const workspaceLabel = ws?.displayName ?? ws?.name ?? null;
488
474
  const extensionNavItems = extensions?.navItems ?? EMPTY_NAV_ITEMS;
489
475
  const primaryNavItems = [
490
476
  ...PRIMARY_NAV_ITEMS,
@@ -681,7 +667,7 @@ export function NavContent({
681
667
  />
682
668
  <div className="min-w-0 flex-1">
683
669
  <div className="truncate text-lg font-bold tracking-tight text-foreground">
684
- {workspaceLabel ?? "Dispatch"}
670
+ Dispatch
685
671
  </div>
686
672
  </div>
687
673
  </>
@@ -745,16 +731,13 @@ export function NavContent({
745
731
  {BOTTOM_NAV_ITEMS.map(renderNavItem)}
746
732
  </ul>
747
733
  </nav>
748
- </div>
749
-
750
- <div className="mt-auto shrink-0">
751
734
  <div
752
735
  className={cn(
753
736
  "py-2",
754
737
  collapsed ? "flex justify-center px-1" : "px-3",
755
738
  )}
756
739
  >
757
- <OrgSwitcher compact={collapsed} />
740
+ <OrgSwitcher compact={collapsed} reserveSpace />
758
741
  </div>
759
742
  </div>
760
743
  <SidebarFooterActions
@@ -4,7 +4,7 @@ import { redirect, type LoaderFunctionArgs } from "react-router";
4
4
  import { Spinner } from "../../components/ui/spinner";
5
5
 
6
6
  const SEO_TITLE =
7
- "Agent-Native Dispatch - Open Source workspace control plane for AI agents";
7
+ "Dispatch - Open Source workspace control plane for AI agents";
8
8
  const SEO_DESCRIPTION =
9
9
  "Open Source workspace control plane for AI agents to manage apps, secrets, approvals, messages, jobs, and cross-app delegation.";
10
10
 
@@ -1,7 +1,7 @@
1
1
  import { DispatchControlPlane } from "../../components/dispatch-control-plane";
2
2
 
3
3
  const SEO_TITLE =
4
- "Agent-Native Dispatch - Open Source workspace control plane for AI agents";
4
+ "Dispatch - Open Source workspace control plane for AI agents";
5
5
  const SEO_DESCRIPTION =
6
6
  "Open Source workspace control plane for AI agents to manage apps, secrets, approvals, messages, jobs, and cross-app delegation.";
7
7
 
@@ -1644,7 +1644,7 @@ function normalizeBuilderRunString(value: unknown, fieldName: string): string {
1644
1644
  throw new Error(`Builder app creation returned a blank ${fieldName}`);
1645
1645
  }
1646
1646
  const trimmed = value.trim();
1647
- if (/[-]/.test(trimmed)) {
1647
+ if (/[\u0000-\u001f\u007f]/.test(trimmed)) {
1648
1648
  throw new Error(`Builder app creation returned a malformed ${fieldName}`);
1649
1649
  }
1650
1650
  return trimmed;
@@ -1,10 +1,16 @@
1
1
  import { afterEach, describe, expect, it, vi } from "vitest";
2
2
 
3
3
  const mocks = vi.hoisted(() => ({
4
+ currentOrgId: vi.fn(),
5
+ currentOwnerEmail: vi.fn(),
4
6
  deleteAppSecret: vi.fn(),
7
+ discoverAgents: vi.fn(),
5
8
  getDb: vi.fn(),
9
+ getOrgSetting: vi.fn(),
10
+ getUserSetting: vi.fn(),
6
11
  listAppSecretsForScope: vi.fn(),
7
12
  readAppSecret: vi.fn(),
13
+ recordAudit: vi.fn(),
8
14
  writeAppSecret: vi.fn(),
9
15
  }));
10
16
 
@@ -15,6 +21,23 @@ vi.mock("@agent-native/core/secrets", () => ({
15
21
  writeAppSecret: mocks.writeAppSecret,
16
22
  }));
17
23
 
24
+ vi.mock("@agent-native/core/server/agent-discovery", () => ({
25
+ discoverAgents: mocks.discoverAgents,
26
+ }));
27
+
28
+ vi.mock("@agent-native/core/settings", () => ({
29
+ getOrgSetting: mocks.getOrgSetting,
30
+ getUserSetting: mocks.getUserSetting,
31
+ putOrgSetting: vi.fn(),
32
+ putUserSetting: vi.fn(),
33
+ }));
34
+
35
+ vi.mock("./dispatch-store.js", () => ({
36
+ currentOrgId: mocks.currentOrgId,
37
+ currentOwnerEmail: mocks.currentOwnerEmail,
38
+ recordAudit: mocks.recordAudit,
39
+ }));
40
+
18
41
  vi.mock("../../db/index.js", async (importOriginal) => {
19
42
  const actual = await importOriginal<typeof import("../../db/index.js")>();
20
43
  return {
@@ -30,6 +53,7 @@ import {
30
53
  credentialStoreScopeForVaultCtx,
31
54
  isTrustedEnvVarSyncAgentUrl,
32
55
  resyncAllVaultSecretsToCredentialStore,
56
+ syncGrantsToApp,
33
57
  syncSecretsToCredentialStore,
34
58
  } from "./vault-store.js";
35
59
 
@@ -319,3 +343,138 @@ describe("resyncAllVaultSecretsToCredentialStore", () => {
319
343
  warnSpy.mockRestore();
320
344
  });
321
345
  });
346
+
347
+ describe("syncGrantsToApp", () => {
348
+ /** In-memory stand-in for app_secrets, keyed scope + scopeId + key. */
349
+ function fakeCredentialStore() {
350
+ const store = new Map<string, string>();
351
+ mocks.writeAppSecret.mockImplementation(async (args: any) => {
352
+ store.set(`${args.scope}:${args.scopeId}:${args.key}`, args.value);
353
+ return "app-secret-id";
354
+ });
355
+ return store;
356
+ }
357
+
358
+ /**
359
+ * `all-apps` vault access, one discovered app, and a caller whose active org
360
+ * is deliberately NOT the org that owns the secrets. The app URL is remote so
361
+ * the best-effort env-var push is skipped and no network call is attempted.
362
+ */
363
+ function mockWorkspace(
364
+ caller: { ownerEmail: string; orgId: string | null },
365
+ secretRows: Array<Record<string, unknown>>,
366
+ ) {
367
+ mocks.currentOwnerEmail.mockReturnValue(caller.ownerEmail);
368
+ mocks.currentOrgId.mockReturnValue(caller.orgId);
369
+ mocks.getOrgSetting.mockResolvedValue(null);
370
+ mocks.getUserSetting.mockResolvedValue(null);
371
+ mocks.discoverAgents.mockResolvedValue([
372
+ { id: "coach", url: "https://coach.example.test" },
373
+ ]);
374
+ mocks.getDb.mockReturnValue({
375
+ select: () => ({
376
+ from: () => ({
377
+ where: () => ({ orderBy: async () => secretRows }),
378
+ }),
379
+ }),
380
+ insert: () => ({ values: async () => undefined }),
381
+ });
382
+ }
383
+
384
+ afterEach(() => {
385
+ mocks.writeAppSecret.mockReset();
386
+ });
387
+
388
+ // The regression: `all-apps` mode lists secrets across orgs, and syncing them
389
+ // under the caller's ctx upserts copies of another org's credentials into
390
+ // whichever org the person clicking Sync happened to be in. Because
391
+ // writeAppSecret upserts, that copies rather than moves, so credential
392
+ // material accumulates permanently in the wrong org.
393
+ it("writes each secret under its own org, not the caller's active org", async () => {
394
+ const store = fakeCredentialStore();
395
+ mockWorkspace({ ownerEmail: "clicker@example.test", orgId: "org_caller" }, [
396
+ {
397
+ id: "secret_builder",
398
+ ownerEmail: "admin@example.test",
399
+ orgId: "org_owner",
400
+ name: "Academy Site URL",
401
+ credentialKey: "ACADEMY_CONVEX_SITE_URL",
402
+ value: "https://academy.example.test",
403
+ },
404
+ {
405
+ id: "secret_solo",
406
+ ownerEmail: "owner@example.test",
407
+ orgId: null,
408
+ name: "Personal API Key",
409
+ credentialKey: "PERSONAL_API_KEY",
410
+ value: "sk-test-personal",
411
+ },
412
+ ]);
413
+
414
+ const result = await syncGrantsToApp("coach");
415
+
416
+ expect(store.get("org:org_owner:ACADEMY_CONVEX_SITE_URL")).toBe(
417
+ "https://academy.example.test",
418
+ );
419
+ expect(
420
+ store.get("workspace:solo:owner@example.test:PERSONAL_API_KEY"),
421
+ ).toBe("sk-test-personal");
422
+
423
+ // Nothing may be written under the caller's org.
424
+ const callerScoped = [...store.keys()].filter((key) =>
425
+ key.startsWith("org:org_caller:"),
426
+ );
427
+ expect(callerScoped).toEqual([]);
428
+
429
+ expect(result.credentialStores).toEqual([
430
+ { scope: "org", scopeId: "org_owner", synced: 1 },
431
+ {
432
+ scope: "workspace",
433
+ scopeId: "solo:owner@example.test",
434
+ synced: 1,
435
+ },
436
+ ]);
437
+ expect(result.synced).toBe(2);
438
+ });
439
+
440
+ it("still syncs the caller's own org secrets into that org", async () => {
441
+ const store = fakeCredentialStore();
442
+ mockWorkspace({ ownerEmail: "clicker@example.test", orgId: "org_caller" }, [
443
+ {
444
+ id: "secret_own",
445
+ ownerEmail: "clicker@example.test",
446
+ orgId: "org_caller",
447
+ name: "Shared API Key",
448
+ credentialKey: "SHARED_API_KEY",
449
+ value: "sk-test-shared",
450
+ },
451
+ ]);
452
+
453
+ const result = await syncGrantsToApp("coach");
454
+
455
+ expect(store.get("org:org_caller:SHARED_API_KEY")).toBe("sk-test-shared");
456
+ expect(result.credentialStores).toEqual([
457
+ { scope: "org", scopeId: "org_caller", synced: 1 },
458
+ ]);
459
+ });
460
+
461
+ // A row with no ownerEmail cannot name its own tenant, so the caller's ctx is
462
+ // the only scope available — the pre-existing ctxForSecretRow fallback.
463
+ it("falls back to the caller ctx for a secret row with no owner", async () => {
464
+ const store = fakeCredentialStore();
465
+ mockWorkspace({ ownerEmail: "clicker@example.test", orgId: "org_caller" }, [
466
+ {
467
+ id: "secret_ownerless",
468
+ ownerEmail: "",
469
+ orgId: null,
470
+ name: "Legacy Key",
471
+ credentialKey: "LEGACY_KEY",
472
+ value: "sk-test-legacy",
473
+ },
474
+ ]);
475
+
476
+ await syncGrantsToApp("coach");
477
+
478
+ expect(store.get("org:org_caller:LEGACY_KEY")).toBe("sk-test-legacy");
479
+ });
480
+ });
@@ -763,6 +763,33 @@ export async function syncSecretsToCredentialStore(
763
763
  return { ...target, keys: syncedKeys };
764
764
  }
765
765
 
766
+ /**
767
+ * Group secrets by the tenant their credential-store rows must land in.
768
+ *
769
+ * Every sync path must write a secret under the org that *owns the row*, not
770
+ * under whoever happens to be syncing: `writeAppSecret` upserts, so syncing
771
+ * with the caller's ctx copies credential material into the caller's org
772
+ * instead of moving it, and it accumulates there permanently.
773
+ */
774
+ function groupSecretsByTenant(
775
+ rows: VaultSecretRow[],
776
+ resolveCtx: (row: VaultSecretRow) => VaultCtx,
777
+ ): { ctx: VaultCtx; rows: VaultSecretRow[] }[] {
778
+ const groups = new Map<string, { ctx: VaultCtx; rows: VaultSecretRow[] }>();
779
+ for (const row of rows) {
780
+ if (!row.credentialKey || !row.value) continue;
781
+ const ctx = resolveCtx(row);
782
+ const groupKey = `${ctx.orgId ?? ""}\u0000${ctx.ownerEmail}`;
783
+ const group = groups.get(groupKey);
784
+ if (group) {
785
+ group.rows.push(row);
786
+ } else {
787
+ groups.set(groupKey, { ctx, rows: [row] });
788
+ }
789
+ }
790
+ return [...groups.values()];
791
+ }
792
+
766
793
  /**
767
794
  * Re-sync every vault secret across every tenant into the shared credential
768
795
  * store, regardless of which request/ctx is currently active.
@@ -788,23 +815,12 @@ export async function resyncAllVaultSecretsToCredentialStore(): Promise<{
788
815
  const db = getDb();
789
816
  const rows = await db.select().from(schema.vaultSecrets);
790
817
 
791
- const groups = new Map<string, { ctx: VaultCtx; rows: VaultSecretRow[] }>();
792
- for (const row of rows) {
793
- if (!row.credentialKey || !row.value) continue;
794
- const ctx: VaultCtx = { ownerEmail: row.ownerEmail, orgId: row.orgId };
795
- const groupKey = `${ctx.orgId ?? ""}${ctx.ownerEmail}`;
796
- const group = groups.get(groupKey);
797
- if (group) {
798
- group.rows.push(row);
799
- } else {
800
- groups.set(groupKey, { ctx, rows: [row] });
801
- }
802
- }
818
+ const groups = groupSecretsByTenant(rows, ctxForRow);
803
819
 
804
820
  let failedGroups = 0;
805
821
  let syncedKeys = 0;
806
822
 
807
- for (const { ctx, rows: groupRows } of groups.values()) {
823
+ for (const { ctx, rows: groupRows } of groups) {
808
824
  try {
809
825
  const result = await syncSecretsToCredentialStore(groupRows, ctx);
810
826
  syncedKeys += result.keys.length;
@@ -818,7 +834,7 @@ export async function resyncAllVaultSecretsToCredentialStore(): Promise<{
818
834
  }
819
835
  }
820
836
 
821
- return { groups: groups.size, failedGroups, syncedKeys };
837
+ return { groups: groups.length, failedGroups, syncedKeys };
822
838
  }
823
839
 
824
840
  export async function cleanupSyncedCredentialKeysIfUnused(
@@ -889,13 +905,37 @@ export async function syncGrantsToApp(
889
905
  }
890
906
 
891
907
  if (secretsToSync.length === 0) {
892
- return { appId, accessMode: access.mode, synced: 0, keys: [] };
908
+ return {
909
+ appId,
910
+ accessMode: access.mode,
911
+ synced: 0,
912
+ keys: [],
913
+ credentialStores: [],
914
+ };
893
915
  }
894
916
 
895
- const credentialStoreSync = await syncSecretsToCredentialStore(
896
- secretsToSync,
897
- ctx,
917
+ // `all-apps` mode lists secrets across every org the caller can see, so each
918
+ // row must be written back under its own org. Syncing them all under `ctx`
919
+ // upserts copies of other orgs' credentials into the caller's org.
920
+ const credentialStoreGroups = groupSecretsByTenant(secretsToSync, (row) =>
921
+ ctxForSecretRow(row, ctx),
898
922
  );
923
+ const credentialStores: {
924
+ scope: ReturnType<typeof credentialStoreScopeForVaultCtx>["scope"];
925
+ scopeId: string;
926
+ synced: number;
927
+ }[] = [];
928
+ const credentialStoreKeys: string[] = [];
929
+ for (const group of credentialStoreGroups) {
930
+ const result = await syncSecretsToCredentialStore(group.rows, group.ctx);
931
+ credentialStores.push({
932
+ scope: result.scope,
933
+ scopeId: result.scopeId,
934
+ synced: result.keys.length,
935
+ });
936
+ credentialStoreKeys.push(...result.keys);
937
+ }
938
+
899
939
  const vars = secretsToSync.map((secret) => ({
900
940
  key: secret.credentialKey,
901
941
  value: secret.value,
@@ -942,7 +982,7 @@ export async function syncGrantsToApp(
942
982
  }
943
983
  }
944
984
 
945
- const syncedKeys = credentialStoreSync.keys;
985
+ const syncedKeys = credentialStoreKeys;
946
986
  const timestamp = now();
947
987
 
948
988
  // Update syncedAt on grants that were successfully pushed to the shared
@@ -964,10 +1004,7 @@ export async function syncGrantsToApp(
964
1004
  metadata: {
965
1005
  syncedKeys,
966
1006
  accessMode: access.mode,
967
- credentialStore: {
968
- scope: credentialStoreSync.scope,
969
- scopeId: credentialStoreSync.scopeId,
970
- },
1007
+ credentialStores,
971
1008
  envVars: envVarSync,
972
1009
  },
973
1010
  });
@@ -977,11 +1014,7 @@ export async function syncGrantsToApp(
977
1014
  accessMode: access.mode,
978
1015
  synced: syncedKeys.length,
979
1016
  keys: syncedKeys,
980
- credentialStore: {
981
- scope: credentialStoreSync.scope,
982
- scopeId: credentialStoreSync.scopeId,
983
- synced: credentialStoreSync.keys.length,
984
- },
1017
+ credentialStores,
985
1018
  envVars: envVarSync,
986
1019
  };
987
1020
  }
@@ -3,7 +3,7 @@ import { createAuthPlugin } from "@agent-native/core/server";
3
3
  import { getDispatchConfig } from "../index.js";
4
4
 
5
5
  const DEFAULT_MARKETING = {
6
- appName: "Agent-Native Dispatch",
6
+ appName: "Dispatch",
7
7
  tagline:
8
8
  "Your AI agent manages secrets, orchestrates other agents, and routes messages across your workspace.",
9
9
  features: [
@@ -1,2 +0,0 @@
1
- export declare function rootDispatchRedirect(pathname: string, search: string): Response | null;
2
- //# sourceMappingURL=pre-auth-routing.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"pre-auth-routing.d.ts","sourceRoot":"","sources":["../../../src/server/lib/pre-auth-routing.ts"],"names":[],"mappings":"AAoFA,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,GACb,QAAQ,GAAG,IAAI,CAuEjB"}
@@ -1,136 +0,0 @@
1
- function normalizeBasePath(value) {
2
- if (!value || value === "/")
3
- return "";
4
- const trimmed = value.trim();
5
- if (!trimmed || trimmed === "/")
6
- return "";
7
- return `/${trimmed.replace(/^\/+/, "").replace(/\/+$/, "")}`;
8
- }
9
- function normalizePathname(value) {
10
- if (value === "/")
11
- return "/";
12
- return value.replace(/\/+$/, "") || "/";
13
- }
14
- const DISPATCH_PAGE_PATHS = new Set([
15
- "/overview",
16
- "/metrics",
17
- "/login",
18
- "/signup",
19
- "/apps",
20
- "/new-app",
21
- "/vault",
22
- "/integrations",
23
- "/agents",
24
- "/workspace",
25
- "/tools",
26
- "/messaging",
27
- "/destinations",
28
- "/identities",
29
- "/approvals",
30
- "/automations",
31
- "/audit",
32
- "/team",
33
- ]);
34
- const DISPATCH_ROOT_ALIASES = new Map([
35
- ...Array.from(DISPATCH_PAGE_PATHS, (path) => [path, path]),
36
- ["/approval", "/approval"],
37
- ["/extensions", "/extensions"],
38
- ["/tools", "/tools"],
39
- ["/apps/new-app", "/new-app"],
40
- ]);
41
- const MOUNTED_DISPATCH_ALIASES = new Map([
42
- ["/apps/new-app", "/new-app"],
43
- ]);
44
- function isDispatchPagePath(pathname) {
45
- if (DISPATCH_PAGE_PATHS.has(pathname))
46
- return true;
47
- if (pathname === "/approval" || pathname === "/extensions")
48
- return true;
49
- if (pathname === "/tools")
50
- return true;
51
- return (/^\/extensions\/[^/]+$/.test(pathname) ||
52
- /^\/tools\/[^/]+$/.test(pathname) ||
53
- /^\/apps\/[^/]+$/.test(pathname));
54
- }
55
- function isDispatchAssetOrFrameworkPath(pathname) {
56
- return (pathname === "/__manifest" ||
57
- pathname.startsWith("/__manifest/") ||
58
- pathname === "/_agent-native" ||
59
- pathname.startsWith("/_agent-native/") ||
60
- pathname === "/.well-known" ||
61
- pathname.startsWith("/.well-known/") ||
62
- pathname.startsWith("/assets/") ||
63
- pathname.startsWith("/_build/") ||
64
- pathname.endsWith(".js") ||
65
- pathname.endsWith(".css") ||
66
- pathname.endsWith(".map") ||
67
- pathname.endsWith(".ico") ||
68
- pathname.endsWith(".png") ||
69
- pathname.endsWith(".svg") ||
70
- pathname.endsWith(".woff2") ||
71
- pathname.endsWith(".woff"));
72
- }
73
- function dispatchNotFoundResponse() {
74
- return new Response("Dispatch route not found", {
75
- status: 404,
76
- headers: { "content-type": "text/plain; charset=utf-8" },
77
- });
78
- }
79
- export function rootDispatchRedirect(pathname, search) {
80
- const normalizedPathname = normalizePathname(pathname);
81
- const basePath = normalizeBasePath(process.env.VITE_APP_BASE_PATH || process.env.APP_BASE_PATH);
82
- if (!basePath)
83
- return null;
84
- if (normalizedPathname === "/__manifest" ||
85
- normalizedPathname.startsWith("/__manifest/") ||
86
- normalizedPathname === "/_agent-native" ||
87
- normalizedPathname.startsWith("/_agent-native/")) {
88
- return null;
89
- }
90
- if (normalizedPathname === "/.well-known" ||
91
- normalizedPathname.startsWith("/.well-known/")) {
92
- return null;
93
- }
94
- if (normalizedPathname === "/") {
95
- return new Response(null, {
96
- status: 302,
97
- headers: { Location: `${basePath}/overview${search}` },
98
- });
99
- }
100
- if (normalizedPathname === basePath) {
101
- return new Response(null, {
102
- status: 302,
103
- headers: { Location: `${basePath}/overview${search}` },
104
- });
105
- }
106
- if (normalizedPathname.startsWith(`${basePath}/`)) {
107
- const dispatchPath = normalizedPathname.slice(basePath.length);
108
- const mountedAlias = MOUNTED_DISPATCH_ALIASES.get(dispatchPath);
109
- if (mountedAlias) {
110
- return new Response(null, {
111
- status: 302,
112
- headers: { Location: `${basePath}${mountedAlias}${search}` },
113
- });
114
- }
115
- if (isDispatchPagePath(dispatchPath) ||
116
- isDispatchAssetOrFrameworkPath(dispatchPath)) {
117
- return null;
118
- }
119
- return dispatchNotFoundResponse();
120
- }
121
- const rootAlias = DISPATCH_ROOT_ALIASES.get(normalizedPathname);
122
- if (rootAlias) {
123
- return new Response(null, {
124
- status: 302,
125
- headers: { Location: `${basePath}${rootAlias}${search}` },
126
- });
127
- }
128
- if (/^\/apps\/[^/]+$/.test(normalizedPathname)) {
129
- return new Response(null, {
130
- status: 302,
131
- headers: { Location: `${basePath}${normalizedPathname}${search}` },
132
- });
133
- }
134
- return dispatchNotFoundResponse();
135
- }
136
- //# sourceMappingURL=pre-auth-routing.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"pre-auth-routing.js","sourceRoot":"","sources":["../../../src/server/lib/pre-auth-routing.ts"],"names":[],"mappings":"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","sourcesContent":["function normalizeBasePath(value?: string): string {\n if (!value || value === \"/\") return \"\";\n const trimmed = value.trim();\n if (!trimmed || trimmed === \"/\") return \"\";\n return `/${trimmed.replace(/^\\/+/, \"\").replace(/\\/+$/, \"\")}`;\n}\n\nfunction normalizePathname(value: string): string {\n if (value === \"/\") return \"/\";\n return value.replace(/\\/+$/, \"\") || \"/\";\n}\n\nconst DISPATCH_PAGE_PATHS = new Set([\n \"/overview\",\n \"/metrics\",\n \"/login\",\n \"/signup\",\n \"/apps\",\n \"/new-app\",\n \"/vault\",\n \"/integrations\",\n \"/agents\",\n \"/workspace\",\n \"/tools\",\n \"/messaging\",\n \"/destinations\",\n \"/identities\",\n \"/approvals\",\n \"/automations\",\n \"/audit\",\n \"/team\",\n]);\n\nconst DISPATCH_ROOT_ALIASES = new Map<string, string>([\n ...Array.from(DISPATCH_PAGE_PATHS, (path) => [path, path] as const),\n [\"/approval\", \"/approval\"],\n [\"/extensions\", \"/extensions\"],\n [\"/tools\", \"/tools\"],\n [\"/apps/new-app\", \"/new-app\"],\n]);\n\nconst MOUNTED_DISPATCH_ALIASES = new Map<string, string>([\n [\"/apps/new-app\", \"/new-app\"],\n]);\n\nfunction isDispatchPagePath(pathname: string): boolean {\n if (DISPATCH_PAGE_PATHS.has(pathname)) return true;\n if (pathname === \"/approval\" || pathname === \"/extensions\") return true;\n if (pathname === \"/tools\") return true;\n return (\n /^\\/extensions\\/[^/]+$/.test(pathname) ||\n /^\\/tools\\/[^/]+$/.test(pathname) ||\n /^\\/apps\\/[^/]+$/.test(pathname)\n );\n}\n\nfunction isDispatchAssetOrFrameworkPath(pathname: string): boolean {\n return (\n pathname === \"/__manifest\" ||\n pathname.startsWith(\"/__manifest/\") ||\n pathname === \"/_agent-native\" ||\n pathname.startsWith(\"/_agent-native/\") ||\n pathname === \"/.well-known\" ||\n pathname.startsWith(\"/.well-known/\") ||\n pathname.startsWith(\"/assets/\") ||\n pathname.startsWith(\"/_build/\") ||\n pathname.endsWith(\".js\") ||\n pathname.endsWith(\".css\") ||\n pathname.endsWith(\".map\") ||\n pathname.endsWith(\".ico\") ||\n pathname.endsWith(\".png\") ||\n pathname.endsWith(\".svg\") ||\n pathname.endsWith(\".woff2\") ||\n pathname.endsWith(\".woff\")\n );\n}\n\nfunction dispatchNotFoundResponse(): Response {\n return new Response(\"Dispatch route not found\", {\n status: 404,\n headers: { \"content-type\": \"text/plain; charset=utf-8\" },\n });\n}\n\nexport function rootDispatchRedirect(\n pathname: string,\n search: string,\n): Response | null {\n const normalizedPathname = normalizePathname(pathname);\n const basePath = normalizeBasePath(\n process.env.VITE_APP_BASE_PATH || process.env.APP_BASE_PATH,\n );\n if (!basePath) return null;\n\n if (\n normalizedPathname === \"/__manifest\" ||\n normalizedPathname.startsWith(\"/__manifest/\") ||\n normalizedPathname === \"/_agent-native\" ||\n normalizedPathname.startsWith(\"/_agent-native/\")\n ) {\n return null;\n }\n\n if (\n normalizedPathname === \"/.well-known\" ||\n normalizedPathname.startsWith(\"/.well-known/\")\n ) {\n return null;\n }\n\n if (normalizedPathname === \"/\") {\n return new Response(null, {\n status: 302,\n headers: { Location: `${basePath}/overview${search}` },\n });\n }\n\n if (normalizedPathname === basePath) {\n return new Response(null, {\n status: 302,\n headers: { Location: `${basePath}/overview${search}` },\n });\n }\n\n if (normalizedPathname.startsWith(`${basePath}/`)) {\n const dispatchPath = normalizedPathname.slice(basePath.length);\n const mountedAlias = MOUNTED_DISPATCH_ALIASES.get(dispatchPath);\n if (mountedAlias) {\n return new Response(null, {\n status: 302,\n headers: { Location: `${basePath}${mountedAlias}${search}` },\n });\n }\n if (\n isDispatchPagePath(dispatchPath) ||\n isDispatchAssetOrFrameworkPath(dispatchPath)\n ) {\n return null;\n }\n return dispatchNotFoundResponse();\n }\n\n const rootAlias = DISPATCH_ROOT_ALIASES.get(normalizedPathname);\n if (rootAlias) {\n return new Response(null, {\n status: 302,\n headers: { Location: `${basePath}${rootAlias}${search}` },\n });\n }\n\n if (/^\\/apps\\/[^/]+$/.test(normalizedPathname)) {\n return new Response(null, {\n status: 302,\n headers: { Location: `${basePath}${normalizedPathname}${search}` },\n });\n }\n\n return dispatchNotFoundResponse();\n}\n"]}