@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,231 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Permission checker form: evaluate a tool permission against a manifest.
|
|
3
|
+
*
|
|
4
|
+
* Uses the server-side evaluation trace (proof tree) from the backend
|
|
5
|
+
* evaluate endpoint. Shows which manifest entries were checked, which
|
|
6
|
+
* one matched (first-match-wins), and the final decision.
|
|
7
|
+
*
|
|
8
|
+
* Tab cycles between fields.
|
|
9
|
+
* Enter evaluates using the store's checkPermission().
|
|
10
|
+
* Escape cancels and returns to normal mode.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import React, { useState, useCallback } from "react";
|
|
14
|
+
import { useAccessStore } from "../../stores/access-store.js";
|
|
15
|
+
import type { PermissionCheck, GovernanceCheckResult } from "../../stores/access-store.js";
|
|
16
|
+
import { useKeyboard } from "../../shared/hooks/use-keyboard.js";
|
|
17
|
+
import { useApi } from "../../shared/hooks/use-api.js";
|
|
18
|
+
|
|
19
|
+
type ActiveField = "manifestId" | "toolName" | "fromAgentId" | "toAgentId";
|
|
20
|
+
|
|
21
|
+
interface PermissionCheckerProps {
|
|
22
|
+
readonly initialManifestId: string;
|
|
23
|
+
readonly lastResult: PermissionCheck | null;
|
|
24
|
+
readonly loading: boolean;
|
|
25
|
+
readonly governanceCheck: GovernanceCheckResult | null;
|
|
26
|
+
readonly governanceCheckLoading: boolean;
|
|
27
|
+
readonly zoneId: string | undefined;
|
|
28
|
+
readonly onClose: () => void;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function PermissionChecker({
|
|
32
|
+
initialManifestId,
|
|
33
|
+
lastResult,
|
|
34
|
+
loading,
|
|
35
|
+
governanceCheck,
|
|
36
|
+
governanceCheckLoading,
|
|
37
|
+
zoneId,
|
|
38
|
+
onClose,
|
|
39
|
+
}: PermissionCheckerProps): React.ReactNode {
|
|
40
|
+
const client = useApi();
|
|
41
|
+
const checkPermission = useAccessStore((s) => s.checkPermission);
|
|
42
|
+
const checkGovernanceEdge = useAccessStore((s) => s.checkGovernanceEdge);
|
|
43
|
+
const manifests = useAccessStore((s) => s.manifests);
|
|
44
|
+
|
|
45
|
+
const [manifestId, setManifestId] = useState(initialManifestId);
|
|
46
|
+
const [toolName, setToolName] = useState("");
|
|
47
|
+
const [fromAgentId, setFromAgentId] = useState("");
|
|
48
|
+
const [toAgentId, setToAgentId] = useState("");
|
|
49
|
+
const [activeField, setActiveField] = useState<ActiveField>("toolName");
|
|
50
|
+
|
|
51
|
+
const FIELD_ORDER: readonly ActiveField[] = ["manifestId", "toolName", "fromAgentId", "toAgentId"];
|
|
52
|
+
|
|
53
|
+
const setters: Readonly<Record<ActiveField, (fn: (b: string) => string) => void>> = {
|
|
54
|
+
manifestId: (fn) => setManifestId((b) => fn(b)),
|
|
55
|
+
toolName: (fn) => setToolName((b) => fn(b)),
|
|
56
|
+
fromAgentId: (fn) => setFromAgentId((b) => fn(b)),
|
|
57
|
+
toAgentId: (fn) => setToAgentId((b) => fn(b)),
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const handleSubmit = useCallback(() => {
|
|
61
|
+
if (!client) return;
|
|
62
|
+
// Manifest permission check (requires both fields)
|
|
63
|
+
if (manifestId.trim() && toolName.trim()) {
|
|
64
|
+
checkPermission(manifestId.trim(), toolName.trim(), client);
|
|
65
|
+
}
|
|
66
|
+
// Governance edge check (requires both agent IDs)
|
|
67
|
+
if (fromAgentId.trim() && toAgentId.trim()) {
|
|
68
|
+
checkGovernanceEdge(fromAgentId.trim(), toAgentId.trim(), zoneId, client);
|
|
69
|
+
}
|
|
70
|
+
}, [client, manifestId, toolName, fromAgentId, toAgentId, zoneId, checkPermission, checkGovernanceEdge]);
|
|
71
|
+
|
|
72
|
+
const handleUnhandledKey = useCallback(
|
|
73
|
+
(keyName: string) => {
|
|
74
|
+
const setter = setters[activeField];
|
|
75
|
+
if (keyName.length === 1) {
|
|
76
|
+
setter((b) => b + keyName);
|
|
77
|
+
} else if (keyName === "space") {
|
|
78
|
+
setter((b) => b + " ");
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
[activeField],
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
useKeyboard(
|
|
85
|
+
{
|
|
86
|
+
return: handleSubmit,
|
|
87
|
+
escape: onClose,
|
|
88
|
+
backspace: () => {
|
|
89
|
+
setters[activeField]((b) => b.slice(0, -1));
|
|
90
|
+
},
|
|
91
|
+
tab: () => {
|
|
92
|
+
const currentIdx = FIELD_ORDER.indexOf(activeField);
|
|
93
|
+
const nextIdx = (currentIdx + 1) % FIELD_ORDER.length;
|
|
94
|
+
const next = FIELD_ORDER[nextIdx];
|
|
95
|
+
if (next) {
|
|
96
|
+
setActiveField(next);
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
handleUnhandledKey,
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
const cursor = "\u2588";
|
|
104
|
+
|
|
105
|
+
// Look up manifest metadata for display
|
|
106
|
+
const matchedManifest = lastResult
|
|
107
|
+
? manifests.find((m) => m.manifest_id === lastResult.manifest_id)
|
|
108
|
+
: null;
|
|
109
|
+
|
|
110
|
+
// Server-side trace from the evaluate endpoint
|
|
111
|
+
const trace = lastResult?.trace ?? null;
|
|
112
|
+
|
|
113
|
+
const fields: readonly { readonly key: ActiveField; readonly label: string; readonly value: string; readonly hint?: string }[] = [
|
|
114
|
+
{ key: "manifestId", label: "Manifest ID ", value: manifestId },
|
|
115
|
+
{ key: "toolName", label: "Tool Name ", value: toolName },
|
|
116
|
+
{ key: "fromAgentId", label: "From Agent ID", value: fromAgentId, hint: "governance check" },
|
|
117
|
+
{ key: "toAgentId", label: "To Agent ID ", value: toAgentId, hint: "governance check" },
|
|
118
|
+
];
|
|
119
|
+
|
|
120
|
+
return (
|
|
121
|
+
<box height="100%" width="100%" flexDirection="column">
|
|
122
|
+
{/* Form fields */}
|
|
123
|
+
{fields.map((f) => (
|
|
124
|
+
<box key={f.key} height={1} width="100%">
|
|
125
|
+
<text>
|
|
126
|
+
{activeField === f.key
|
|
127
|
+
? `> ${f.label}: ${f.value}${cursor}${f.hint ? ` (${f.hint})` : ""}`
|
|
128
|
+
: ` ${f.label}: ${f.value}${f.hint && !f.value ? ` (${f.hint})` : ""}`}
|
|
129
|
+
</text>
|
|
130
|
+
</box>
|
|
131
|
+
))}
|
|
132
|
+
|
|
133
|
+
{/* Loading indicator */}
|
|
134
|
+
{(loading || governanceCheckLoading) && (
|
|
135
|
+
<box height={1} width="100%">
|
|
136
|
+
<text>Evaluating...</text>
|
|
137
|
+
</box>
|
|
138
|
+
)}
|
|
139
|
+
|
|
140
|
+
{/* Structured result display — server-side proof tree */}
|
|
141
|
+
{lastResult && !loading && (
|
|
142
|
+
<box flexGrow={1} width="100%" flexDirection="column">
|
|
143
|
+
{/* Decision banner */}
|
|
144
|
+
<box height={1} width="100%">
|
|
145
|
+
<text>
|
|
146
|
+
{lastResult.permission === "allow"
|
|
147
|
+
? `[ALLOW] ${lastResult.tool_name} -> agent=${lastResult.agent_id}`
|
|
148
|
+
: `[DENY] ${lastResult.tool_name} -> agent=${lastResult.agent_id}`}
|
|
149
|
+
</text>
|
|
150
|
+
</box>
|
|
151
|
+
|
|
152
|
+
{/* Server-side evaluation trace (proof tree) */}
|
|
153
|
+
{trace && trace.entries.length > 0 && (
|
|
154
|
+
<>
|
|
155
|
+
<box height={1} width="100%">
|
|
156
|
+
<text>{"--- Evaluation Trace (server-side, first-match-wins) ---"}</text>
|
|
157
|
+
</box>
|
|
158
|
+
{trace.entries.map((entry) => {
|
|
159
|
+
const prefix = entry.matched && entry.index === trace.matched_index ? ">> " : " ";
|
|
160
|
+
const matchLabel = entry.matched ? (entry.index === trace.matched_index ? "MATCH" : "match") : " ";
|
|
161
|
+
const rateStr = entry.max_calls_per_minute
|
|
162
|
+
? ` rate=${entry.max_calls_per_minute}/min`
|
|
163
|
+
: "";
|
|
164
|
+
return (
|
|
165
|
+
<box key={`trace-${entry.index}`} height={1} width="100%">
|
|
166
|
+
<text>
|
|
167
|
+
{`${prefix}[${matchLabel}] ${entry.tool_pattern.padEnd(30)} ${entry.permission.padEnd(6)}${rateStr}`}
|
|
168
|
+
</text>
|
|
169
|
+
</box>
|
|
170
|
+
);
|
|
171
|
+
})}
|
|
172
|
+
</>
|
|
173
|
+
)}
|
|
174
|
+
|
|
175
|
+
{/* Default deny notice */}
|
|
176
|
+
{trace?.default_applied && (
|
|
177
|
+
<box height={1} width="100%">
|
|
178
|
+
<text>{"No entry matched -> default DENY applied"}</text>
|
|
179
|
+
</box>
|
|
180
|
+
)}
|
|
181
|
+
|
|
182
|
+
{/* Matched entry summary */}
|
|
183
|
+
{trace && trace.matched_index >= 0 && trace.entries[trace.matched_index] && (
|
|
184
|
+
<box height={1} width="100%">
|
|
185
|
+
<text>
|
|
186
|
+
{`Deciding entry #${trace.matched_index}: pattern="${trace.entries[trace.matched_index]!.tool_pattern}" permission=${trace.entries[trace.matched_index]!.permission}`}
|
|
187
|
+
</text>
|
|
188
|
+
</box>
|
|
189
|
+
)}
|
|
190
|
+
|
|
191
|
+
{/* Manifest metadata */}
|
|
192
|
+
{matchedManifest && (
|
|
193
|
+
<box height={1} width="100%">
|
|
194
|
+
<text>
|
|
195
|
+
{`Manifest: ${matchedManifest.name} status=${matchedManifest.status} zone=${matchedManifest.zone_id}`}
|
|
196
|
+
</text>
|
|
197
|
+
</box>
|
|
198
|
+
)}
|
|
199
|
+
</box>
|
|
200
|
+
)}
|
|
201
|
+
|
|
202
|
+
{/* Governance edge check result */}
|
|
203
|
+
{governanceCheck && !governanceCheckLoading && (
|
|
204
|
+
<box flexDirection="column" width="100%">
|
|
205
|
+
<box height={1} width="100%">
|
|
206
|
+
<text>{"--- Governance Edge Check ---"}</text>
|
|
207
|
+
</box>
|
|
208
|
+
<box height={1} width="100%">
|
|
209
|
+
<text>
|
|
210
|
+
{governanceCheck.allowed
|
|
211
|
+
? `[ALLOWED] ${governanceCheck.reason}`
|
|
212
|
+
: `[BLOCKED] ${governanceCheck.reason}`}
|
|
213
|
+
</text>
|
|
214
|
+
</box>
|
|
215
|
+
{governanceCheck.constraint_type && (
|
|
216
|
+
<box height={1} width="100%">
|
|
217
|
+
<text>{` Constraint: ${governanceCheck.constraint_type} edge=${governanceCheck.edge_id}`}</text>
|
|
218
|
+
</box>
|
|
219
|
+
)}
|
|
220
|
+
</box>
|
|
221
|
+
)}
|
|
222
|
+
|
|
223
|
+
{/* Help */}
|
|
224
|
+
<box height={1} width="100%">
|
|
225
|
+
<text>
|
|
226
|
+
{"Tab:cycle fields Enter:evaluate Escape:cancel Backspace:delete"}
|
|
227
|
+
</text>
|
|
228
|
+
</box>
|
|
229
|
+
</box>
|
|
230
|
+
);
|
|
231
|
+
}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent status detail view: phase badge, conditions, resource usage, identity.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import React from "react";
|
|
6
|
+
import type { AgentStatus, AgentSpec, AgentIdentity, AgentPhase } from "../../stores/agents-store.js";
|
|
7
|
+
import { LoadingIndicator } from "../../shared/components/loading-indicator.js";
|
|
8
|
+
|
|
9
|
+
interface AgentStatusViewProps {
|
|
10
|
+
readonly status: AgentStatus | null;
|
|
11
|
+
readonly spec: AgentSpec | null;
|
|
12
|
+
readonly identity: AgentIdentity | null;
|
|
13
|
+
readonly loading: boolean;
|
|
14
|
+
readonly trustScore?: number | null;
|
|
15
|
+
readonly reputation?: unknown | null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const PHASE_BADGES: Readonly<Record<AgentPhase, string>> = {
|
|
19
|
+
warming: "[WRM]",
|
|
20
|
+
ready: "[RDY]",
|
|
21
|
+
active: "[ACT]",
|
|
22
|
+
thinking: "[THK]",
|
|
23
|
+
idle: "[IDL]",
|
|
24
|
+
suspended: "[SUS]",
|
|
25
|
+
evicted: "[EVT]",
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
function formatTimestamp(ts: string | null): string {
|
|
29
|
+
if (!ts) return "n/a";
|
|
30
|
+
try {
|
|
31
|
+
return new Date(ts).toLocaleString();
|
|
32
|
+
} catch {
|
|
33
|
+
return ts;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function renderUsageBar(pct: number, width: number): string {
|
|
38
|
+
const filled = Math.round((pct / 100) * width);
|
|
39
|
+
const empty = width - filled;
|
|
40
|
+
return `[${"#".repeat(filled)}${"-".repeat(empty)}] ${pct.toFixed(0)}%`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function AgentStatusView({
|
|
44
|
+
status,
|
|
45
|
+
spec,
|
|
46
|
+
identity,
|
|
47
|
+
loading,
|
|
48
|
+
trustScore,
|
|
49
|
+
reputation,
|
|
50
|
+
}: AgentStatusViewProps): React.ReactNode {
|
|
51
|
+
if (loading) {
|
|
52
|
+
return <LoadingIndicator message="Loading agent status..." />;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (!status) {
|
|
56
|
+
return (
|
|
57
|
+
<box height="100%" width="100%" justifyContent="center" alignItems="center">
|
|
58
|
+
<text>Select an agent to view status</text>
|
|
59
|
+
</box>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const badge = PHASE_BADGES[status.phase] ?? `[${status.phase.toUpperCase()}]`;
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<scrollbox height="100%" width="100%">
|
|
67
|
+
{/* Phase and generation */}
|
|
68
|
+
<box height={1} width="100%">
|
|
69
|
+
<text>{`Phase: ${badge} ${status.phase} | Generation: ${status.observed_generation}`}</text>
|
|
70
|
+
</box>
|
|
71
|
+
|
|
72
|
+
{/* Timestamps */}
|
|
73
|
+
<box height={1} width="100%">
|
|
74
|
+
<text>{`Last heartbeat: ${formatTimestamp(status.last_heartbeat)}`}</text>
|
|
75
|
+
</box>
|
|
76
|
+
<box height={1} width="100%">
|
|
77
|
+
<text>{`Last activity: ${formatTimestamp(status.last_activity)}`}</text>
|
|
78
|
+
</box>
|
|
79
|
+
|
|
80
|
+
{/* Inbox and context */}
|
|
81
|
+
<box height={1} width="100%">
|
|
82
|
+
<text>{`Inbox depth: ${status.inbox_depth} | Context usage: ${status.context_usage_pct}%`}</text>
|
|
83
|
+
</box>
|
|
84
|
+
|
|
85
|
+
{/* Resource usage */}
|
|
86
|
+
<box height={1} width="100%" marginTop={1}>
|
|
87
|
+
<text>--- Resource Usage ---</text>
|
|
88
|
+
</box>
|
|
89
|
+
<box height={1} width="100%">
|
|
90
|
+
<text>{`Tokens used: ${status.resource_usage.tokens_used}`}</text>
|
|
91
|
+
</box>
|
|
92
|
+
<box height={1} width="100%">
|
|
93
|
+
<text>{`Storage: ${status.resource_usage.storage_used_mb} MB`}</text>
|
|
94
|
+
</box>
|
|
95
|
+
<box height={1} width="100%">
|
|
96
|
+
<text>{`Context: ${renderUsageBar(status.resource_usage.context_usage_pct, 20)}`}</text>
|
|
97
|
+
</box>
|
|
98
|
+
|
|
99
|
+
{/* Conditions */}
|
|
100
|
+
{status.conditions.length > 0 && (
|
|
101
|
+
<>
|
|
102
|
+
<box height={1} width="100%" marginTop={1}>
|
|
103
|
+
<text>--- Conditions ---</text>
|
|
104
|
+
</box>
|
|
105
|
+
{status.conditions.map((cond, i) => (
|
|
106
|
+
<box key={`cond-${i}`} height={1} width="100%">
|
|
107
|
+
<text>{`[${cond.status}] ${cond.type}: ${cond.reason} - ${cond.message}`}</text>
|
|
108
|
+
</box>
|
|
109
|
+
))}
|
|
110
|
+
</>
|
|
111
|
+
)}
|
|
112
|
+
|
|
113
|
+
{/* Spec info */}
|
|
114
|
+
{spec && (
|
|
115
|
+
<>
|
|
116
|
+
<box height={1} width="100%" marginTop={1}>
|
|
117
|
+
<text>--- Spec ---</text>
|
|
118
|
+
</box>
|
|
119
|
+
<box height={1} width="100%">
|
|
120
|
+
<text>{`Type: ${spec.agent_type} | QoS: ${spec.qos_class} | Gen: ${spec.spec_generation}`}</text>
|
|
121
|
+
</box>
|
|
122
|
+
{spec.zone_affinity && (
|
|
123
|
+
<box height={1} width="100%">
|
|
124
|
+
<text>{`Zone affinity: ${spec.zone_affinity}`}</text>
|
|
125
|
+
</box>
|
|
126
|
+
)}
|
|
127
|
+
{spec.capabilities.length > 0 && (
|
|
128
|
+
<box height={1} width="100%">
|
|
129
|
+
<text>{`Capabilities: ${spec.capabilities.join(", ")}`}</text>
|
|
130
|
+
</box>
|
|
131
|
+
)}
|
|
132
|
+
</>
|
|
133
|
+
)}
|
|
134
|
+
|
|
135
|
+
{/* Identity */}
|
|
136
|
+
{identity && (
|
|
137
|
+
<>
|
|
138
|
+
<box height={1} width="100%" marginTop={1}>
|
|
139
|
+
<text>--- Identity ---</text>
|
|
140
|
+
</box>
|
|
141
|
+
<box height={1} width="100%">
|
|
142
|
+
<text>{`DID: ${identity.did}`}</text>
|
|
143
|
+
</box>
|
|
144
|
+
<box height={1} width="100%">
|
|
145
|
+
<text>{`Key ID: ${identity.key_id}`}</text>
|
|
146
|
+
</box>
|
|
147
|
+
<box height={1} width="100%">
|
|
148
|
+
<text>{`Algorithm: ${identity.algorithm}`}</text>
|
|
149
|
+
</box>
|
|
150
|
+
<box height={1} width="100%">
|
|
151
|
+
<text>{`Public key: ${truncateHex(identity.public_key_hex)}`}</text>
|
|
152
|
+
</box>
|
|
153
|
+
{identity.created_at && (
|
|
154
|
+
<box height={1} width="100%">
|
|
155
|
+
<text>{`Created: ${formatTimestamp(identity.created_at)}`}</text>
|
|
156
|
+
</box>
|
|
157
|
+
)}
|
|
158
|
+
{identity.expires_at && (
|
|
159
|
+
<box height={1} width="100%">
|
|
160
|
+
<text>{`Expires: ${formatTimestamp(identity.expires_at)}`}</text>
|
|
161
|
+
</box>
|
|
162
|
+
)}
|
|
163
|
+
</>
|
|
164
|
+
)}
|
|
165
|
+
|
|
166
|
+
{/* Trust Score */}
|
|
167
|
+
{trustScore != null && (
|
|
168
|
+
<>
|
|
169
|
+
<box height={1} width="100%" marginTop={1}>
|
|
170
|
+
<text>--- Trust ---</text>
|
|
171
|
+
</box>
|
|
172
|
+
<box height={1} width="100%">
|
|
173
|
+
<text>{`Trust score: ${trustScore.toFixed(2)} ${renderUsageBar(trustScore * 100, 20)}`}</text>
|
|
174
|
+
</box>
|
|
175
|
+
</>
|
|
176
|
+
)}
|
|
177
|
+
|
|
178
|
+
{/* Reputation */}
|
|
179
|
+
{reputation != null && (
|
|
180
|
+
<>
|
|
181
|
+
<box height={1} width="100%" marginTop={1}>
|
|
182
|
+
<text>--- Reputation ---</text>
|
|
183
|
+
</box>
|
|
184
|
+
<box height={1} width="100%">
|
|
185
|
+
<text>{`${JSON.stringify(reputation, null, 2)}`}</text>
|
|
186
|
+
</box>
|
|
187
|
+
</>
|
|
188
|
+
)}
|
|
189
|
+
</scrollbox>
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function truncateHex(hex: string): string {
|
|
194
|
+
if (hex.length <= 20) return hex;
|
|
195
|
+
return `${hex.slice(0, 10)}...${hex.slice(-10)}`;
|
|
196
|
+
}
|