cvc-tui 0.4.0 → 0.4.2

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 (133) hide show
  1. package/dist/entry.js +71148 -61
  2. package/package.json +2 -2
  3. package/dist/app/completion.js +0 -102
  4. package/dist/app/createGatewayEventHandler.js +0 -508
  5. package/dist/app/createSlashHandler.js +0 -101
  6. package/dist/app/delegationStore.js +0 -51
  7. package/dist/app/gatewayContext.js +0 -17
  8. package/dist/app/historyStore.js +0 -123
  9. package/dist/app/inputBuffer.js +0 -120
  10. package/dist/app/inputSelectionStore.js +0 -8
  11. package/dist/app/inputStore.js +0 -28
  12. package/dist/app/interfaces.js +0 -6
  13. package/dist/app/overlayStore.js +0 -40
  14. package/dist/app/promptStore.js +0 -44
  15. package/dist/app/queueStore.js +0 -25
  16. package/dist/app/scroll.js +0 -44
  17. package/dist/app/setupHandoff.js +0 -28
  18. package/dist/app/slash/commands/core.js +0 -479
  19. package/dist/app/slash/commands/debug.js +0 -44
  20. package/dist/app/slash/commands/ops.js +0 -498
  21. package/dist/app/slash/commands/session.js +0 -431
  22. package/dist/app/slash/commands/setup.js +0 -20
  23. package/dist/app/slash/commands/toggles.js +0 -40
  24. package/dist/app/slash/registry.js +0 -18
  25. package/dist/app/slash/types.js +0 -1
  26. package/dist/app/spawnHistoryStore.js +0 -105
  27. package/dist/app/turnController.js +0 -650
  28. package/dist/app/turnStore.js +0 -48
  29. package/dist/app/uiStore.js +0 -36
  30. package/dist/app/useComposerState.js +0 -265
  31. package/dist/app/useConfigSync.js +0 -144
  32. package/dist/app/useInputHandlers.js +0 -403
  33. package/dist/app/useLongRunToolCharms.js +0 -50
  34. package/dist/app/useMainApp.js +0 -629
  35. package/dist/app/useSessionLifecycle.js +0 -175
  36. package/dist/app/useSubmission.js +0 -287
  37. package/dist/app.js +0 -15
  38. package/dist/banner.js +0 -57
  39. package/dist/components/agentsOverlay.js +0 -474
  40. package/dist/components/appChrome.js +0 -252
  41. package/dist/components/appLayout.js +0 -121
  42. package/dist/components/appOverlays.js +0 -65
  43. package/dist/components/branding.js +0 -97
  44. package/dist/components/fpsOverlay.js +0 -22
  45. package/dist/components/helpHint.js +0 -21
  46. package/dist/components/markdown.js +0 -501
  47. package/dist/components/maskedPrompt.js +0 -12
  48. package/dist/components/messageLine.js +0 -82
  49. package/dist/components/modelPicker.js +0 -254
  50. package/dist/components/overlayControls.js +0 -30
  51. package/dist/components/overlays/confirmPrompt.js +0 -25
  52. package/dist/components/overlays/helpOverlay.js +0 -76
  53. package/dist/components/overlays/historySearch.js +0 -49
  54. package/dist/components/overlays/modelPicker.js +0 -60
  55. package/dist/components/overlays/overlayUtils.js +0 -19
  56. package/dist/components/overlays/secretPrompt.js +0 -36
  57. package/dist/components/overlays/sessionPicker.js +0 -93
  58. package/dist/components/overlays/skillsHub.js +0 -71
  59. package/dist/components/prompts.js +0 -95
  60. package/dist/components/queuedMessages.js +0 -24
  61. package/dist/components/sessionPicker.js +0 -130
  62. package/dist/components/skillsHub.js +0 -165
  63. package/dist/components/streamingAssistant.js +0 -35
  64. package/dist/components/streamingMarkdown.js +0 -144
  65. package/dist/components/textInput.js +0 -794
  66. package/dist/components/themed.js +0 -12
  67. package/dist/components/thinking.js +0 -496
  68. package/dist/components/todoPanel.js +0 -40
  69. package/dist/components/transcript.js +0 -22
  70. package/dist/config/env.js +0 -18
  71. package/dist/config/limits.js +0 -22
  72. package/dist/config/timing.js +0 -18
  73. package/dist/content/charms.js +0 -5
  74. package/dist/content/faces.js +0 -21
  75. package/dist/content/fortunes.js +0 -29
  76. package/dist/content/hotkeys.js +0 -38
  77. package/dist/content/placeholders.js +0 -15
  78. package/dist/content/setup.js +0 -14
  79. package/dist/content/verbs.js +0 -41
  80. package/dist/domain/details.js +0 -53
  81. package/dist/domain/messages.js +0 -63
  82. package/dist/domain/paths.js +0 -16
  83. package/dist/domain/providers.js +0 -11
  84. package/dist/domain/roles.js +0 -6
  85. package/dist/domain/slash.js +0 -11
  86. package/dist/domain/usage.js +0 -1
  87. package/dist/domain/viewport.js +0 -33
  88. package/dist/gateway/client.js +0 -312
  89. package/dist/gatewayClient.js +0 -574
  90. package/dist/gatewayTypes.js +0 -1
  91. package/dist/hooks/useCompletion.js +0 -86
  92. package/dist/hooks/useGitBranch.js +0 -58
  93. package/dist/hooks/useInputHistory.js +0 -12
  94. package/dist/hooks/useQueue.js +0 -57
  95. package/dist/hooks/useVirtualHistory.js +0 -401
  96. package/dist/lib/circularBuffer.js +0 -43
  97. package/dist/lib/clipboard.js +0 -126
  98. package/dist/lib/editor.js +0 -41
  99. package/dist/lib/editor.test.js +0 -58
  100. package/dist/lib/emoji.js +0 -49
  101. package/dist/lib/externalCli.js +0 -11
  102. package/dist/lib/forceTruecolor.js +0 -26
  103. package/dist/lib/fpsStore.js +0 -36
  104. package/dist/lib/gracefulExit.js +0 -29
  105. package/dist/lib/history.js +0 -69
  106. package/dist/lib/inputMetrics.js +0 -143
  107. package/dist/lib/liveProgress.js +0 -51
  108. package/dist/lib/liveProgress.test.js +0 -89
  109. package/dist/lib/mathUnicode.js +0 -685
  110. package/dist/lib/memory.js +0 -123
  111. package/dist/lib/memoryMonitor.js +0 -76
  112. package/dist/lib/messages.js +0 -3
  113. package/dist/lib/messages.test.js +0 -25
  114. package/dist/lib/osc52.js +0 -53
  115. package/dist/lib/perfPane.js +0 -94
  116. package/dist/lib/platform.js +0 -312
  117. package/dist/lib/precisionWheel.js +0 -25
  118. package/dist/lib/reasoning.js +0 -39
  119. package/dist/lib/rpc.js +0 -26
  120. package/dist/lib/subagentTree.js +0 -287
  121. package/dist/lib/syntax.js +0 -89
  122. package/dist/lib/terminalModes.js +0 -46
  123. package/dist/lib/terminalParity.js +0 -48
  124. package/dist/lib/terminalSetup.js +0 -321
  125. package/dist/lib/text.js +0 -203
  126. package/dist/lib/text.test.js +0 -18
  127. package/dist/lib/todo.js +0 -2
  128. package/dist/lib/todo.test.js +0 -22
  129. package/dist/lib/viewportStore.js +0 -82
  130. package/dist/lib/virtualHeights.js +0 -61
  131. package/dist/lib/wheelAccel.js +0 -143
  132. package/dist/theme.js +0 -398
  133. package/dist/types.js +0 -1
@@ -1,12 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- // @ts-nocheck
3
- // SPDX-License-Identifier: MIT
4
- // Ported from CVC Agent (https://github.com/NousResearch/cvc)
5
- // Original Copyright (c) 2025 Nous Research. CVC adaptations (c) 2026 Jai Kumar Meena.
6
- import { Text } from '@cvc/ink';
7
- import { useStore } from '@nanostores/react';
8
- import { $uiState } from '../app/uiStore.js';
9
- export function Fg({ bold, c, children, dim, italic, literal, strikethrough, underline, wrap }) {
10
- const { theme } = useStore($uiState);
11
- return (_jsx(Text, { color: literal ?? (c && theme.color[c]), dimColor: dim, bold, italic, strikethrough, underline, wrap, children: children }));
12
- }
@@ -1,496 +0,0 @@
1
- import { createElement as _createElement } from "react";
2
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
- // @ts-nocheck
4
- // SPDX-License-Identifier: MIT
5
- // Ported from CVC Agent (https://github.com/NousResearch/cvc)
6
- // Original Copyright (c) 2025 Nous Research. CVC adaptations (c) 2026 Jai Kumar Meena.
7
- import { Box, NoSelect, Text } from '@cvc/ink';
8
- import { memo, useEffect, useMemo, useState } from 'react';
9
- import spinners from 'unicode-animations';
10
- import { THINKING_COT_MAX } from '../config/limits.js';
11
- import { sectionMode } from '../domain/details.js';
12
- import { buildSubagentTree, fmtCost, fmtTokens, formatSummary as formatSpawnSummary, hotnessBucket, peakHotness, sparkline, treeTotals, widthByDepth } from '../lib/subagentTree.js';
13
- import { boundedLiveRenderText, compactPreview, estimateTokensRough, fmtK, formatToolCall, parseToolTrailResultLine, pick, splitToolDuration, thinkingPreview, toolTrailLabel } from '../lib/text.js';
14
- const THINK = ['helix', 'breathe', 'orbit', 'dna', 'waverows', 'snake', 'pulse'];
15
- const TOOL = ['cascade', 'scan', 'diagswipe', 'fillsweep', 'rain', 'columns', 'sparkle'];
16
- const fmtElapsed = (ms) => {
17
- const sec = Math.max(0, ms) / 1000;
18
- return sec < 10 ? `${sec.toFixed(1)}s` : `${Math.round(sec)}s`;
19
- };
20
- const nextTreeRails = (rails, branch) => [...rails, branch === 'mid'];
21
- const treeLead = (rails, branch) => `${rails.map(on => (on ? '│ ' : ' ')).join('')}${branch === 'mid' ? '├─ ' : '└─ '}`;
22
- // ── Primitives ───────────────────────────────────────────────────────
23
- function TreeRow({ branch, children, rails = [], stemColor, stemDim = true, t }) {
24
- const lead = treeLead(rails, branch);
25
- return (_jsxs(Box, { children: [_jsx(NoSelect, { flexShrink: 0, fromLeftEdge: true, width: lead.length, children: _jsx(Text, { color: stemColor ?? t.color.muted, dim: stemDim, children: lead }) }), _jsx(Box, { flexDirection: "column", flexGrow: 1, children: children })] }));
26
- }
27
- function TreeTextRow({ branch, color, content, dimColor, rails = [], t, wrap = 'wrap-trim' }) {
28
- const text = dimColor ? (_jsx(Text, { color: color, dim: true, wrap: wrap, children: content })) : (_jsx(Text, { color: color, wrap: wrap, children: content }));
29
- return (_jsx(TreeRow, { branch: branch, rails: rails, t: t, children: text }));
30
- }
31
- function TreeNode({ branch, children, header, open, rails = [], stemColor, stemDim, t }) {
32
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(TreeRow, { branch: branch, rails: rails, stemColor: stemColor, stemDim: stemDim, t: t, children: header }), open ? children?.(nextTreeRails(rails, branch)) : null] }));
33
- }
34
- export function Spinner({ color, variant = 'think' }) {
35
- const spin = useMemo(() => {
36
- const raw = spinners[pick(variant === 'tool' ? TOOL : THINK)];
37
- return { ...raw, frames: raw.frames.map(f => [...f][0] ?? '⠀') };
38
- }, [variant]);
39
- const [frame, setFrame] = useState(0);
40
- useEffect(() => {
41
- setFrame(0);
42
- }, [spin]);
43
- useEffect(() => {
44
- const id = setInterval(() => setFrame(f => (f + 1) % spin.frames.length), spin.interval);
45
- return () => clearInterval(id);
46
- }, [spin]);
47
- return _jsx(Text, { color: color, children: spin.frames[frame] });
48
- }
49
- function Detail({ branch = 'last', color, content, dimColor, rails = [], t }) {
50
- return _jsx(TreeTextRow, { branch: branch, color: color, content: content, dimColor: dimColor, rails: rails, t: t });
51
- }
52
- function StreamCursor({ color, dimColor, streaming = false, visible = false }) {
53
- const [on, setOn] = useState(true);
54
- useEffect(() => {
55
- if (!visible || !streaming) {
56
- setOn(true);
57
- return;
58
- }
59
- const id = setInterval(() => setOn(v => !v), 420);
60
- return () => clearInterval(id);
61
- }, [streaming, visible]);
62
- if (!visible) {
63
- return null;
64
- }
65
- return dimColor ? (_jsx(Text, { color: color, dim: true, children: streaming && on ? '▍' : ' ' })) : (_jsx(Text, { color: color, children: streaming && on ? '▍' : ' ' }));
66
- }
67
- function Chevron({ count, onClick, open, suffix, t, title, tone = 'dim' }) {
68
- const color = tone === 'error' ? t.color.error : tone === 'warn' ? t.color.warn : t.color.muted;
69
- return (_jsx(Box, { onClick: (e) => onClick(!!e?.shiftKey || !!e?.ctrlKey), children: _jsxs(Text, { color: color, dim: tone === 'dim', children: [_jsx(Text, { color: t.color.accent, children: open ? '▾ ' : '▸ ' }), title, typeof count === 'number' ? ` (${count})` : '', suffix ? (_jsxs(Text, { color: t.color.statusFg, dim: true, children: [' ', suffix] })) : null] }) }));
70
- }
71
- function heatColor(node, peak, theme) {
72
- const palette = [theme.color.border, theme.color.accent, theme.color.primary, theme.color.warn, theme.color.error];
73
- const idx = hotnessBucket(node.aggregate.hotness, peak, palette.length);
74
- // Below the median bucket we keep the default dim stem so cool branches
75
- // fade into the chrome — only "hot" branches draw the eye.
76
- if (idx < 2) {
77
- return undefined;
78
- }
79
- return palette[idx];
80
- }
81
- function SubagentAccordion({ branch, expanded, node, peak, rails = [], t }) {
82
- const [open, setOpen] = useState(expanded);
83
- const [deep, setDeep] = useState(expanded);
84
- const [openThinking, setOpenThinking] = useState(expanded);
85
- const [openTools, setOpenTools] = useState(expanded);
86
- const [openNotes, setOpenNotes] = useState(expanded);
87
- const [openKids, setOpenKids] = useState(expanded);
88
- useEffect(() => {
89
- if (!expanded) {
90
- return;
91
- }
92
- setOpen(true);
93
- setDeep(true);
94
- setOpenThinking(true);
95
- setOpenTools(true);
96
- setOpenNotes(true);
97
- setOpenKids(true);
98
- }, [expanded]);
99
- const expandAll = () => {
100
- setOpen(true);
101
- setDeep(true);
102
- setOpenThinking(true);
103
- setOpenTools(true);
104
- setOpenNotes(true);
105
- setOpenKids(true);
106
- };
107
- const item = node.item;
108
- const children = node.children;
109
- const aggregate = node.aggregate;
110
- const statusTone = item.status === 'failed' ? 'error' : item.status === 'interrupted' ? 'warn' : 'dim';
111
- const prefix = item.taskCount > 1 ? `[${item.index + 1}/${item.taskCount}] ` : '';
112
- const goalLabel = item.goal || `Subagent ${item.index + 1}`;
113
- const title = `${prefix}${open ? goalLabel : compactPreview(goalLabel, 60)}`;
114
- const summary = compactPreview((item.summary || '').replace(/\s+/g, ' ').trim(), 72);
115
- // Suffix packs branch rollup: status · elapsed · per-branch tool/agent/token/cost.
116
- // Emphasises the numbers the user can't easily eyeball from a flat list.
117
- const statusLabel = item.status === 'queued' ? 'queued' : item.status === 'running' ? 'running' : String(item.status);
118
- const rollupBits = [statusLabel];
119
- if (item.durationSeconds) {
120
- rollupBits.push(fmtElapsed(item.durationSeconds * 1000));
121
- }
122
- const localTools = item.toolCount ?? 0;
123
- const subtreeTools = aggregate.totalTools - localTools;
124
- if (localTools > 0) {
125
- rollupBits.push(`${localTools} tool${localTools === 1 ? '' : 's'}`);
126
- }
127
- const localTokens = (item.inputTokens ?? 0) + (item.outputTokens ?? 0);
128
- if (localTokens > 0) {
129
- rollupBits.push(`${fmtTokens(localTokens)} tok`);
130
- }
131
- const localCost = item.costUsd ?? 0;
132
- if (localCost > 0) {
133
- rollupBits.push(fmtCost(localCost));
134
- }
135
- const filesLocal = (item.filesWritten?.length ?? 0) + (item.filesRead?.length ?? 0);
136
- if (filesLocal > 0) {
137
- rollupBits.push(`⎘${filesLocal}`);
138
- }
139
- if (children.length > 0) {
140
- rollupBits.push(`${aggregate.descendantCount}↓`);
141
- if (subtreeTools > 0) {
142
- rollupBits.push(`+${subtreeTools}t sub`);
143
- }
144
- const subCost = aggregate.costUsd - localCost;
145
- if (subCost >= 0.01) {
146
- rollupBits.push(`+${fmtCost(subCost)} sub`);
147
- }
148
- if (aggregate.activeCount > 0 && item.status !== 'running') {
149
- rollupBits.push(`⚡${aggregate.activeCount}`);
150
- }
151
- }
152
- const suffix = rollupBits.join(' · ');
153
- const thinkingText = item.thinking.join('\n');
154
- const hasThinking = Boolean(thinkingText);
155
- const hasTools = item.tools.length > 0;
156
- const noteRows = [...(summary ? [summary] : []), ...item.notes];
157
- const hasNotes = noteRows.length > 0;
158
- const noteColor = statusTone === 'error' ? t.color.error : statusTone === 'warn' ? t.color.warn : t.color.muted;
159
- const sections = [];
160
- if (hasThinking) {
161
- sections.push({
162
- header: (_jsx(Chevron, { count: item.thinking.length, onClick: shift => {
163
- if (shift) {
164
- expandAll();
165
- }
166
- else {
167
- setOpenThinking(v => !v);
168
- }
169
- }, open: openThinking, t: t, title: "Thinking" })),
170
- key: 'thinking',
171
- open: openThinking,
172
- render: childRails => (_jsx(Thinking, { active: item.status === 'running', branch: "last", mode: "full", rails: childRails, reasoning: thinkingText, streaming: item.status === 'running', t: t }))
173
- });
174
- }
175
- if (hasTools) {
176
- sections.push({
177
- header: (_jsx(Chevron, { count: item.tools.length, onClick: shift => {
178
- if (shift) {
179
- expandAll();
180
- }
181
- else {
182
- setOpenTools(v => !v);
183
- }
184
- }, open: openTools, t: t, title: "Tool calls" })),
185
- key: 'tools',
186
- open: openTools,
187
- render: childRails => (_jsx(Box, { flexDirection: "column", children: item.tools.map((line, index) => (_jsx(TreeTextRow, { branch: index === item.tools.length - 1 ? 'last' : 'mid', color: t.color.text, content: _jsxs(_Fragment, { children: [_jsx(Text, { color: t.color.accent, children: "\u25CF " }), line] }), rails: childRails, t: t }, `${item.id}-tool-${index}`))) }))
188
- });
189
- }
190
- if (hasNotes) {
191
- sections.push({
192
- header: (_jsx(Chevron, { count: noteRows.length, onClick: shift => {
193
- if (shift) {
194
- expandAll();
195
- }
196
- else {
197
- setOpenNotes(v => !v);
198
- }
199
- }, open: openNotes, t: t, title: "Progress", tone: statusTone })),
200
- key: 'notes',
201
- open: openNotes,
202
- render: childRails => (_jsx(Box, { flexDirection: "column", children: noteRows.map((line, index) => (_jsx(TreeTextRow, { branch: index === noteRows.length - 1 ? 'last' : 'mid', color: noteColor, content: line, dimColor: statusTone === 'dim', rails: childRails, t: t }, `${item.id}-note-${index}`))) }))
203
- });
204
- }
205
- if (children.length > 0) {
206
- // Nested grandchildren — rendered recursively via SubagentAccordion,
207
- // sharing the same keybindings / expand semantics as top-level nodes.
208
- sections.push({
209
- header: (_jsx(Chevron, { count: children.length, onClick: shift => {
210
- if (shift) {
211
- expandAll();
212
- }
213
- else {
214
- setOpenKids(v => !v);
215
- }
216
- }, open: openKids, suffix: `d${item.depth + 1} · ${aggregate.descendantCount} total`, t: t, title: "Spawned" })),
217
- key: 'subagents',
218
- open: openKids,
219
- render: childRails => (_jsx(Box, { flexDirection: "column", children: children.map((child, i) => (_jsx(SubagentAccordion, { branch: i === children.length - 1 ? 'last' : 'mid', expanded: expanded || deep, node: child, peak: peak, rails: childRails, t: t }, child.item.id))) }))
220
- });
221
- }
222
- // Heatmap: amber→error gradient on the stem when this branch is "hot"
223
- // (high tools/sec) relative to the whole tree's peak.
224
- const stem = heatColor(node, peak, t);
225
- return (_jsx(TreeNode, { branch: branch, header: _jsx(Chevron, { onClick: shift => {
226
- if (shift) {
227
- expandAll();
228
- return;
229
- }
230
- setOpen(v => {
231
- if (!v) {
232
- setDeep(false);
233
- }
234
- return !v;
235
- });
236
- }, open: open, suffix: suffix, t: t, title: title, tone: statusTone }), open: open, rails: rails, stemColor: stem, stemDim: stem == null, t: t, children: childRails => (_jsx(Box, { flexDirection: "column", children: sections.map((section, index) => (_jsx(TreeNode, { branch: index === sections.length - 1 ? 'last' : 'mid', header: section.header, open: section.open, rails: childRails, t: t, children: section.render }, `${item.id}-${section.key}`))) })) }));
237
- }
238
- // ── Thinking ─────────────────────────────────────────────────────────
239
- export const Thinking = memo(function Thinking({ active = false, branch = 'last', mode = 'truncated', rails = [], reasoning, streaming = false, t }) {
240
- const preview = useMemo(() => {
241
- const raw = thinkingPreview(reasoning, mode, THINKING_COT_MAX);
242
- return mode === 'full' ? boundedLiveRenderText(raw) : raw;
243
- }, [mode, reasoning]);
244
- const lines = useMemo(() => preview.split('\n').map(line => line.replace(/\t/g, ' ')), [preview]);
245
- if (!preview && !active) {
246
- return null;
247
- }
248
- return (_jsx(TreeRow, { branch: branch, rails: rails, t: t, children: _jsx(Box, { flexDirection: "column", flexGrow: 1, children: preview ? (mode === 'full' ? (lines.map((line, index) => (_jsxs(Text, { color: t.color.muted, wrap: "wrap-trim", children: [line || ' ', index === lines.length - 1 ? (_jsx(StreamCursor, { color: t.color.muted, streaming: streaming, visible: active })) : null] }, index)))) : (_jsxs(Text, { color: t.color.muted, wrap: "truncate-end", children: [preview, _jsx(StreamCursor, { color: t.color.muted, streaming: streaming, visible: active })] }))) : (_jsx(Text, { color: t.color.muted, children: _jsx(StreamCursor, { color: t.color.muted, streaming: streaming, visible: active }) })) }) }));
249
- });
250
- export const ToolTrail = memo(function ToolTrail({ busy = false, commandOverride = false, detailsMode = 'collapsed', outcome = '', reasoningActive = false, reasoning = '', reasoningTokens, reasoningStreaming = false, sections, subagents = [], t, tools = [], toolTokens, trail = [], activity = [] }) {
251
- const visible = useMemo(() => ({
252
- thinking: sectionMode('thinking', detailsMode, sections, commandOverride),
253
- tools: sectionMode('tools', detailsMode, sections, commandOverride),
254
- subagents: sectionMode('subagents', detailsMode, sections, commandOverride),
255
- activity: sectionMode('activity', detailsMode, sections, commandOverride)
256
- }), [commandOverride, detailsMode, sections]);
257
- const [now, setNow] = useState(() => Date.now());
258
- // Local toggles own the open state once mounted. Init from the resolved
259
- // section visibility so default-expanded sections (thinking/tools) render
260
- // open on first paint; the useEffect below re-syncs when the user mutates
261
- // visibility at runtime via /details. NEVER OR these against
262
- // `visible.X === 'expanded'` at render time — that locks the panel open
263
- // and silently breaks manual chevron clicks for default-expanded
264
- // sections (regression caught after #14968).
265
- const [openThinking, setOpenThinking] = useState(visible.thinking === 'expanded');
266
- const [openTools, setOpenTools] = useState(visible.tools === 'expanded');
267
- const [openSubagents, setOpenSubagents] = useState(visible.subagents === 'expanded');
268
- const [deepSubagents, setDeepSubagents] = useState(visible.subagents === 'expanded');
269
- const [openMeta, setOpenMeta] = useState(visible.activity === 'expanded');
270
- useEffect(() => {
271
- if (!tools.length || (visible.tools !== 'expanded' && !openTools)) {
272
- return;
273
- }
274
- const id = setInterval(() => setNow(Date.now()), 500);
275
- return () => clearInterval(id);
276
- }, [openTools, tools.length, visible.tools]);
277
- useEffect(() => {
278
- setOpenThinking(visible.thinking === 'expanded');
279
- setOpenTools(visible.tools === 'expanded');
280
- setOpenSubagents(visible.subagents === 'expanded');
281
- setOpenMeta(visible.activity === 'expanded');
282
- }, [visible]);
283
- const cot = useMemo(() => thinkingPreview(reasoning, 'full', THINKING_COT_MAX), [reasoning]);
284
- // Spawn-tree derivations must live above any early return so React's
285
- // rules-of-hooks sees a stable call order. Cheap O(N) builds memoised
286
- // by subagent-list identity.
287
- const spawnTree = useMemo(() => buildSubagentTree(subagents), [subagents]);
288
- const spawnPeak = useMemo(() => peakHotness(spawnTree), [spawnTree]);
289
- const spawnTotals = useMemo(() => treeTotals(spawnTree), [spawnTree]);
290
- const spawnWidths = useMemo(() => widthByDepth(spawnTree), [spawnTree]);
291
- const spawnSpark = useMemo(() => sparkline(spawnWidths), [spawnWidths]);
292
- const spawnSummaryLabel = useMemo(() => formatSpawnSummary(spawnTotals), [spawnTotals]);
293
- if (!busy &&
294
- !trail.length &&
295
- !tools.length &&
296
- !subagents.length &&
297
- !activity.length &&
298
- !cot &&
299
- !reasoningActive &&
300
- !outcome) {
301
- return null;
302
- }
303
- // ── Build groups + meta ────────────────────────────────────────
304
- const groups = [];
305
- const meta = [];
306
- const pushDetail = (row) => (groups.at(-1)?.details ?? meta).push(row);
307
- for (const [i, line] of trail.entries()) {
308
- const parsed = parseToolTrailResultLine(line);
309
- if (parsed) {
310
- groups.push({
311
- color: parsed.mark === '✗' ? t.color.error : t.color.text,
312
- content: parsed.call,
313
- details: [],
314
- key: `tr-${i}`,
315
- label: parsed.call
316
- });
317
- if (parsed.detail) {
318
- pushDetail({
319
- color: parsed.mark === '✗' ? t.color.error : t.color.muted,
320
- content: parsed.detail,
321
- dimColor: parsed.mark !== '✗',
322
- key: `tr-${i}-d`
323
- });
324
- }
325
- continue;
326
- }
327
- if (line.startsWith('drafting ')) {
328
- const label = toolTrailLabel(line.slice(9).replace(/…$/, '').trim());
329
- groups.push({
330
- color: t.color.text,
331
- content: label,
332
- details: [{ color: t.color.muted, content: 'drafting...', dimColor: true, key: `tr-${i}-d` }],
333
- key: `tr-${i}`,
334
- label
335
- });
336
- continue;
337
- }
338
- if (line === 'analyzing tool output…') {
339
- pushDetail({
340
- color: t.color.muted,
341
- dimColor: true,
342
- key: `tr-${i}`,
343
- content: groups.length ? (_jsxs(_Fragment, { children: [_jsx(Spinner, { color: t.color.accent, variant: "think" }), " ", line] })) : (line)
344
- });
345
- continue;
346
- }
347
- meta.push({ color: t.color.muted, content: line, dimColor: true, key: `tr-${i}` });
348
- }
349
- for (const tool of tools) {
350
- const label = formatToolCall(tool.name, tool.context || '');
351
- groups.push({
352
- color: t.color.text,
353
- key: tool.id,
354
- label,
355
- details: [],
356
- content: (_jsxs(_Fragment, { children: [_jsx(Spinner, { color: t.color.accent, variant: "tool" }), " ", label, tool.startedAt ? ` (${fmtElapsed(now - tool.startedAt)})` : ''] }))
357
- });
358
- }
359
- for (const item of activity.slice(-4)) {
360
- const glyph = item.tone === 'error' ? '✗' : item.tone === 'warn' ? '!' : '·';
361
- const color = item.tone === 'error' ? t.color.error : item.tone === 'warn' ? t.color.warn : t.color.muted;
362
- meta.push({ color, content: `${glyph} ${item.text}`, dimColor: item.tone === 'info', key: `a-${item.id}` });
363
- }
364
- // ── Derived ────────────────────────────────────────────────────
365
- const hasTools = groups.length > 0;
366
- const hasSubagents = subagents.length > 0;
367
- const hasMeta = meta.length > 0;
368
- const hasThinking = !!cot || reasoningActive || reasoningStreaming;
369
- const thinkingLive = reasoningActive || reasoningStreaming;
370
- const tokenCount = reasoningTokens && reasoningTokens > 0 ? reasoningTokens : reasoning ? estimateTokensRough(reasoning) : 0;
371
- const toolTokenCount = toolTokens ?? 0;
372
- const totalTokenCount = tokenCount + toolTokenCount;
373
- const thinkingTokensLabel = tokenCount > 0 ? `~${fmtK(tokenCount)} tokens` : null;
374
- const toolTokensLabel = toolTokens !== undefined && toolTokens > 0 ? `~${fmtK(toolTokens)} tokens` : undefined;
375
- const totalTokensLabel = tokenCount > 0 && toolTokenCount > 0 ? `~${fmtK(totalTokenCount)} total` : null;
376
- const delegateGroups = groups.filter(g => g.label.startsWith('Delegate Task'));
377
- const inlineDelegateKey = hasSubagents && delegateGroups.length === 1 ? delegateGroups[0].key : null;
378
- const toolLabel = (group) => {
379
- const { duration, label } = splitToolDuration(String(group.content));
380
- return duration ? (_jsxs(_Fragment, { children: [label, _jsx(Text, { color: t.color.statusFg, dim: true, children: duration })] })) : (group.content);
381
- };
382
- // ── Backstop: floating alerts when every panel is hidden ─────────
383
- //
384
- // Per-section overrides win over the global details_mode (they're computed
385
- // by sectionMode), so we only collapse to nothing when EVERY section is
386
- // resolved to hidden — that way `details_mode: hidden` + `sections.tools:
387
- // expanded` still renders the tools panel. When all panels are hidden
388
- // AND ambient errors/warnings exist, surface them as a compact inline
389
- // backstop so quiet-mode users aren't blind to failures.
390
- const allHidden = visible.thinking === 'hidden' &&
391
- visible.tools === 'hidden' &&
392
- visible.subagents === 'hidden' &&
393
- visible.activity === 'hidden';
394
- if (allHidden) {
395
- const alerts = activity.filter(i => i.tone !== 'info').slice(-2);
396
- return alerts.length ? (_jsx(Box, { flexDirection: "column", children: alerts.map(i => (_jsxs(Text, { color: i.tone === 'error' ? t.color.error : t.color.warn, children: [i.tone === 'error' ? '✗' : '!', " ", i.text] }, `ha-${i.id}`))) })) : null;
397
- }
398
- // ── Tree render fragments ──────────────────────────────────────
399
- //
400
- // Shift+click on any chevron expands every NON-hidden section at once —
401
- // hidden sections stay hidden so the override is honoured.
402
- const expandAll = () => {
403
- if (visible.thinking !== 'hidden') {
404
- setOpenThinking(true);
405
- }
406
- if (visible.tools !== 'hidden') {
407
- setOpenTools(true);
408
- }
409
- if (visible.subagents !== 'hidden') {
410
- setOpenSubagents(true);
411
- setDeepSubagents(true);
412
- }
413
- if (visible.activity !== 'hidden') {
414
- setOpenMeta(true);
415
- }
416
- };
417
- const metaTone = activity.some(i => i.tone === 'error')
418
- ? 'error'
419
- : activity.some(i => i.tone === 'warn')
420
- ? 'warn'
421
- : 'dim';
422
- const renderSubagentList = (rails) => (_jsx(Box, { flexDirection: "column", children: spawnTree.map((node, index) => (_jsx(SubagentAccordion, { branch: index === spawnTree.length - 1 ? 'last' : 'mid', expanded: visible.subagents === 'expanded' || deepSubagents, node: node, peak: spawnPeak, rails: rails, t: t }, node.item.id))) }));
423
- const panels = [];
424
- if (hasThinking && visible.thinking !== 'hidden') {
425
- panels.push({
426
- header: (_jsx(Box, { onClick: (e) => {
427
- if (e?.shiftKey || e?.ctrlKey) {
428
- expandAll();
429
- }
430
- else {
431
- setOpenThinking(v => !v);
432
- }
433
- }, children: _jsxs(Text, { color: t.color.muted, dim: !thinkingLive, children: [_jsx(Text, { color: t.color.accent, children: openThinking ? '▾ ' : '▸ ' }), thinkingLive ? (_jsx(Text, { bold: true, color: t.color.text, children: "Thinking" })) : (_jsx(Text, { color: t.color.muted, dim: true, children: "Thinking" })), thinkingTokensLabel ? (_jsxs(Text, { color: t.color.statusFg, dim: true, children: [' ', thinkingTokensLabel] })) : null] }) })),
434
- key: 'thinking',
435
- open: openThinking,
436
- render: rails => (_jsx(Thinking, { active: reasoningActive, branch: "last", mode: "full", rails: rails, reasoning: busy ? reasoning : cot, streaming: busy && reasoningStreaming, t: t }))
437
- });
438
- }
439
- if (hasTools && visible.tools !== 'hidden') {
440
- panels.push({
441
- header: (_jsx(Chevron, { count: groups.length, onClick: shift => {
442
- if (shift) {
443
- expandAll();
444
- }
445
- else {
446
- setOpenTools(v => !v);
447
- }
448
- }, open: openTools, suffix: toolTokensLabel, t: t, title: "Tool calls" })),
449
- key: 'tools',
450
- open: openTools,
451
- render: rails => (_jsx(Box, { flexDirection: "column", children: groups.map((group, index) => {
452
- const branch = index === groups.length - 1 ? 'last' : 'mid';
453
- const childRails = nextTreeRails(rails, branch);
454
- const hasInlineSubagents = inlineDelegateKey === group.key;
455
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(TreeTextRow, { branch: branch, color: group.color, content: _jsxs(_Fragment, { children: [_jsx(Text, { color: t.color.accent, children: "\u25CF " }), toolLabel(group)] }), rails: rails, t: t }), group.details.map((detail, detailIndex) => (_createElement(Detail, { ...detail, branch: detailIndex === group.details.length - 1 && !hasInlineSubagents ? 'last' : 'mid', key: detail.key, rails: childRails, t: t }))), hasInlineSubagents ? renderSubagentList(childRails) : null] }, group.key));
456
- }) }))
457
- });
458
- }
459
- if (hasSubagents && !inlineDelegateKey && visible.subagents !== 'hidden') {
460
- // Spark + summary give a one-line read on the branch shape before
461
- // opening the subtree. `/agents` opens the full-screen audit overlay.
462
- const suffix = spawnSpark ? `${spawnSummaryLabel} ${spawnSpark} (/agents)` : `${spawnSummaryLabel} (/agents)`;
463
- panels.push({
464
- header: (_jsx(Chevron, { count: spawnTotals.descendantCount, onClick: shift => {
465
- if (shift) {
466
- expandAll();
467
- setDeepSubagents(true);
468
- }
469
- else {
470
- setOpenSubagents(v => !v);
471
- setDeepSubagents(false);
472
- }
473
- }, open: openSubagents, suffix: suffix, t: t, title: "Spawn tree" })),
474
- key: 'subagents',
475
- open: openSubagents,
476
- render: renderSubagentList
477
- });
478
- }
479
- if (hasMeta && visible.activity !== 'hidden') {
480
- panels.push({
481
- header: (_jsx(Chevron, { count: meta.length, onClick: shift => {
482
- if (shift) {
483
- expandAll();
484
- }
485
- else {
486
- setOpenMeta(v => !v);
487
- }
488
- }, open: openMeta, t: t, title: "Activity", tone: metaTone })),
489
- key: 'meta',
490
- open: openMeta,
491
- render: rails => (_jsx(Box, { flexDirection: "column", children: meta.map((row, index) => (_jsx(TreeTextRow, { branch: index === meta.length - 1 ? 'last' : 'mid', color: row.color, content: row.content, dimColor: row.dimColor, rails: rails, t: t }, row.key))) }))
492
- });
493
- }
494
- const topCount = panels.length + (totalTokensLabel ? 1 : 0);
495
- return (_jsxs(Box, { flexDirection: "column", children: [panels.map((panel, index) => (_jsx(TreeNode, { branch: index === topCount - 1 ? 'last' : 'mid', header: panel.header, open: panel.open, t: t, children: panel.render }, panel.key))), totalTokensLabel ? (_jsx(TreeTextRow, { branch: "last", color: t.color.statusFg, content: _jsxs(_Fragment, { children: [_jsx(Text, { color: t.color.accent, children: "\u03A3 " }), totalTokensLabel] }), dimColor: true, t: t })) : null, outcome ? (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: t.color.muted, dim: true, children: ["\u00B7 ", outcome] }) })) : null] }));
496
- });
@@ -1,40 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- // @ts-nocheck
3
- // SPDX-License-Identifier: MIT
4
- // Ported from CVC Agent (https://github.com/NousResearch/cvc)
5
- // Original Copyright (c) 2025 Nous Research. CVC adaptations (c) 2026 Jai Kumar Meena.
6
- import { Box, Text } from '@cvc/ink';
7
- import { memo, useState } from 'react';
8
- import { countPendingTodos } from '../lib/liveProgress.js';
9
- import { todoGlyph, todoTone } from '../lib/todo.js';
10
- const rowColor = (t, status) => {
11
- const tone = todoTone(status);
12
- return tone === 'active' ? t.color.text : tone === 'body' ? t.color.statusFg : t.color.muted;
13
- };
14
- export const TodoPanel = memo(function TodoPanel({ collapsed, defaultCollapsed = false, incomplete = false, onToggle, t, todos }) {
15
- // Fallback local state for archived todos in transcript where there's no
16
- // external controller. Live TodoPanel passes collapsed+onToggle from the
17
- // turn store so clicks still work there.
18
- const [localCollapsed, setLocalCollapsed] = useState(defaultCollapsed);
19
- const isControlled = typeof collapsed === 'boolean';
20
- const effectiveCollapsed = isControlled ? collapsed : localCollapsed;
21
- const handleToggle = () => {
22
- if (onToggle) {
23
- onToggle();
24
- return;
25
- }
26
- if (!isControlled) {
27
- setLocalCollapsed(v => !v);
28
- }
29
- };
30
- if (!todos.length) {
31
- return null;
32
- }
33
- const done = todos.filter(todo => todo.status === 'completed').length;
34
- const pending = countPendingTodos(todos);
35
- return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Box, { onClick: handleToggle, children: _jsxs(Text, { color: t.color.muted, children: [_jsx(Text, { color: t.color.accent, children: effectiveCollapsed ? '▸ ' : '▾ ' }), _jsx(Text, { bold: true, color: t.color.text, children: "Todo" }), ' ', _jsxs(Text, { color: t.color.statusFg, dim: true, children: ["(", done, "/", todos.length, ")"] }), incomplete && pending > 0 && (_jsxs(Text, { color: t.color.muted, dim: true, children: [' ', "\u00B7 incomplete \u00B7 ", pending, " still ", pending === 1 ? 'pending' : 'pending/in_progress'] }))] }) }), !effectiveCollapsed && (_jsx(Box, { flexDirection: "column", marginLeft: 2, children: todos.map(todo => {
36
- const tone = todoTone(todo.status);
37
- const color = rowColor(t, todo.status);
38
- return (_jsxs(Text, { color: color, dim: tone === 'dim', children: [_jsxs(Text, { color: color, children: [todoGlyph(todo.status), " "] }), todo.content] }, todo.id));
39
- }) }))] }));
40
- });
@@ -1,22 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Box, Text } from 'ink';
3
- import { useStore } from '@nanostores/react';
4
- import { $messages, $turn } from '../app/turnStore.js';
5
- import { Markdown, StreamingMarkdown } from './streamingMarkdown.js';
6
- import { CVC_THEME } from '../types.js';
7
- const Bubble = ({ msg, streaming }) => {
8
- const isUser = msg.role === 'user';
9
- const borderColor = isUser ? CVC_THEME.primary : CVC_THEME.dim;
10
- const tag = isUser ? 'you' : msg.role === 'assistant' ? 'cvc' : msg.role;
11
- const tagColor = isUser ? CVC_THEME.primary : CVC_THEME.accent;
12
- return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { bold: true, color: tagColor, children: `▌ ${tag}` }), _jsx(Box, { borderStyle: "round", borderColor: borderColor, paddingX: 1, flexDirection: "column", children: isUser ? (_jsx(Text, { children: msg.content })) : streaming ? (_jsx(StreamingMarkdown, { content: msg.content })) : (_jsx(Markdown, { text: msg.content })) })] }));
13
- };
14
- export const Transcript = () => {
15
- const messages = useStore($messages);
16
- const turn = useStore($turn);
17
- if (messages.length === 0) {
18
- return (_jsx(Box, { marginY: 1, children: _jsx(Text, { color: CVC_THEME.dim, children: "\u2014 no messages yet \u2014 type below to start \u2014" }) }));
19
- }
20
- const lastId = messages[messages.length - 1]?.id;
21
- return (_jsx(Box, { flexDirection: "column", marginY: 1, children: messages.map((m) => (_jsx(Bubble, { msg: m, streaming: m.role === 'assistant' && m.id === lastId && turn?.status === 'streaming' }, m.id))) }));
22
- };
@@ -1,18 +0,0 @@
1
- // @ts-nocheck
2
- // SPDX-License-Identifier: MIT
3
- // Ported from CVC Agent (https://github.com/NousResearch/cvc)
4
- // Original Copyright (c) 2025 Nous Research. CVC adaptations (c) 2026 Jai Kumar Meena.
5
- const truthy = (v) => /^(?:1|true|yes|on)$/i.test((v ?? '').trim());
6
- export const STARTUP_RESUME_ID = (process.env.CVC_TUI_RESUME ?? '').trim();
7
- export const STARTUP_QUERY = (process.env.CVC_TUI_QUERY ?? '').trim();
8
- export const STARTUP_IMAGE = (process.env.CVC_TUI_IMAGE ?? '').trim();
9
- export const MOUSE_TRACKING = !truthy(process.env.CVC_TUI_DISABLE_MOUSE);
10
- export const NO_CONFIRM_DESTRUCTIVE = truthy(process.env.CVC_TUI_NO_CONFIRM);
11
- // Skip AlternateScreen — TUI renders into the primary buffer so the host
12
- // terminal's native scrollback captures whatever scrolls off the top.
13
- // Experiment gate: lets us measure native scroll vs our virtualization on
14
- // the same pipeline.
15
- export const INLINE_MODE = truthy(process.env.CVC_TUI_INLINE);
16
- // Live FPS counter overlay, fed by ink's onFrame (real render rate, not a
17
- // synthetic timer).
18
- export const SHOW_FPS = truthy(process.env.CVC_TUI_FPS);
@@ -1,22 +0,0 @@
1
- // @ts-nocheck
2
- // SPDX-License-Identifier: MIT
3
- // Ported from CVC Agent (https://github.com/NousResearch/cvc)
4
- // Original Copyright (c) 2025 Nous Research. CVC adaptations (c) 2026 Jai Kumar Meena.
5
- export const LARGE_PASTE = { chars: 8000, lines: 80 };
6
- export const LIVE_RENDER_MAX_CHARS = 16_000;
7
- export const LIVE_RENDER_MAX_LINES = 240;
8
- // History-render bounds for messages outside FULL_RENDER_TAIL. Each rendered
9
- // line ≈ 1 Yoga/Text node + inline spans, so this is the dominant lever on
10
- // cold-mount cost during PageUp catch-up. 16 lines × 25 mounted ≈ 400 nodes
11
- // — comfortably inside the 16ms per-frame budget. User pages back to
12
- // recognize, not to read; full re-render once it falls inside the tail.
13
- export const HISTORY_RENDER_MAX_CHARS = 800;
14
- export const HISTORY_RENDER_MAX_LINES = 16;
15
- export const FULL_RENDER_TAIL_ITEMS = 8;
16
- export const LONG_MSG = 300;
17
- export const MAX_HISTORY = 800;
18
- export const THINKING_COT_MAX = 160;
19
- // Rows per wheel event (pre-accel). 1 keeps Ink's DECSTBM fast path live
20
- // (each scroll < viewport-1) and produces smooth motion. wheelAccel.ts
21
- // ramps this on sustained scrolls.
22
- export const WHEEL_SCROLL_STEP = 1;
@@ -1,18 +0,0 @@
1
- // @ts-nocheck
2
- // SPDX-License-Identifier: MIT
3
- // Ported from CVC Agent (https://github.com/NousResearch/cvc)
4
- // Original Copyright (c) 2025 Nous Research. CVC adaptations (c) 2026 Jai Kumar Meena.
5
- // Centralised timing constants for the CVC TUI.
6
- // Keep all magic numbers controlling UI cadence in this file.
7
- export const TIMING = {
8
- /** Spinner frame interval (ms) */
9
- SPINNER_FRAME_MS: 80,
10
- /** Throttle for streaming markdown re-renders (ms) */
11
- STREAM_THROTTLE_MS: 16,
12
- /** Debounce on text-input change handlers (ms) */
13
- INPUT_DEBOUNCE_MS: 0,
14
- /** Gateway connect retry backoff (ms) */
15
- GATEWAY_RECONNECT_MS: 1500,
16
- /** Heartbeat ping interval (ms) */
17
- HEARTBEAT_MS: 15000,
18
- };
@@ -1,5 +0,0 @@
1
- // @ts-nocheck
2
- // SPDX-License-Identifier: MIT
3
- // Ported from CVC Agent (https://github.com/NousResearch/cvc)
4
- // Original Copyright (c) 2025 Nous Research. CVC adaptations (c) 2026 Jai Kumar Meena.
5
- export const LONG_RUN_CHARMS = ['still cooking…', 'polishing edges…', 'asking the void nicely…'];