@nexus-ai-fs/tui 0.9.18

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 (193) hide show
  1. package/README.md +30 -0
  2. package/package.json +48 -0
  3. package/src/app.tsx +349 -0
  4. package/src/index.tsx +137 -0
  5. package/src/opentui-env.d.ts +61 -0
  6. package/src/panels/access/access-panel.tsx +597 -0
  7. package/src/panels/access/alert-list.tsx +77 -0
  8. package/src/panels/access/constraint-creator.tsx +128 -0
  9. package/src/panels/access/constraint-list.tsx +72 -0
  10. package/src/panels/access/credential-list.tsx +68 -0
  11. package/src/panels/access/delegation-chain-view.tsx +110 -0
  12. package/src/panels/access/delegation-completer.tsx +120 -0
  13. package/src/panels/access/delegation-creator.tsx +237 -0
  14. package/src/panels/access/delegation-list.tsx +74 -0
  15. package/src/panels/access/fraud-score-view.tsx +94 -0
  16. package/src/panels/access/manifest-creator.tsx +167 -0
  17. package/src/panels/access/manifest-list.tsx +105 -0
  18. package/src/panels/access/namespace-config-view.tsx +525 -0
  19. package/src/panels/access/permission-checker.tsx +231 -0
  20. package/src/panels/agents/agent-status-view.tsx +196 -0
  21. package/src/panels/agents/agents-panel.tsx +493 -0
  22. package/src/panels/agents/delegation-list.tsx +154 -0
  23. package/src/panels/agents/inbox-view.tsx +96 -0
  24. package/src/panels/agents/trajectories-tab.tsx +40 -0
  25. package/src/panels/api-console/api-console-panel.tsx +189 -0
  26. package/src/panels/api-console/codegen-viewer.tsx +36 -0
  27. package/src/panels/api-console/codegen.ts +112 -0
  28. package/src/panels/api-console/endpoint-list.tsx +57 -0
  29. package/src/panels/api-console/request-builder.tsx +69 -0
  30. package/src/panels/api-console/response-viewer.tsx +54 -0
  31. package/src/panels/connectors/available-tab.tsx +357 -0
  32. package/src/panels/connectors/connector-row.tsx +121 -0
  33. package/src/panels/connectors/connectors-panel.tsx +88 -0
  34. package/src/panels/connectors/error-parser.ts +116 -0
  35. package/src/panels/connectors/mounted-tab.tsx +179 -0
  36. package/src/panels/connectors/skills-tab.tsx +235 -0
  37. package/src/panels/connectors/template-generator.ts +211 -0
  38. package/src/panels/connectors/write-tab.tsx +514 -0
  39. package/src/panels/events/audit-tab.tsx +69 -0
  40. package/src/panels/events/audit-trail.tsx +75 -0
  41. package/src/panels/events/connector-detail.tsx +49 -0
  42. package/src/panels/events/connector-list.tsx +73 -0
  43. package/src/panels/events/connectors-tab.tsx +92 -0
  44. package/src/panels/events/event-replay.tsx +80 -0
  45. package/src/panels/events/events-panel.tsx +414 -0
  46. package/src/panels/events/events-tab.tsx +212 -0
  47. package/src/panels/events/lock-list.tsx +54 -0
  48. package/src/panels/events/locks-tab.tsx +103 -0
  49. package/src/panels/events/mcl-replay.tsx +77 -0
  50. package/src/panels/events/mcl-tab.tsx +83 -0
  51. package/src/panels/events/operations-tab-wrapper.tsx +62 -0
  52. package/src/panels/events/operations-tab.tsx +41 -0
  53. package/src/panels/events/replay-tab.tsx +76 -0
  54. package/src/panels/events/secrets-audit.tsx +64 -0
  55. package/src/panels/events/secrets-tab.tsx +75 -0
  56. package/src/panels/events/subscription-list.tsx +54 -0
  57. package/src/panels/events/subscriptions-tab.tsx +82 -0
  58. package/src/panels/files/file-aspects.tsx +93 -0
  59. package/src/panels/files/file-editor.tsx +160 -0
  60. package/src/panels/files/file-explorer-keybindings.ts +468 -0
  61. package/src/panels/files/file-explorer-panel.tsx +545 -0
  62. package/src/panels/files/file-lineage.tsx +163 -0
  63. package/src/panels/files/file-list-item.tsx +28 -0
  64. package/src/panels/files/file-metadata.tsx +62 -0
  65. package/src/panels/files/file-preview.tsx +108 -0
  66. package/src/panels/files/file-schema.tsx +89 -0
  67. package/src/panels/files/file-tree-node.tsx +44 -0
  68. package/src/panels/files/file-tree.tsx +169 -0
  69. package/src/panels/files/share-links-tab.tsx +33 -0
  70. package/src/panels/files/uploads-tab.tsx +45 -0
  71. package/src/panels/payments/approval-list.tsx +83 -0
  72. package/src/panels/payments/balance-card.tsx +43 -0
  73. package/src/panels/payments/budget-card.tsx +70 -0
  74. package/src/panels/payments/payments-panel.tsx +451 -0
  75. package/src/panels/payments/policy-list.tsx +64 -0
  76. package/src/panels/payments/reservation-list.tsx +78 -0
  77. package/src/panels/payments/transaction-list.tsx +103 -0
  78. package/src/panels/payments/transfer-form.tsx +109 -0
  79. package/src/panels/search/column-search.tsx +79 -0
  80. package/src/panels/search/knowledge-view.tsx +100 -0
  81. package/src/panels/search/memory-list.tsx +197 -0
  82. package/src/panels/search/playbook-list.tsx +77 -0
  83. package/src/panels/search/rlm-answer-view.tsx +105 -0
  84. package/src/panels/search/search-panel.tsx +405 -0
  85. package/src/panels/search/search-results.tsx +116 -0
  86. package/src/panels/stack/stack-panel.tsx +474 -0
  87. package/src/panels/versions/conflicts-tab.tsx +59 -0
  88. package/src/panels/versions/entry-detail.tsx +89 -0
  89. package/src/panels/versions/transaction-actions.tsx +34 -0
  90. package/src/panels/versions/transaction-list.tsx +90 -0
  91. package/src/panels/versions/versions-panel.tsx +276 -0
  92. package/src/panels/workflows/execution-list.tsx +102 -0
  93. package/src/panels/workflows/scheduler-view.tsx +135 -0
  94. package/src/panels/workflows/workflow-list.tsx +88 -0
  95. package/src/panels/workflows/workflows-panel.tsx +295 -0
  96. package/src/panels/zones/brick-detail.tsx +136 -0
  97. package/src/panels/zones/brick-list.tsx +56 -0
  98. package/src/panels/zones/cache-tab.tsx +118 -0
  99. package/src/panels/zones/drift-view.tsx +97 -0
  100. package/src/panels/zones/mcp-mounts-tab.tsx +38 -0
  101. package/src/panels/zones/memories-tab.tsx +37 -0
  102. package/src/panels/zones/reindex-status.tsx +84 -0
  103. package/src/panels/zones/workspaces-tab.tsx +37 -0
  104. package/src/panels/zones/zone-list.tsx +73 -0
  105. package/src/panels/zones/zones-panel.tsx +559 -0
  106. package/src/services/command-runner.ts +303 -0
  107. package/src/shared/accessibility-announcements.ts +44 -0
  108. package/src/shared/action-registry.ts +466 -0
  109. package/src/shared/brick-states.ts +91 -0
  110. package/src/shared/command-palette.ts +35 -0
  111. package/src/shared/components/announcement-bar.tsx +30 -0
  112. package/src/shared/components/app-confirm-dialog.tsx +29 -0
  113. package/src/shared/components/breadcrumb.tsx +21 -0
  114. package/src/shared/components/brick-gate.tsx +60 -0
  115. package/src/shared/components/command-output.tsx +95 -0
  116. package/src/shared/components/command-palette.tsx +97 -0
  117. package/src/shared/components/confirm-dialog.tsx +61 -0
  118. package/src/shared/components/diff-viewer.tsx +219 -0
  119. package/src/shared/components/empty-state.tsx +36 -0
  120. package/src/shared/components/error-bar.tsx +60 -0
  121. package/src/shared/components/error-boundary.tsx +53 -0
  122. package/src/shared/components/help-overlay.tsx +99 -0
  123. package/src/shared/components/identity-switcher.tsx +168 -0
  124. package/src/shared/components/loading-indicator.tsx +40 -0
  125. package/src/shared/components/pagination-bar.tsx +68 -0
  126. package/src/shared/components/pre-connection-screen.tsx +398 -0
  127. package/src/shared/components/scroll-indicator.tsx +46 -0
  128. package/src/shared/components/side-nav-utils.ts +68 -0
  129. package/src/shared/components/side-nav.tsx +287 -0
  130. package/src/shared/components/spinner.tsx +26 -0
  131. package/src/shared/components/status-bar.tsx +117 -0
  132. package/src/shared/components/styled-text.tsx +72 -0
  133. package/src/shared/components/sub-tab-bar-utils.ts +100 -0
  134. package/src/shared/components/sub-tab-bar.tsx +40 -0
  135. package/src/shared/components/tab-bar-utils.ts +36 -0
  136. package/src/shared/components/tab-bar.tsx +50 -0
  137. package/src/shared/components/text-input.tsx +73 -0
  138. package/src/shared/components/tooltip.tsx +53 -0
  139. package/src/shared/components/virtual-list.tsx +93 -0
  140. package/src/shared/components/welcome-screen.tsx +111 -0
  141. package/src/shared/hooks/use-api.ts +10 -0
  142. package/src/shared/hooks/use-brick-available.ts +42 -0
  143. package/src/shared/hooks/use-confirm.ts +66 -0
  144. package/src/shared/hooks/use-connection-state.ts +67 -0
  145. package/src/shared/hooks/use-copy.ts +31 -0
  146. package/src/shared/hooks/use-fresh-server.ts +62 -0
  147. package/src/shared/hooks/use-keyboard.ts +58 -0
  148. package/src/shared/hooks/use-list-navigation.ts +106 -0
  149. package/src/shared/hooks/use-swr.ts +117 -0
  150. package/src/shared/hooks/use-tab-fallback.ts +32 -0
  151. package/src/shared/hooks/use-text-input.ts +113 -0
  152. package/src/shared/hooks/use-visible-tabs.ts +61 -0
  153. package/src/shared/lib/circular-buffer.ts +82 -0
  154. package/src/shared/lib/clipboard.ts +14 -0
  155. package/src/shared/nav-items.ts +73 -0
  156. package/src/shared/navigation.ts +110 -0
  157. package/src/shared/status-breadcrumb.ts +74 -0
  158. package/src/shared/syntax-style.ts +3 -0
  159. package/src/shared/tab-visibility.ts +15 -0
  160. package/src/shared/text-style.ts +23 -0
  161. package/src/shared/theme.ts +179 -0
  162. package/src/shared/utils/format-size.ts +20 -0
  163. package/src/shared/utils/format-text.ts +10 -0
  164. package/src/shared/utils/format-time.ts +72 -0
  165. package/src/shared/utils/lru-cache.ts +75 -0
  166. package/src/stores/access-store-types.ts +154 -0
  167. package/src/stores/access-store.ts +674 -0
  168. package/src/stores/agents-store.ts +404 -0
  169. package/src/stores/announcement-store.ts +46 -0
  170. package/src/stores/api-console-store.ts +476 -0
  171. package/src/stores/connectors-store.ts +434 -0
  172. package/src/stores/create-api-action.ts +140 -0
  173. package/src/stores/delegation-store.ts +300 -0
  174. package/src/stores/error-store.ts +102 -0
  175. package/src/stores/events-store.ts +163 -0
  176. package/src/stores/files-store.ts +630 -0
  177. package/src/stores/first-run-store.ts +34 -0
  178. package/src/stores/global-store.ts +255 -0
  179. package/src/stores/infra-store.ts +461 -0
  180. package/src/stores/knowledge-store.ts +358 -0
  181. package/src/stores/lineage-store.ts +126 -0
  182. package/src/stores/mcp-store.ts +147 -0
  183. package/src/stores/payments-store.ts +545 -0
  184. package/src/stores/search-store-types.ts +155 -0
  185. package/src/stores/search-store.ts +656 -0
  186. package/src/stores/share-link-store.ts +151 -0
  187. package/src/stores/stack-store.ts +352 -0
  188. package/src/stores/ui-store.ts +161 -0
  189. package/src/stores/upload-store.ts +131 -0
  190. package/src/stores/versions-store.ts +355 -0
  191. package/src/stores/workflows-store.ts +402 -0
  192. package/src/stores/workspace-store.ts +185 -0
  193. package/src/stores/zones-store.ts +378 -0
@@ -0,0 +1,674 @@
1
+ /**
2
+ * Zustand store for the Access Control panel:
3
+ * manifests (+ tuple entries), permission evaluation, governance alerts,
4
+ * credentials, fraud scores.
5
+ */
6
+
7
+ import { create } from "zustand";
8
+ import type { FetchClient } from "@nexus-ai-fs/api-client";
9
+ import { createApiAction, categorizeError } from "./create-api-action.js";
10
+ import { useErrorStore } from "./error-store.js";
11
+ export type { DelegationItem } from "./delegation-store.js";
12
+ import type { DelegationItem } from "./delegation-store.js";
13
+
14
+ // Re-export all types for backward compatibility
15
+ export type {
16
+ ManifestEntry, AccessManifest, EvaluationTraceEntry,
17
+ EvaluationTraceResult, PermissionCheck, GovernanceAlert,
18
+ Credential, FraudScore, DelegationCreateResponse,
19
+ DelegationChainEntry, DelegationChain, NamespaceDetail,
20
+ GovernanceCheckResult, GovernanceConstraint, AccessTab,
21
+ } from "./access-store-types.js";
22
+
23
+ import type {
24
+ AccessManifest, EvaluationTraceResult, PermissionCheck,
25
+ GovernanceAlert, Credential, FraudScore,
26
+ DelegationCreateResponse, DelegationChain, NamespaceDetail,
27
+ GovernanceCheckResult, GovernanceConstraint, AccessTab,
28
+ } from "./access-store-types.js";
29
+
30
+ // =============================================================================
31
+ // Store
32
+ // =============================================================================
33
+
34
+ export interface AccessState {
35
+ // Manifests
36
+ readonly manifests: readonly AccessManifest[];
37
+ readonly selectedManifestIndex: number;
38
+ readonly manifestsLoading: boolean;
39
+
40
+ // Permission check
41
+ readonly lastPermissionCheck: PermissionCheck | null;
42
+ readonly permissionCheckLoading: boolean;
43
+
44
+ // Governance alerts
45
+ readonly alerts: readonly GovernanceAlert[];
46
+ readonly alertsLoading: boolean;
47
+ readonly selectedAlertIndex: number;
48
+
49
+ // Credentials
50
+ readonly credentials: readonly Credential[];
51
+ readonly credentialsLoading: boolean;
52
+
53
+ // Fraud scores
54
+ readonly fraudScores: readonly FraudScore[];
55
+ readonly fraudScoresLoading: boolean;
56
+ readonly selectedFraudIndex: number;
57
+
58
+ // Delegations
59
+ readonly delegations: readonly DelegationItem[];
60
+ readonly delegationsLoading: boolean;
61
+ readonly selectedDelegationIndex: number;
62
+ readonly lastDelegationCreate: DelegationCreateResponse | null;
63
+ readonly delegationChain: DelegationChain | null;
64
+ readonly delegationChainLoading: boolean;
65
+
66
+ // Namespace detail
67
+ readonly namespaceDetail: NamespaceDetail | null;
68
+ readonly namespaceDetailLoading: boolean;
69
+
70
+ // Governance check
71
+ readonly governanceCheck: GovernanceCheckResult | null;
72
+ readonly governanceCheckLoading: boolean;
73
+
74
+ // Collusion detection
75
+ readonly collusionRings: readonly unknown[];
76
+ readonly collusionLoading: boolean;
77
+
78
+ // Governance constraints
79
+ readonly constraints: readonly GovernanceConstraint[];
80
+ readonly constraintsLoading: boolean;
81
+ readonly selectedConstraintIndex: number;
82
+
83
+ // UI state
84
+ readonly activeTab: AccessTab;
85
+ readonly error: string | null;
86
+
87
+ // Actions — manifests
88
+ readonly fetchManifests: (client: FetchClient) => Promise<void>;
89
+ readonly fetchManifestDetail: (manifestId: string, client: FetchClient) => Promise<void>;
90
+ readonly checkPermission: (
91
+ manifestId: string,
92
+ toolName: string,
93
+ client: FetchClient,
94
+ ) => Promise<void>;
95
+
96
+ // Actions — alerts
97
+ readonly fetchAlerts: (zoneId: string | undefined, client: FetchClient) => Promise<void>;
98
+ readonly resolveAlert: (alertId: string, resolvedBy: string, zoneId: string | undefined, client: FetchClient) => Promise<void>;
99
+
100
+ // Actions — credentials
101
+ readonly fetchCredentials: (agentId: string, client: FetchClient) => Promise<void>;
102
+ readonly issueCredential: (agentId: string, claims: Record<string, unknown>, client: FetchClient) => Promise<void>;
103
+ readonly revokeCredential: (credentialId: string, agentId: string, client: FetchClient) => Promise<void>;
104
+
105
+ // Actions — fraud scores
106
+ readonly fetchFraudScores: (zoneId: string | undefined, client: FetchClient) => Promise<void>;
107
+ readonly computeFraudScores: (zoneId: string | undefined, client: FetchClient) => Promise<void>;
108
+
109
+ // Actions — delegations
110
+ readonly fetchDelegations: (client: FetchClient, status?: string | null) => Promise<void>;
111
+ readonly createDelegation: (
112
+ request: {
113
+ readonly worker_id: string;
114
+ readonly worker_name: string;
115
+ readonly namespace_mode: string;
116
+ readonly scope_prefix?: string;
117
+ readonly intent: string;
118
+ readonly can_sub_delegate: boolean;
119
+ readonly ttl_seconds?: number;
120
+ },
121
+ client: FetchClient,
122
+ ) => Promise<void>;
123
+ readonly revokeDelegation: (delegationId: string, client: FetchClient) => Promise<void>;
124
+ readonly completeDelegation: (
125
+ delegationId: string,
126
+ outcome: string,
127
+ qualityScore: number | null,
128
+ client: FetchClient,
129
+ ) => Promise<void>;
130
+ readonly fetchDelegationChain: (delegationId: string, client: FetchClient) => Promise<void>;
131
+ readonly fetchNamespaceDetail: (delegationId: string, client: FetchClient) => Promise<void>;
132
+ readonly updateNamespaceConfig: (
133
+ delegationId: string,
134
+ update: {
135
+ readonly scope_prefix?: string;
136
+ readonly remove_grants?: readonly string[];
137
+ readonly add_grants?: readonly string[];
138
+ readonly readonly_paths?: readonly string[];
139
+ },
140
+ client: FetchClient,
141
+ ) => Promise<void>;
142
+
143
+ // Actions — governance check
144
+ readonly checkGovernanceEdge: (
145
+ fromAgentId: string,
146
+ toAgentId: string,
147
+ zoneId: string | undefined,
148
+ client: FetchClient,
149
+ ) => Promise<void>;
150
+
151
+ // Actions — governance constraints
152
+ readonly fetchConstraints: (zoneId: string, client: FetchClient) => Promise<void>;
153
+ readonly createConstraint: (constraint: { from_agent_id: string; to_agent_id: string; constraint_type: string; zone_id: string }, client: FetchClient) => Promise<void>;
154
+ readonly deleteConstraint: (constraintId: string, client: FetchClient) => Promise<void>;
155
+ readonly setSelectedConstraintIndex: (index: number) => void;
156
+
157
+ // Actions — governance deep features
158
+ readonly fetchCollusionRings: (zoneId: string | undefined, client: FetchClient) => Promise<void>;
159
+ readonly suspendAgent: (agentId: string, reason: string, zoneId: string | undefined, client: FetchClient) => Promise<void>;
160
+
161
+ // Actions — manifests (create/revoke)
162
+ readonly createManifest: (
163
+ payload: {
164
+ readonly agent_id: string;
165
+ readonly name: string;
166
+ readonly entries: readonly { readonly tool_pattern: string; readonly permission: string; readonly max_calls_per_minute?: number }[];
167
+ readonly valid_from?: string;
168
+ readonly valid_until?: string;
169
+ },
170
+ client: FetchClient,
171
+ ) => Promise<void>;
172
+ readonly revokeManifest: (manifestId: string, client: FetchClient) => Promise<void>;
173
+
174
+ // Actions — UI
175
+ readonly setActiveTab: (tab: AccessTab) => void;
176
+ readonly setSelectedManifestIndex: (index: number) => void;
177
+ readonly setSelectedAlertIndex: (index: number) => void;
178
+ readonly setSelectedFraudIndex: (index: number) => void;
179
+ readonly setSelectedDelegationIndex: (index: number) => void;
180
+ }
181
+
182
+ const SOURCE = "access";
183
+
184
+ export const useAccessStore = create<AccessState>((set, get) => ({
185
+ manifests: [],
186
+ selectedManifestIndex: 0,
187
+ manifestsLoading: false,
188
+ lastPermissionCheck: null,
189
+ permissionCheckLoading: false,
190
+ alerts: [],
191
+ alertsLoading: false,
192
+ selectedAlertIndex: 0,
193
+ credentials: [],
194
+ credentialsLoading: false,
195
+ fraudScores: [],
196
+ fraudScoresLoading: false,
197
+ selectedFraudIndex: 0,
198
+ delegations: [],
199
+ delegationsLoading: false,
200
+ selectedDelegationIndex: 0,
201
+ lastDelegationCreate: null,
202
+ delegationChain: null,
203
+ delegationChainLoading: false,
204
+ namespaceDetail: null,
205
+ namespaceDetailLoading: false,
206
+ governanceCheck: null,
207
+ governanceCheckLoading: false,
208
+ collusionRings: [],
209
+ collusionLoading: false,
210
+ constraints: [],
211
+ constraintsLoading: false,
212
+ selectedConstraintIndex: 0,
213
+ activeTab: "manifests",
214
+ error: null,
215
+
216
+ // =========================================================================
217
+ // Actions migrated to createApiAction (Decision 6A)
218
+ // =========================================================================
219
+
220
+ // ── Manifests ──────────────────────────────────────────────────────────
221
+
222
+ fetchManifests: createApiAction<AccessState, [FetchClient]>(set, {
223
+ loadingKey: "manifestsLoading",
224
+ source: SOURCE,
225
+ action: async (client) => {
226
+ const response = await client.get<{
227
+ readonly manifests: readonly AccessManifest[];
228
+ readonly offset: number;
229
+ readonly limit: number;
230
+ readonly count: number;
231
+ }>("/api/v2/access-manifests");
232
+ return {
233
+ manifests: response.manifests,
234
+ selectedManifestIndex: 0,
235
+ };
236
+ },
237
+ }),
238
+
239
+ fetchManifestDetail: async (manifestId, client) => {
240
+ try {
241
+ const manifest = await client.get<AccessManifest>(
242
+ `/api/v2/access-manifests/${encodeURIComponent(manifestId)}`,
243
+ );
244
+ set((state) => {
245
+ const idx = state.manifests.findIndex((m) => m.manifest_id === manifestId);
246
+ if (idx >= 0) {
247
+ return {
248
+ manifests: state.manifests.map((m, i) => (i === idx ? manifest : m)),
249
+ };
250
+ }
251
+ return {};
252
+ });
253
+ } catch {
254
+ // Non-critical: entries just won't be available for the checker trace
255
+ }
256
+ },
257
+
258
+ checkPermission: createApiAction<AccessState, [string, string, FetchClient]>(set, {
259
+ loadingKey: "permissionCheckLoading",
260
+ source: SOURCE,
261
+ action: async (manifestId, toolName, client) => {
262
+ const response = await client.post<{
263
+ readonly tool_name: string;
264
+ readonly permission: string;
265
+ readonly agent_id: string;
266
+ readonly manifest_id: string;
267
+ readonly trace?: EvaluationTraceResult;
268
+ }>(`/api/v2/access-manifests/${encodeURIComponent(manifestId)}/evaluate`, {
269
+ tool_name: toolName,
270
+ });
271
+ return {
272
+ lastPermissionCheck: {
273
+ tool_name: response.tool_name,
274
+ permission: response.permission,
275
+ agent_id: response.agent_id,
276
+ manifest_id: response.manifest_id,
277
+ trace: response.trace ?? null,
278
+ },
279
+ };
280
+ },
281
+ }),
282
+
283
+ // ── Alerts ─────────────────────────────────────────────────────────────
284
+
285
+ fetchAlerts: createApiAction<AccessState, [string | undefined, FetchClient]>(set, {
286
+ loadingKey: "alertsLoading",
287
+ source: SOURCE,
288
+ action: async (zoneId, client) => {
289
+ const params = zoneId ? `?zone_id=${encodeURIComponent(zoneId)}` : "";
290
+ const response = await client.get<{
291
+ readonly alerts: readonly GovernanceAlert[];
292
+ }>(`/api/v2/governance/alerts${params}`);
293
+ return {
294
+ alerts: response.alerts,
295
+ selectedAlertIndex: 0,
296
+ };
297
+ },
298
+ }),
299
+
300
+ resolveAlert: async (alertId, resolvedBy, zoneId, client) => {
301
+ set({ alertsLoading: true, error: null });
302
+ try {
303
+ const params = zoneId ? `?zone_id=${encodeURIComponent(zoneId)}` : "";
304
+ await client.post<{
305
+ readonly alert_id: string;
306
+ readonly resolved: boolean;
307
+ readonly resolved_by: string;
308
+ }>(`/api/v2/governance/alerts/${encodeURIComponent(alertId)}/resolve${params}`, {
309
+ resolved_by: resolvedBy,
310
+ });
311
+ set((state) => ({
312
+ alerts: state.alerts.map((a) =>
313
+ a.alert_id === alertId ? { ...a, resolved: true } : a,
314
+ ),
315
+ alertsLoading: false,
316
+ }));
317
+ } catch (err) {
318
+ const message = err instanceof Error ? err.message : "Failed to resolve alert";
319
+ set({ alertsLoading: false, error: message });
320
+ useErrorStore.getState().pushError({ message, category: categorizeError(message), source: SOURCE });
321
+ }
322
+ },
323
+
324
+ // ── Credentials ────────────────────────────────────────────────────────
325
+
326
+ fetchCredentials: createApiAction<AccessState, [string, FetchClient]>(set, {
327
+ loadingKey: "credentialsLoading",
328
+ source: SOURCE,
329
+ action: async (agentId, client) => {
330
+ const response = await client.get<{
331
+ readonly agent_id: string;
332
+ readonly count: number;
333
+ readonly credentials: readonly Credential[];
334
+ }>(`/api/v2/agents/${encodeURIComponent(agentId)}/credentials`);
335
+ return { credentials: response.credentials };
336
+ },
337
+ }),
338
+
339
+ issueCredential: async (agentId, claims, client) => {
340
+ set({ credentialsLoading: true, error: null });
341
+ try {
342
+ await client.post(`/api/v2/agents/${encodeURIComponent(agentId)}/credentials`, { claims });
343
+ await get().fetchCredentials(agentId, client);
344
+ } catch (err) {
345
+ const message = err instanceof Error ? err.message : "Failed to issue credential";
346
+ set({ credentialsLoading: false, error: message });
347
+ useErrorStore.getState().pushError({ message, category: categorizeError(message), source: SOURCE });
348
+ }
349
+ },
350
+
351
+ revokeCredential: async (credentialId, agentId, client) => {
352
+ set({ credentialsLoading: true, error: null });
353
+ try {
354
+ await client.post(`/api/v2/agents/${encodeURIComponent(agentId)}/credentials/${encodeURIComponent(credentialId)}/revoke`, {});
355
+ set((state) => ({
356
+ credentials: state.credentials.map((c) =>
357
+ c.credential_id === credentialId ? { ...c, is_active: false, revoked_at: new Date().toISOString() } : c,
358
+ ),
359
+ credentialsLoading: false,
360
+ }));
361
+ } catch (err) {
362
+ const message = err instanceof Error ? err.message : "Failed to revoke credential";
363
+ set({ credentialsLoading: false, error: message });
364
+ useErrorStore.getState().pushError({ message, category: categorizeError(message), source: SOURCE });
365
+ }
366
+ },
367
+
368
+ // ── Fraud scores ───────────────────────────────────────────────────────
369
+
370
+ fetchFraudScores: createApiAction<AccessState, [string | undefined, FetchClient]>(set, {
371
+ loadingKey: "fraudScoresLoading",
372
+ source: SOURCE,
373
+ action: async (zoneId, client) => {
374
+ const params = zoneId ? `?zone_id=${encodeURIComponent(zoneId)}` : "";
375
+ const response = await client.get<{
376
+ readonly scores: readonly FraudScore[];
377
+ readonly count: number;
378
+ }>(`/api/v2/governance/fraud-scores${params}`);
379
+ return {
380
+ fraudScores: response.scores,
381
+ selectedFraudIndex: 0,
382
+ };
383
+ },
384
+ }),
385
+
386
+ computeFraudScores: createApiAction<AccessState, [string | undefined, FetchClient]>(set, {
387
+ loadingKey: "fraudScoresLoading",
388
+ source: SOURCE,
389
+ action: async (zoneId, client) => {
390
+ const params = zoneId ? `?zone_id=${encodeURIComponent(zoneId)}` : "";
391
+ const response = await client.post<{
392
+ readonly scores: readonly FraudScore[];
393
+ readonly count: number;
394
+ }>(`/api/v2/governance/fraud-scores/compute${params}`, {});
395
+ return {
396
+ fraudScores: response.scores,
397
+ selectedFraudIndex: 0,
398
+ };
399
+ },
400
+ }),
401
+
402
+ // ── Manifests (create/revoke) ─────────────────────────────────────────
403
+
404
+ createManifest: async (payload, client) => {
405
+ set({ manifestsLoading: true, error: null });
406
+ try {
407
+ await client.post<AccessManifest>("/api/v2/access-manifests", payload);
408
+ // Re-fetch manifest list
409
+ const response = await client.get<{ manifests: readonly AccessManifest[]; }>("/api/v2/access-manifests");
410
+ set({ manifests: response.manifests, manifestsLoading: false, selectedManifestIndex: 0 });
411
+ } catch (err) {
412
+ const message = err instanceof Error ? err.message : "Failed to create manifest";
413
+ set({ manifestsLoading: false, error: message });
414
+ useErrorStore.getState().pushError({ message, category: categorizeError(message), source: SOURCE });
415
+ }
416
+ },
417
+
418
+ revokeManifest: async (manifestId, client) => {
419
+ set({ manifestsLoading: true, error: null });
420
+ try {
421
+ await client.post(`/api/v2/access-manifests/${encodeURIComponent(manifestId)}/revoke`, {});
422
+ set((state) => ({
423
+ manifests: state.manifests.map((m) =>
424
+ m.manifest_id === manifestId ? { ...m, status: "revoked" } : m,
425
+ ),
426
+ manifestsLoading: false,
427
+ }));
428
+ } catch (err) {
429
+ const message = err instanceof Error ? err.message : "Failed to revoke manifest";
430
+ set({ manifestsLoading: false, error: message });
431
+ useErrorStore.getState().pushError({ message, category: categorizeError(message), source: SOURCE });
432
+ }
433
+ },
434
+
435
+ // ── UI ─────────────────────────────────────────────────────────────────
436
+
437
+ setActiveTab: (tab) => {
438
+ set({ activeTab: tab, error: null });
439
+ },
440
+
441
+ setSelectedManifestIndex: (index) => {
442
+ set({ selectedManifestIndex: index });
443
+ },
444
+
445
+ setSelectedAlertIndex: (index) => {
446
+ set({ selectedAlertIndex: index });
447
+ },
448
+
449
+ setSelectedFraudIndex: (index) => {
450
+ set({ selectedFraudIndex: index });
451
+ },
452
+
453
+ // ── Delegations ─────────────────────────────────────────────────────────
454
+
455
+ fetchDelegations: async (client, status) => {
456
+ set({ delegationsLoading: true, error: null });
457
+ try {
458
+ let url = "/api/v2/agents/delegate";
459
+ if (status) url += `?status=${encodeURIComponent(status)}`;
460
+ const response = await client.get<{
461
+ readonly delegations: readonly DelegationItem[];
462
+ readonly count: number;
463
+ }>(url);
464
+ set({
465
+ delegations: response.delegations,
466
+ delegationsLoading: false,
467
+ selectedDelegationIndex: 0,
468
+ });
469
+ } catch (err) {
470
+ const message = err instanceof Error ? err.message : "Failed to fetch delegations";
471
+ set({ delegationsLoading: false, error: message });
472
+ useErrorStore.getState().pushError({ message, category: categorizeError(message), source: SOURCE });
473
+ }
474
+ },
475
+
476
+ createDelegation: async (request, client) => {
477
+ set({ delegationsLoading: true, error: null });
478
+ try {
479
+ const response = await client.post<DelegationCreateResponse>(
480
+ "/api/v2/agents/delegate",
481
+ request,
482
+ );
483
+ set({ lastDelegationCreate: response, delegationsLoading: false });
484
+ } catch (err) {
485
+ const message = err instanceof Error ? err.message : "Failed to create delegation";
486
+ set({ delegationsLoading: false, error: message });
487
+ useErrorStore.getState().pushError({ message, category: categorizeError(message), source: SOURCE });
488
+ return;
489
+ }
490
+ // Re-fetch list separately — a GET failure here must not mask the successful POST
491
+ try {
492
+ const listResponse = await client.get<{
493
+ readonly delegations: readonly DelegationItem[];
494
+ readonly count: number;
495
+ }>("/api/v2/agents/delegate");
496
+ set({
497
+ delegations: listResponse.delegations,
498
+ selectedDelegationIndex: 0,
499
+ });
500
+ } catch {
501
+ // Non-critical: list will refresh on next tab visit
502
+ }
503
+ },
504
+
505
+ revokeDelegation: async (delegationId, client) => {
506
+ set({ delegationsLoading: true, error: null });
507
+ try {
508
+ await client.delete<{
509
+ readonly status: string;
510
+ readonly delegation_id: string;
511
+ }>(`/api/v2/agents/delegate/${encodeURIComponent(delegationId)}`);
512
+ set((state) => ({
513
+ delegations: state.delegations.map((d) =>
514
+ d.delegation_id === delegationId ? { ...d, status: "revoked" } : d,
515
+ ),
516
+ delegationsLoading: false,
517
+ }));
518
+ } catch (err) {
519
+ const message = err instanceof Error ? err.message : "Failed to revoke delegation";
520
+ set({ delegationsLoading: false, error: message });
521
+ useErrorStore.getState().pushError({ message, category: categorizeError(message), source: SOURCE });
522
+ }
523
+ },
524
+
525
+ completeDelegation: async (delegationId, outcome, qualityScore, client) => {
526
+ set({ delegationsLoading: true, error: null });
527
+ try {
528
+ const body: { outcome: string; quality_score?: number } = { outcome };
529
+ if (qualityScore !== null) {
530
+ body.quality_score = qualityScore;
531
+ }
532
+ await client.post<{
533
+ readonly status: string;
534
+ readonly delegation_id: string;
535
+ readonly outcome: string;
536
+ }>(`/api/v2/agents/delegate/${encodeURIComponent(delegationId)}/complete`, body);
537
+ set((state) => ({
538
+ delegations: state.delegations.map((d) =>
539
+ d.delegation_id === delegationId ? { ...d, status: "completed" } : d,
540
+ ),
541
+ delegationsLoading: false,
542
+ }));
543
+ } catch (err) {
544
+ const message = err instanceof Error ? err.message : "Failed to complete delegation";
545
+ set({ delegationsLoading: false, error: message });
546
+ useErrorStore.getState().pushError({ message, category: categorizeError(message), source: SOURCE });
547
+ }
548
+ },
549
+
550
+ fetchDelegationChain: createApiAction<AccessState, [string, FetchClient]>(set, {
551
+ loadingKey: "delegationChainLoading",
552
+ source: SOURCE,
553
+ action: async (delegationId, client) => {
554
+ const response = await client.get<DelegationChain>(
555
+ `/api/v2/agents/delegate/${encodeURIComponent(delegationId)}/chain`,
556
+ );
557
+ return { delegationChain: response };
558
+ },
559
+ }),
560
+
561
+ fetchNamespaceDetail: createApiAction<AccessState, [string, FetchClient]>(set, {
562
+ loadingKey: "namespaceDetailLoading",
563
+ source: SOURCE,
564
+ action: async (delegationId, client) => {
565
+ const response = await client.get<NamespaceDetail>(
566
+ `/api/v2/agents/delegate/${encodeURIComponent(delegationId)}/namespace`,
567
+ );
568
+ return { namespaceDetail: response };
569
+ },
570
+ }),
571
+
572
+ updateNamespaceConfig: createApiAction<AccessState, [string, { readonly scope_prefix?: string; readonly remove_grants?: readonly string[]; readonly add_grants?: readonly string[]; readonly readonly_paths?: readonly string[] }, FetchClient]>(set, {
573
+ loadingKey: "namespaceDetailLoading",
574
+ source: SOURCE,
575
+ action: async (delegationId, update, client) => {
576
+ const response = await client.patch<NamespaceDetail>(
577
+ `/api/v2/agents/delegate/${encodeURIComponent(delegationId)}/namespace`,
578
+ update,
579
+ );
580
+ return { namespaceDetail: response };
581
+ },
582
+ }),
583
+
584
+ setSelectedDelegationIndex: (index) => {
585
+ set({ selectedDelegationIndex: index });
586
+ },
587
+
588
+ // ── Governance check ────────────────────────────────────────────────────
589
+
590
+ checkGovernanceEdge: createApiAction<AccessState, [string, string, string | undefined, FetchClient]>(set, {
591
+ loadingKey: "governanceCheckLoading",
592
+ source: SOURCE,
593
+ action: async (fromAgentId, toAgentId, zoneId, client) => {
594
+ const params = zoneId ? `?zone_id=${encodeURIComponent(zoneId)}` : "";
595
+ const response = await client.get<GovernanceCheckResult>(
596
+ `/api/v2/governance/check/${encodeURIComponent(fromAgentId)}/${encodeURIComponent(toAgentId)}${params}`,
597
+ );
598
+ return { governanceCheck: response };
599
+ },
600
+ }),
601
+
602
+ // ── Governance constraints ─────────────────────────────────────────────
603
+
604
+ fetchConstraints: createApiAction<AccessState, [string, FetchClient]>(set, {
605
+ loadingKey: "constraintsLoading",
606
+ source: SOURCE,
607
+ action: async (zoneId, client) => {
608
+ const response = await client.get<{
609
+ readonly constraints: readonly GovernanceConstraint[];
610
+ }>(`/api/v2/governance/constraints?zone_id=${encodeURIComponent(zoneId)}`);
611
+ return {
612
+ constraints: response.constraints,
613
+ selectedConstraintIndex: 0,
614
+ };
615
+ },
616
+ }),
617
+
618
+ createConstraint: async (constraint, client) => {
619
+ set({ constraintsLoading: true, error: null });
620
+ try {
621
+ await client.post("/api/v2/governance/constraints", constraint);
622
+ await get().fetchConstraints(constraint.zone_id, client);
623
+ } catch (err) {
624
+ const message = err instanceof Error ? err.message : "Failed to create constraint";
625
+ set({ constraintsLoading: false, error: message });
626
+ useErrorStore.getState().pushError({ message, category: categorizeError(message), source: SOURCE });
627
+ }
628
+ },
629
+
630
+ deleteConstraint: async (constraintId, client) => {
631
+ set({ constraintsLoading: true, error: null });
632
+ try {
633
+ await client.delete(`/api/v2/governance/constraints/${encodeURIComponent(constraintId)}`);
634
+ set((state) => ({
635
+ constraints: state.constraints.filter((c) => c.id !== constraintId),
636
+ constraintsLoading: false,
637
+ }));
638
+ } catch (err) {
639
+ const message = err instanceof Error ? err.message : "Failed to delete constraint";
640
+ set({ constraintsLoading: false, error: message });
641
+ useErrorStore.getState().pushError({ message, category: categorizeError(message), source: SOURCE });
642
+ }
643
+ },
644
+
645
+ setSelectedConstraintIndex: (index) => {
646
+ set({ selectedConstraintIndex: index });
647
+ },
648
+
649
+ // ── Governance deep features ───────────────────────────────────────────
650
+
651
+ fetchCollusionRings: createApiAction<AccessState, [string | undefined, FetchClient]>(set, {
652
+ loadingKey: "collusionLoading",
653
+ source: SOURCE,
654
+ action: async (zoneId, client) => {
655
+ const params = zoneId ? `?zone_id=${encodeURIComponent(zoneId)}` : "";
656
+ const response = await client.get<{ rings: readonly unknown[] }>(
657
+ `/api/v2/governance/collusion-rings${params}`,
658
+ );
659
+ return { collusionRings: response.rings ?? [] };
660
+ },
661
+ }),
662
+
663
+ suspendAgent: async (agentId, reason, zoneId, client) => {
664
+ set({ error: null });
665
+ try {
666
+ const params = zoneId ? `?zone_id=${encodeURIComponent(zoneId)}` : "";
667
+ await client.post(`/api/v2/governance/suspend/${encodeURIComponent(agentId)}${params}`, { reason });
668
+ } catch (err) {
669
+ const message = err instanceof Error ? err.message : "Failed to suspend agent";
670
+ set({ error: message });
671
+ useErrorStore.getState().pushError({ message, category: categorizeError(message), source: SOURCE });
672
+ }
673
+ },
674
+ }));