@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.
- package/dist/actions/provider-api-register.d.ts +11 -11
- package/dist/actions/sync-vault-to-app.d.ts +4 -4
- package/dist/components/layout/Layout.d.ts.map +1 -1
- package/dist/components/layout/Layout.js +1 -5
- package/dist/components/layout/Layout.js.map +1 -1
- package/dist/routes/pages/_index.js +1 -1
- package/dist/routes/pages/_index.js.map +1 -1
- package/dist/routes/pages/overview.js +1 -1
- package/dist/routes/pages/overview.js.map +1 -1
- package/dist/server/lib/app-creation-store.js +1 -1
- package/dist/server/lib/app-creation-store.js.map +1 -1
- package/dist/server/lib/vault-store.d.ts +4 -4
- package/dist/server/lib/vault-store.d.ts.map +1 -1
- package/dist/server/lib/vault-store.js +53 -28
- package/dist/server/lib/vault-store.js.map +1 -1
- package/dist/server/plugins/auth.js +1 -1
- package/dist/server/plugins/auth.js.map +1 -1
- package/package.json +3 -5
- package/src/components/layout/Layout.spec.tsx +33 -0
- package/src/components/layout/Layout.tsx +2 -19
- package/src/routes/pages/_index.tsx +1 -1
- package/src/routes/pages/overview.tsx +1 -1
- package/src/server/lib/app-creation-store.ts +1 -1
- package/src/server/lib/vault-store.spec.ts +159 -0
- package/src/server/lib/vault-store.ts +61 -28
- package/src/server/plugins/auth.ts +1 -1
- package/dist/server/lib/pre-auth-routing.d.ts +0 -2
- package/dist/server/lib/pre-auth-routing.d.ts.map +0 -1
- package/dist/server/lib/pre-auth-routing.js +0 -136
- package/dist/server/lib/pre-auth-routing.js.map +0 -1
- 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
|
-
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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 (/[
|
|
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 =
|
|
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
|
|
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.
|
|
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 {
|
|
908
|
+
return {
|
|
909
|
+
appId,
|
|
910
|
+
accessMode: access.mode,
|
|
911
|
+
synced: 0,
|
|
912
|
+
keys: [],
|
|
913
|
+
credentialStores: [],
|
|
914
|
+
};
|
|
893
915
|
}
|
|
894
916
|
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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: "
|
|
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 +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"]}
|