@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,128 @@
1
+ /**
2
+ * Constraint creator form: create a new ReBAC governance constraint.
3
+ *
4
+ * Tab cycles between fields.
5
+ * Enter submits via the access store's createConstraint().
6
+ * Escape cancels and returns to normal mode.
7
+ */
8
+
9
+ import React, { useState, useCallback } from "react";
10
+ import { useAccessStore } from "../../stores/access-store.js";
11
+ import { useKeyboard } from "../../shared/hooks/use-keyboard.js";
12
+ import { useApi } from "../../shared/hooks/use-api.js";
13
+
14
+ type ActiveField = "fromAgentId" | "toAgentId" | "constraintType";
15
+
16
+ const FIELD_ORDER: readonly ActiveField[] = [
17
+ "fromAgentId",
18
+ "toAgentId",
19
+ "constraintType",
20
+ ];
21
+
22
+ interface ConstraintCreatorProps {
23
+ readonly zoneId: string;
24
+ readonly onClose: () => void;
25
+ }
26
+
27
+ export function ConstraintCreator({ zoneId, onClose }: ConstraintCreatorProps): React.ReactNode {
28
+ const client = useApi();
29
+ const createConstraint = useAccessStore((s) => s.createConstraint);
30
+ const constraintsLoading = useAccessStore((s) => s.constraintsLoading);
31
+ const error = useAccessStore((s) => s.error);
32
+
33
+ const [fromAgentId, setFromAgentId] = useState("");
34
+ const [toAgentId, setToAgentId] = useState("");
35
+ const [constraintType, setConstraintType] = useState("");
36
+ const [activeField, setActiveField] = useState<ActiveField>("fromAgentId");
37
+
38
+ const setters: Readonly<Record<ActiveField, (fn: (b: string) => string) => void>> = {
39
+ fromAgentId: (fn) => setFromAgentId((b) => fn(b)),
40
+ toAgentId: (fn) => setToAgentId((b) => fn(b)),
41
+ constraintType: (fn) => setConstraintType((b) => fn(b)),
42
+ };
43
+
44
+ const handleSubmit = useCallback(() => {
45
+ if (!client || !fromAgentId.trim() || !toAgentId.trim() || !constraintType.trim()) return;
46
+ createConstraint(
47
+ {
48
+ from_agent_id: fromAgentId.trim(),
49
+ to_agent_id: toAgentId.trim(),
50
+ constraint_type: constraintType.trim(),
51
+ zone_id: zoneId,
52
+ },
53
+ client,
54
+ );
55
+ }, [client, fromAgentId, toAgentId, constraintType, zoneId, createConstraint]);
56
+
57
+ const handleUnhandledKey = useCallback(
58
+ (keyName: string) => {
59
+ const setter = setters[activeField];
60
+ if (keyName.length === 1) {
61
+ setter((b) => b + keyName);
62
+ } else if (keyName === "space") {
63
+ setter((b) => b + " ");
64
+ }
65
+ },
66
+ [activeField],
67
+ );
68
+
69
+ useKeyboard(
70
+ {
71
+ return: handleSubmit,
72
+ escape: onClose,
73
+ backspace: () => {
74
+ setters[activeField]((b) => b.slice(0, -1));
75
+ },
76
+ tab: () => {
77
+ const currentIdx = FIELD_ORDER.indexOf(activeField);
78
+ const nextIdx = (currentIdx + 1) % FIELD_ORDER.length;
79
+ const next = FIELD_ORDER[nextIdx];
80
+ if (next) {
81
+ setActiveField(next);
82
+ }
83
+ },
84
+ },
85
+ handleUnhandledKey,
86
+ );
87
+
88
+ const cursor = "\u2588";
89
+
90
+ const fields: readonly { readonly key: ActiveField; readonly label: string; readonly value: string; readonly hint?: string }[] = [
91
+ { key: "fromAgentId", label: "From Agent ID ", value: fromAgentId },
92
+ { key: "toAgentId", label: "To Agent ID ", value: toAgentId },
93
+ { key: "constraintType", label: "Constraint Type ", value: constraintType, hint: "e.g. deny, rate_limit" },
94
+ ];
95
+
96
+ return (
97
+ <box height="100%" width="100%" flexDirection="column">
98
+ {/* Form fields */}
99
+ {fields.map((f) => (
100
+ <box key={f.key} height={1} width="100%">
101
+ <text>
102
+ {activeField === f.key
103
+ ? `> ${f.label}: ${f.value}${cursor}${f.hint ? ` (${f.hint})` : ""}`
104
+ : ` ${f.label}: ${f.value}${f.hint && !f.value ? ` (${f.hint})` : ""}`}
105
+ </text>
106
+ </box>
107
+ ))}
108
+
109
+ {constraintsLoading && (
110
+ <box height={1} width="100%">
111
+ <text>Creating constraint...</text>
112
+ </box>
113
+ )}
114
+
115
+ {error && !constraintsLoading && (
116
+ <box height={1} width="100%">
117
+ <text>{`Error: ${error}`}</text>
118
+ </box>
119
+ )}
120
+
121
+ <box height={1} width="100%">
122
+ <text>
123
+ {"Tab:next field Enter:create Escape:cancel Backspace:delete"}
124
+ </text>
125
+ </box>
126
+ </box>
127
+ );
128
+ }
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Constraint list: displays ReBAC governance constraints with
3
+ * from/to agents, type, and creation time.
4
+ */
5
+
6
+ import React from "react";
7
+ import type { GovernanceConstraint } from "../../stores/access-store.js";
8
+ import { LoadingIndicator } from "../../shared/components/loading-indicator.js";
9
+ import { textStyle } from "../../shared/text-style.js";
10
+
11
+ interface ConstraintListProps {
12
+ readonly constraints: readonly GovernanceConstraint[];
13
+ readonly selectedIndex: number;
14
+ readonly loading: boolean;
15
+ }
16
+
17
+ function shortId(id: string): string {
18
+ if (id.length <= 14) return id;
19
+ return `${id.slice(0, 11)}..`;
20
+ }
21
+
22
+ function formatTime(ts: string): string {
23
+ try {
24
+ return new Date(ts).toLocaleString();
25
+ } catch {
26
+ return ts;
27
+ }
28
+ }
29
+
30
+ export function ConstraintList({
31
+ constraints,
32
+ selectedIndex,
33
+ loading,
34
+ }: ConstraintListProps): React.ReactNode {
35
+ if (loading) {
36
+ return <LoadingIndicator message="Loading constraints..." />;
37
+ }
38
+
39
+ if (constraints.length === 0) {
40
+ return (
41
+ <box height="100%" width="100%" justifyContent="center" alignItems="center">
42
+ <text style={textStyle({ dim: true })}>No governance constraints found</text>
43
+ </box>
44
+ );
45
+ }
46
+
47
+ return (
48
+ <scrollbox height="100%" width="100%">
49
+ {/* Header */}
50
+ <box height={1} width="100%">
51
+ <text>{" FROM AGENT TO AGENT TYPE CREATED AT"}</text>
52
+ </box>
53
+ <box height={1} width="100%">
54
+ <text>{" ------------- ------------- -------------- -----------------------"}</text>
55
+ </box>
56
+
57
+ {/* Rows */}
58
+ {constraints.map((c, i) => {
59
+ const isSelected = i === selectedIndex;
60
+ const prefix = isSelected ? "> " : " ";
61
+
62
+ return (
63
+ <box key={c.id} height={1} width="100%">
64
+ <text>
65
+ {`${prefix}${shortId(c.from_agent_id).padEnd(13)} ${shortId(c.to_agent_id).padEnd(13)} ${c.constraint_type.padEnd(14)} ${formatTime(c.created_at)}`}
66
+ </text>
67
+ </box>
68
+ );
69
+ })}
70
+ </scrollbox>
71
+ );
72
+ }
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Credential list with active status badge, DIDs, and delegation depth.
3
+ */
4
+
5
+ import React from "react";
6
+ import type { Credential } from "../../stores/access-store.js";
7
+ import { EmptyState } from "../../shared/components/empty-state.js";
8
+
9
+ interface CredentialListProps {
10
+ readonly credentials: readonly Credential[];
11
+ readonly loading: boolean;
12
+ }
13
+
14
+ function shortId(id: string): string {
15
+ if (id.length <= 16) return id;
16
+ return `${id.slice(0, 12)}..`;
17
+ }
18
+
19
+ function formatTimestamp(ts: string | null): string {
20
+ if (!ts) return "never";
21
+ try {
22
+ return new Date(ts).toLocaleString();
23
+ } catch {
24
+ return ts;
25
+ }
26
+ }
27
+
28
+ export function CredentialList({
29
+ credentials,
30
+ loading,
31
+ }: CredentialListProps): React.ReactNode {
32
+ if (loading) {
33
+ return (
34
+ <box height="100%" width="100%" justifyContent="center" alignItems="center">
35
+ <text>Loading credentials...</text>
36
+ </box>
37
+ );
38
+ }
39
+
40
+ if (credentials.length === 0) {
41
+ return <EmptyState message="No credentials found." hint="Press i to issue a credential." />;
42
+ }
43
+
44
+ return (
45
+ <scrollbox height="100%" width="100%">
46
+ {/* Header */}
47
+ <box height={1} width="100%">
48
+ <text>{" ST CREDENTIAL ID ISSUER DID SUBJECT DID DEPTH EXPIRES REVOKED"}</text>
49
+ </box>
50
+ <box height={1} width="100%">
51
+ <text>{" -- --------------- --------------- --------------- ----- ----------------- -----------------"}</text>
52
+ </box>
53
+
54
+ {/* Rows */}
55
+ {credentials.map((cred) => {
56
+ const badge = cred.is_active ? "●" : "○";
57
+
58
+ return (
59
+ <box key={cred.credential_id} height={1} width="100%">
60
+ <text>
61
+ {` ${badge} ${shortId(cred.credential_id).padEnd(15)} ${shortId(cred.issuer_did).padEnd(15)} ${shortId(cred.subject_did).padEnd(15)} ${String(cred.delegation_depth).padEnd(5)} ${formatTimestamp(cred.expires_at).padEnd(17)} ${formatTimestamp(cred.revoked_at)}`}
62
+ </text>
63
+ </box>
64
+ );
65
+ })}
66
+ </scrollbox>
67
+ );
68
+ }
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Delegation chain view: read-only overlay showing the delegation
3
+ * hierarchy from root to leaf, fetched via the chain endpoint.
4
+ *
5
+ * Escape closes.
6
+ */
7
+
8
+ import React, { useEffect } from "react";
9
+ import { useAccessStore } from "../../stores/access-store.js";
10
+ import { useKeyboard } from "../../shared/hooks/use-keyboard.js";
11
+ import { useApi } from "../../shared/hooks/use-api.js";
12
+
13
+ interface DelegationChainViewProps {
14
+ readonly delegationId: string;
15
+ readonly onClose: () => void;
16
+ }
17
+
18
+ export function DelegationChainView({
19
+ delegationId,
20
+ onClose,
21
+ }: DelegationChainViewProps): React.ReactNode {
22
+ const client = useApi();
23
+ const chain = useAccessStore((s) => s.delegationChain);
24
+ const loading = useAccessStore((s) => s.delegationChainLoading);
25
+ const error = useAccessStore((s) => s.error);
26
+ const fetchDelegationChain = useAccessStore((s) => s.fetchDelegationChain);
27
+
28
+ useEffect(() => {
29
+ if (client) {
30
+ fetchDelegationChain(delegationId, client);
31
+ }
32
+ }, [delegationId, client, fetchDelegationChain]);
33
+
34
+ useKeyboard({ escape: onClose });
35
+
36
+ if (loading) {
37
+ return (
38
+ <box height="100%" width="100%" justifyContent="center" alignItems="center">
39
+ <text>Loading delegation chain...</text>
40
+ </box>
41
+ );
42
+ }
43
+
44
+ if (error) {
45
+ return (
46
+ <box height="100%" width="100%" flexDirection="column">
47
+ <box height={1} width="100%">
48
+ <text>{`Error: ${error}`}</text>
49
+ </box>
50
+ <box height={1} width="100%">
51
+ <text>{"Escape:close"}</text>
52
+ </box>
53
+ </box>
54
+ );
55
+ }
56
+
57
+ if (!chain || chain.chain.length === 0) {
58
+ return (
59
+ <box height="100%" width="100%" flexDirection="column">
60
+ <box height={1} width="100%">
61
+ <text>{`No chain found for delegation ${delegationId}`}</text>
62
+ </box>
63
+ <box height={1} width="100%">
64
+ <text>{"Escape:close"}</text>
65
+ </box>
66
+ </box>
67
+ );
68
+ }
69
+
70
+ return (
71
+ <box height="100%" width="100%" flexDirection="column">
72
+ <box height={1} width="100%">
73
+ <text>{`Delegation Chain (depth: ${chain.total_depth})`}</text>
74
+ </box>
75
+ <box height={1} width="100%">
76
+ <text>{" DEPTH AGENT PARENT MODE STATUS INTENT"}</text>
77
+ </box>
78
+ <box height={1} width="100%">
79
+ <text>{" ----- --------------- --------------- --------- --------- ----------"}</text>
80
+ </box>
81
+
82
+ <scrollbox flexGrow={1} width="100%">
83
+ {chain.chain.map((entry) => {
84
+ const indent = " ".repeat(entry.depth);
85
+ const agent = entry.agent_id.length > 15
86
+ ? `${entry.agent_id.slice(0, 12)}..`
87
+ : entry.agent_id;
88
+ const parent = entry.parent_agent_id.length > 15
89
+ ? `${entry.parent_agent_id.slice(0, 12)}..`
90
+ : entry.parent_agent_id;
91
+ const intentStr = entry.intent.length > 20
92
+ ? `${entry.intent.slice(0, 17)}...`
93
+ : entry.intent;
94
+
95
+ return (
96
+ <box key={entry.delegation_id} height={1} width="100%">
97
+ <text>
98
+ {`${indent} ${String(entry.depth).padEnd(5)} ${agent.padEnd(15)} ${parent.padEnd(15)} ${entry.delegation_mode.padEnd(9)} ${entry.status.padEnd(9)} ${intentStr}`}
99
+ </text>
100
+ </box>
101
+ );
102
+ })}
103
+ </scrollbox>
104
+
105
+ <box height={1} width="100%">
106
+ <text>{"Escape:close"}</text>
107
+ </box>
108
+ </box>
109
+ );
110
+ }
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Delegation completer form: mark a delegation as completed/failed/timeout.
3
+ *
4
+ * Tab cycles between outcome and quality score fields.
5
+ * Enter submits via the store's completeDelegation().
6
+ * Escape cancels and returns to normal mode.
7
+ */
8
+
9
+ import React, { useState, useCallback } from "react";
10
+ import { useAccessStore } from "../../stores/access-store.js";
11
+ import { useKeyboard } from "../../shared/hooks/use-keyboard.js";
12
+ import { useApi } from "../../shared/hooks/use-api.js";
13
+
14
+ type ActiveField = "outcome" | "qualityScore";
15
+
16
+ const FIELD_ORDER: readonly ActiveField[] = ["outcome", "qualityScore"];
17
+
18
+ interface DelegationCompleterProps {
19
+ readonly delegationId: string;
20
+ readonly onClose: () => void;
21
+ }
22
+
23
+ export function DelegationCompleter({
24
+ delegationId,
25
+ onClose,
26
+ }: DelegationCompleterProps): React.ReactNode {
27
+ const client = useApi();
28
+ const completeDelegation = useAccessStore((s) => s.completeDelegation);
29
+ const delegationsLoading = useAccessStore((s) => s.delegationsLoading);
30
+ const error = useAccessStore((s) => s.error);
31
+
32
+ const [outcome, setOutcome] = useState("completed");
33
+ const [qualityScore, setQualityScore] = useState("");
34
+ const [activeField, setActiveField] = useState<ActiveField>("outcome");
35
+
36
+ const setters: Readonly<Record<ActiveField, (fn: (b: string) => string) => void>> = {
37
+ outcome: (fn) => setOutcome((b) => fn(b)),
38
+ qualityScore: (fn) => setQualityScore((b) => fn(b)),
39
+ };
40
+
41
+ const handleSubmit = useCallback(() => {
42
+ if (!client || !outcome.trim()) return;
43
+ const score = qualityScore.trim() ? parseFloat(qualityScore.trim()) : null;
44
+ completeDelegation(delegationId, outcome.trim(), score, client);
45
+ }, [client, delegationId, outcome, qualityScore, completeDelegation]);
46
+
47
+ const handleUnhandledKey = useCallback(
48
+ (keyName: string) => {
49
+ const setter = setters[activeField];
50
+ if (keyName.length === 1) {
51
+ setter((b) => b + keyName);
52
+ } else if (keyName === "space") {
53
+ setter((b) => b + " ");
54
+ }
55
+ },
56
+ [activeField],
57
+ );
58
+
59
+ useKeyboard(
60
+ {
61
+ return: handleSubmit,
62
+ escape: onClose,
63
+ backspace: () => {
64
+ setters[activeField]((b) => b.slice(0, -1));
65
+ },
66
+ tab: () => {
67
+ const currentIdx = FIELD_ORDER.indexOf(activeField);
68
+ const nextIdx = (currentIdx + 1) % FIELD_ORDER.length;
69
+ const next = FIELD_ORDER[nextIdx];
70
+ if (next) {
71
+ setActiveField(next);
72
+ }
73
+ },
74
+ },
75
+ handleUnhandledKey,
76
+ );
77
+
78
+ const cursor = "\u2588";
79
+
80
+ return (
81
+ <box height="100%" width="100%" flexDirection="column">
82
+ <box height={1} width="100%">
83
+ <text>{`Complete delegation: ${delegationId}`}</text>
84
+ </box>
85
+
86
+ <box height={1} width="100%">
87
+ <text>
88
+ {activeField === "outcome"
89
+ ? `> Outcome: ${outcome}${cursor} (completed|failed|timeout)`
90
+ : ` Outcome: ${outcome}`}
91
+ </text>
92
+ </box>
93
+ <box height={1} width="100%">
94
+ <text>
95
+ {activeField === "qualityScore"
96
+ ? `> Quality Score: ${qualityScore}${cursor} (0.0-1.0, optional)`
97
+ : ` Quality Score: ${qualityScore}`}
98
+ </text>
99
+ </box>
100
+
101
+ {delegationsLoading && (
102
+ <box height={1} width="100%">
103
+ <text>Completing delegation...</text>
104
+ </box>
105
+ )}
106
+
107
+ {error && !delegationsLoading && (
108
+ <box height={1} width="100%">
109
+ <text>{`Error: ${error}`}</text>
110
+ </box>
111
+ )}
112
+
113
+ <box height={1} width="100%">
114
+ <text>
115
+ {"Tab:next field Enter:complete Escape:cancel Backspace:delete"}
116
+ </text>
117
+ </box>
118
+ </box>
119
+ );
120
+ }