@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
@@ -1,4 +1,5 @@
1
1
  import crypto from "node:crypto";
2
+ import { getDbExec } from "@agent-native/core/db";
2
3
  import { and, desc, eq, isNull, or, sql } from "@agent-native/core/db/schema";
3
4
  import { ssrfSafeFetch } from "@agent-native/core/extensions/url-safety";
4
5
  import { deleteAppSecret, listAppSecretsForScope, writeAppSecret, } from "@agent-native/core/secrets";
@@ -27,6 +28,12 @@ function ctxScope(table, ctx) {
27
28
  }
28
29
  return or(eq(table.ownerEmail, ctx.ownerEmail), eq(table.orgId, ctx.orgId));
29
30
  }
31
+ /** Scope an approval operation to the active tenant, never just the owner email. */
32
+ function tenantScope(table, ctx) {
33
+ if (ctx.orgId)
34
+ return eq(table.orgId, ctx.orgId);
35
+ return and(eq(table.ownerEmail, ctx.ownerEmail), isNull(table.orgId));
36
+ }
30
37
  /** Build a ctx that scopes to a specific row's owner/org (used when a
31
38
  * request approver acts on behalf of the original requester so the
32
39
  * created secret lands in the request's org). */
@@ -85,6 +92,42 @@ export function isTrustedEnvVarSyncAgentUrl(agentUrl) {
85
92
  function scopedFilter(table) {
86
93
  return ctxScope(table, requireVaultCtx());
87
94
  }
95
+ /**
96
+ * Shared vault values and grants are organization administration data. Keep
97
+ * the member path available for request creation and safe key metadata, but
98
+ * fail closed before a member can read or mutate a raw secret.
99
+ */
100
+ export async function assertCanManageVault() {
101
+ const orgId = currentOrgId();
102
+ if (!orgId)
103
+ return;
104
+ const email = currentOwnerEmail().trim().toLowerCase();
105
+ let role = null;
106
+ try {
107
+ const result = await getDbExec().execute({
108
+ sql: "SELECT role FROM org_members WHERE org_id = ? AND LOWER(email) = ? LIMIT 1",
109
+ args: [orgId, email],
110
+ });
111
+ role = result.rows[0]?.role;
112
+ }
113
+ catch {
114
+ // coercion-ok: unknown membership is treated as unauthorized below.
115
+ // A failed membership check must not become an authorization bypass.
116
+ }
117
+ if (role !== "owner" && role !== "admin") {
118
+ throw new Error("Only organization owners and admins can manage the workspace vault.");
119
+ }
120
+ }
121
+ export async function canManageVault() {
122
+ try {
123
+ await assertCanManageVault();
124
+ return true;
125
+ }
126
+ catch {
127
+ // coercion-ok: the UI must treat an authorization lookup failure as no access.
128
+ return false;
129
+ }
130
+ }
88
131
  function normalizeCredentialKey(value) {
89
132
  return value.trim();
90
133
  }
@@ -108,6 +151,7 @@ export async function getVaultAccessSettings() {
108
151
  };
109
152
  }
110
153
  export async function setVaultAccessSettings(input) {
154
+ await assertCanManageVault();
111
155
  const scope = vaultAccessScope();
112
156
  const next = { mode: parseVaultAccessMode(input.mode) };
113
157
  if (scope.scope === "org") {
@@ -144,6 +188,7 @@ export async function recordVaultAudit(input) {
144
188
  });
145
189
  }
146
190
  export async function listVaultAudit(limit = 50) {
191
+ await assertCanManageVault();
147
192
  const db = getDb();
148
193
  return db
149
194
  .select()
@@ -154,6 +199,7 @@ export async function listVaultAudit(limit = 50) {
154
199
  }
155
200
  // ─── Secrets ──────────────────────────────────────────────────────
156
201
  export async function listSecrets() {
202
+ await assertCanManageVault();
157
203
  const db = getDb();
158
204
  return db
159
205
  .select()
@@ -161,7 +207,23 @@ export async function listSecrets() {
161
207
  .where(scopedFilter(schema.vaultSecrets))
162
208
  .orderBy(desc(schema.vaultSecrets.updatedAt));
163
209
  }
210
+ /** Safe metadata for app creation and integration readiness checks. */
211
+ export async function listSecretOptions() {
212
+ const db = getDb();
213
+ return db
214
+ .select({
215
+ id: schema.vaultSecrets.id,
216
+ name: schema.vaultSecrets.name,
217
+ credentialKey: schema.vaultSecrets.credentialKey,
218
+ provider: schema.vaultSecrets.provider,
219
+ description: schema.vaultSecrets.description,
220
+ })
221
+ .from(schema.vaultSecrets)
222
+ .where(scopedFilter(schema.vaultSecrets))
223
+ .orderBy(desc(schema.vaultSecrets.updatedAt));
224
+ }
164
225
  export async function getSecret(secretId, ctx) {
226
+ await assertCanManageVault();
165
227
  const db = getDb();
166
228
  const [row] = await db
167
229
  .select()
@@ -171,6 +233,7 @@ export async function getSecret(secretId, ctx) {
171
233
  return row ?? null;
172
234
  }
173
235
  export async function createSecret(input, ctx = requireVaultCtx()) {
236
+ await assertCanManageVault();
174
237
  const db = getDb();
175
238
  const timestamp = now();
176
239
  const credentialKey = normalizeCredentialKey(input.credentialKey);
@@ -246,6 +309,7 @@ export async function createSecret(input, ctx = requireVaultCtx()) {
246
309
  return created;
247
310
  }
248
311
  export async function updateSecret(secretId, input, ctx = requireVaultCtx()) {
312
+ await assertCanManageVault();
249
313
  const db = getDb();
250
314
  const existing = await getSecret(secretId, ctx);
251
315
  if (!existing)
@@ -328,6 +392,7 @@ export async function updateSecret(secretId, input, ctx = requireVaultCtx()) {
328
392
  return updated;
329
393
  }
330
394
  export async function deleteSecret(secretId, ctx = requireVaultCtx()) {
395
+ await assertCanManageVault();
331
396
  const db = getDb();
332
397
  const existing = await getSecret(secretId, ctx);
333
398
  if (!existing)
@@ -360,6 +425,7 @@ export async function deleteSecret(secretId, ctx = requireVaultCtx()) {
360
425
  }
361
426
  // ─── Grants ──────────────────────────────────────────────────────
362
427
  export async function listGrants(filter) {
428
+ await assertCanManageVault();
363
429
  const db = getDb();
364
430
  const conditions = [scopedFilter(schema.vaultGrants)];
365
431
  if (filter?.secretId) {
@@ -375,6 +441,7 @@ export async function listGrants(filter) {
375
441
  .orderBy(desc(schema.vaultGrants.updatedAt));
376
442
  }
377
443
  export async function getGrant(grantId, ctx = requireVaultCtx()) {
444
+ await assertCanManageVault();
378
445
  const db = getDb();
379
446
  const [row] = await db
380
447
  .select()
@@ -384,6 +451,7 @@ export async function getGrant(grantId, ctx = requireVaultCtx()) {
384
451
  return row ?? null;
385
452
  }
386
453
  export async function createGrant(secretId, appId, ctx = requireVaultCtx()) {
454
+ await assertCanManageVault();
387
455
  const db = getDb();
388
456
  const secret = await getSecret(secretId, ctx);
389
457
  if (!secret)
@@ -453,6 +521,7 @@ export async function createGrant(secretId, appId, ctx = requireVaultCtx()) {
453
521
  return getGrant(grantId);
454
522
  }
455
523
  export async function grantSecretsToApp(secretIds, appId, ctx = requireVaultCtx()) {
524
+ await assertCanManageVault();
456
525
  const access = await getVaultAccessSettings();
457
526
  const uniqueSecretIds = Array.from(new Set(secretIds));
458
527
  if (access.mode === "all-apps") {
@@ -481,6 +550,7 @@ export async function grantSecretsToApp(secretIds, appId, ctx = requireVaultCtx(
481
550
  return { appId, accessMode: access.mode, created, skipped };
482
551
  }
483
552
  export async function revokeGrant(grantId, ctx = requireVaultCtx()) {
553
+ await assertCanManageVault();
484
554
  const db = getDb();
485
555
  const grant = await getGrant(grantId, ctx);
486
556
  if (!grant)
@@ -613,6 +683,7 @@ export async function cleanupSyncedCredentialKeysIfUnused(ctx, candidateKeys) {
613
683
  }
614
684
  // ─── Sync ──────────────────────────────────────────────────────
615
685
  export async function syncGrantsToApp(appId, ctx = requireVaultCtx()) {
686
+ await assertCanManageVault();
616
687
  const db = getDb();
617
688
  const access = await getVaultAccessSettings();
618
689
  const agents = await discoverAgents("dispatch");
@@ -736,7 +807,13 @@ export async function syncGrantsToApp(appId, ctx = requireVaultCtx()) {
736
807
  // ─── Requests ──────────────────────────────────────────────────────
737
808
  export async function listRequests(filter) {
738
809
  const db = getDb();
739
- const conditions = [scopedFilter(schema.vaultRequests)];
810
+ const ctx = requireVaultCtx();
811
+ const isAdmin = await canManageVault();
812
+ const conditions = [
813
+ isAdmin
814
+ ? ctxScope(schema.vaultRequests, ctx)
815
+ : eq(schema.vaultRequests.ownerEmail, ctx.ownerEmail),
816
+ ];
740
817
  if (filter?.status) {
741
818
  conditions.push(eq(schema.vaultRequests.status, filter.status));
742
819
  }
@@ -755,6 +832,15 @@ export async function getRequest(requestId, ctx = requireVaultCtx()) {
755
832
  .limit(1);
756
833
  return row ?? null;
757
834
  }
835
+ async function getRequestForTenant(requestId, ctx) {
836
+ const db = getDb();
837
+ const [row] = await db
838
+ .select()
839
+ .from(schema.vaultRequests)
840
+ .where(and(eq(schema.vaultRequests.id, requestId), tenantScope(schema.vaultRequests, ctx)))
841
+ .limit(1);
842
+ return row ?? null;
843
+ }
758
844
  export async function createRequest(input) {
759
845
  const db = getDb();
760
846
  const timestamp = now();
@@ -784,8 +870,9 @@ export async function createRequest(input) {
784
870
  return getRequest(requestId);
785
871
  }
786
872
  export async function approveRequest(requestId, secretValue, secretName, ctx = requireVaultCtx()) {
873
+ await assertCanManageVault();
787
874
  const db = getDb();
788
- const request = await getRequest(requestId, ctx);
875
+ const request = await getRequestForTenant(requestId, ctx);
789
876
  if (!request)
790
877
  throw new Error("Request not found");
791
878
  const timestamp = now();
@@ -804,10 +891,10 @@ export async function approveRequest(requestId, secretValue, secretName, ctx = r
804
891
  status: "applying",
805
892
  updatedAt: timestamp,
806
893
  })
807
- .where(and(eq(schema.vaultRequests.id, requestId), ctxScope(schema.vaultRequests, ctx), or(eq(schema.vaultRequests.status, "pending"), and(eq(schema.vaultRequests.status, "applying"), sql `${schema.vaultRequests.updatedAt} < ${staleApplyingBefore}`))))
894
+ .where(and(eq(schema.vaultRequests.id, requestId), tenantScope(schema.vaultRequests, ctx), or(eq(schema.vaultRequests.status, "pending"), and(eq(schema.vaultRequests.status, "applying"), sql `${schema.vaultRequests.updatedAt} < ${staleApplyingBefore}`))))
808
895
  .returning();
809
896
  if (claimed.length === 0) {
810
- const current = await getRequest(requestId, ctx);
897
+ const current = await getRequestForTenant(requestId, ctx);
811
898
  if (current?.status === "applying") {
812
899
  throw new Error("Vault request is already being applied");
813
900
  }
@@ -840,7 +927,7 @@ export async function approveRequest(requestId, secretValue, secretName, ctx = r
840
927
  await db
841
928
  .update(schema.vaultRequests)
842
929
  .set({ status: "pending", updatedAt: now() })
843
- .where(and(eq(schema.vaultRequests.id, requestId), ctxScope(schema.vaultRequests, ctx), eq(schema.vaultRequests.status, "applying")));
930
+ .where(and(eq(schema.vaultRequests.id, requestId), tenantScope(schema.vaultRequests, ctx), eq(schema.vaultRequests.status, "applying")));
844
931
  throw error;
845
932
  }
846
933
  await db
@@ -851,18 +938,19 @@ export async function approveRequest(requestId, secretValue, secretName, ctx = r
851
938
  reviewedAt: timestamp,
852
939
  updatedAt: now(),
853
940
  })
854
- .where(and(eq(schema.vaultRequests.id, requestId), ctxScope(schema.vaultRequests, ctx), eq(schema.vaultRequests.status, "applying")));
941
+ .where(and(eq(schema.vaultRequests.id, requestId), tenantScope(schema.vaultRequests, ctx), eq(schema.vaultRequests.status, "applying")));
855
942
  await recordVaultAudit({
856
943
  action: "request.approved",
857
944
  appId: claimedRequest.appId,
858
945
  summary: `Approved ${claimedRequest.credentialKey} for ${claimedRequest.appId} (requested by ${claimedRequest.requestedBy})`,
859
946
  metadata: { requestId, reviewer },
860
947
  });
861
- return getRequest(requestId, ctx);
948
+ return getRequestForTenant(requestId, ctx);
862
949
  }
863
950
  export async function denyRequest(requestId, reason, ctx = requireVaultCtx()) {
951
+ await assertCanManageVault();
864
952
  const db = getDb();
865
- const request = await getRequest(requestId, ctx);
953
+ const request = await getRequestForTenant(requestId, ctx);
866
954
  if (!request)
867
955
  throw new Error("Request not found");
868
956
  const timestamp = now();
@@ -875,10 +963,10 @@ export async function denyRequest(requestId, reason, ctx = requireVaultCtx()) {
875
963
  reviewedAt: timestamp,
876
964
  updatedAt: timestamp,
877
965
  })
878
- .where(and(eq(schema.vaultRequests.id, requestId), ctxScope(schema.vaultRequests, ctx), eq(schema.vaultRequests.status, "pending")))
966
+ .where(and(eq(schema.vaultRequests.id, requestId), tenantScope(schema.vaultRequests, ctx), eq(schema.vaultRequests.status, "pending")))
879
967
  .returning();
880
968
  if (claimed.length === 0) {
881
- return getRequest(requestId, ctx);
969
+ return getRequestForTenant(requestId, ctx);
882
970
  }
883
971
  const claimedRequest = claimed[0];
884
972
  await recordVaultAudit({
@@ -887,13 +975,13 @@ export async function denyRequest(requestId, reason, ctx = requireVaultCtx()) {
887
975
  summary: `Denied ${claimedRequest.credentialKey} for ${claimedRequest.appId} (requested by ${claimedRequest.requestedBy})`,
888
976
  metadata: { requestId, reviewer, reason },
889
977
  });
890
- return getRequest(requestId, ctx);
978
+ return getRequestForTenant(requestId, ctx);
891
979
  }
892
980
  export async function listIntegrationsCatalog() {
893
981
  const access = await getVaultAccessSettings();
894
982
  const agents = await discoverAgents("dispatch");
895
- const grants = await listGrants();
896
- const secrets = await listSecrets();
983
+ const grants = (await canManageVault()) ? await listGrants() : [];
984
+ const secrets = await listSecretOptions();
897
985
  const secretByKey = new Map(secrets.map((s) => [s.credentialKey, s]));
898
986
  const results = [];
899
987
  for (const agent of agents) {
@@ -955,9 +1043,10 @@ export async function listIntegrationsCatalog() {
955
1043
  }
956
1044
  // ─── Vault Overview (for dashboard) ──────────────────────────────
957
1045
  export async function listVaultOverview() {
1046
+ const isAdmin = await canManageVault();
958
1047
  const [secrets, grants, requests, access] = await Promise.all([
959
- listSecrets(),
960
- listGrants(),
1048
+ listSecretOptions(),
1049
+ isAdmin ? listGrants() : Promise.resolve([]),
961
1050
  listRequests(),
962
1051
  getVaultAccessSettings(),
963
1052
  ]);
@@ -965,8 +1054,12 @@ export async function listVaultOverview() {
965
1054
  return {
966
1055
  accessMode: access.mode,
967
1056
  secretCount: secrets.length,
968
- activeGrantCount: access.mode === "all-apps" ? secrets.length : manualGrantCount,
969
- manualGrantCount,
1057
+ activeGrantCount: access.mode === "all-apps"
1058
+ ? secrets.length
1059
+ : isAdmin
1060
+ ? manualGrantCount
1061
+ : 0,
1062
+ manualGrantCount: isAdmin ? manualGrantCount : 0,
970
1063
  pendingRequestCount: requests.filter((r) => r.status === "pending").length,
971
1064
  };
972
1065
  }