@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,493 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agents panel: left sidebar with agent list, right pane with tabbed detail views.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import React, { useEffect, useState } from "react";
|
|
6
|
+
import { useAgentsStore } from "../../stores/agents-store.js";
|
|
7
|
+
import type { AgentTab, DelegationItem } from "../../stores/agents-store.js";
|
|
8
|
+
import { useGlobalStore } from "../../stores/global-store.js";
|
|
9
|
+
import { useKeyboard } from "../../shared/hooks/use-keyboard.js";
|
|
10
|
+
import { useCopy } from "../../shared/hooks/use-copy.js";
|
|
11
|
+
import { jumpToStart, jumpToEnd } from "../../shared/hooks/use-list-navigation.js";
|
|
12
|
+
import { useConfirmStore } from "../../shared/hooks/use-confirm.js";
|
|
13
|
+
import { useApi } from "../../shared/hooks/use-api.js";
|
|
14
|
+
import { useVisibleTabs, type TabDef } from "../../shared/hooks/use-visible-tabs.js";
|
|
15
|
+
import { AgentStatusView } from "./agent-status-view.js";
|
|
16
|
+
import { DelegationList } from "./delegation-list.js";
|
|
17
|
+
import { InboxView } from "./inbox-view.js";
|
|
18
|
+
import { TrajectoriesTab } from "./trajectories-tab.js";
|
|
19
|
+
import { EmptyState } from "../../shared/components/empty-state.js";
|
|
20
|
+
import { StyledText } from "../../shared/components/styled-text.js";
|
|
21
|
+
import { LoadingIndicator } from "../../shared/components/loading-indicator.js";
|
|
22
|
+
import { CommandOutput } from "../../shared/components/command-output.js";
|
|
23
|
+
import { useCommandRunnerStore, executeLocalCommand } from "../../services/command-runner.js";
|
|
24
|
+
import { useUiStore } from "../../stores/ui-store.js";
|
|
25
|
+
import { agentStateColor, focusColor, statusColor } from "../../shared/theme.js";
|
|
26
|
+
import { ScrollIndicator } from "../../shared/components/scroll-indicator.js";
|
|
27
|
+
import { textStyle } from "../../shared/text-style.js";
|
|
28
|
+
|
|
29
|
+
const ALL_TABS: readonly TabDef<AgentTab>[] = [
|
|
30
|
+
{ id: "status", label: "Status", brick: "agent_runtime" },
|
|
31
|
+
{ id: "delegations", label: "Delegations", brick: "delegation" },
|
|
32
|
+
{ id: "inbox", label: "Inbox", brick: "ipc" },
|
|
33
|
+
{ id: "trajectories", label: "Trajectories", brick: "agent_runtime" },
|
|
34
|
+
];
|
|
35
|
+
const TAB_LABELS: Readonly<Record<AgentTab, string>> = {
|
|
36
|
+
status: "Status",
|
|
37
|
+
delegations: "Delegations",
|
|
38
|
+
inbox: "Inbox",
|
|
39
|
+
trajectories: "Trajectories",
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export default function AgentsPanel(): React.ReactNode {
|
|
43
|
+
const client = useApi();
|
|
44
|
+
const confirm = useConfirmStore((s) => s.confirm);
|
|
45
|
+
const visibleTabs = useVisibleTabs(ALL_TABS);
|
|
46
|
+
|
|
47
|
+
// Reactive subscription to command runner status (Codex finding 2)
|
|
48
|
+
const commandRunnerStatus = useCommandRunnerStore((s) => s.status);
|
|
49
|
+
|
|
50
|
+
// Zone ID for fetchAgents
|
|
51
|
+
const configZoneId = useGlobalStore((s) => s.config.zoneId);
|
|
52
|
+
const serverZoneId = useGlobalStore((s) => s.zoneId);
|
|
53
|
+
const effectiveZoneId = configZoneId ?? serverZoneId ?? "root";
|
|
54
|
+
|
|
55
|
+
const knownAgents = useAgentsStore((s) => s.knownAgents);
|
|
56
|
+
const agents = useAgentsStore((s) => s.agents);
|
|
57
|
+
const agentsLoading = useAgentsStore((s) => s.agentsLoading);
|
|
58
|
+
const selectedAgentId = useAgentsStore((s) => s.selectedAgentId);
|
|
59
|
+
const selectedAgentIndex = useAgentsStore((s) => s.selectedAgentIndex);
|
|
60
|
+
const activeTab = useAgentsStore((s) => s.activeTab);
|
|
61
|
+
const agentStatus = useAgentsStore((s) => s.agentStatus);
|
|
62
|
+
const agentSpec = useAgentsStore((s) => s.agentSpec);
|
|
63
|
+
const agentIdentity = useAgentsStore((s) => s.agentIdentity);
|
|
64
|
+
const statusLoading = useAgentsStore((s) => s.statusLoading);
|
|
65
|
+
const trustScore = useAgentsStore((s) => s.trustScore);
|
|
66
|
+
const reputation = useAgentsStore((s) => s.reputation);
|
|
67
|
+
const delegations = useAgentsStore((s) => s.delegations);
|
|
68
|
+
const delegationsLoading = useAgentsStore((s) => s.delegationsLoading);
|
|
69
|
+
const selectedDelegationIndex = useAgentsStore((s) => s.selectedDelegationIndex);
|
|
70
|
+
const inboxMessages = useAgentsStore((s) => s.inboxMessages);
|
|
71
|
+
const inboxCount = useAgentsStore((s) => s.inboxCount);
|
|
72
|
+
const inboxLoading = useAgentsStore((s) => s.inboxLoading);
|
|
73
|
+
const trajectories = useAgentsStore((s) => s.trajectories);
|
|
74
|
+
const trajectoriesLoading = useAgentsStore((s) => s.trajectoriesLoading);
|
|
75
|
+
const error = useAgentsStore((s) => s.error);
|
|
76
|
+
|
|
77
|
+
const setSelectedAgentId = useAgentsStore((s) => s.setSelectedAgentId);
|
|
78
|
+
const setSelectedAgentIndex = useAgentsStore((s) => s.setSelectedAgentIndex);
|
|
79
|
+
const setActiveTab = useAgentsStore((s) => s.setActiveTab);
|
|
80
|
+
const addKnownAgent = useAgentsStore((s) => s.addKnownAgent);
|
|
81
|
+
const fetchAgents = useAgentsStore((s) => s.fetchAgents);
|
|
82
|
+
const fetchAgentStatus = useAgentsStore((s) => s.fetchAgentStatus);
|
|
83
|
+
const fetchAgentSpec = useAgentsStore((s) => s.fetchAgentSpec);
|
|
84
|
+
const fetchAgentIdentity = useAgentsStore((s) => s.fetchAgentIdentity);
|
|
85
|
+
const fetchTrustScore = useAgentsStore((s) => s.fetchTrustScore);
|
|
86
|
+
const fetchAgentReputation = useAgentsStore((s) => s.fetchAgentReputation);
|
|
87
|
+
const fetchDelegations = useAgentsStore((s) => s.fetchDelegations);
|
|
88
|
+
const fetchInbox = useAgentsStore((s) => s.fetchInbox);
|
|
89
|
+
const fetchTrajectories = useAgentsStore((s) => s.fetchTrajectories);
|
|
90
|
+
const revokeDelegation = useAgentsStore((s) => s.revokeDelegation);
|
|
91
|
+
const warmupAgent = useAgentsStore((s) => s.warmupAgent);
|
|
92
|
+
const evictAgent = useAgentsStore((s) => s.evictAgent);
|
|
93
|
+
const verifyAgent = useAgentsStore((s) => s.verifyAgent);
|
|
94
|
+
const setSelectedDelegationIndex = useAgentsStore((s) => s.setSelectedDelegationIndex);
|
|
95
|
+
|
|
96
|
+
// Focus pane (ui-store)
|
|
97
|
+
const uiFocusPane = useUiStore((s) => s.getFocusPane("agents"));
|
|
98
|
+
const toggleFocus = useUiStore((s) => s.toggleFocusPane);
|
|
99
|
+
const overlayActive = useUiStore((s) => s.overlayActive);
|
|
100
|
+
|
|
101
|
+
// Clipboard copy
|
|
102
|
+
const { copy, copied } = useCopy();
|
|
103
|
+
|
|
104
|
+
// Local loading state for async warmup/evict/verify operations
|
|
105
|
+
const [operationLoading, setOperationLoading] = useState<string | null>(null);
|
|
106
|
+
|
|
107
|
+
// Expanded delegation detail
|
|
108
|
+
const [expandedDelegation, setExpandedDelegation] = useState<DelegationItem | null>(null);
|
|
109
|
+
|
|
110
|
+
// Merge fetched agents into a display list: fetched agents + any manually added knownAgents not in the fetched list
|
|
111
|
+
const fetchedAgentIds = agents.map((a) => a.agent_id);
|
|
112
|
+
const extraKnown = knownAgents.filter((id) => !fetchedAgentIds.includes(id));
|
|
113
|
+
const displayAgentIds = [...fetchedAgentIds, ...extraKnown];
|
|
114
|
+
|
|
115
|
+
// Fetch agents on mount when zone is available
|
|
116
|
+
useEffect(() => {
|
|
117
|
+
if (client && effectiveZoneId) {
|
|
118
|
+
fetchAgents(effectiveZoneId, client);
|
|
119
|
+
}
|
|
120
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
121
|
+
}, [client, effectiveZoneId]);
|
|
122
|
+
|
|
123
|
+
// Fall back to first visible tab if the active tab becomes hidden
|
|
124
|
+
const visibleIds = visibleTabs.map((t) => t.id);
|
|
125
|
+
useEffect(() => {
|
|
126
|
+
if (visibleIds.length > 0 && !visibleIds.includes(activeTab)) {
|
|
127
|
+
setActiveTab(visibleIds[0]!);
|
|
128
|
+
}
|
|
129
|
+
}, [visibleIds.join(","), activeTab, setActiveTab]);
|
|
130
|
+
|
|
131
|
+
// Refresh current view based on active tab
|
|
132
|
+
const refreshCurrentView = (): void => {
|
|
133
|
+
if (!client) return;
|
|
134
|
+
|
|
135
|
+
if (activeTab === "status" && selectedAgentId) {
|
|
136
|
+
// Fetch permissions for all agents (works for registered + running)
|
|
137
|
+
client.get<{ permissions: readonly { relation: string; object_type: string; object_id: string }[] }>(
|
|
138
|
+
`/api/v2/agents/${encodeURIComponent(selectedAgentId)}/permissions`,
|
|
139
|
+
).then((r) => useAgentsStore.setState({ agentPermissions: r.permissions }))
|
|
140
|
+
.catch(() => useAgentsStore.setState({ agentPermissions: [] }));
|
|
141
|
+
// Only fetch live status for running agents
|
|
142
|
+
const selectedAgent = agents.find((a) => a.agent_id === selectedAgentId);
|
|
143
|
+
if (selectedAgent && selectedAgent.state !== "registered" && selectedAgent.state !== "delegated") {
|
|
144
|
+
fetchAgentStatus(selectedAgentId, client);
|
|
145
|
+
fetchAgentSpec(selectedAgentId, client);
|
|
146
|
+
fetchAgentIdentity(selectedAgentId, client);
|
|
147
|
+
fetchTrustScore(selectedAgentId, client);
|
|
148
|
+
fetchAgentReputation(selectedAgentId, client);
|
|
149
|
+
}
|
|
150
|
+
} else if (activeTab === "delegations" && selectedAgentId) {
|
|
151
|
+
fetchDelegations(selectedAgentId, client);
|
|
152
|
+
} else if (activeTab === "inbox" && selectedAgentId) {
|
|
153
|
+
fetchInbox(selectedAgentId, client);
|
|
154
|
+
} else if (activeTab === "trajectories" && selectedAgentId) {
|
|
155
|
+
fetchTrajectories(selectedAgentId, client);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Also refresh agent list
|
|
159
|
+
if (effectiveZoneId) {
|
|
160
|
+
fetchAgents(effectiveZoneId, client);
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
// Auto-fetch when agent or tab changes
|
|
165
|
+
useEffect(() => {
|
|
166
|
+
refreshCurrentView();
|
|
167
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
168
|
+
}, [selectedAgentId, activeTab, client]);
|
|
169
|
+
|
|
170
|
+
useKeyboard(overlayActive ? {} : {
|
|
171
|
+
j: () => {
|
|
172
|
+
if (activeTab === "delegations") {
|
|
173
|
+
if (delegations.length === 0) return;
|
|
174
|
+
setSelectedDelegationIndex(
|
|
175
|
+
Math.max(0, Math.min(selectedDelegationIndex + 1, delegations.length - 1)),
|
|
176
|
+
);
|
|
177
|
+
} else {
|
|
178
|
+
if (displayAgentIds.length === 0) return;
|
|
179
|
+
const newIdx = Math.max(0, Math.min(selectedAgentIndex + 1, displayAgentIds.length - 1));
|
|
180
|
+
setSelectedAgentIndex(newIdx);
|
|
181
|
+
const agentId = displayAgentIds[newIdx];
|
|
182
|
+
if (agentId) setSelectedAgentId(agentId);
|
|
183
|
+
}
|
|
184
|
+
},
|
|
185
|
+
down: () => {
|
|
186
|
+
if (activeTab === "delegations") {
|
|
187
|
+
if (delegations.length === 0) return;
|
|
188
|
+
setSelectedDelegationIndex(
|
|
189
|
+
Math.max(0, Math.min(selectedDelegationIndex + 1, delegations.length - 1)),
|
|
190
|
+
);
|
|
191
|
+
} else {
|
|
192
|
+
if (displayAgentIds.length === 0) return;
|
|
193
|
+
const newIdx = Math.max(0, Math.min(selectedAgentIndex + 1, displayAgentIds.length - 1));
|
|
194
|
+
setSelectedAgentIndex(newIdx);
|
|
195
|
+
const agentId = displayAgentIds[newIdx];
|
|
196
|
+
if (agentId) setSelectedAgentId(agentId);
|
|
197
|
+
}
|
|
198
|
+
},
|
|
199
|
+
k: () => {
|
|
200
|
+
if (activeTab === "delegations") {
|
|
201
|
+
setSelectedDelegationIndex(Math.max(selectedDelegationIndex - 1, 0));
|
|
202
|
+
} else {
|
|
203
|
+
const newIdx = Math.max(0, selectedAgentIndex - 1);
|
|
204
|
+
setSelectedAgentIndex(newIdx);
|
|
205
|
+
const agentId = displayAgentIds[newIdx];
|
|
206
|
+
if (agentId) setSelectedAgentId(agentId);
|
|
207
|
+
}
|
|
208
|
+
},
|
|
209
|
+
up: () => {
|
|
210
|
+
if (activeTab === "delegations") {
|
|
211
|
+
setSelectedDelegationIndex(Math.max(selectedDelegationIndex - 1, 0));
|
|
212
|
+
} else {
|
|
213
|
+
const newIdx = Math.max(0, selectedAgentIndex - 1);
|
|
214
|
+
setSelectedAgentIndex(newIdx);
|
|
215
|
+
const agentId = displayAgentIds[newIdx];
|
|
216
|
+
if (agentId) setSelectedAgentId(agentId);
|
|
217
|
+
}
|
|
218
|
+
},
|
|
219
|
+
tab: () => {
|
|
220
|
+
const ids = visibleTabs.map((t) => t.id);
|
|
221
|
+
const currentIdx = ids.indexOf(activeTab);
|
|
222
|
+
const nextIdx = (currentIdx + 1) % ids.length;
|
|
223
|
+
const nextTab = ids[nextIdx];
|
|
224
|
+
if (nextTab) {
|
|
225
|
+
setActiveTab(nextTab);
|
|
226
|
+
}
|
|
227
|
+
},
|
|
228
|
+
"shift+tab": () => toggleFocus("agents"),
|
|
229
|
+
r: () => refreshCurrentView(),
|
|
230
|
+
d: async () => {
|
|
231
|
+
if (activeTab !== "delegations" || !client) return;
|
|
232
|
+
const selected = delegations[selectedDelegationIndex];
|
|
233
|
+
if (selected && selected.status === "active") {
|
|
234
|
+
const ok = await confirm("Revoke delegation?", `Revoke delegation ${selected.delegation_id}. The agent will lose delegated access.`);
|
|
235
|
+
if (!ok) return;
|
|
236
|
+
revokeDelegation(selected.delegation_id, client);
|
|
237
|
+
}
|
|
238
|
+
},
|
|
239
|
+
return: () => {
|
|
240
|
+
if (activeTab === "delegations") {
|
|
241
|
+
// Toggle delegation detail drill-down
|
|
242
|
+
const selected = delegations[selectedDelegationIndex];
|
|
243
|
+
if (selected) {
|
|
244
|
+
setExpandedDelegation(
|
|
245
|
+
expandedDelegation?.delegation_id === selected.delegation_id ? null : selected,
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
// If an agent is highlighted in the agents list, select it
|
|
251
|
+
const agent = displayAgentIds[selectedAgentIndex];
|
|
252
|
+
if (agent) {
|
|
253
|
+
setSelectedAgentId(agent);
|
|
254
|
+
addKnownAgent(agent);
|
|
255
|
+
}
|
|
256
|
+
},
|
|
257
|
+
escape: () => {
|
|
258
|
+
if (expandedDelegation) {
|
|
259
|
+
setExpandedDelegation(null);
|
|
260
|
+
}
|
|
261
|
+
},
|
|
262
|
+
"shift+w": async () => {
|
|
263
|
+
if (!client || !selectedAgentId) return;
|
|
264
|
+
setOperationLoading("Warming up agent...");
|
|
265
|
+
try {
|
|
266
|
+
await warmupAgent(selectedAgentId, client);
|
|
267
|
+
} finally {
|
|
268
|
+
setOperationLoading(null);
|
|
269
|
+
}
|
|
270
|
+
},
|
|
271
|
+
"shift+e": async () => {
|
|
272
|
+
if (!client || !selectedAgentId) return;
|
|
273
|
+
// Only evict if agent is not already evicted
|
|
274
|
+
const agentEntry = agents.find((a) => a.agent_id === selectedAgentId);
|
|
275
|
+
if (agentEntry && agentEntry.state === "evicted") return;
|
|
276
|
+
setOperationLoading("Evicting agent...");
|
|
277
|
+
try {
|
|
278
|
+
await evictAgent(selectedAgentId, client);
|
|
279
|
+
} finally {
|
|
280
|
+
setOperationLoading(null);
|
|
281
|
+
}
|
|
282
|
+
},
|
|
283
|
+
"shift+v": async () => {
|
|
284
|
+
if (!client || !selectedAgentId) return;
|
|
285
|
+
setOperationLoading("Verifying agent...");
|
|
286
|
+
try {
|
|
287
|
+
await verifyAgent(selectedAgentId, client);
|
|
288
|
+
} finally {
|
|
289
|
+
setOperationLoading(null);
|
|
290
|
+
}
|
|
291
|
+
},
|
|
292
|
+
g: () => {
|
|
293
|
+
if (activeTab === "delegations") {
|
|
294
|
+
setSelectedDelegationIndex(jumpToStart());
|
|
295
|
+
} else {
|
|
296
|
+
setSelectedAgentIndex(jumpToStart());
|
|
297
|
+
const firstAgent = displayAgentIds[0];
|
|
298
|
+
if (firstAgent) {
|
|
299
|
+
setSelectedAgentId(firstAgent);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
},
|
|
303
|
+
"shift+g": () => {
|
|
304
|
+
if (activeTab === "delegations") {
|
|
305
|
+
setSelectedDelegationIndex(jumpToEnd(delegations.length));
|
|
306
|
+
} else {
|
|
307
|
+
const lastIdx = jumpToEnd(displayAgentIds.length);
|
|
308
|
+
setSelectedAgentIndex(lastIdx);
|
|
309
|
+
const lastAgent = displayAgentIds[lastIdx];
|
|
310
|
+
if (lastAgent) {
|
|
311
|
+
setSelectedAgentId(lastAgent);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
},
|
|
315
|
+
y: () => {
|
|
316
|
+
if (selectedAgentId) {
|
|
317
|
+
copy(selectedAgentId);
|
|
318
|
+
}
|
|
319
|
+
},
|
|
320
|
+
// Issue #3078: spawn new agent via local CLI command
|
|
321
|
+
n: () => {
|
|
322
|
+
useCommandRunnerStore.getState().reset();
|
|
323
|
+
executeLocalCommand("agent", ["spawn"]);
|
|
324
|
+
},
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
return (
|
|
328
|
+
<box height="100%" width="100%" flexDirection="column">
|
|
329
|
+
{/* Main content */}
|
|
330
|
+
<box flexGrow={1} flexDirection="row">
|
|
331
|
+
{/* Left sidebar: agent list (30%) */}
|
|
332
|
+
<box width="30%" height="100%" borderStyle="single" borderColor={uiFocusPane === "left" ? focusColor.activeBorder : focusColor.inactiveBorder} flexDirection="column">
|
|
333
|
+
<box height={1} width="100%">
|
|
334
|
+
{agentsLoading
|
|
335
|
+
? <LoadingIndicator message="Agents" centered={false} />
|
|
336
|
+
: <text>{"--- Agents ---"}</text>}
|
|
337
|
+
</box>
|
|
338
|
+
|
|
339
|
+
{/* Agents list */}
|
|
340
|
+
{displayAgentIds.length === 0 ? (
|
|
341
|
+
<EmptyState
|
|
342
|
+
message="No agents registered."
|
|
343
|
+
hint="Start an agent with 'nexus agent spawn' or add one with the API."
|
|
344
|
+
/>
|
|
345
|
+
) : (
|
|
346
|
+
<ScrollIndicator selectedIndex={selectedAgentIndex} totalItems={displayAgentIds.length} visibleItems={20}>
|
|
347
|
+
<scrollbox flexGrow={1} width="100%">
|
|
348
|
+
{displayAgentIds.map((agentId, i) => {
|
|
349
|
+
const isSelected = i === selectedAgentIndex;
|
|
350
|
+
const isActive = agentId === selectedAgentId;
|
|
351
|
+
const prefix = isSelected ? "> " : " ";
|
|
352
|
+
const suffix = isActive ? " *" : "";
|
|
353
|
+
const agentEntry = agents.find((a) => a.agent_id === agentId);
|
|
354
|
+
const state = agentEntry?.state ?? "";
|
|
355
|
+
const stateColor = agentStateColor[state] ?? statusColor.dim;
|
|
356
|
+
return (
|
|
357
|
+
<box key={agentId} height={1} width="100%">
|
|
358
|
+
<text>
|
|
359
|
+
<span>{prefix}</span>
|
|
360
|
+
<span style={textStyle({ bold: isActive })}>{agentId}</span>
|
|
361
|
+
{state ? <span style={textStyle({ fg: stateColor })}>{` [${state}]`}</span> : ""}
|
|
362
|
+
<span style={textStyle({ dim: true })}>{suffix}</span>
|
|
363
|
+
</text>
|
|
364
|
+
</box>
|
|
365
|
+
);
|
|
366
|
+
})}
|
|
367
|
+
</scrollbox>
|
|
368
|
+
</ScrollIndicator>
|
|
369
|
+
)}
|
|
370
|
+
</box>
|
|
371
|
+
|
|
372
|
+
{/* Right pane: detail views (70%) */}
|
|
373
|
+
<box width="70%" height="100%" borderStyle="single" borderColor={uiFocusPane === "right" ? focusColor.activeBorder : focusColor.inactiveBorder} flexDirection="column">
|
|
374
|
+
{/* Tab bar */}
|
|
375
|
+
<box height={1} width="100%">
|
|
376
|
+
<text>
|
|
377
|
+
{visibleTabs.map((tab) => {
|
|
378
|
+
return tab.id === activeTab ? `[${tab.label}]` : ` ${tab.label} `;
|
|
379
|
+
}).join(" ")}
|
|
380
|
+
</text>
|
|
381
|
+
</box>
|
|
382
|
+
|
|
383
|
+
{/* Operation in-progress feedback */}
|
|
384
|
+
{operationLoading && (
|
|
385
|
+
<box height={1} width="100%">
|
|
386
|
+
<LoadingIndicator message={operationLoading} centered={false} />
|
|
387
|
+
</box>
|
|
388
|
+
)}
|
|
389
|
+
|
|
390
|
+
{/* Error display */}
|
|
391
|
+
{error && (
|
|
392
|
+
<box height={1} width="100%">
|
|
393
|
+
<StyledText>{`Error: ${error}`}</StyledText>
|
|
394
|
+
</box>
|
|
395
|
+
)}
|
|
396
|
+
|
|
397
|
+
{/* Detail content */}
|
|
398
|
+
<box flexGrow={1} borderStyle="single">
|
|
399
|
+
{activeTab === "status" && (() => {
|
|
400
|
+
const selectedAgent = agents.find((a) => a.agent_id === selectedAgentId);
|
|
401
|
+
if (selectedAgent?.state === "registered" || selectedAgent?.state === "delegated") {
|
|
402
|
+
const perms = useAgentsStore.getState().agentPermissions;
|
|
403
|
+
return (
|
|
404
|
+
<box height="100%" width="100%" flexDirection="column" padding={1}>
|
|
405
|
+
<text style={textStyle({ bold: true })}>{`Agent: ${selectedAgent.agent_id}`}</text>
|
|
406
|
+
<text>{""}</text>
|
|
407
|
+
<text><span style={textStyle({ fg: "cyan" })}>{"State: "}</span><span>{"registered"}</span></text>
|
|
408
|
+
<text><span style={textStyle({ fg: "cyan" })}>{"Name: "}</span><span>{selectedAgent.name ?? selectedAgent.agent_id}</span></text>
|
|
409
|
+
<text><span style={textStyle({ fg: "cyan" })}>{"Owner: "}</span><span>{selectedAgent.owner_id}</span></text>
|
|
410
|
+
<text><span style={textStyle({ fg: "cyan" })}>{"Zone: "}</span><span>{selectedAgent.zone_id ?? "root"}</span></text>
|
|
411
|
+
<text>{""}</text>
|
|
412
|
+
<text style={textStyle({ fg: "cyan", bold: true })}>{"Effective Permissions:"}</text>
|
|
413
|
+
{perms.length === 0 ? (
|
|
414
|
+
<text style={textStyle({ dim: true })}>{" No permissions assigned"}</text>
|
|
415
|
+
) : (
|
|
416
|
+
perms.map((p, i) => {
|
|
417
|
+
// Translate ReBAC tuples into human-readable capabilities
|
|
418
|
+
const tool = p.object_id.replace("/tools/", "");
|
|
419
|
+
const accessLevel = p.relation.replace("direct_", "");
|
|
420
|
+
const icon = accessLevel === "viewer" || accessLevel === "reader" ? "R" : accessLevel === "editor" || accessLevel === "writer" ? "W" : "?";
|
|
421
|
+
const color = icon === "R" ? "cyan" : icon === "W" ? "yellow" : "gray";
|
|
422
|
+
return (
|
|
423
|
+
<text key={`perm-${i}`}>
|
|
424
|
+
<span style={textStyle({ fg: color })}>{` [${icon}] `}</span>
|
|
425
|
+
<span>{tool}</span>
|
|
426
|
+
<span style={textStyle({ dim: true })}>{` (${accessLevel})`}</span>
|
|
427
|
+
</text>
|
|
428
|
+
);
|
|
429
|
+
})
|
|
430
|
+
)}
|
|
431
|
+
<text>{""}</text>
|
|
432
|
+
<text style={textStyle({ dim: true })}>{" View Access panel (5) for manifests & delegations"}</text>
|
|
433
|
+
<text>{""}</text>
|
|
434
|
+
<text style={textStyle({ dim: true })}>{"Agent is registered but not running."}</text>
|
|
435
|
+
</box>
|
|
436
|
+
);
|
|
437
|
+
}
|
|
438
|
+
return (
|
|
439
|
+
<AgentStatusView
|
|
440
|
+
status={agentStatus}
|
|
441
|
+
spec={agentSpec}
|
|
442
|
+
identity={agentIdentity}
|
|
443
|
+
loading={statusLoading}
|
|
444
|
+
trustScore={trustScore}
|
|
445
|
+
reputation={reputation}
|
|
446
|
+
/>
|
|
447
|
+
);
|
|
448
|
+
})()}
|
|
449
|
+
{activeTab === "delegations" && (
|
|
450
|
+
<DelegationList
|
|
451
|
+
delegations={delegations}
|
|
452
|
+
selectedIndex={selectedDelegationIndex}
|
|
453
|
+
loading={delegationsLoading}
|
|
454
|
+
expandedDelegation={expandedDelegation}
|
|
455
|
+
/>
|
|
456
|
+
)}
|
|
457
|
+
{activeTab === "inbox" && (
|
|
458
|
+
<InboxView
|
|
459
|
+
messages={inboxMessages}
|
|
460
|
+
count={inboxCount}
|
|
461
|
+
processedMessages={useAgentsStore.getState().processedMessages}
|
|
462
|
+
deadLetterMessages={useAgentsStore.getState().deadLetterMessages}
|
|
463
|
+
loading={inboxLoading}
|
|
464
|
+
/>
|
|
465
|
+
)}
|
|
466
|
+
{activeTab === "trajectories" && (
|
|
467
|
+
<TrajectoriesTab
|
|
468
|
+
trajectories={trajectories}
|
|
469
|
+
loading={trajectoriesLoading}
|
|
470
|
+
/>
|
|
471
|
+
)}
|
|
472
|
+
</box>
|
|
473
|
+
</box>
|
|
474
|
+
</box>
|
|
475
|
+
|
|
476
|
+
{/* Command runner output (when agent spawn is running) */}
|
|
477
|
+
{commandRunnerStatus !== "idle" && (
|
|
478
|
+
<box borderStyle="single" height={6} width="100%">
|
|
479
|
+
<CommandOutput />
|
|
480
|
+
</box>
|
|
481
|
+
)}
|
|
482
|
+
|
|
483
|
+
{/* Help bar */}
|
|
484
|
+
<box height={1} width="100%">
|
|
485
|
+
{copied
|
|
486
|
+
? <text style={textStyle({ fg: "green" })}>Copied!</text>
|
|
487
|
+
: <text>
|
|
488
|
+
{"j/k:navigate Tab:switch tab r:refresh n:spawn agent Enter:detail d:revoke Shift+W:warmup Shift+E:evict y:copy q:quit"}
|
|
489
|
+
</text>}
|
|
490
|
+
</box>
|
|
491
|
+
</box>
|
|
492
|
+
);
|
|
493
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Delegation list table with status badges and expandable detail view.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import React, { useEffect, useState } from "react";
|
|
6
|
+
import type { DelegationItem } from "../../stores/agents-store.js";
|
|
7
|
+
import { LoadingIndicator } from "../../shared/components/loading-indicator.js";
|
|
8
|
+
import { useApi } from "../../shared/hooks/use-api.js";
|
|
9
|
+
import { delegationModeColor, delegationStatusColor, statusColor } from "../../shared/theme.js";
|
|
10
|
+
import { textStyle } from "../../shared/text-style.js";
|
|
11
|
+
|
|
12
|
+
interface DelegationListProps {
|
|
13
|
+
readonly delegations: readonly DelegationItem[];
|
|
14
|
+
readonly selectedIndex: number;
|
|
15
|
+
readonly loading: boolean;
|
|
16
|
+
readonly expandedDelegation?: DelegationItem | null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const STATUS_BADGES: Readonly<Record<DelegationItem["status"], string>> = {
|
|
20
|
+
active: "●",
|
|
21
|
+
revoked: "✗",
|
|
22
|
+
expired: "○",
|
|
23
|
+
completed: "✓",
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
interface PermTuple {
|
|
27
|
+
readonly relation: string;
|
|
28
|
+
readonly object_type: string;
|
|
29
|
+
readonly object_id: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function DelegationDetail({ delegation }: { delegation: DelegationItem }): React.ReactNode {
|
|
33
|
+
const client = useApi();
|
|
34
|
+
const [perms, setPerms] = useState<readonly PermTuple[]>([]);
|
|
35
|
+
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
if (!client) return;
|
|
38
|
+
client.get<{ permissions: PermTuple[] }>(
|
|
39
|
+
`/api/v2/agents/${encodeURIComponent(delegation.agent_id)}/permissions`,
|
|
40
|
+
).then((r) => setPerms(r.permissions))
|
|
41
|
+
.catch(() => setPerms([]));
|
|
42
|
+
}, [client, delegation.agent_id]);
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<box height={11 + Math.max(perms.length, 1)} width="100%" borderStyle="single" flexDirection="column">
|
|
46
|
+
<text>{"Delegation Detail (Esc to close)"}</text>
|
|
47
|
+
<text>{` ID: ${delegation.delegation_id}`}</text>
|
|
48
|
+
<text>{` Worker: ${delegation.agent_id} → Parent: ${delegation.parent_agent_id}`}</text>
|
|
49
|
+
<text>{` Mode: ${delegation.delegation_mode} Status: ${delegation.status} Depth: ${delegation.depth} Sub-delegate: ${delegation.can_sub_delegate ? "yes" : "no"}`}</text>
|
|
50
|
+
<text>{` Intent: ${delegation.intent}`}</text>
|
|
51
|
+
<text>{` Scope: ${delegation.scope_prefix ?? "(none)"} Zone: ${delegation.zone_id ?? "(none)"}`}</text>
|
|
52
|
+
<text>{` Created: ${delegation.created_at}`}</text>
|
|
53
|
+
<text>{` Expires: ${formatExpiry(delegation.lease_expires_at)}`}</text>
|
|
54
|
+
<text>{""}</text>
|
|
55
|
+
<text style={textStyle({ fg: "cyan", bold: true })}>{" Granted Capabilities:"}</text>
|
|
56
|
+
{perms.length === 0 ? (
|
|
57
|
+
<text style={textStyle({ dim: true })}>{" (none or loading...)"}</text>
|
|
58
|
+
) : (
|
|
59
|
+
perms.map((p, i) => {
|
|
60
|
+
const tool = p.object_id.replace("/tools/", "");
|
|
61
|
+
const accessLevel = p.relation.replace("direct_", "");
|
|
62
|
+
const icon = accessLevel === "viewer" || accessLevel === "reader" ? "R" : accessLevel === "editor" || accessLevel === "writer" ? "W" : "?";
|
|
63
|
+
const color = icon === "R" ? "cyan" : icon === "W" ? "yellow" : "gray";
|
|
64
|
+
return (
|
|
65
|
+
<text key={`perm-${i}`}>
|
|
66
|
+
<span style={textStyle({ fg: color })}>{` [${icon}] `}</span>
|
|
67
|
+
<span>{tool}</span>
|
|
68
|
+
<span style={textStyle({ dim: true })}>{` (${accessLevel})`}</span>
|
|
69
|
+
</text>
|
|
70
|
+
);
|
|
71
|
+
})
|
|
72
|
+
)}
|
|
73
|
+
</box>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function shortId(id: string): string {
|
|
78
|
+
if (id.length <= 12) return id;
|
|
79
|
+
return `${id.slice(0, 8)}..`;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function formatExpiry(ts: string | null): string {
|
|
83
|
+
if (!ts) return "never";
|
|
84
|
+
try {
|
|
85
|
+
return new Date(ts).toLocaleString();
|
|
86
|
+
} catch {
|
|
87
|
+
return ts;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function DelegationList({
|
|
92
|
+
delegations,
|
|
93
|
+
selectedIndex,
|
|
94
|
+
loading,
|
|
95
|
+
expandedDelegation,
|
|
96
|
+
}: DelegationListProps): React.ReactNode {
|
|
97
|
+
if (loading) {
|
|
98
|
+
return <LoadingIndicator message="Loading delegations..." />;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (delegations.length === 0) {
|
|
102
|
+
return (
|
|
103
|
+
<box height="100%" width="100%" justifyContent="center" alignItems="center">
|
|
104
|
+
<text>No delegations found</text>
|
|
105
|
+
</box>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return (
|
|
110
|
+
<box height="100%" width="100%" flexDirection="column">
|
|
111
|
+
<scrollbox flexGrow={expandedDelegation ? 0 : 1} width="100%">
|
|
112
|
+
{/* Header */}
|
|
113
|
+
<box height={1} width="100%">
|
|
114
|
+
<text>{" ST ID MODE AGENT->PARENT INTENT DEPTH EXPIRES"}</text>
|
|
115
|
+
</box>
|
|
116
|
+
<box height={1} width="100%">
|
|
117
|
+
<text>{" -- ---------- ------ ------------------- ------------------- ----- -------"}</text>
|
|
118
|
+
</box>
|
|
119
|
+
|
|
120
|
+
{/* Rows */}
|
|
121
|
+
{delegations.map((d, i) => {
|
|
122
|
+
const isSelected = i === selectedIndex;
|
|
123
|
+
const badge = STATUS_BADGES[d.status] ?? "?";
|
|
124
|
+
const badgeColor = delegationStatusColor[d.status] ?? statusColor.dim;
|
|
125
|
+
const modeColor = delegationModeColor[d.delegation_mode] ?? statusColor.dim;
|
|
126
|
+
const prefix = isSelected ? "> " : " ";
|
|
127
|
+
|
|
128
|
+
return (
|
|
129
|
+
<box key={d.delegation_id} height={1} width="100%">
|
|
130
|
+
<text>
|
|
131
|
+
<span>{prefix}</span>
|
|
132
|
+
<span style={textStyle({ fg: badgeColor })}>{badge}</span>
|
|
133
|
+
<span style={textStyle({ dim: true })}>{` ${shortId(d.delegation_id).padEnd(10)} `}</span>
|
|
134
|
+
<span style={textStyle({ fg: modeColor })}>{d.delegation_mode.padEnd(6)}</span>
|
|
135
|
+
<span>{" "}</span>
|
|
136
|
+
<span style={textStyle({ fg: statusColor.identity })}>{shortId(d.agent_id)}</span>
|
|
137
|
+
<span style={textStyle({ dim: true })}>{"→"}</span>
|
|
138
|
+
<span>{shortId(d.parent_agent_id).padEnd(12)}</span>
|
|
139
|
+
<span style={textStyle({ dim: true })}>{" "}</span>
|
|
140
|
+
<span>{(d.intent.length > 19 ? `${d.intent.slice(0, 16)}...` : d.intent).padEnd(19)}</span>
|
|
141
|
+
<span style={textStyle({ dim: true })}>{` ${String(d.depth).padEnd(5)} ${formatExpiry(d.lease_expires_at)}`}</span>
|
|
142
|
+
</text>
|
|
143
|
+
</box>
|
|
144
|
+
);
|
|
145
|
+
})}
|
|
146
|
+
</scrollbox>
|
|
147
|
+
|
|
148
|
+
{/* Expanded delegation detail */}
|
|
149
|
+
{expandedDelegation && (
|
|
150
|
+
<DelegationDetail delegation={expandedDelegation} />
|
|
151
|
+
)}
|
|
152
|
+
</box>
|
|
153
|
+
);
|
|
154
|
+
}
|