@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.
- package/README.md +30 -0
- package/package.json +48 -0
- package/src/app.tsx +349 -0
- package/src/index.tsx +137 -0
- package/src/opentui-env.d.ts +61 -0
- package/src/panels/access/access-panel.tsx +597 -0
- package/src/panels/access/alert-list.tsx +77 -0
- package/src/panels/access/constraint-creator.tsx +128 -0
- package/src/panels/access/constraint-list.tsx +72 -0
- package/src/panels/access/credential-list.tsx +68 -0
- package/src/panels/access/delegation-chain-view.tsx +110 -0
- package/src/panels/access/delegation-completer.tsx +120 -0
- package/src/panels/access/delegation-creator.tsx +237 -0
- package/src/panels/access/delegation-list.tsx +74 -0
- package/src/panels/access/fraud-score-view.tsx +94 -0
- package/src/panels/access/manifest-creator.tsx +167 -0
- package/src/panels/access/manifest-list.tsx +105 -0
- package/src/panels/access/namespace-config-view.tsx +525 -0
- package/src/panels/access/permission-checker.tsx +231 -0
- package/src/panels/agents/agent-status-view.tsx +196 -0
- package/src/panels/agents/agents-panel.tsx +493 -0
- package/src/panels/agents/delegation-list.tsx +154 -0
- package/src/panels/agents/inbox-view.tsx +96 -0
- package/src/panels/agents/trajectories-tab.tsx +40 -0
- package/src/panels/api-console/api-console-panel.tsx +189 -0
- package/src/panels/api-console/codegen-viewer.tsx +36 -0
- package/src/panels/api-console/codegen.ts +112 -0
- package/src/panels/api-console/endpoint-list.tsx +57 -0
- package/src/panels/api-console/request-builder.tsx +69 -0
- package/src/panels/api-console/response-viewer.tsx +54 -0
- package/src/panels/connectors/available-tab.tsx +357 -0
- package/src/panels/connectors/connector-row.tsx +121 -0
- package/src/panels/connectors/connectors-panel.tsx +88 -0
- package/src/panels/connectors/error-parser.ts +116 -0
- package/src/panels/connectors/mounted-tab.tsx +179 -0
- package/src/panels/connectors/skills-tab.tsx +235 -0
- package/src/panels/connectors/template-generator.ts +211 -0
- package/src/panels/connectors/write-tab.tsx +514 -0
- package/src/panels/events/audit-tab.tsx +69 -0
- package/src/panels/events/audit-trail.tsx +75 -0
- package/src/panels/events/connector-detail.tsx +49 -0
- package/src/panels/events/connector-list.tsx +73 -0
- package/src/panels/events/connectors-tab.tsx +92 -0
- package/src/panels/events/event-replay.tsx +80 -0
- package/src/panels/events/events-panel.tsx +414 -0
- package/src/panels/events/events-tab.tsx +212 -0
- package/src/panels/events/lock-list.tsx +54 -0
- package/src/panels/events/locks-tab.tsx +103 -0
- package/src/panels/events/mcl-replay.tsx +77 -0
- package/src/panels/events/mcl-tab.tsx +83 -0
- package/src/panels/events/operations-tab-wrapper.tsx +62 -0
- package/src/panels/events/operations-tab.tsx +41 -0
- package/src/panels/events/replay-tab.tsx +76 -0
- package/src/panels/events/secrets-audit.tsx +64 -0
- package/src/panels/events/secrets-tab.tsx +75 -0
- package/src/panels/events/subscription-list.tsx +54 -0
- package/src/panels/events/subscriptions-tab.tsx +82 -0
- package/src/panels/files/file-aspects.tsx +93 -0
- package/src/panels/files/file-editor.tsx +160 -0
- package/src/panels/files/file-explorer-keybindings.ts +468 -0
- package/src/panels/files/file-explorer-panel.tsx +545 -0
- package/src/panels/files/file-lineage.tsx +163 -0
- package/src/panels/files/file-list-item.tsx +28 -0
- package/src/panels/files/file-metadata.tsx +62 -0
- package/src/panels/files/file-preview.tsx +108 -0
- package/src/panels/files/file-schema.tsx +89 -0
- package/src/panels/files/file-tree-node.tsx +44 -0
- package/src/panels/files/file-tree.tsx +169 -0
- package/src/panels/files/share-links-tab.tsx +33 -0
- package/src/panels/files/uploads-tab.tsx +45 -0
- package/src/panels/payments/approval-list.tsx +83 -0
- package/src/panels/payments/balance-card.tsx +43 -0
- package/src/panels/payments/budget-card.tsx +70 -0
- package/src/panels/payments/payments-panel.tsx +451 -0
- package/src/panels/payments/policy-list.tsx +64 -0
- package/src/panels/payments/reservation-list.tsx +78 -0
- package/src/panels/payments/transaction-list.tsx +103 -0
- package/src/panels/payments/transfer-form.tsx +109 -0
- package/src/panels/search/column-search.tsx +79 -0
- package/src/panels/search/knowledge-view.tsx +100 -0
- package/src/panels/search/memory-list.tsx +197 -0
- package/src/panels/search/playbook-list.tsx +77 -0
- package/src/panels/search/rlm-answer-view.tsx +105 -0
- package/src/panels/search/search-panel.tsx +405 -0
- package/src/panels/search/search-results.tsx +116 -0
- package/src/panels/stack/stack-panel.tsx +474 -0
- package/src/panels/versions/conflicts-tab.tsx +59 -0
- package/src/panels/versions/entry-detail.tsx +89 -0
- package/src/panels/versions/transaction-actions.tsx +34 -0
- package/src/panels/versions/transaction-list.tsx +90 -0
- package/src/panels/versions/versions-panel.tsx +276 -0
- package/src/panels/workflows/execution-list.tsx +102 -0
- package/src/panels/workflows/scheduler-view.tsx +135 -0
- package/src/panels/workflows/workflow-list.tsx +88 -0
- package/src/panels/workflows/workflows-panel.tsx +295 -0
- package/src/panels/zones/brick-detail.tsx +136 -0
- package/src/panels/zones/brick-list.tsx +56 -0
- package/src/panels/zones/cache-tab.tsx +118 -0
- package/src/panels/zones/drift-view.tsx +97 -0
- package/src/panels/zones/mcp-mounts-tab.tsx +38 -0
- package/src/panels/zones/memories-tab.tsx +37 -0
- package/src/panels/zones/reindex-status.tsx +84 -0
- package/src/panels/zones/workspaces-tab.tsx +37 -0
- package/src/panels/zones/zone-list.tsx +73 -0
- package/src/panels/zones/zones-panel.tsx +559 -0
- package/src/services/command-runner.ts +303 -0
- package/src/shared/accessibility-announcements.ts +44 -0
- package/src/shared/action-registry.ts +466 -0
- package/src/shared/brick-states.ts +91 -0
- package/src/shared/command-palette.ts +35 -0
- package/src/shared/components/announcement-bar.tsx +30 -0
- package/src/shared/components/app-confirm-dialog.tsx +29 -0
- package/src/shared/components/breadcrumb.tsx +21 -0
- package/src/shared/components/brick-gate.tsx +60 -0
- package/src/shared/components/command-output.tsx +95 -0
- package/src/shared/components/command-palette.tsx +97 -0
- package/src/shared/components/confirm-dialog.tsx +61 -0
- package/src/shared/components/diff-viewer.tsx +219 -0
- package/src/shared/components/empty-state.tsx +36 -0
- package/src/shared/components/error-bar.tsx +60 -0
- package/src/shared/components/error-boundary.tsx +53 -0
- package/src/shared/components/help-overlay.tsx +99 -0
- package/src/shared/components/identity-switcher.tsx +168 -0
- package/src/shared/components/loading-indicator.tsx +40 -0
- package/src/shared/components/pagination-bar.tsx +68 -0
- package/src/shared/components/pre-connection-screen.tsx +398 -0
- package/src/shared/components/scroll-indicator.tsx +46 -0
- package/src/shared/components/side-nav-utils.ts +68 -0
- package/src/shared/components/side-nav.tsx +287 -0
- package/src/shared/components/spinner.tsx +26 -0
- package/src/shared/components/status-bar.tsx +117 -0
- package/src/shared/components/styled-text.tsx +72 -0
- package/src/shared/components/sub-tab-bar-utils.ts +100 -0
- package/src/shared/components/sub-tab-bar.tsx +40 -0
- package/src/shared/components/tab-bar-utils.ts +36 -0
- package/src/shared/components/tab-bar.tsx +50 -0
- package/src/shared/components/text-input.tsx +73 -0
- package/src/shared/components/tooltip.tsx +53 -0
- package/src/shared/components/virtual-list.tsx +93 -0
- package/src/shared/components/welcome-screen.tsx +111 -0
- package/src/shared/hooks/use-api.ts +10 -0
- package/src/shared/hooks/use-brick-available.ts +42 -0
- package/src/shared/hooks/use-confirm.ts +66 -0
- package/src/shared/hooks/use-connection-state.ts +67 -0
- package/src/shared/hooks/use-copy.ts +31 -0
- package/src/shared/hooks/use-fresh-server.ts +62 -0
- package/src/shared/hooks/use-keyboard.ts +58 -0
- package/src/shared/hooks/use-list-navigation.ts +106 -0
- package/src/shared/hooks/use-swr.ts +117 -0
- package/src/shared/hooks/use-tab-fallback.ts +32 -0
- package/src/shared/hooks/use-text-input.ts +113 -0
- package/src/shared/hooks/use-visible-tabs.ts +61 -0
- package/src/shared/lib/circular-buffer.ts +82 -0
- package/src/shared/lib/clipboard.ts +14 -0
- package/src/shared/nav-items.ts +73 -0
- package/src/shared/navigation.ts +110 -0
- package/src/shared/status-breadcrumb.ts +74 -0
- package/src/shared/syntax-style.ts +3 -0
- package/src/shared/tab-visibility.ts +15 -0
- package/src/shared/text-style.ts +23 -0
- package/src/shared/theme.ts +179 -0
- package/src/shared/utils/format-size.ts +20 -0
- package/src/shared/utils/format-text.ts +10 -0
- package/src/shared/utils/format-time.ts +72 -0
- package/src/shared/utils/lru-cache.ts +75 -0
- package/src/stores/access-store-types.ts +154 -0
- package/src/stores/access-store.ts +674 -0
- package/src/stores/agents-store.ts +404 -0
- package/src/stores/announcement-store.ts +46 -0
- package/src/stores/api-console-store.ts +476 -0
- package/src/stores/connectors-store.ts +434 -0
- package/src/stores/create-api-action.ts +140 -0
- package/src/stores/delegation-store.ts +300 -0
- package/src/stores/error-store.ts +102 -0
- package/src/stores/events-store.ts +163 -0
- package/src/stores/files-store.ts +630 -0
- package/src/stores/first-run-store.ts +34 -0
- package/src/stores/global-store.ts +255 -0
- package/src/stores/infra-store.ts +461 -0
- package/src/stores/knowledge-store.ts +358 -0
- package/src/stores/lineage-store.ts +126 -0
- package/src/stores/mcp-store.ts +147 -0
- package/src/stores/payments-store.ts +545 -0
- package/src/stores/search-store-types.ts +155 -0
- package/src/stores/search-store.ts +656 -0
- package/src/stores/share-link-store.ts +151 -0
- package/src/stores/stack-store.ts +352 -0
- package/src/stores/ui-store.ts +161 -0
- package/src/stores/upload-store.ts +131 -0
- package/src/stores/versions-store.ts +355 -0
- package/src/stores/workflows-store.ts +402 -0
- package/src/stores/workspace-store.ts +185 -0
- 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
|
+
}));
|