@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,358 @@
1
+ /**
2
+ * Zustand store for knowledge platform data: aspects, schemas, MCL replay.
3
+ * Issue #2930.
4
+ */
5
+
6
+ import { create } from "zustand";
7
+ import type { FetchClient } from "@nexus-ai-fs/api-client";
8
+ import { createApiAction, categorizeError } from "./create-api-action.js";
9
+ import { useErrorStore } from "./error-store.js";
10
+
11
+ // =============================================================================
12
+ // Types
13
+ // =============================================================================
14
+
15
+ export interface AspectEntry {
16
+ readonly name: string;
17
+ readonly payload: Record<string, unknown>;
18
+ readonly version: number;
19
+ readonly createdBy: string;
20
+ }
21
+
22
+ export interface SchemaColumn {
23
+ readonly name: string;
24
+ readonly type: string;
25
+ readonly nullable: string;
26
+ }
27
+
28
+ export interface SchemaInfo {
29
+ readonly columns: readonly SchemaColumn[];
30
+ readonly format: string;
31
+ readonly rowCount: number | null;
32
+ readonly confidence: number;
33
+ }
34
+
35
+ export interface ReplayEntry {
36
+ readonly sequenceNumber: number;
37
+ readonly entityUrn: string;
38
+ readonly aspectName: string;
39
+ readonly changeType: string;
40
+ readonly timestamp: string;
41
+ }
42
+
43
+ /** Event from the historical event replay endpoint. */
44
+ export interface EventReplayEntry {
45
+ readonly event_id: string;
46
+ readonly event_type: string;
47
+ readonly agent_id: string | null;
48
+ readonly path: string | null;
49
+ readonly timestamp: string;
50
+ readonly payload: Record<string, unknown>;
51
+ }
52
+
53
+ // =============================================================================
54
+ // Store
55
+ // =============================================================================
56
+
57
+ export interface KnowledgeState {
58
+ // Aspects cache (keyed by URN)
59
+ readonly aspectsCache: ReadonlyMap<string, readonly string[]>;
60
+ readonly aspectDetailCache: ReadonlyMap<string, AspectEntry>;
61
+ readonly aspectsLoading: boolean;
62
+ readonly aspectDetailLoading: boolean;
63
+
64
+ // Schema cache (keyed by URN)
65
+ readonly schemaCache: ReadonlyMap<string, SchemaInfo | null>;
66
+ readonly schemaLoading: boolean;
67
+
68
+ // MCL replay (bounded by fetch limit parameter, typically 50 per page)
69
+ readonly replayEntries: readonly ReplayEntry[];
70
+ readonly replayLoading: boolean;
71
+ readonly replayHasMore: boolean;
72
+ readonly replayNextCursor: number;
73
+
74
+ // Historical event replay
75
+ readonly eventReplayEntries: readonly EventReplayEntry[];
76
+ readonly eventReplayLoading: boolean;
77
+ readonly eventReplayHasMore: boolean;
78
+ readonly eventReplayNextCursor: string | null;
79
+
80
+ // Column search
81
+ readonly columnSearchResults: readonly {
82
+ entityUrn: string;
83
+ columnName: string;
84
+ columnType: string;
85
+ }[];
86
+ readonly columnSearchLoading: boolean;
87
+
88
+ readonly error: string | null;
89
+
90
+ // Actions
91
+ readonly fetchAspects: (urn: string, client: FetchClient) => Promise<void>;
92
+ readonly fetchAspectDetail: (
93
+ urn: string,
94
+ name: string,
95
+ client: FetchClient,
96
+ ) => Promise<void>;
97
+ readonly fetchSchema: (path: string, client: FetchClient) => Promise<void>;
98
+ readonly fetchReplay: (
99
+ client: FetchClient,
100
+ fromSequence?: number,
101
+ limit?: number,
102
+ entityUrn?: string,
103
+ aspectName?: string,
104
+ ) => Promise<void>;
105
+ readonly fetchEventReplay: (
106
+ filters: {
107
+ event_types?: string;
108
+ path_pattern?: string;
109
+ agent_id?: string;
110
+ since?: string;
111
+ },
112
+ client: FetchClient,
113
+ ) => Promise<void>;
114
+ readonly searchByColumn: (
115
+ column: string,
116
+ client: FetchClient,
117
+ ) => Promise<void>;
118
+ readonly clearReplay: () => void;
119
+ readonly clearEventReplay: () => void;
120
+ }
121
+
122
+ const SOURCE = "knowledge";
123
+
124
+ export const useKnowledgeStore = create<KnowledgeState>((set, get) => ({
125
+ aspectsCache: new Map(),
126
+ aspectDetailCache: new Map(),
127
+ aspectsLoading: false,
128
+ aspectDetailLoading: false,
129
+ schemaCache: new Map(),
130
+ schemaLoading: false,
131
+ replayEntries: [],
132
+ replayLoading: false,
133
+ replayHasMore: false,
134
+ replayNextCursor: 0,
135
+ eventReplayEntries: [],
136
+ eventReplayLoading: false,
137
+ eventReplayHasMore: false,
138
+ eventReplayNextCursor: null,
139
+ columnSearchResults: [],
140
+ columnSearchLoading: false,
141
+ error: null,
142
+
143
+ // =========================================================================
144
+ // Actions — inline with error store integration (use get() for cache)
145
+ // =========================================================================
146
+
147
+ fetchAspects: async (urn, client) => {
148
+ // Check cache
149
+ if (get().aspectsCache.has(urn)) return;
150
+
151
+ set({ aspectsLoading: true, error: null });
152
+ try {
153
+ const result = await client.get<{ aspects: string[] }>(
154
+ `/api/v2/aspects/${encodeURIComponent(urn)}`,
155
+ );
156
+ const newCache = new Map(get().aspectsCache);
157
+ newCache.set(urn, result.aspects ?? []);
158
+ // Evict oldest entry if cache exceeds 100 URNs
159
+ if (newCache.size > 100) {
160
+ const oldest = newCache.keys().next().value;
161
+ if (oldest !== undefined) newCache.delete(oldest);
162
+ }
163
+ set({ aspectsCache: newCache, aspectsLoading: false });
164
+ } catch (err) {
165
+ const message = err instanceof Error ? err.message : "Failed to fetch aspects";
166
+ set({ aspectsLoading: false, error: message });
167
+ useErrorStore.getState().pushError({ message, category: categorizeError(message), source: SOURCE });
168
+ }
169
+ },
170
+
171
+ fetchAspectDetail: async (urn, name, client) => {
172
+ const key = `${urn}::${name}`;
173
+ if (get().aspectDetailCache.has(key)) return;
174
+
175
+ set({ aspectDetailLoading: true, error: null });
176
+ try {
177
+ const result = await client.get<{
178
+ aspectName: string;
179
+ version: number;
180
+ payload: Record<string, unknown>;
181
+ createdBy: string;
182
+ }>(
183
+ `/api/v2/aspects/${encodeURIComponent(urn)}/${encodeURIComponent(name)}`,
184
+ );
185
+ const entry: AspectEntry = {
186
+ name: result.aspectName ?? name,
187
+ payload: result.payload ?? {},
188
+ version: result.version ?? 0,
189
+ createdBy: result.createdBy ?? "system",
190
+ };
191
+ const newCache = new Map(get().aspectDetailCache);
192
+ newCache.set(key, entry);
193
+ // Evict oldest entry if cache exceeds 50 aspect details
194
+ if (newCache.size > 50) {
195
+ const oldest = newCache.keys().next().value;
196
+ if (oldest !== undefined) newCache.delete(oldest);
197
+ }
198
+ set({ aspectDetailCache: newCache, aspectDetailLoading: false });
199
+ } catch (err) {
200
+ const message = err instanceof Error ? err.message : "Failed to fetch aspect";
201
+ set({ aspectDetailLoading: false, error: message });
202
+ useErrorStore.getState().pushError({ message, category: categorizeError(message), source: SOURCE });
203
+ }
204
+ },
205
+
206
+ fetchSchema: async (path, client) => {
207
+ const cacheKey = path;
208
+ if (get().schemaCache.has(cacheKey)) return;
209
+
210
+ set({ schemaLoading: true, error: null });
211
+ try {
212
+ const result = await client.get<{
213
+ schema: {
214
+ columns: SchemaColumn[];
215
+ format: string;
216
+ rowCount: number | null;
217
+ confidence: number;
218
+ } | null;
219
+ }>(
220
+ `/api/v2/catalog/schema/${encodeURIComponent(path.replace(/^\//, ""))}`,
221
+ );
222
+ const schema = result.schema
223
+ ? {
224
+ columns: result.schema.columns ?? [],
225
+ format: result.schema.format ?? "unknown",
226
+ rowCount: result.schema.rowCount ?? null,
227
+ confidence: result.schema.confidence ?? 0,
228
+ }
229
+ : null;
230
+ const newCache = new Map(get().schemaCache);
231
+ newCache.set(cacheKey, schema);
232
+ set({ schemaCache: newCache, schemaLoading: false });
233
+ } catch (err) {
234
+ const message = err instanceof Error ? err.message : "Failed to fetch schema";
235
+ set({ schemaLoading: false, error: message });
236
+ useErrorStore.getState().pushError({ message, category: categorizeError(message), source: SOURCE });
237
+ }
238
+ },
239
+
240
+ fetchReplay: async (
241
+ client,
242
+ fromSequence = 0,
243
+ limit = 50,
244
+ entityUrn?: string,
245
+ aspectName?: string,
246
+ ) => {
247
+ set({ replayLoading: true, error: null });
248
+ try {
249
+ let url = `/api/v2/ops/replay?from_sequence=${fromSequence}&limit=${limit}`;
250
+ if (entityUrn) url += `&entity_urn=${encodeURIComponent(entityUrn)}`;
251
+ if (aspectName) url += `&aspect_name=${encodeURIComponent(aspectName)}`;
252
+ const result = await client.get<{
253
+ records: any[];
254
+ next_cursor?: number | null;
255
+ nextCursor?: number | null;
256
+ has_more?: boolean;
257
+ hasMore?: boolean;
258
+ }>(url);
259
+ const records = result.records ?? [];
260
+ const entries: ReplayEntry[] = records.map((r: any) => ({
261
+ sequenceNumber: r.sequenceNumber ?? r.sequence_number ?? 0,
262
+ entityUrn: r.entityUrn ?? r.entity_urn ?? "",
263
+ aspectName: r.aspectName ?? r.aspect_name ?? "",
264
+ changeType: r.changeType ?? r.change_type ?? "",
265
+ timestamp: r.timestamp ?? "",
266
+ }));
267
+ set({
268
+ replayEntries:
269
+ fromSequence === 0
270
+ ? entries
271
+ : [...get().replayEntries, ...entries],
272
+ replayLoading: false,
273
+ replayHasMore: result.hasMore ?? result.has_more ?? false,
274
+ replayNextCursor: result.nextCursor ?? result.next_cursor ?? 0,
275
+ });
276
+ } catch (err) {
277
+ const message = err instanceof Error ? err.message : "Failed to fetch replay";
278
+ set({ replayLoading: false, error: message });
279
+ useErrorStore.getState().pushError({ message, category: categorizeError(message), source: SOURCE });
280
+ }
281
+ },
282
+
283
+ fetchEventReplay: async (filters, client) => {
284
+ set({ eventReplayLoading: true, error: null });
285
+ try {
286
+ const params = new URLSearchParams();
287
+ if (filters.event_types) params.set("event_types", filters.event_types);
288
+ if (filters.path_pattern) params.set("path_pattern", filters.path_pattern);
289
+ if (filters.agent_id) params.set("agent_id", filters.agent_id);
290
+ if (filters.since) params.set("since", filters.since);
291
+ const cursor = get().eventReplayNextCursor;
292
+ if (cursor) params.set("cursor", cursor);
293
+ const qs = params.toString();
294
+ const url = `/api/v2/events/replay${qs ? `?${qs}` : ""}`;
295
+ const result = await client.get<{
296
+ events: EventReplayEntry[];
297
+ has_more: boolean;
298
+ next_cursor: string | null;
299
+ }>(url);
300
+ const events: EventReplayEntry[] = (result.events ?? []).map((e: any) => ({
301
+ event_id: e.event_id ?? "",
302
+ event_type: e.event_type ?? e.type ?? "",
303
+ agent_id: e.agent_id ?? null,
304
+ path: e.path ?? null,
305
+ timestamp: e.timestamp ?? "",
306
+ payload: e.payload ?? {},
307
+ }));
308
+ set((state) => ({
309
+ eventReplayEntries: cursor
310
+ ? [...state.eventReplayEntries, ...events]
311
+ : events,
312
+ eventReplayLoading: false,
313
+ eventReplayHasMore: result.has_more ?? false,
314
+ eventReplayNextCursor: result.next_cursor ?? null,
315
+ }));
316
+ } catch (err) {
317
+ const message = err instanceof Error ? err.message : "Failed to fetch event replay";
318
+ set({ eventReplayLoading: false, error: message });
319
+ useErrorStore.getState().pushError({ message, category: categorizeError(message), source: SOURCE });
320
+ }
321
+ },
322
+
323
+ searchByColumn: createApiAction<KnowledgeState, [string, FetchClient]>(set, {
324
+ loadingKey: "columnSearchLoading",
325
+ source: SOURCE,
326
+ errorMessage: "Failed to search",
327
+ action: async (column, client) => {
328
+ const result = await client.get<{
329
+ results: {
330
+ entity_urn?: string;
331
+ entityUrn?: string;
332
+ column_name?: string;
333
+ columnName?: string;
334
+ column_type?: string;
335
+ columnType?: string;
336
+ schema?: unknown;
337
+ }[];
338
+ }>(
339
+ `/api/v2/catalog/search?column=${encodeURIComponent(column)}`,
340
+ );
341
+ // Normalize snake_case → camelCase (API may return either)
342
+ const normalized = (result.results ?? []).map((r: any) => ({
343
+ entityUrn: r.entityUrn ?? r.entity_urn ?? "",
344
+ columnName: r.columnName ?? r.column_name ?? "",
345
+ columnType: r.columnType ?? r.column_type ?? "",
346
+ path: r.path ?? null,
347
+ schema: r.schema,
348
+ }));
349
+ return { columnSearchResults: normalized };
350
+ },
351
+ }),
352
+
353
+ clearReplay: () =>
354
+ set({ replayEntries: [], replayNextCursor: 0, replayHasMore: false }),
355
+
356
+ clearEventReplay: () =>
357
+ set({ eventReplayEntries: [], eventReplayHasMore: false, eventReplayNextCursor: null }),
358
+ }));
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Zustand store for lineage data: upstream inputs, downstream dependents.
3
+ * Issue #3417.
4
+ */
5
+
6
+ import { create } from "zustand";
7
+ import type { FetchClient } from "@nexus-ai-fs/api-client";
8
+ import { useErrorStore } from "./error-store.js";
9
+
10
+ // =============================================================================
11
+ // Types
12
+ // =============================================================================
13
+
14
+ export interface UpstreamEntry {
15
+ readonly path: string;
16
+ readonly version: number;
17
+ readonly etag: string;
18
+ readonly access_type: string;
19
+ }
20
+
21
+ export interface LineageData {
22
+ readonly upstream: readonly UpstreamEntry[];
23
+ readonly agent_id: string;
24
+ readonly agent_generation: number | null;
25
+ readonly operation: string;
26
+ readonly duration_ms: number | null;
27
+ readonly truncated: boolean;
28
+ }
29
+
30
+ export interface DownstreamEntry {
31
+ readonly downstream_urn: string;
32
+ readonly downstream_path: string | null;
33
+ readonly upstream_version: number;
34
+ readonly upstream_etag: string;
35
+ readonly agent_id: string;
36
+ readonly created_at: string | null;
37
+ }
38
+
39
+ // =============================================================================
40
+ // Store
41
+ // =============================================================================
42
+
43
+ export interface LineageState {
44
+ // Cache keyed by URN
45
+ readonly lineageCache: ReadonlyMap<string, LineageData | null>;
46
+ readonly downstreamCache: ReadonlyMap<string, readonly DownstreamEntry[]>;
47
+ readonly loading: boolean;
48
+ readonly error: string | null;
49
+
50
+ // Actions
51
+ readonly fetchLineage: (urn: string, path: string, client: FetchClient) => Promise<void>;
52
+ readonly clearCache: () => void;
53
+ }
54
+
55
+ export const useLineageStore = create<LineageState>((set, get) => ({
56
+ lineageCache: new Map(),
57
+ downstreamCache: new Map(),
58
+ loading: false,
59
+ error: null,
60
+
61
+ fetchLineage: async (urn: string, path: string, client: FetchClient) => {
62
+ // Skip if already cached
63
+ if (get().lineageCache.has(urn)) return;
64
+
65
+ set({ loading: true, error: null });
66
+ try {
67
+ // Fetch upstream lineage
68
+ const encodedUrn = encodeURIComponent(urn);
69
+ let lineageData: LineageData | null = null;
70
+ try {
71
+ const resp = await client.get<LineageData & { entity_urn: string }>(
72
+ `/api/v2/lineage/${encodedUrn}`,
73
+ );
74
+ lineageData = {
75
+ upstream: resp.upstream ?? [],
76
+ agent_id: resp.agent_id ?? "",
77
+ agent_generation: resp.agent_generation ?? null,
78
+ operation: resp.operation ?? "",
79
+ duration_ms: resp.duration_ms ?? null,
80
+ truncated: resp.truncated ?? false,
81
+ };
82
+ } catch {
83
+ // 404 = no lineage, not an error
84
+ lineageData = null;
85
+ }
86
+
87
+ // Fetch downstream dependents
88
+ let downstream: DownstreamEntry[] = [];
89
+ try {
90
+ const encodedPath = encodeURIComponent(path);
91
+ const dsResp = await client.get<{ downstream: DownstreamEntry[] }>(
92
+ `/api/v2/lineage/downstream/query?path=${encodedPath}`,
93
+ );
94
+ downstream = dsResp.downstream ?? [];
95
+ } catch {
96
+ // best effort
97
+ }
98
+
99
+ const newLineageCache = new Map(get().lineageCache);
100
+ newLineageCache.set(urn, lineageData);
101
+ const newDownstreamCache = new Map(get().downstreamCache);
102
+ newDownstreamCache.set(urn, downstream);
103
+
104
+ set({
105
+ lineageCache: newLineageCache,
106
+ downstreamCache: newDownstreamCache,
107
+ loading: false,
108
+ });
109
+ } catch (err: unknown) {
110
+ const msg = err instanceof Error ? err.message : String(err);
111
+ set({ loading: false, error: msg });
112
+ useErrorStore.getState().pushError({
113
+ title: "Lineage fetch failed",
114
+ detail: msg,
115
+ category: "api",
116
+ });
117
+ }
118
+ },
119
+
120
+ clearCache: () => {
121
+ set({
122
+ lineageCache: new Map(),
123
+ downstreamCache: new Map(),
124
+ });
125
+ },
126
+ }));
@@ -0,0 +1,147 @@
1
+ /**
2
+ * Zustand store for MCP (Model Context Protocol) server management.
3
+ *
4
+ * All operations use JSON-RPC via POST /api/nfs/{method}.
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
+
12
+ // =============================================================================
13
+ // Types (snake_case matching API wire format)
14
+ // =============================================================================
15
+
16
+ export interface McpMount {
17
+ readonly name: string;
18
+ readonly description: string | null;
19
+ readonly transport: "stdio" | "sse" | "klavis";
20
+ readonly mounted: boolean;
21
+ readonly tool_count: number;
22
+ readonly last_sync: string | null;
23
+ }
24
+
25
+ export interface McpTool {
26
+ readonly name: string;
27
+ readonly description: string | null;
28
+ readonly input_schema: unknown;
29
+ }
30
+
31
+ // =============================================================================
32
+ // Store
33
+ // =============================================================================
34
+
35
+ export interface McpState {
36
+ readonly mounts: readonly McpMount[];
37
+ readonly mountsLoading: boolean;
38
+ readonly selectedMountIndex: number;
39
+ readonly tools: readonly McpTool[];
40
+ readonly toolsLoading: boolean;
41
+ readonly error: string | null;
42
+
43
+ readonly fetchMounts: (client: FetchClient) => Promise<void>;
44
+ readonly mountServer: (
45
+ params: {
46
+ name: string;
47
+ command?: string;
48
+ url?: string;
49
+ description?: string;
50
+ },
51
+ client: FetchClient,
52
+ ) => Promise<void>;
53
+ readonly unmountServer: (name: string, client: FetchClient) => Promise<void>;
54
+ readonly syncServer: (name: string, client: FetchClient) => Promise<void>;
55
+ readonly fetchTools: (name: string, client: FetchClient) => Promise<void>;
56
+ readonly setSelectedMountIndex: (index: number) => void;
57
+ }
58
+
59
+ const SOURCE = "mcp";
60
+
61
+ export const useMcpStore = create<McpState>((set, get) => ({
62
+ mounts: [],
63
+ mountsLoading: false,
64
+ selectedMountIndex: 0,
65
+ tools: [],
66
+ toolsLoading: false,
67
+ error: null,
68
+
69
+ // =========================================================================
70
+ // Actions with loading keys — createApiAction
71
+ // =========================================================================
72
+
73
+ fetchMounts: createApiAction<McpState, [FetchClient]>(set, {
74
+ loadingKey: "mountsLoading",
75
+ source: SOURCE,
76
+ errorMessage: "Failed to fetch MCP mounts",
77
+ action: async (client) => {
78
+ const response = await client.post<{
79
+ result: readonly McpMount[];
80
+ }>("/api/nfs/mcp_list_mounts", { params: {} });
81
+ return {
82
+ mounts: response.result ?? [],
83
+ selectedMountIndex: 0,
84
+ };
85
+ },
86
+ }),
87
+
88
+ fetchTools: createApiAction<McpState, [string, FetchClient]>(set, {
89
+ loadingKey: "toolsLoading",
90
+ source: SOURCE,
91
+ errorMessage: "Failed to fetch MCP tools",
92
+ action: async (name, client) => {
93
+ const response = await client.post<{
94
+ result: readonly McpTool[];
95
+ }>("/api/nfs/mcp_list_tools", { params: { name } });
96
+ return { tools: response.result ?? [] };
97
+ },
98
+ }),
99
+
100
+ // =========================================================================
101
+ // Actions without loading keys — inline with error store integration
102
+ // =========================================================================
103
+
104
+ mountServer: async (params, client) => {
105
+ set({ error: null });
106
+ try {
107
+ await client.post("/api/nfs/mcp_mount", { params });
108
+ await get().fetchMounts(client);
109
+ } catch (err) {
110
+ const message = err instanceof Error ? err.message : "Failed to mount MCP server";
111
+ set({ error: message });
112
+ useErrorStore.getState().pushError({ message, category: categorizeError(message), source: SOURCE });
113
+ }
114
+ },
115
+
116
+ unmountServer: async (name, client) => {
117
+ set({ error: null });
118
+ try {
119
+ await client.post("/api/nfs/mcp_unmount", { params: { name } });
120
+ set((state) => ({
121
+ mounts: state.mounts.filter((m) => m.name !== name),
122
+ selectedMountIndex: Math.min(
123
+ state.selectedMountIndex,
124
+ Math.max(state.mounts.length - 2, 0),
125
+ ),
126
+ }));
127
+ } catch (err) {
128
+ const message = err instanceof Error ? err.message : "Failed to unmount MCP server";
129
+ set({ error: message });
130
+ useErrorStore.getState().pushError({ message, category: categorizeError(message), source: SOURCE });
131
+ }
132
+ },
133
+
134
+ syncServer: async (name, client) => {
135
+ set({ error: null });
136
+ try {
137
+ await client.post("/api/nfs/mcp_sync", { params: { name } });
138
+ await get().fetchMounts(client);
139
+ } catch (err) {
140
+ const message = err instanceof Error ? err.message : "Failed to sync MCP server";
141
+ set({ error: message });
142
+ useErrorStore.getState().pushError({ message, category: categorizeError(message), source: SOURCE });
143
+ }
144
+ },
145
+
146
+ setSelectedMountIndex: (index) => set({ selectedMountIndex: index }),
147
+ }));