@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,414 @@
1
+ /**
2
+ * Infrastructure & Events panel.
3
+ *
4
+ * Tabbed layout: Events (SSE stream) | Connectors | Subscriptions | Locks | Secrets
5
+ *
6
+ * Press 'f' to enter event type filter mode, 's' to enter search filter mode.
7
+ * In filter mode, type the filter value, Enter to apply, Escape to cancel.
8
+ */
9
+
10
+ import React, { useState, useEffect, useCallback, useMemo } from "react";
11
+ import { useEventsStore } from "../../stores/events-store.js";
12
+ import { useInfraStore } from "../../stores/infra-store.js";
13
+ import { useGlobalStore } from "../../stores/global-store.js";
14
+ import { useKeyboard } from "../../shared/hooks/use-keyboard.js";
15
+ import { useCopy } from "../../shared/hooks/use-copy.js";
16
+ import { useConfirmStore } from "../../shared/hooks/use-confirm.js";
17
+ import { useApi } from "../../shared/hooks/use-api.js";
18
+ import { useUiStore } from "../../stores/ui-store.js";
19
+ import { useVisibleTabs } from "../../shared/hooks/use-visible-tabs.js";
20
+ import { ConnectorList } from "./connector-list.js";
21
+ import { ConnectorDetail } from "./connector-detail.js";
22
+ import { SubscriptionList } from "./subscription-list.js";
23
+ import { LockList } from "./lock-list.js";
24
+ import { SecretsAudit } from "./secrets-audit.js";
25
+ import { MclReplay } from "./mcl-replay.js";
26
+ import { EventReplay } from "./event-replay.js";
27
+ import { OperationsTab } from "./operations-tab.js";
28
+ import { AuditTrail } from "./audit-trail.js";
29
+ import { useKnowledgeStore } from "../../stores/knowledge-store.js";
30
+ import { EmptyState } from "../../shared/components/empty-state.js";
31
+ import { ScrollIndicator } from "../../shared/components/scroll-indicator.js";
32
+ import { Tooltip } from "../../shared/components/tooltip.js";
33
+ import { SubTabBar } from "../../shared/components/sub-tab-bar.js";
34
+ import { useTabFallback } from "../../shared/hooks/use-tab-fallback.js";
35
+ import { EVENTS_TABS } from "../../shared/navigation.js";
36
+ import { statusColor } from "../../shared/theme.js";
37
+ import {
38
+ type FilterMode,
39
+ type EventsBindingContext,
40
+ getEventsKeyBindings,
41
+ getEventsHelpText,
42
+ handleEventsUnhandledKey,
43
+ formatEventData,
44
+ } from "./events-panel-keybindings.js";
45
+
46
+
47
+ export default function EventsPanel(): React.ReactNode {
48
+ const apiClient = useApi();
49
+ const confirm = useConfirmStore((s) => s.confirm);
50
+ const overlayActive = useUiStore((s) => s.overlayActive);
51
+ const visibleTabs = useVisibleTabs(EVENTS_TABS);
52
+ const config = useGlobalStore((s) => s.config);
53
+
54
+ // Clipboard copy
55
+ const { copy, copied } = useCopy();
56
+
57
+ // Filter input state
58
+ const [filterMode, setFilterMode] = useState<FilterMode>("none");
59
+ const [filterBuffer, setFilterBuffer] = useState("");
60
+
61
+ // Event detail expansion
62
+ const [selectedEventIndex, setSelectedEventIndex] = useState(-1);
63
+ const [expandedEventIndex, setExpandedEventIndex] = useState<number | null>(null);
64
+
65
+ // MCL filter state
66
+ const [mclUrnFilter, setMclUrnFilter] = useState("");
67
+ const [mclAspectFilter, setMclAspectFilter] = useState("");
68
+
69
+ // Secrets filter state
70
+ const [secretsFilter, setSecretsFilter] = useState("");
71
+
72
+ // Replay filter state
73
+ const [replayTypeFilter, setReplayTypeFilter] = useState("");
74
+
75
+ // Connector detail state
76
+ const [connectorDetailView, setConnectorDetailView] = useState(false);
77
+
78
+ // Audit selected index
79
+ const [selectedAuditIndex, setSelectedAuditIndex] = useState(0);
80
+
81
+ // Events store (SSE)
82
+ const connected = useEventsStore((s) => s.connected);
83
+ const events = useEventsStore((s) => s.filteredEvents);
84
+ const reconnectCount = useEventsStore((s) => s.reconnectCount);
85
+ const reconnectExhausted = useEventsStore((s) => s.reconnectExhausted);
86
+ const filters = useEventsStore((s) => s.filters);
87
+ const eventsOverflowed = useEventsStore((s) => s.eventsOverflowed);
88
+ const evictedCount = useEventsStore((s) => s.evictedCount);
89
+ const eventsBuffer = useEventsStore((s) => s.eventsBuffer);
90
+ const connect = useEventsStore((s) => s.connect);
91
+ const disconnect = useEventsStore((s) => s.disconnect);
92
+ const clearEvents = useEventsStore((s) => s.clearEvents);
93
+ const setFilter = useEventsStore((s) => s.setFilter);
94
+
95
+ // Infra store
96
+ const infraTab = useInfraStore((s) => s.activeTab);
97
+ const connectors = useInfraStore((s) => s.connectors);
98
+ const connectorsLoading = useInfraStore((s) => s.connectorsLoading);
99
+ const selectedConnectorIndex = useInfraStore((s) => s.selectedConnectorIndex);
100
+ const subscriptions = useInfraStore((s) => s.subscriptions);
101
+ const subscriptionsLoading = useInfraStore((s) => s.subscriptionsLoading);
102
+ const selectedSubscriptionIndex = useInfraStore((s) => s.selectedSubscriptionIndex);
103
+ const locks = useInfraStore((s) => s.locks);
104
+ const locksLoading = useInfraStore((s) => s.locksLoading);
105
+ const selectedLockIndex = useInfraStore((s) => s.selectedLockIndex);
106
+ const secretAuditEntries = useInfraStore((s) => s.secretAuditEntries);
107
+ const secretsLoading = useInfraStore((s) => s.secretsLoading);
108
+ const operations = useInfraStore((s) => s.operations);
109
+ const operationsLoading = useInfraStore((s) => s.operationsLoading);
110
+ const selectedOperationIndex = useInfraStore((s) => s.selectedOperationIndex);
111
+ const infraError = useInfraStore((s) => s.error);
112
+
113
+ const connectorCapabilities = useInfraStore((s) => s.connectorCapabilities);
114
+ const capabilitiesLoading = useInfraStore((s) => s.capabilitiesLoading);
115
+ const auditTransactions = useInfraStore((s) => s.auditTransactions);
116
+ const auditLoading = useInfraStore((s) => s.auditLoading);
117
+ const auditHasMore = useInfraStore((s) => s.auditHasMore);
118
+ const auditNextCursor = useInfraStore((s) => s.auditNextCursor);
119
+
120
+ const fetchConnectors = useInfraStore((s) => s.fetchConnectors);
121
+ const fetchSubscriptions = useInfraStore((s) => s.fetchSubscriptions);
122
+ const deleteSubscription = useInfraStore((s) => s.deleteSubscription);
123
+ const testSubscription = useInfraStore((s) => s.testSubscription);
124
+ const fetchLocks = useInfraStore((s) => s.fetchLocks);
125
+ const acquireLock = useInfraStore((s) => s.acquireLock);
126
+ const releaseLock = useInfraStore((s) => s.releaseLock);
127
+ const extendLock = useInfraStore((s) => s.extendLock);
128
+ const fetchSecretAudit = useInfraStore((s) => s.fetchSecretAudit);
129
+ const fetchOperations = useInfraStore((s) => s.fetchOperations);
130
+ const fetchConnectorCapabilities = useInfraStore((s) => s.fetchConnectorCapabilities);
131
+ const fetchAuditTransactions = useInfraStore((s) => s.fetchAuditTransactions);
132
+ const setSelectedOperationIndex = useInfraStore((s) => s.setSelectedOperationIndex);
133
+ const activeTab = useInfraStore((s) => s.activePanelTab);
134
+ const setActiveTab = useInfraStore((s) => s.setActivePanelTab);
135
+ const setSelectedConnectorIndex = useInfraStore((s) => s.setSelectedConnectorIndex);
136
+ const setSelectedSubscriptionIndex = useInfraStore((s) => s.setSelectedSubscriptionIndex);
137
+ const setSelectedLockIndex = useInfraStore((s) => s.setSelectedLockIndex);
138
+
139
+ useTabFallback(visibleTabs, activeTab, setActiveTab);
140
+
141
+ // Reset expanded event when events change (index may become stale after SSE adds/evicts)
142
+ const eventsLength = events.length;
143
+ useEffect(() => {
144
+ setExpandedEventIndex(null);
145
+ }, [eventsLength]);
146
+
147
+ // Auto-connect SSE on mount, reconnect when identity changes
148
+ useEffect(() => {
149
+ if (config.apiKey && config.baseUrl) {
150
+ connect(config.baseUrl, config.apiKey, {
151
+ agentId: config.agentId,
152
+ subject: config.subject,
153
+ zoneId: config.zoneId,
154
+ });
155
+ }
156
+ return () => disconnect();
157
+ }, [config.apiKey, config.baseUrl, config.agentId, config.subject, config.zoneId, connect, disconnect]);
158
+
159
+ // Knowledge store (MCL replay + event replay)
160
+ const fetchReplay = useKnowledgeStore((s) => s.fetchReplay);
161
+ const clearReplay = useKnowledgeStore((s) => s.clearReplay);
162
+ const fetchEventReplay = useKnowledgeStore((s) => s.fetchEventReplay);
163
+ const clearEventReplay = useKnowledgeStore((s) => s.clearEventReplay);
164
+
165
+ // Fetch infra data when switching tabs
166
+ useEffect(() => {
167
+ if (!apiClient || activeTab === "events") return;
168
+
169
+ if (activeTab === "mcl") void fetchReplay(apiClient, 0, 50);
170
+ else if (activeTab === "replay") void fetchEventReplay({}, apiClient);
171
+ else if (activeTab === "connectors") { fetchConnectors(apiClient); setConnectorDetailView(false); }
172
+ else if (activeTab === "subscriptions") fetchSubscriptions(apiClient);
173
+ else if (activeTab === "locks") fetchLocks(apiClient);
174
+ else if (activeTab === "secrets") fetchSecretAudit(apiClient);
175
+ else if (activeTab === "operations") fetchOperations(apiClient);
176
+ else if (activeTab === "audit") void fetchAuditTransactions({}, apiClient);
177
+ }, [activeTab, apiClient, fetchConnectors, fetchSubscriptions, fetchLocks, fetchSecretAudit, fetchOperations, fetchReplay, fetchEventReplay, fetchAuditTransactions]);
178
+
179
+ // Build binding context for keybinding builders (Decision 6A)
180
+ const bindingCtx: EventsBindingContext = {
181
+ activeTab, visibleTabs, setActiveTab,
182
+ filterMode, filterBuffer, setFilterMode, setFilterBuffer,
183
+ events, selectedEventIndex, setSelectedEventIndex,
184
+ expandedEventIndex, setExpandedEventIndex,
185
+ filters, setFilter, clearEvents, copy,
186
+ config, disconnect, connect,
187
+ mclUrnFilter, setMclUrnFilter, mclAspectFilter, setMclAspectFilter,
188
+ clearReplay, fetchReplay,
189
+ replayTypeFilter, setReplayTypeFilter, clearEventReplay, fetchEventReplay,
190
+ connectors, selectedConnectorIndex, setSelectedConnectorIndex,
191
+ connectorDetailView, setConnectorDetailView,
192
+ fetchConnectors, fetchConnectorCapabilities,
193
+ subscriptions, selectedSubscriptionIndex, setSelectedSubscriptionIndex,
194
+ deleteSubscription, testSubscription, fetchSubscriptions,
195
+ locks, selectedLockIndex, setSelectedLockIndex,
196
+ acquireLock, releaseLock, extendLock, fetchLocks,
197
+ secretsFilter, setSecretsFilter, fetchSecretAudit,
198
+ operations, selectedOperationIndex, setSelectedOperationIndex, fetchOperations,
199
+ auditTransactions, selectedAuditIndex, setSelectedAuditIndex,
200
+ auditHasMore, auditNextCursor, fetchAuditTransactions,
201
+ apiClient, confirm,
202
+ };
203
+
204
+ // Handle unhandled keys in filter input mode
205
+ const handleUnhandledKey = useCallback(
206
+ (keyName: string) => handleEventsUnhandledKey(filterMode, setFilterBuffer, keyName),
207
+ [filterMode],
208
+ );
209
+
210
+ useKeyboard(
211
+ getEventsKeyBindings(overlayActive, bindingCtx),
212
+ overlayActive ? undefined : handleUnhandledKey,
213
+ );
214
+
215
+ return (
216
+ <box height="100%" width="100%" flexDirection="column">
217
+ <Tooltip tooltipKey="events-panel" message="Tip: Press ? for keybinding help" />
218
+ {/* Tab bar */}
219
+ <SubTabBar tabs={visibleTabs} activeTab={activeTab} />
220
+
221
+ {/* Filter bar (events tab) */}
222
+ {activeTab === "events" && (
223
+ <box height={1} width="100%">
224
+ <text>
225
+ {filterMode === "type"
226
+ ? `Filter type: ${filterBuffer}\u2588`
227
+ : filterMode === "search"
228
+ ? `Filter search: ${filterBuffer}\u2588`
229
+ : `Filter: type=${filters.eventType ?? "*"} search=${filters.search ?? "*"}`}
230
+ </text>
231
+ </box>
232
+ )}
233
+
234
+ {/* Filter bar (MCL tab) */}
235
+ {activeTab === "mcl" && (
236
+ <box height={1} width="100%">
237
+ <text>
238
+ {filterMode === "mcl_urn"
239
+ ? `Filter URN: ${filterBuffer}\u2588`
240
+ : filterMode === "mcl_aspect"
241
+ ? `Filter aspect: ${filterBuffer}\u2588`
242
+ : `Filter: URN=${mclUrnFilter || "*"} aspect=${mclAspectFilter || "*"}`}
243
+ </text>
244
+ </box>
245
+ )}
246
+
247
+ {/* Acquire lock input bar (locks tab) */}
248
+ {activeTab === "locks" && filterMode === "acquire_path" && (
249
+ <box height={1} width="100%">
250
+ <text>{`Acquire lock path: ${filterBuffer}\u2588`}</text>
251
+ </box>
252
+ )}
253
+
254
+ {/* Replay filter bar */}
255
+ {activeTab === "replay" && (
256
+ <box height={1} width="100%">
257
+ <text>
258
+ {filterMode === "replay_filter"
259
+ ? `Filter event type: ${filterBuffer}\u2588`
260
+ : `Filter: event_type=${replayTypeFilter || "*"}`}
261
+ </text>
262
+ </box>
263
+ )}
264
+
265
+ {/* Secrets filter bar */}
266
+ {activeTab === "secrets" && (
267
+ <box height={1} width="100%">
268
+ <text>
269
+ {filterMode === "secrets_filter"
270
+ ? `Filter: ${filterBuffer}\u2588`
271
+ : secretsFilter
272
+ ? `Filter: ${secretsFilter}`
273
+ : ""}
274
+ </text>
275
+ </box>
276
+ )}
277
+
278
+ {/* Error display */}
279
+ {infraError && activeTab !== "events" && activeTab !== "mcl" && activeTab !== "replay" && (
280
+ <box height={1} width="100%">
281
+ <text>{`Error: ${infraError}`}</text>
282
+ </box>
283
+ )}
284
+
285
+ {/* Main content */}
286
+ <box flexGrow={1} width="100%" borderStyle="single">
287
+ {activeTab === "events" && (
288
+ <box height="100%" width="100%" flexDirection="column">
289
+ {/* SSE status */}
290
+ <box height={1} width="100%">
291
+ <text>
292
+ {connected
293
+ ? `● Connected — ${events.length} events`
294
+ : reconnectExhausted
295
+ ? `✕ Reconnect failed after ${reconnectCount} attempts — press r to retry`
296
+ : reconnectCount > 0
297
+ ? `◐ Auto-reconnecting (attempt ${reconnectCount}/10)...`
298
+ : "○ Disconnected"}
299
+ </text>
300
+ </box>
301
+
302
+ {/* Overflow indicator */}
303
+ {eventsOverflowed && (
304
+ <box height={1} width="100%">
305
+ <text dimColor>
306
+ {`Showing latest ${eventsBuffer.size} of ${eventsBuffer.totalAdded} events (${evictedCount} evicted)`}
307
+ </text>
308
+ </box>
309
+ )}
310
+
311
+ {/* Event stream */}
312
+ {expandedEventIndex !== null && expandedEventIndex < events.length ? (
313
+ <box flexGrow={1} width="100%" flexDirection="column">
314
+ <box height={1} width="100%">
315
+ <text bold>{`[${events[expandedEventIndex]!.event}] — Event #${expandedEventIndex} (Escape to close)`}</text>
316
+ </box>
317
+ <scrollbox flexGrow={1} width="100%">
318
+ <text>{formatEventData(events[expandedEventIndex]!.data)}</text>
319
+ </scrollbox>
320
+ </box>
321
+ ) : (
322
+ <ScrollIndicator selectedIndex={selectedEventIndex >= 0 ? selectedEventIndex : events.length - 1} totalItems={events.length} visibleItems={20}>
323
+ <scrollbox flexGrow={1} width="100%">
324
+ {events.length === 0 ? (
325
+ <EmptyState
326
+ message="Listening for events..."
327
+ hint="Waiting for activity on the server."
328
+ />
329
+ ) : (
330
+ events.map((event, index) => (
331
+ <box key={event.id ?? index} height={1} width="100%" flexDirection="row">
332
+ <text inverse={index === selectedEventIndex || undefined}>
333
+ {`[${event.event}] ${event.data}`}
334
+ </text>
335
+ </box>
336
+ ))
337
+ )}
338
+ </scrollbox>
339
+ </ScrollIndicator>
340
+ )}
341
+ </box>
342
+ )}
343
+
344
+ {activeTab === "mcl" && <MclReplay urnFilter={mclUrnFilter} aspectFilter={mclAspectFilter} />}
345
+
346
+ {activeTab === "replay" && <EventReplay typeFilter={replayTypeFilter} />}
347
+
348
+ {activeTab === "connectors" && (
349
+ connectorDetailView && connectors[selectedConnectorIndex] ? (
350
+ <ConnectorDetail
351
+ connectorName={connectors[selectedConnectorIndex]!.name}
352
+ capabilities={connectorCapabilities}
353
+ loading={capabilitiesLoading}
354
+ />
355
+ ) : (
356
+ <ConnectorList
357
+ connectors={connectors}
358
+ selectedIndex={selectedConnectorIndex}
359
+ loading={connectorsLoading}
360
+ />
361
+ )
362
+ )}
363
+
364
+ {activeTab === "subscriptions" && (
365
+ <SubscriptionList
366
+ subscriptions={subscriptions}
367
+ selectedIndex={selectedSubscriptionIndex}
368
+ loading={subscriptionsLoading}
369
+ />
370
+ )}
371
+
372
+ {activeTab === "locks" && (
373
+ <LockList
374
+ locks={locks}
375
+ selectedIndex={selectedLockIndex}
376
+ loading={locksLoading}
377
+ />
378
+ )}
379
+
380
+ {activeTab === "secrets" && (
381
+ <SecretsAudit
382
+ entries={secretAuditEntries}
383
+ loading={secretsLoading}
384
+ filter={secretsFilter}
385
+ />
386
+ )}
387
+
388
+ {activeTab === "operations" && (
389
+ <OperationsTab
390
+ operations={operations}
391
+ selectedIndex={selectedOperationIndex}
392
+ loading={operationsLoading}
393
+ />
394
+ )}
395
+
396
+ {activeTab === "audit" && (
397
+ <AuditTrail
398
+ transactions={auditTransactions}
399
+ loading={auditLoading}
400
+ hasMore={auditHasMore}
401
+ selectedIndex={selectedAuditIndex}
402
+ />
403
+ )}
404
+ </box>
405
+
406
+ {/* Help bar */}
407
+ <box height={1} width="100%">
408
+ {copied
409
+ ? <text foregroundColor={statusColor.healthy}>Copied!</text>
410
+ : <text>{getEventsHelpText(filterMode, activeTab, connectorDetailView)}</text>}
411
+ </box>
412
+ </box>
413
+ );
414
+ }
@@ -0,0 +1,212 @@
1
+ /**
2
+ * Events SSE stream tab: live event stream with connection states,
3
+ * filtering, expansion, and copy support.
4
+ *
5
+ * Extracted from events-panel.tsx (Issue 2A: split into per-tab sub-panels).
6
+ */
7
+
8
+ import React, { useState, useEffect } from "react";
9
+ import { useEventsStore } from "../../stores/events-store.js";
10
+ import { useGlobalStore } from "../../stores/global-store.js";
11
+ import { useKeyboard } from "../../shared/hooks/use-keyboard.js";
12
+ import { useCopy } from "../../shared/hooks/use-copy.js";
13
+ import { useTextInput } from "../../shared/hooks/use-text-input.js";
14
+ import { listNavigationBindings } from "../../shared/hooks/use-list-navigation.js";
15
+ import { statusColor } from "../../shared/theme.js";
16
+ import { EmptyState } from "../../shared/components/empty-state.js";
17
+ import { ScrollIndicator } from "../../shared/components/scroll-indicator.js";
18
+
19
+ function formatEventData(data: string): string {
20
+ try {
21
+ const parsed = JSON.parse(data);
22
+ return JSON.stringify(parsed, null, 2);
23
+ } catch {
24
+ return data;
25
+ }
26
+ }
27
+
28
+ const HELP_NORMAL = "j/k:navigate Enter:expand f:filter type s:search c:clear r:reconnect y:copy Tab:switch";
29
+ const HELP_INPUT = "Type value, Enter:apply, Escape:cancel, Backspace:delete";
30
+
31
+ interface EventsTabProps {
32
+ /** Tab-level keybindings (tab cycling) to merge. */
33
+ readonly tabBindings: Readonly<Record<string, () => void>>;
34
+ readonly overlayActive: boolean;
35
+ }
36
+
37
+ export function EventsTab({ tabBindings, overlayActive }: EventsTabProps): React.ReactNode {
38
+ const config = useGlobalStore((s) => s.config);
39
+ const { copy, copied } = useCopy();
40
+
41
+ // SSE state
42
+ const connected = useEventsStore((s) => s.connected);
43
+ const events = useEventsStore((s) => s.filteredEvents);
44
+ const reconnectCount = useEventsStore((s) => s.reconnectCount);
45
+ const reconnectExhausted = useEventsStore((s) => s.reconnectExhausted);
46
+ const filters = useEventsStore((s) => s.filters);
47
+ const eventsOverflowed = useEventsStore((s) => s.eventsOverflowed);
48
+ const evictedCount = useEventsStore((s) => s.evictedCount);
49
+ const eventsBuffer = useEventsStore((s) => s.eventsBuffer);
50
+ const connect = useEventsStore((s) => s.connect);
51
+ const disconnect = useEventsStore((s) => s.disconnect);
52
+ const clearEvents = useEventsStore((s) => s.clearEvents);
53
+ const setFilter = useEventsStore((s) => s.setFilter);
54
+
55
+ // Selection and expansion
56
+ const [selectedEventIndex, setSelectedEventIndex] = useState(-1);
57
+ const [expandedEventIndex, setExpandedEventIndex] = useState<number | null>(null);
58
+
59
+ // Reset expanded event when events change (index may become stale)
60
+ const eventsLength = events.length;
61
+ useEffect(() => {
62
+ setExpandedEventIndex(null);
63
+ }, [eventsLength]);
64
+
65
+ // Auto-connect SSE on mount
66
+ useEffect(() => {
67
+ if (config.apiKey && config.baseUrl) {
68
+ connect(config.baseUrl, config.apiKey, {
69
+ agentId: config.agentId,
70
+ subject: config.subject,
71
+ zoneId: config.zoneId,
72
+ });
73
+ }
74
+ return () => disconnect();
75
+ }, [config.apiKey, config.baseUrl, config.agentId, config.subject, config.zoneId, connect, disconnect]);
76
+
77
+ // Text inputs for type and search filters
78
+ const typeFilter = useTextInput({
79
+ onSubmit: (val) => setFilter({ eventType: val || null }),
80
+ });
81
+ const searchFilter = useTextInput({
82
+ onSubmit: (val) => setFilter({ search: val || null }),
83
+ });
84
+ const anyFilterActive = typeFilter.active || searchFilter.active;
85
+
86
+ // List navigation
87
+ const listNav = listNavigationBindings({
88
+ getIndex: () => selectedEventIndex,
89
+ setIndex: (i) => setSelectedEventIndex(i),
90
+ getLength: () => events.length,
91
+ });
92
+
93
+ useKeyboard(
94
+ overlayActive
95
+ ? {}
96
+ : anyFilterActive
97
+ ? (typeFilter.active ? typeFilter.inputBindings : searchFilter.inputBindings)
98
+ : {
99
+ ...listNav,
100
+ ...tabBindings,
101
+ return: () => {
102
+ if (selectedEventIndex >= 0 && selectedEventIndex < events.length) {
103
+ setExpandedEventIndex((prev) => prev === selectedEventIndex ? null : selectedEventIndex);
104
+ }
105
+ },
106
+ escape: () => {
107
+ if (expandedEventIndex !== null) setExpandedEventIndex(null);
108
+ },
109
+ c: () => clearEvents(),
110
+ r: () => {
111
+ if (config.apiKey && config.baseUrl) {
112
+ disconnect();
113
+ connect(config.baseUrl, config.apiKey, {
114
+ agentId: config.agentId,
115
+ subject: config.subject,
116
+ zoneId: config.zoneId,
117
+ });
118
+ }
119
+ },
120
+ f: () => typeFilter.activate(filters.eventType ?? ""),
121
+ s: () => searchFilter.activate(filters.search ?? ""),
122
+ y: () => {
123
+ const idx = selectedEventIndex >= 0 ? selectedEventIndex : events.length - 1;
124
+ const event = events[idx];
125
+ if (event) copy(event.data);
126
+ },
127
+ },
128
+ overlayActive ? undefined : anyFilterActive
129
+ ? (typeFilter.active ? typeFilter.onUnhandled : searchFilter.onUnhandled)
130
+ : undefined,
131
+ );
132
+
133
+ return (
134
+ <box height="100%" width="100%" flexDirection="column">
135
+ {/* Filter bar */}
136
+ <box height={1} width="100%">
137
+ <text>
138
+ {typeFilter.active
139
+ ? `Filter type: ${typeFilter.buffer}\u2588`
140
+ : searchFilter.active
141
+ ? `Filter search: ${searchFilter.buffer}\u2588`
142
+ : `Filter: type=${filters.eventType ?? "*"} search=${filters.search ?? "*"}`}
143
+ </text>
144
+ </box>
145
+
146
+ {/* Main content */}
147
+ <box flexGrow={1} width="100%" borderStyle="single">
148
+ <box height="100%" width="100%" flexDirection="column">
149
+ {/* SSE status */}
150
+ <box height={1} width="100%">
151
+ <text>
152
+ {connected
153
+ ? `● Connected — ${events.length} events`
154
+ : reconnectExhausted
155
+ ? `✕ Reconnect failed after ${reconnectCount} attempts — press r to retry`
156
+ : reconnectCount > 0
157
+ ? `◐ Auto-reconnecting (attempt ${reconnectCount}/10)...`
158
+ : "○ Disconnected"}
159
+ </text>
160
+ </box>
161
+
162
+ {/* Overflow indicator */}
163
+ {eventsOverflowed && (
164
+ <box height={1} width="100%">
165
+ <text dimColor>
166
+ {`Showing latest ${eventsBuffer.size} of ${eventsBuffer.totalAdded} events (${evictedCount} evicted)`}
167
+ </text>
168
+ </box>
169
+ )}
170
+
171
+ {/* Event stream */}
172
+ {expandedEventIndex !== null && expandedEventIndex < events.length ? (
173
+ <box flexGrow={1} width="100%" flexDirection="column">
174
+ <box height={1} width="100%">
175
+ <text bold>{`[${events[expandedEventIndex]!.event}] — Event #${expandedEventIndex} (Escape to close)`}</text>
176
+ </box>
177
+ <scrollbox flexGrow={1} width="100%">
178
+ <text>{formatEventData(events[expandedEventIndex]!.data)}</text>
179
+ </scrollbox>
180
+ </box>
181
+ ) : (
182
+ <ScrollIndicator selectedIndex={selectedEventIndex >= 0 ? selectedEventIndex : events.length - 1} totalItems={events.length} visibleItems={20}>
183
+ <scrollbox flexGrow={1} width="100%">
184
+ {events.length === 0 ? (
185
+ <EmptyState
186
+ message="Listening for events..."
187
+ hint="Waiting for activity on the server."
188
+ />
189
+ ) : (
190
+ events.map((event, index) => (
191
+ <box key={event.id ?? index} height={1} width="100%" flexDirection="row">
192
+ <text inverse={index === selectedEventIndex || undefined}>
193
+ {`[${event.event}] ${event.data}`}
194
+ </text>
195
+ </box>
196
+ ))
197
+ )}
198
+ </scrollbox>
199
+ </ScrollIndicator>
200
+ )}
201
+ </box>
202
+ </box>
203
+
204
+ {/* Help bar */}
205
+ <box height={1} width="100%">
206
+ {copied
207
+ ? <text foregroundColor={statusColor.success}>Copied!</text>
208
+ : <text>{anyFilterActive ? HELP_INPUT : HELP_NORMAL}</text>}
209
+ </box>
210
+ </box>
211
+ );
212
+ }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Lock list view: shows distributed locks with holder and TTL info.
3
+ */
4
+
5
+ import React from "react";
6
+ import type { Lock } from "../../stores/infra-store.js";
7
+ import { Spinner } from "../../shared/components/spinner.js";
8
+
9
+ const MODE_ICON: Record<string, string> = {
10
+ mutex: "🔒",
11
+ semaphore: "🔗",
12
+ };
13
+
14
+ export function LockList({
15
+ locks,
16
+ selectedIndex,
17
+ loading,
18
+ }: {
19
+ readonly locks: readonly Lock[];
20
+ readonly selectedIndex: number;
21
+ readonly loading: boolean;
22
+ }): React.ReactNode {
23
+ if (loading) {
24
+ return <Spinner label="Loading locks..." />;
25
+ }
26
+
27
+ if (locks.length === 0) {
28
+ return <text>No active locks</text>;
29
+ }
30
+
31
+ return (
32
+ <scrollbox height="100%" width="100%">
33
+ {/* Header */}
34
+ <box height={1} width="100%">
35
+ <text>{" Mode Resource Holder Fence Expires"}</text>
36
+ </box>
37
+
38
+ {locks.map((lock, i) => {
39
+ const prefix = i === selectedIndex ? "> " : " ";
40
+ const icon = MODE_ICON[lock.mode] ?? "?";
41
+ const resource = lock.resource.padEnd(24).slice(0, 24);
42
+ const holder = lock.holder_info.padEnd(20).slice(0, 20);
43
+ const fence = String(lock.fence_token).padStart(6);
44
+ const expires = new Date(lock.expires_at * 1000).toISOString().slice(11, 19);
45
+
46
+ return (
47
+ <box key={lock.lock_id} height={1} width="100%">
48
+ <text>{`${prefix}${icon} ${resource} ${holder} ${fence} ${expires}`}</text>
49
+ </box>
50
+ );
51
+ })}
52
+ </scrollbox>
53
+ );
54
+ }