@agent-native/dispatch 0.18.0 → 0.19.0

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 (128) hide show
  1. package/dist/actions/create-workspace-resource.d.ts +13 -13
  2. package/dist/actions/delete-workspace-resource.d.ts +13 -13
  3. package/dist/actions/grant-vault-secrets-to-app.js +1 -1
  4. package/dist/actions/grant-vault-secrets-to-app.js.map +1 -1
  5. package/dist/actions/list-vault-audit.js +1 -1
  6. package/dist/actions/list-vault-audit.js.map +1 -1
  7. package/dist/actions/list-vault-secret-options.js +2 -9
  8. package/dist/actions/list-vault-secret-options.js.map +1 -1
  9. package/dist/actions/list-vault-secrets.js +1 -1
  10. package/dist/actions/list-vault-secrets.js.map +1 -1
  11. package/dist/actions/provider-api-register.d.ts +7 -7
  12. package/dist/actions/set-app-creation-settings.js +1 -1
  13. package/dist/actions/set-app-creation-settings.js.map +1 -1
  14. package/dist/actions/set-vault-access-settings.js +1 -1
  15. package/dist/actions/set-vault-access-settings.js.map +1 -1
  16. package/dist/actions/sync-vault-to-app.js +1 -1
  17. package/dist/actions/sync-vault-to-app.js.map +1 -1
  18. package/dist/actions/view-screen.js +4 -3
  19. package/dist/actions/view-screen.js.map +1 -1
  20. package/dist/components/admin-navigation.d.ts +15 -0
  21. package/dist/components/admin-navigation.d.ts.map +1 -0
  22. package/dist/components/admin-navigation.js +174 -0
  23. package/dist/components/admin-navigation.js.map +1 -0
  24. package/dist/components/automation-run-history-dialog.js +1 -1
  25. package/dist/components/automation-run-history-dialog.js.map +1 -1
  26. package/dist/components/index.d.ts +1 -0
  27. package/dist/components/index.d.ts.map +1 -1
  28. package/dist/components/index.js +1 -0
  29. package/dist/components/index.js.map +1 -1
  30. package/dist/components/layout/Header.d.ts.map +1 -1
  31. package/dist/components/layout/Header.js +9 -0
  32. package/dist/components/layout/Header.js.map +1 -1
  33. package/dist/components/layout/Layout.d.ts +4 -1
  34. package/dist/components/layout/Layout.d.ts.map +1 -1
  35. package/dist/components/layout/Layout.js +14 -117
  36. package/dist/components/layout/Layout.js.map +1 -1
  37. package/dist/components/layout/workspace-apps-rail.d.ts +5 -0
  38. package/dist/components/layout/workspace-apps-rail.d.ts.map +1 -0
  39. package/dist/components/layout/workspace-apps-rail.js +76 -0
  40. package/dist/components/layout/workspace-apps-rail.js.map +1 -0
  41. package/dist/hooks/use-navigation-state.js +39 -16
  42. package/dist/hooks/use-navigation-state.js.map +1 -1
  43. package/dist/lib/automation-display.js +1 -1
  44. package/dist/lib/automation-display.js.map +1 -1
  45. package/dist/routes/index.d.ts.map +1 -1
  46. package/dist/routes/index.js +22 -0
  47. package/dist/routes/index.js.map +1 -1
  48. package/dist/routes/pages/admin._index.d.ts +5 -0
  49. package/dist/routes/pages/admin._index.d.ts.map +1 -0
  50. package/dist/routes/pages/admin._index.js +19 -0
  51. package/dist/routes/pages/admin._index.js.map +1 -0
  52. package/dist/routes/pages/admin.d.ts +5 -0
  53. package/dist/routes/pages/admin.d.ts.map +1 -0
  54. package/dist/routes/pages/admin.js +10 -0
  55. package/dist/routes/pages/admin.js.map +1 -0
  56. package/dist/routes/pages/approval.js +2 -2
  57. package/dist/routes/pages/approval.js.map +1 -1
  58. package/dist/routes/pages/apps.$appId.d.ts.map +1 -1
  59. package/dist/routes/pages/apps.$appId.js +3 -2
  60. package/dist/routes/pages/apps.$appId.js.map +1 -1
  61. package/dist/routes/pages/integrations.d.ts.map +1 -1
  62. package/dist/routes/pages/integrations.js +2 -1
  63. package/dist/routes/pages/integrations.js.map +1 -1
  64. package/dist/routes/pages/operations.js +3 -3
  65. package/dist/routes/pages/operations.js.map +1 -1
  66. package/dist/routes/pages/settings.d.ts.map +1 -1
  67. package/dist/routes/pages/settings.js +1 -1
  68. package/dist/routes/pages/settings.js.map +1 -1
  69. package/dist/routes/pages/team.js +1 -1
  70. package/dist/routes/pages/team.js.map +1 -1
  71. package/dist/routes/pages/transactional-email.$appId.$id.d.ts.map +1 -1
  72. package/dist/routes/pages/transactional-email.$appId.$id.js +5 -2
  73. package/dist/routes/pages/transactional-email.$appId.$id.js.map +1 -1
  74. package/dist/routes/pages/transactional-email.d.ts.map +1 -1
  75. package/dist/routes/pages/transactional-email.js +6 -2
  76. package/dist/routes/pages/transactional-email.js.map +1 -1
  77. package/dist/routes/pages/vault.d.ts.map +1 -1
  78. package/dist/routes/pages/vault.js +20 -16
  79. package/dist/routes/pages/vault.js.map +1 -1
  80. package/dist/server/lib/app-creation-store.js +2 -2
  81. package/dist/server/lib/app-creation-store.js.map +1 -1
  82. package/dist/server/lib/thread-debug-store.d.ts.map +1 -1
  83. package/dist/server/lib/thread-debug-store.js +2 -1
  84. package/dist/server/lib/thread-debug-store.js.map +1 -1
  85. package/dist/server/lib/vault-store.d.ts +15 -0
  86. package/dist/server/lib/vault-store.d.ts.map +1 -1
  87. package/dist/server/lib/vault-store.js +110 -17
  88. package/dist/server/lib/vault-store.js.map +1 -1
  89. package/package.json +3 -3
  90. package/src/actions/grant-vault-secrets-to-app.ts +1 -1
  91. package/src/actions/list-vault-audit.ts +1 -1
  92. package/src/actions/list-vault-secret-options.ts +2 -9
  93. package/src/actions/list-vault-secrets.ts +1 -1
  94. package/src/actions/set-app-creation-settings.ts +1 -1
  95. package/src/actions/set-vault-access-settings.ts +1 -1
  96. package/src/actions/sync-vault-to-app.ts +1 -1
  97. package/src/actions/view-screen.spec.ts +1 -0
  98. package/src/actions/view-screen.ts +5 -3
  99. package/src/components/admin-navigation.tsx +278 -0
  100. package/src/components/automation-run-history-dialog.tsx +1 -1
  101. package/src/components/index.ts +1 -0
  102. package/src/components/layout/Header.tsx +8 -0
  103. package/src/components/layout/Layout.spec.tsx +85 -6
  104. package/src/components/layout/Layout.tsx +62 -206
  105. package/src/components/layout/workspace-apps-rail.tsx +154 -0
  106. package/src/hooks/use-navigation-state.spec.ts +11 -0
  107. package/src/hooks/use-navigation-state.ts +37 -16
  108. package/src/lib/automation-display.spec.ts +1 -1
  109. package/src/lib/automation-display.ts +1 -1
  110. package/src/routes/index.spec.ts +8 -0
  111. package/src/routes/index.ts +25 -0
  112. package/src/routes/pages/admin._index.tsx +39 -0
  113. package/src/routes/pages/admin.tsx +15 -0
  114. package/src/routes/pages/approval.tsx +2 -2
  115. package/src/routes/pages/apps.$appId.tsx +7 -2
  116. package/src/routes/pages/integrations.tsx +3 -2
  117. package/src/routes/pages/operations.tsx +3 -3
  118. package/src/routes/pages/settings.tsx +7 -3
  119. package/src/routes/pages/team.tsx +1 -1
  120. package/src/routes/pages/transactional-email.$appId.$id.tsx +9 -2
  121. package/src/routes/pages/transactional-email.tsx +6 -2
  122. package/src/routes/pages/vault.tsx +152 -89
  123. package/src/server/lib/app-creation-store.ts +2 -2
  124. package/src/server/lib/approval-fencing.spec.ts +69 -0
  125. package/src/server/lib/thread-debug-store.spec.ts +7 -3
  126. package/src/server/lib/thread-debug-store.ts +2 -1
  127. package/src/server/lib/vault-store.spec.ts +59 -0
  128. package/src/server/lib/vault-store.ts +123 -17
@@ -36,6 +36,31 @@ export const dispatchRoutes: RouteConfig = [
36
36
  route("chat", "./pages/chat.js"),
37
37
  route("chat/:threadId", "./pages/chat.js"),
38
38
  route("overview", "./pages/overview.js"),
39
+ route("admin", "./pages/admin.js", [
40
+ index("./pages/admin._index.js"),
41
+ route("apps", "./pages/apps.js"),
42
+ route("apps/:appId", "./pages/apps.$appId.js"),
43
+ route("new-app", "./pages/new-app.js"),
44
+ route("operations", "./pages/operations.js"),
45
+ route("metrics", "./pages/metrics.js"),
46
+ route("automations", "./pages/automations.js"),
47
+ route("approvals", "./pages/approvals.js"),
48
+ route("destinations", "./pages/destinations.js"),
49
+ route("integrations", "./pages/integrations.js"),
50
+ route("transactional-email", "./pages/transactional-email.js"),
51
+ route(
52
+ "transactional-email/:appId/:id",
53
+ "./pages/transactional-email.$appId.$id.js",
54
+ ),
55
+ route("vault", "./pages/vault.js"),
56
+ route("agents", "./pages/agents.js"),
57
+ route("workspace", "./pages/workspace.js"),
58
+ route("messaging", "./pages/messaging.js"),
59
+ route("identities", "./pages/identities.js"),
60
+ route("audit", "./pages/audit.js"),
61
+ route("dreams", "./pages/dreams.js"),
62
+ route("thread-debug", "./pages/thread-debug.js"),
63
+ ]),
39
64
  route("metrics", "./pages/metrics.js"),
40
65
  route("operations", "./pages/operations.js"),
41
66
  route("apps", "./pages/apps.js"),
@@ -0,0 +1,39 @@
1
+ import { useT } from "@agent-native/core/client/i18n";
2
+
3
+ import { DispatchShell } from "../../components/dispatch-shell";
4
+
5
+ export function meta() {
6
+ return [{ title: "Admin — Dispatch" }];
7
+ }
8
+
9
+ export default function AdminOverviewRoute() {
10
+ const t = useT();
11
+
12
+ return (
13
+ <DispatchShell
14
+ title={t("dispatch.nav.admin", { defaultValue: "Admin" })}
15
+ description={t("dispatch.pages.adminDescription", {
16
+ defaultValue: "Workspace controls and operations",
17
+ })}
18
+ >
19
+ <section className="max-w-2xl space-y-2">
20
+ <h2 className="text-base font-semibold text-foreground">
21
+ {t("dispatch.pages.adminWelcome", {
22
+ defaultValue: "Workspace control plane",
23
+ })}
24
+ </h2>
25
+ <p className="text-sm leading-6 text-muted-foreground">
26
+ {t("dispatch.pages.adminChooseArea", {
27
+ defaultValue:
28
+ "Manage apps, connections, automations, and operational tools from one place.",
29
+ })}
30
+ </p>
31
+ <p className="text-sm leading-6 text-muted-foreground">
32
+ {t("dispatch.pages.adminChooseAreaHint", {
33
+ defaultValue: "Choose an area from the Admin navigation.",
34
+ })}
35
+ </p>
36
+ </section>
37
+ </DispatchShell>
38
+ );
39
+ }
@@ -0,0 +1,15 @@
1
+ import { Outlet } from "react-router";
2
+
3
+ import { AdminShell } from "../../components/admin-navigation";
4
+
5
+ export function meta() {
6
+ return [{ title: "Admin — Dispatch" }];
7
+ }
8
+
9
+ export default function AdminRoute() {
10
+ return (
11
+ <AdminShell>
12
+ <Outlet />
13
+ </AdminShell>
14
+ );
15
+ }
@@ -175,7 +175,7 @@ export default function ApprovalPreviewRoute() {
175
175
  size="sm"
176
176
  variant="outline"
177
177
  className="mt-4 gap-1.5"
178
- onClick={() => postNavigate(appPath("/approvals"))}
178
+ onClick={() => postNavigate(appPath("/admin/approvals"))}
179
179
  >
180
180
  <IconArrowUpRight size={14} />
181
181
  View all approvals
@@ -288,7 +288,7 @@ export default function ApprovalPreviewRoute() {
288
288
  size="sm"
289
289
  variant="ghost"
290
290
  className="gap-1.5 text-muted-foreground"
291
- onClick={() => postNavigate(appPath("/approvals"))}
291
+ onClick={() => postNavigate(appPath("/admin/approvals"))}
292
292
  >
293
293
  <IconArrowUpRight size={14} />
294
294
  Open in app
@@ -7,7 +7,7 @@ import {
7
7
  IconClockHour4,
8
8
  } from "@tabler/icons-react";
9
9
  import { useEffect, useMemo } from "react";
10
- import { Link, useParams } from "react-router";
10
+ import { Link, useLocation, useParams } from "react-router";
11
11
 
12
12
  import { ActionQueryError } from "../../components/action-query-error";
13
13
  import { DispatchShell } from "../../components/dispatch-shell";
@@ -25,6 +25,7 @@ export function meta() {
25
25
 
26
26
  export default function WorkspaceAppRoute() {
27
27
  const t = useT();
28
+ const location = useLocation();
28
29
  const { appId } = useParams();
29
30
  const appsQuery = useActionQuery("list-workspace-apps", {
30
31
  includeAgentCards: false,
@@ -49,7 +50,11 @@ export default function WorkspaceAppRoute() {
49
50
  >
50
51
  <div className="max-w-2xl rounded-lg bg-card p-5">
51
52
  <Button asChild size="sm" variant="ghost" className="-ml-2 mb-4">
52
- <Link to="/apps">
53
+ <Link
54
+ to={
55
+ location.pathname.startsWith("/admin/") ? "/admin/apps" : "/apps"
56
+ }
57
+ >
53
58
  <IconArrowLeft size={15} className="mr-1.5" />
54
59
  {t("dispatch.nav.apps")}
55
60
  </Link>
@@ -12,6 +12,7 @@ import {
12
12
  } from "@tabler/icons-react";
13
13
  import { useQueryClient } from "@tanstack/react-query";
14
14
  import { useMemo, useState } from "react";
15
+ import { Link } from "react-router";
15
16
  import { toast } from "sonner";
16
17
 
17
18
  import { ActionQueryError } from "../../components/action-query-error";
@@ -449,9 +450,9 @@ export default function ConnectionsRoute() {
449
450
  </div>
450
451
  <div className="flex items-center justify-end gap-1.5 border-t px-4 py-2.5 text-xs text-muted-foreground">
451
452
  <IconLink size={12} />
452
- <a href="/vault" className="hover:underline">
453
+ <Link to="/admin/vault" className="hover:underline">
453
454
  Open vault for advanced sharing
454
- </a>
455
+ </Link>
455
456
  </div>
456
457
  </CollapsibleContent>
457
458
  </Collapsible>
@@ -30,17 +30,17 @@ function OperationsShortcuts() {
30
30
 
31
31
  const tools = [
32
32
  {
33
- to: "/thread-debug",
33
+ to: "/admin/thread-debug",
34
34
  icon: IconMessages,
35
35
  title: t("dispatch.nav.threadDebug", { defaultValue: "Thread debug" }),
36
36
  },
37
37
  {
38
- to: "/audit",
38
+ to: "/admin/audit",
39
39
  icon: IconHistory,
40
40
  title: t("dispatch.nav.audit"),
41
41
  },
42
42
  {
43
- to: "/destinations",
43
+ to: "/admin/destinations",
44
44
  icon: IconSend,
45
45
  title: t("dispatch.pages.deliveryQueue"),
46
46
  },
@@ -62,7 +62,7 @@ export default function SettingsRoute() {
62
62
  </CardHeader>
63
63
  <CardContent>
64
64
  <Button variant="outline" asChild>
65
- <Link to="/workspace">
65
+ <Link to="/admin/workspace">
66
66
  {t("settings.openResourceSettings")}
67
67
  </Link>
68
68
  </Button>
@@ -80,7 +80,9 @@ export default function SettingsRoute() {
80
80
  </CardHeader>
81
81
  <CardContent>
82
82
  <Button variant="outline" asChild>
83
- <Link to="/automations">{t("settings.openAutomations")}</Link>
83
+ <Link to="/admin/automations">
84
+ {t("settings.openAutomations")}
85
+ </Link>
84
86
  </Button>
85
87
  </CardContent>
86
88
  </Card>
@@ -96,7 +98,9 @@ export default function SettingsRoute() {
96
98
  </CardHeader>
97
99
  <CardContent>
98
100
  <Button variant="outline" asChild>
99
- <Link to="/destinations">{t("settings.openDelivery")}</Link>
101
+ <Link to="/admin/destinations">
102
+ {t("settings.openDelivery")}
103
+ </Link>
100
104
  </Button>
101
105
  </CardContent>
102
106
  </Card>
@@ -5,5 +5,5 @@ export function meta() {
5
5
  }
6
6
 
7
7
  export default function TeamRoute() {
8
- return <Navigate to="/settings#organization" replace />;
8
+ return <Navigate to="/settings/organization" replace />;
9
9
  }
@@ -3,7 +3,7 @@ import { useT } from "@agent-native/core/client/i18n";
3
3
  import { IconAlertTriangle, IconArrowLeft } from "@tabler/icons-react";
4
4
  import { useQueries, useQuery } from "@tanstack/react-query";
5
5
  import { useMemo } from "react";
6
- import { Link, useParams } from "react-router";
6
+ import { Link, useLocation, useParams } from "react-router";
7
7
 
8
8
  import {
9
9
  aggregateSharedEmails,
@@ -155,6 +155,7 @@ function useEmailDetail(appId: string, id: string) {
155
155
 
156
156
  export default function TransactionalEmailDetailRoute() {
157
157
  const t = useT();
158
+ const location = useLocation();
158
159
  const params = useParams<{ appId: string; id: string }>();
159
160
  const appId = params.appId ?? "";
160
161
  const id = params.id ?? "";
@@ -213,7 +214,13 @@ export default function TransactionalEmailDetailRoute() {
213
214
  >
214
215
  <div className="max-w-3xl space-y-4">
215
216
  <Button asChild size="sm" variant="ghost" className="-ml-2">
216
- <Link to="/transactional-email">
217
+ <Link
218
+ to={
219
+ location.pathname.startsWith("/admin/")
220
+ ? "/admin/transactional-email"
221
+ : "/transactional-email"
222
+ }
223
+ >
217
224
  <IconArrowLeft size={15} className="mr-1.5" />
218
225
  {t("dispatch.transactionalEmail.title")}
219
226
  </Link>
@@ -10,7 +10,7 @@ import {
10
10
  } from "@tabler/icons-react";
11
11
  import { useQueries, useQuery } from "@tanstack/react-query";
12
12
  import { useMemo, useState } from "react";
13
- import { Link } from "react-router";
13
+ import { Link, useLocation } from "react-router";
14
14
 
15
15
  import {
16
16
  aggregateSharedEmails,
@@ -180,14 +180,18 @@ function EmailRow({
180
180
  engagementLoading: boolean;
181
181
  }) {
182
182
  const t = useT();
183
+ const location = useLocation();
183
184
  const [previewOpen, setPreviewOpen] = useState(false);
184
185
  const [activityOpen, setActivityOpen] = useState(false);
186
+ const detailBase = location.pathname.startsWith("/admin/")
187
+ ? "/admin/transactional-email"
188
+ : "/transactional-email";
185
189
 
186
190
  return (
187
191
  <TableRow>
188
192
  <TableCell className="align-top">
189
193
  <Link
190
- to={`/transactional-email/${appId}/${email.id}`}
194
+ to={`${detailBase}/${appId}/${email.id}`}
191
195
  className="text-sm font-medium text-foreground hover:underline"
192
196
  >
193
197
  {email.name}
@@ -2,6 +2,7 @@ import {
2
2
  useActionMutation,
3
3
  useActionQuery,
4
4
  } from "@agent-native/core/client/hooks";
5
+ import { useOrgRole } from "@agent-native/core/client/org";
5
6
  import {
6
7
  IconChevronDown,
7
8
  IconChevronRight,
@@ -648,7 +649,13 @@ function SecretRow({
648
649
  );
649
650
  }
650
651
 
651
- function RequestRow({ request }: { request: any }) {
652
+ function RequestRow({
653
+ request,
654
+ canManage,
655
+ }: {
656
+ request: any;
657
+ canManage: boolean;
658
+ }) {
652
659
  const [secretValue, setSecretValue] = useState("");
653
660
 
654
661
  const approve = useActionMutation("approve-vault-request", {
@@ -706,7 +713,7 @@ function RequestRow({ request }: { request: any }) {
706
713
  </Badge>
707
714
  )}
708
715
  </div>
709
- {request.status === "pending" && (
716
+ {request.status === "pending" && canManage ? (
710
717
  <div className="mt-3 flex items-end gap-2 border-t pt-3">
711
718
  <div className="flex-1 space-y-1">
712
719
  <Label className="text-xs">Secret value to provision</Label>
@@ -734,17 +741,45 @@ function RequestRow({ request }: { request: any }) {
734
741
  Deny
735
742
  </Button>
736
743
  </div>
737
- )}
744
+ ) : request.status === "pending" ? (
745
+ <p className="mt-3 border-t pt-3 text-xs text-muted-foreground">
746
+ Waiting for a workspace owner or admin to review this request.
747
+ </p>
748
+ ) : null}
738
749
  </div>
739
750
  );
740
751
  }
741
752
 
742
753
  export default function VaultRoute() {
743
- const secretsQuery = useActionQuery("list-vault-secrets", {});
744
- const grantsQuery = useActionQuery("list-vault-grants", {});
745
- const requestsQuery = useActionQuery("list-vault-requests", {});
746
- const auditQuery = useActionQuery("list-vault-audit", { limit: 20 });
747
- const accessQuery = useActionQuery("get-vault-access-settings", {});
754
+ const { org, role, isLoading: orgLoading, error: orgError } = useOrgRole();
755
+ const accessReady = !orgLoading && !orgError && !!org;
756
+ const canManageVault =
757
+ accessReady && (!org.orgId || role === "owner" || role === "admin");
758
+ const secretsQuery = useActionQuery(
759
+ "list-vault-secrets",
760
+ {},
761
+ { enabled: canManageVault },
762
+ );
763
+ const grantsQuery = useActionQuery(
764
+ "list-vault-grants",
765
+ {},
766
+ { enabled: canManageVault },
767
+ );
768
+ const requestsQuery = useActionQuery(
769
+ "list-vault-requests",
770
+ {},
771
+ { enabled: accessReady },
772
+ );
773
+ const auditQuery = useActionQuery(
774
+ "list-vault-audit",
775
+ { limit: 20 },
776
+ { enabled: canManageVault },
777
+ );
778
+ const accessQuery = useActionQuery(
779
+ "get-vault-access-settings",
780
+ {},
781
+ { enabled: accessReady },
782
+ );
748
783
  const { data: secrets, isLoading: secretsLoading } = secretsQuery;
749
784
  const { data: grants } = grantsQuery;
750
785
  const { data: requests } = requestsQuery;
@@ -787,13 +822,31 @@ export default function VaultRoute() {
787
822
  </Badge>
788
823
  )}
789
824
  </TabsTrigger>
790
- <TabsTrigger value="audit">Audit</TabsTrigger>
825
+ {canManageVault ? (
826
+ <TabsTrigger value="audit">Audit</TabsTrigger>
827
+ ) : null}
791
828
  </TabsList>
792
829
 
793
830
  <TabsContent value="secrets" className="mt-4 space-y-3">
794
- {secretsQuery.isError ||
795
- grantsQuery.isError ||
796
- accessQuery.isError ? (
831
+ {!accessReady ? (
832
+ <div className="rounded-2xl border border-dashed px-6 py-12 text-center text-sm text-muted-foreground">
833
+ Checking workspace access…
834
+ </div>
835
+ ) : !canManageVault ? (
836
+ <div className="rounded-2xl border border-dashed px-6 py-12 text-center">
837
+ <IconKey size={32} className="mx-auto text-muted-foreground/50" />
838
+ <h3 className="mt-3 text-sm font-medium text-foreground">
839
+ Vault management is restricted
840
+ </h3>
841
+ <p className="mx-auto mt-1 max-w-md text-sm text-muted-foreground">
842
+ Workspace owners and admins manage shared secret values. Use a
843
+ request from the app that needs a key, and an admin can review
844
+ it from the Requests tab.
845
+ </p>
846
+ </div>
847
+ ) : secretsQuery.isError ||
848
+ grantsQuery.isError ||
849
+ accessQuery.isError ? (
797
850
  <ActionQueryError
798
851
  error={
799
852
  secretsQuery.error ?? grantsQuery.error ?? accessQuery.error
@@ -805,60 +858,64 @@ export default function VaultRoute() {
805
858
  }}
806
859
  />
807
860
  ) : null}
808
- <VaultAccessSettingsCard mode={accessMode} />
809
-
810
- <div className="flex items-center justify-between">
811
- <div className="flex items-center gap-2 text-sm text-muted-foreground">
812
- <IconKey size={16} />
813
- {secretsLoading ? (
814
- <Skeleton className="h-4 w-20" />
815
- ) : (
816
- <span>
817
- {`${secrets?.length || 0} secret${(secrets?.length || 0) !== 1 ? "s" : ""}`}
818
- </span>
819
- )}
820
- </div>
821
- <AddSecretDialog />
822
- </div>
823
-
824
- {!secretsQuery.isError &&
825
- secretsLoading &&
826
- (secrets ?? []).length === 0
827
- ? Array.from({ length: 3 }).map((_, index) => (
828
- <div
829
- key={index}
830
- className="rounded-2xl bg-card px-5 py-4 space-y-2"
831
- >
832
- <Skeleton className="h-4 w-1/3" />
833
- <Skeleton className="h-3 w-2/3" />
861
+ {accessReady && canManageVault ? (
862
+ <>
863
+ <VaultAccessSettingsCard mode={accessMode} />
864
+
865
+ <div className="flex items-center justify-between">
866
+ <div className="flex items-center gap-2 text-sm text-muted-foreground">
867
+ <IconKey size={16} />
868
+ {secretsLoading ? (
869
+ <Skeleton className="h-4 w-20" />
870
+ ) : (
871
+ <span>
872
+ {`${secrets?.length || 0} secret${(secrets?.length || 0) !== 1 ? "s" : ""}`}
873
+ </span>
874
+ )}
834
875
  </div>
835
- ))
836
- : (secrets || []).map((secret: any) => (
837
- <SecretRow
838
- key={secret.id}
839
- secret={secret}
840
- grants={grantsBySecret[secret.id] || []}
841
- accessMode={accessMode}
842
- />
843
- ))}
844
-
845
- {!secretsQuery.isError &&
846
- !secretsLoading &&
847
- (secrets?.length || 0) === 0 && (
848
- <div className="rounded-2xl border border-dashed px-6 py-12 text-center">
849
- <IconKey
850
- size={32}
851
- className="mx-auto text-muted-foreground/50"
852
- />
853
- <h3 className="mt-3 text-sm font-medium text-foreground">
854
- No secrets yet
855
- </h3>
856
- <p className="mt-1 text-sm text-muted-foreground">
857
- Add your first secret to start sharing credentials across
858
- workspace apps.
859
- </p>
876
+ <AddSecretDialog />
860
877
  </div>
861
- )}
878
+
879
+ {!secretsQuery.isError &&
880
+ secretsLoading &&
881
+ (secrets ?? []).length === 0
882
+ ? Array.from({ length: 3 }).map((_, index) => (
883
+ <div
884
+ key={index}
885
+ className="rounded-2xl bg-card px-5 py-4 space-y-2"
886
+ >
887
+ <Skeleton className="h-4 w-1/3" />
888
+ <Skeleton className="h-3 w-2/3" />
889
+ </div>
890
+ ))
891
+ : (secrets || []).map((secret: any) => (
892
+ <SecretRow
893
+ key={secret.id}
894
+ secret={secret}
895
+ grants={grantsBySecret[secret.id] || []}
896
+ accessMode={accessMode}
897
+ />
898
+ ))}
899
+
900
+ {!secretsQuery.isError &&
901
+ !secretsLoading &&
902
+ (secrets?.length || 0) === 0 && (
903
+ <div className="rounded-2xl border border-dashed px-6 py-12 text-center">
904
+ <IconKey
905
+ size={32}
906
+ className="mx-auto text-muted-foreground/50"
907
+ />
908
+ <h3 className="mt-3 text-sm font-medium text-foreground">
909
+ No secrets yet
910
+ </h3>
911
+ <p className="mt-1 text-sm text-muted-foreground">
912
+ Add your first secret to start sharing credentials across
913
+ workspace apps.
914
+ </p>
915
+ </div>
916
+ )}
917
+ </>
918
+ ) : null}
862
919
  </TabsContent>
863
920
 
864
921
  <TabsContent value="requests" className="mt-4 space-y-3">
@@ -869,7 +926,11 @@ export default function VaultRoute() {
869
926
  />
870
927
  ) : null}
871
928
  {(requests || []).map((request: any) => (
872
- <RequestRow key={request.id} request={request} />
929
+ <RequestRow
930
+ key={request.id}
931
+ request={request}
932
+ canManage={canManageVault}
933
+ />
873
934
  ))}
874
935
  {!requestsQuery.isError && (requests?.length || 0) === 0 && (
875
936
  <div className="rounded-2xl border border-dashed px-6 py-12 text-center text-sm text-muted-foreground">
@@ -878,32 +939,34 @@ export default function VaultRoute() {
878
939
  )}
879
940
  </TabsContent>
880
941
 
881
- <TabsContent value="audit" className="mt-4 space-y-2">
882
- {auditQuery.isError ? (
883
- <ActionQueryError
884
- error={auditQuery.error}
885
- onRetry={() => void auditQuery.refetch()}
886
- />
887
- ) : null}
888
- {(audit || []).map((event: any) => (
889
- <div
890
- key={event.id}
891
- className="rounded-xl border bg-muted/30 px-4 py-3"
892
- >
893
- <div className="text-sm font-medium text-foreground">
894
- {event.summary}
942
+ {canManageVault ? (
943
+ <TabsContent value="audit" className="mt-4 space-y-2">
944
+ {auditQuery.isError ? (
945
+ <ActionQueryError
946
+ error={auditQuery.error}
947
+ onRetry={() => void auditQuery.refetch()}
948
+ />
949
+ ) : null}
950
+ {(audit || []).map((event: any) => (
951
+ <div
952
+ key={event.id}
953
+ className="rounded-xl border bg-muted/30 px-4 py-3"
954
+ >
955
+ <div className="text-sm font-medium text-foreground">
956
+ {event.summary}
957
+ </div>
958
+ <div className="mt-1 text-xs text-muted-foreground">
959
+ {event.actor} · {new Date(event.createdAt).toLocaleString()}
960
+ </div>
895
961
  </div>
896
- <div className="mt-1 text-xs text-muted-foreground">
897
- {event.actor} · {new Date(event.createdAt).toLocaleString()}
962
+ ))}
963
+ {!auditQuery.isError && (audit?.length || 0) === 0 && (
964
+ <div className="rounded-2xl border border-dashed px-6 py-12 text-center text-sm text-muted-foreground">
965
+ No vault activity yet.
898
966
  </div>
899
- </div>
900
- ))}
901
- {!auditQuery.isError && (audit?.length || 0) === 0 && (
902
- <div className="rounded-2xl border border-dashed px-6 py-12 text-center text-sm text-muted-foreground">
903
- No vault activity yet.
904
- </div>
905
- )}
906
- </TabsContent>
967
+ )}
968
+ </TabsContent>
969
+ ) : null}
907
970
  </Tabs>
908
971
  </DispatchShell>
909
972
  );
@@ -27,7 +27,7 @@ import {
27
27
  recordAudit,
28
28
  resolveLinkedOwner,
29
29
  } from "./dispatch-store.js";
30
- import { createRequest, listSecrets } from "./vault-store.js";
30
+ import { createRequest, listSecretOptions } from "./vault-store.js";
31
31
  import {
32
32
  grantWorkspaceResourcesToApp,
33
33
  listWorkspaceResourceOptions,
@@ -2013,7 +2013,7 @@ export async function startWorkspaceAppCreation(input: {
2013
2013
  }
2014
2014
 
2015
2015
  const selectedKeys = input.secretIds?.length
2016
- ? (await listSecrets())
2016
+ ? (await listSecretOptions())
2017
2017
  .filter((secret) => input.secretIds?.includes(secret.id))
2018
2018
  .map((secret) => secret.credentialKey)
2019
2019
  : [];