brownian-code 2026.2.10

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 (120) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +97 -0
  3. package/bin/brownian +25 -0
  4. package/env.example +21 -0
  5. package/package.json +87 -0
  6. package/src/agent/agent.test.ts +414 -0
  7. package/src/agent/agent.ts +385 -0
  8. package/src/agent/index.ts +27 -0
  9. package/src/agent/prompts.ts +271 -0
  10. package/src/agent/scratchpad.test.ts +482 -0
  11. package/src/agent/scratchpad.ts +526 -0
  12. package/src/agent/token-counter.test.ts +59 -0
  13. package/src/agent/token-counter.ts +33 -0
  14. package/src/agent/types.ts +137 -0
  15. package/src/cli.tsx +385 -0
  16. package/src/commands/builtin.test.ts +271 -0
  17. package/src/commands/builtin.ts +200 -0
  18. package/src/commands/registry.test.ts +188 -0
  19. package/src/commands/registry.ts +111 -0
  20. package/src/commands/types.ts +64 -0
  21. package/src/components/AgentEventView.tsx +487 -0
  22. package/src/components/AnswerBox.tsx +81 -0
  23. package/src/components/ApiKeyPrompt.tsx +75 -0
  24. package/src/components/CommandMenu.test.tsx +64 -0
  25. package/src/components/CommandMenu.tsx +38 -0
  26. package/src/components/CursorText.tsx +43 -0
  27. package/src/components/DebugPanel.tsx +48 -0
  28. package/src/components/ErrorBox.test.tsx +58 -0
  29. package/src/components/ErrorBox.tsx +26 -0
  30. package/src/components/HelpView.test.tsx +70 -0
  31. package/src/components/HelpView.tsx +61 -0
  32. package/src/components/HistoryItemView.tsx +108 -0
  33. package/src/components/Input.tsx +193 -0
  34. package/src/components/Intro.test.tsx +59 -0
  35. package/src/components/Intro.tsx +35 -0
  36. package/src/components/ModelSelector.tsx +288 -0
  37. package/src/components/StatusBar.test.tsx +78 -0
  38. package/src/components/StatusBar.tsx +56 -0
  39. package/src/components/WorkingIndicator.tsx +133 -0
  40. package/src/components/index.ts +23 -0
  41. package/src/e2e/agent-flow.test.ts +378 -0
  42. package/src/evals/components/EvalApp.tsx +206 -0
  43. package/src/evals/components/EvalCurrentQuestion.tsx +42 -0
  44. package/src/evals/components/EvalProgress.tsx +33 -0
  45. package/src/evals/components/EvalRecentResults.tsx +63 -0
  46. package/src/evals/components/EvalStats.tsx +49 -0
  47. package/src/evals/components/index.ts +5 -0
  48. package/src/evals/dataset/crypto_agent.csv +16 -0
  49. package/src/evals/run.ts +355 -0
  50. package/src/gateway/channels/whatsapp/auth-store.ts +15 -0
  51. package/src/gateway/channels/whatsapp/inbound.ts +86 -0
  52. package/src/gateway/channels/whatsapp/login.ts +28 -0
  53. package/src/gateway/channels/whatsapp/outbound.ts +27 -0
  54. package/src/gateway/channels/whatsapp/session.ts +69 -0
  55. package/src/gateway/config.ts +81 -0
  56. package/src/gateway/index.ts +62 -0
  57. package/src/hooks/useAgentRunner.ts +317 -0
  58. package/src/hooks/useDebugLogs.ts +22 -0
  59. package/src/hooks/useInputHistory.ts +106 -0
  60. package/src/hooks/useModelSelection.ts +249 -0
  61. package/src/hooks/useTextBuffer.test.ts +121 -0
  62. package/src/hooks/useTextBuffer.ts +97 -0
  63. package/src/index.tsx +74 -0
  64. package/src/mcp/cache.ts +205 -0
  65. package/src/mcp/client.test.ts +126 -0
  66. package/src/mcp/client.ts +145 -0
  67. package/src/mcp/index.ts +2 -0
  68. package/src/model/llm.test.ts +158 -0
  69. package/src/model/llm.ts +233 -0
  70. package/src/providers.ts +94 -0
  71. package/src/skills/index.ts +17 -0
  72. package/src/skills/loader.ts +73 -0
  73. package/src/skills/registry.ts +125 -0
  74. package/src/skills/types.ts +31 -0
  75. package/src/test-utils/mocks.ts +110 -0
  76. package/src/theme.ts +21 -0
  77. package/src/tools/browser/browser.ts +357 -0
  78. package/src/tools/browser/index.ts +1 -0
  79. package/src/tools/crypto/hive-tools.ts +171 -0
  80. package/src/tools/crypto/index.ts +1 -0
  81. package/src/tools/descriptions/browser.ts +105 -0
  82. package/src/tools/descriptions/crypto-search.ts +58 -0
  83. package/src/tools/descriptions/index.ts +8 -0
  84. package/src/tools/descriptions/web-fetch.ts +44 -0
  85. package/src/tools/descriptions/web-search.ts +26 -0
  86. package/src/tools/fetch/cache.ts +95 -0
  87. package/src/tools/fetch/external-content.ts +200 -0
  88. package/src/tools/fetch/index.ts +1 -0
  89. package/src/tools/fetch/web-fetch-utils.ts +122 -0
  90. package/src/tools/fetch/web-fetch.ts +371 -0
  91. package/src/tools/index.ts +12 -0
  92. package/src/tools/registry.ts +130 -0
  93. package/src/tools/search/exa.ts +43 -0
  94. package/src/tools/search/index.ts +2 -0
  95. package/src/tools/search/tavily.ts +35 -0
  96. package/src/tools/skill.ts +62 -0
  97. package/src/tools/types.ts +53 -0
  98. package/src/utils/ai-message.ts +26 -0
  99. package/src/utils/config.ts +54 -0
  100. package/src/utils/cost-calculator.test.ts +101 -0
  101. package/src/utils/cost-calculator.ts +74 -0
  102. package/src/utils/env.ts +101 -0
  103. package/src/utils/error-classifier.test.ts +146 -0
  104. package/src/utils/error-classifier.ts +91 -0
  105. package/src/utils/in-memory-chat-history.test.ts +291 -0
  106. package/src/utils/in-memory-chat-history.ts +224 -0
  107. package/src/utils/index.ts +19 -0
  108. package/src/utils/input-key-handlers.test.ts +155 -0
  109. package/src/utils/input-key-handlers.ts +64 -0
  110. package/src/utils/logger.ts +67 -0
  111. package/src/utils/long-term-chat-history.ts +138 -0
  112. package/src/utils/markdown-table.ts +227 -0
  113. package/src/utils/ollama.ts +37 -0
  114. package/src/utils/progress-channel.ts +84 -0
  115. package/src/utils/text-navigation.test.ts +222 -0
  116. package/src/utils/text-navigation.ts +81 -0
  117. package/src/utils/thinking-verbs.ts +29 -0
  118. package/src/utils/tokens.test.ts +163 -0
  119. package/src/utils/tokens.ts +67 -0
  120. package/src/utils/tool-description.ts +88 -0
@@ -0,0 +1,137 @@
1
+ /**
2
+ * Agent configuration
3
+ */
4
+ export interface AgentConfig {
5
+ /** Model to use for LLM calls (e.g., 'gpt-5.2', 'claude-sonnet-4-20250514') */
6
+ model?: string;
7
+ /** Model provider (e.g., 'openai', 'anthropic', 'google', 'ollama') */
8
+ modelProvider?: string;
9
+ /** Maximum agent loop iterations (default: 10) */
10
+ maxIterations?: number;
11
+ /** AbortSignal for cancelling agent execution */
12
+ signal?: AbortSignal;
13
+ }
14
+
15
+ /**
16
+ * Message in conversation history
17
+ */
18
+ export interface Message {
19
+ role: 'user' | 'assistant' | 'tool';
20
+ content: string;
21
+ }
22
+
23
+ // ============================================================================
24
+ // Agent Events (for real-time streaming UI)
25
+ // ============================================================================
26
+
27
+ /**
28
+ * Agent is processing/thinking
29
+ */
30
+ export interface ThinkingEvent {
31
+ type: 'thinking';
32
+ message: string;
33
+ }
34
+
35
+ /**
36
+ * Tool execution started
37
+ */
38
+ export interface ToolStartEvent {
39
+ type: 'tool_start';
40
+ tool: string;
41
+ args: Record<string, unknown>;
42
+ }
43
+
44
+ /**
45
+ * Tool execution completed successfully
46
+ */
47
+ export interface ToolEndEvent {
48
+ type: 'tool_end';
49
+ tool: string;
50
+ args: Record<string, unknown>;
51
+ result: string;
52
+ duration: number;
53
+ }
54
+
55
+ /**
56
+ * Tool execution failed
57
+ */
58
+ export interface ToolErrorEvent {
59
+ type: 'tool_error';
60
+ tool: string;
61
+ error: string;
62
+ }
63
+
64
+ /**
65
+ * Mid-execution progress update from a subagent tool
66
+ */
67
+ export interface ToolProgressEvent {
68
+ type: 'tool_progress';
69
+ tool: string;
70
+ message: string;
71
+ }
72
+
73
+ /**
74
+ * Tool call warning due to approaching/exceeding suggested limits
75
+ */
76
+ export interface ToolLimitEvent {
77
+ type: 'tool_limit';
78
+ tool: string;
79
+ /** Warning message about tool usage limits */
80
+ warning?: string;
81
+ /** Whether the tool call was blocked (always false - we only warn, never block) */
82
+ blocked: boolean;
83
+ }
84
+
85
+ /**
86
+ * Context was cleared due to exceeding token threshold (Anthropic-style)
87
+ */
88
+ export interface ContextClearedEvent {
89
+ type: 'context_cleared';
90
+ /** Number of tool results that were cleared from context */
91
+ clearedCount: number;
92
+ /** Number of most recent tool results that were kept */
93
+ keptCount: number;
94
+ }
95
+
96
+ /**
97
+ * Final answer generation started
98
+ */
99
+ export interface AnswerStartEvent {
100
+ type: 'answer_start';
101
+ }
102
+
103
+ /**
104
+ * Token usage statistics
105
+ */
106
+ export interface TokenUsage {
107
+ inputTokens: number;
108
+ outputTokens: number;
109
+ totalTokens: number;
110
+ }
111
+
112
+ /**
113
+ * Agent completed with final result
114
+ */
115
+ export interface DoneEvent {
116
+ type: 'done';
117
+ answer: string;
118
+ toolCalls: Array<{ tool: string; args: Record<string, unknown>; result: string }>;
119
+ iterations: number;
120
+ totalTime: number;
121
+ tokenUsage?: TokenUsage;
122
+ tokensPerSecond?: number;
123
+ }
124
+
125
+ /**
126
+ * Union type for all agent events
127
+ */
128
+ export type AgentEvent =
129
+ | ThinkingEvent
130
+ | ToolStartEvent
131
+ | ToolProgressEvent
132
+ | ToolEndEvent
133
+ | ToolErrorEvent
134
+ | ToolLimitEvent
135
+ | ContextClearedEvent
136
+ | AnswerStartEvent
137
+ | DoneEvent;
package/src/cli.tsx ADDED
@@ -0,0 +1,385 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * CLI - Real-time agentic loop interface
4
+ * Shows tool calls and progress in Claude Code style
5
+ */
6
+ import React, { useCallback, useRef, useState } from 'react';
7
+ import { Box, Static, Text, useApp, useInput } from 'ink';
8
+ import { config } from 'dotenv';
9
+
10
+ import { Input } from './components/Input.js';
11
+ import { Intro } from './components/Intro.js';
12
+ import { ProviderSelector, ModelSelector, ModelInputField } from './components/ModelSelector.js';
13
+ import { ApiKeyConfirm, ApiKeyInput } from './components/ApiKeyPrompt.js';
14
+ import { DebugPanel } from './components/DebugPanel.js';
15
+ import { HistoryItemView, WorkingIndicator, type HistoryItem } from './components/index.js';
16
+ import { StatusBar } from './components/StatusBar.js';
17
+ import { CommandMenu } from './components/CommandMenu.js';
18
+ import { ErrorBox } from './components/ErrorBox.js';
19
+ import { getApiKeyNameForProvider, getProviderDisplayName } from './utils/env.js';
20
+ import { getModelDisplayName } from './components/ModelSelector.js';
21
+ import { getSetting, setSetting } from './utils/config.js';
22
+
23
+ import { useModelSelection } from './hooks/useModelSelection.js';
24
+ import { useAgentRunner } from './hooks/useAgentRunner.js';
25
+ import { useInputHistory } from './hooks/useInputHistory.js';
26
+ import { getContextWindow } from './utils/tokens.js';
27
+
28
+ import { registerBuiltinCommands } from './commands/builtin.js';
29
+ import { commandRegistry, parseCommand, executeCommand } from './commands/registry.js';
30
+ import type { CommandContext } from './commands/types.js';
31
+ import type { CommandDef } from './commands/types.js';
32
+
33
+ // Load environment variables
34
+ config({ quiet: true });
35
+
36
+ // Register all builtin commands on startup
37
+ registerBuiltinCommands();
38
+
39
+ export function CLI() {
40
+ const { exit } = useApp();
41
+
42
+ // Debug panel state
43
+ const [showDebug, setShowDebug] = useState(() => getSetting('showDebug', false));
44
+
45
+ // Command autocomplete state
46
+ const [commandMenuVisible, setCommandMenuVisible] = useState(false);
47
+ const [commandMenuItems, setCommandMenuItems] = useState<CommandDef[]>([]);
48
+ const [commandMenuIndex, setCommandMenuIndex] = useState(0);
49
+
50
+ // Command JSX output (for commands that render components like /help)
51
+ const [commandOutput, setCommandOutput] = useState<React.ReactNode | null>(null);
52
+
53
+ // Ref to hold setError - avoids TDZ issue since useModelSelection needs to call
54
+ // setError but useAgentRunner (which provides setError) depends on useModelSelection's outputs
55
+ const setErrorRef = useRef<((error: string | null) => void) | null>(null);
56
+
57
+ // Model selection state and handlers
58
+ const {
59
+ selectionState,
60
+ provider,
61
+ model,
62
+ inMemoryChatHistoryRef,
63
+ startSelection,
64
+ cancelSelection,
65
+ handleProviderSelect,
66
+ handleModelSelect,
67
+ handleModelInputSubmit,
68
+ handleApiKeyConfirm,
69
+ handleApiKeySubmit,
70
+ isInSelectionFlow,
71
+ } = useModelSelection((errorMsg) => setErrorRef.current?.(errorMsg));
72
+
73
+ // Agent execution state and handlers
74
+ const {
75
+ history,
76
+ workingState,
77
+ error,
78
+ isProcessing,
79
+ cumulativeTokens,
80
+ cumulativeCost,
81
+ turnCount,
82
+ runQuery,
83
+ runCompactQuery,
84
+ cancelExecution,
85
+ setError,
86
+ clearHistory,
87
+ } = useAgentRunner({ model, modelProvider: provider, maxIterations: 10 }, inMemoryChatHistoryRef);
88
+
89
+ // Assign setError to ref so useModelSelection's callback can access it
90
+ setErrorRef.current = setError;
91
+
92
+ // Input history for up/down arrow navigation
93
+ const {
94
+ historyValue,
95
+ navigateUp,
96
+ navigateDown,
97
+ saveMessage,
98
+ updateAgentResponse,
99
+ resetNavigation,
100
+ } = useInputHistory();
101
+
102
+ // Toggle debug panel
103
+ const toggleDebug = useCallback(() => {
104
+ setShowDebug(prev => {
105
+ const next = !prev;
106
+ setSetting('showDebug', next);
107
+ return next;
108
+ });
109
+ }, []);
110
+
111
+ // Build command context
112
+ const getCommandContext = useCallback((): CommandContext => ({
113
+ provider,
114
+ model,
115
+ history,
116
+ inMemoryChatHistoryRef,
117
+ setError,
118
+ clearHistory,
119
+ startSelection,
120
+ toggleDebug,
121
+ showDebug,
122
+ cumulativeTokens,
123
+ cumulativeCost,
124
+ turnCount,
125
+ runCompactQuery,
126
+ }), [provider, model, history, inMemoryChatHistoryRef, setError, clearHistory, startSelection, toggleDebug, showDebug, cumulativeTokens, cumulativeCost, turnCount, runCompactQuery]);
127
+
128
+ // Handle history navigation from Input component
129
+ const handleHistoryNavigate = useCallback((direction: 'up' | 'down') => {
130
+ if (direction === 'up') {
131
+ navigateUp();
132
+ } else {
133
+ navigateDown();
134
+ }
135
+ }, [navigateUp, navigateDown]);
136
+
137
+ // Handle slash command input changes (for autocomplete)
138
+ const handleSlashChange = useCallback((text: string) => {
139
+ if (text.startsWith('/') && text.length >= 1) {
140
+ const prefix = text.slice(1); // Remove the "/"
141
+ const matches = prefix.length === 0
142
+ ? commandRegistry.getVisible()
143
+ : commandRegistry.search(prefix);
144
+ setCommandMenuItems(matches);
145
+ setCommandMenuIndex(0);
146
+ setCommandMenuVisible(matches.length > 0);
147
+ } else {
148
+ setCommandMenuVisible(false);
149
+ }
150
+ }, []);
151
+
152
+ // Handle command menu navigation
153
+ const handleCommandMenuNavigate = useCallback((direction: 'up' | 'down') => {
154
+ setCommandMenuIndex(prev => {
155
+ if (direction === 'up') {
156
+ return prev > 0 ? prev - 1 : commandMenuItems.length - 1;
157
+ }
158
+ return prev < commandMenuItems.length - 1 ? prev + 1 : 0;
159
+ });
160
+ }, [commandMenuItems.length]);
161
+
162
+ // Get selected command from menu
163
+ const getSelectedCommand = useCallback((): string | null => {
164
+ if (!commandMenuVisible || commandMenuItems.length === 0) return null;
165
+ return '/' + commandMenuItems[commandMenuIndex].name;
166
+ }, [commandMenuVisible, commandMenuItems, commandMenuIndex]);
167
+
168
+ // Handle user input submission
169
+ const handleSubmit = useCallback(async (query: string) => {
170
+ // Clear any previous command output
171
+ setCommandOutput(null);
172
+
173
+ // Handle exit
174
+ if (query.toLowerCase() === 'exit' || query.toLowerCase() === 'quit') {
175
+ console.log('Goodbye!');
176
+ exit();
177
+ return;
178
+ }
179
+
180
+ // Handle slash commands
181
+ const parsed = parseCommand(query);
182
+ if (parsed) {
183
+ // Dismiss autocomplete menu
184
+ setCommandMenuVisible(false);
185
+
186
+ const cmd = commandRegistry.get(parsed.name);
187
+ if (!cmd) {
188
+ setError(`Unknown command: /${parsed.name}. Type /help for available commands.`);
189
+ return;
190
+ }
191
+
192
+ try {
193
+ const result = await executeCommand(parsed.name, parsed.args, getCommandContext());
194
+ if (result !== undefined && result !== null) {
195
+ if (typeof result === 'string') {
196
+ // Show as a text message in history
197
+ setCommandOutput(
198
+ <Box marginBottom={1}>
199
+ <Text color="#a6a6a6">{result}</Text>
200
+ </Box>
201
+ );
202
+ } else {
203
+ // Render as JSX (React component)
204
+ setCommandOutput(result);
205
+ }
206
+ }
207
+ } catch (e) {
208
+ setError(`Command error: ${e instanceof Error ? e.message : String(e)}`);
209
+ }
210
+ return;
211
+ }
212
+
213
+ // Ignore if not idle (processing or in selection flow)
214
+ if (isInSelectionFlow() || workingState.status !== 'idle') return;
215
+
216
+ // Save user message to history immediately and reset navigation
217
+ await saveMessage(query);
218
+ resetNavigation();
219
+
220
+ // Run query and save agent response when complete
221
+ const result = await runQuery(query);
222
+ if (result?.answer) {
223
+ await updateAgentResponse(result.answer);
224
+ }
225
+ }, [exit, getCommandContext, isInSelectionFlow, workingState.status, runQuery, saveMessage, updateAgentResponse, resetNavigation, setError]);
226
+
227
+ // Handle keyboard shortcuts
228
+ useInput((input, key) => {
229
+ // Escape key - cancel selection flows or running agent, or dismiss command menu
230
+ if (key.escape) {
231
+ if (commandMenuVisible) {
232
+ setCommandMenuVisible(false);
233
+ return;
234
+ }
235
+ if (isInSelectionFlow()) {
236
+ cancelSelection();
237
+ return;
238
+ }
239
+ if (isProcessing) {
240
+ cancelExecution();
241
+ return;
242
+ }
243
+ }
244
+
245
+ // Ctrl+D - toggle debug panel
246
+ if (key.ctrl && input === 'd') {
247
+ toggleDebug();
248
+ return;
249
+ }
250
+
251
+ // Ctrl+C - cancel or exit
252
+ if (key.ctrl && input === 'c') {
253
+ if (isInSelectionFlow()) {
254
+ cancelSelection();
255
+ } else if (isProcessing) {
256
+ cancelExecution();
257
+ } else {
258
+ console.log('\nGoodbye!');
259
+ exit();
260
+ }
261
+ }
262
+ });
263
+
264
+ // Render selection screens
265
+ const { appState, pendingProvider, pendingModels } = selectionState;
266
+
267
+ if (appState === 'provider_select') {
268
+ return (
269
+ <Box flexDirection="column">
270
+ <ProviderSelector provider={provider} onSelect={handleProviderSelect} />
271
+ </Box>
272
+ );
273
+ }
274
+
275
+ if (appState === 'model_select' && pendingProvider) {
276
+ return (
277
+ <Box flexDirection="column">
278
+ <ModelSelector
279
+ providerId={pendingProvider}
280
+ models={pendingModels}
281
+ currentModel={provider === pendingProvider ? model : undefined}
282
+ onSelect={handleModelSelect}
283
+ />
284
+ </Box>
285
+ );
286
+ }
287
+
288
+ if (appState === 'model_input' && pendingProvider) {
289
+ return (
290
+ <Box flexDirection="column">
291
+ <ModelInputField
292
+ providerId={pendingProvider}
293
+ currentModel={provider === pendingProvider ? model : undefined}
294
+ onSubmit={handleModelInputSubmit}
295
+ />
296
+ </Box>
297
+ );
298
+ }
299
+
300
+ if (appState === 'api_key_confirm' && pendingProvider) {
301
+ return (
302
+ <Box flexDirection="column">
303
+ <ApiKeyConfirm
304
+ providerName={getProviderDisplayName(pendingProvider)}
305
+ onConfirm={handleApiKeyConfirm}
306
+ />
307
+ </Box>
308
+ );
309
+ }
310
+
311
+ if (appState === 'api_key_input' && pendingProvider) {
312
+ const apiKeyName = getApiKeyNameForProvider(pendingProvider) || '';
313
+ return (
314
+ <Box flexDirection="column">
315
+ <ApiKeyInput
316
+ providerName={getProviderDisplayName(pendingProvider)}
317
+ apiKeyName={apiKeyName}
318
+ onSubmit={handleApiKeySubmit}
319
+ />
320
+ </Box>
321
+ );
322
+ }
323
+
324
+ // Main chat interface
325
+ return (
326
+ <Box flexDirection="column">
327
+ {/* Intro + completed history — written once to scrollback, never re-rendered */}
328
+ <Static items={[{ id: '__intro__' } as { id: string }, ...history.filter(item => item.status !== 'processing')]}>
329
+ {(item) => item.id === '__intro__'
330
+ ? <Intro key="__intro__" provider={provider} model={model} />
331
+ : <HistoryItemView key={item.id} item={item as HistoryItem} />
332
+ }
333
+ </Static>
334
+
335
+ {/* Active (processing) item — dynamic, repainted on updates */}
336
+ {history.length > 0 && history[history.length - 1].status === 'processing' && (
337
+ <HistoryItemView key={history[history.length - 1].id} item={history[history.length - 1]} />
338
+ )}
339
+
340
+ {/* Command output (from slash commands like /help) */}
341
+ {commandOutput}
342
+
343
+ {/* Error display */}
344
+ {error && <ErrorBox error={error} />}
345
+
346
+ {/* Working indicator - only show when processing */}
347
+ {isProcessing && <WorkingIndicator state={workingState} />}
348
+
349
+ {/* Status bar */}
350
+ <StatusBar
351
+ modelDisplayName={getModelDisplayName(model)}
352
+ cumulativeTokens={cumulativeTokens}
353
+ cumulativeCost={cumulativeCost}
354
+ turnCount={turnCount}
355
+ contextPercentage={getContextWindow(model) > 0
356
+ ? Math.round((cumulativeTokens / getContextWindow(model)) * 100)
357
+ : undefined}
358
+ />
359
+
360
+ {/* Command autocomplete menu */}
361
+ {commandMenuVisible && (
362
+ <CommandMenu
363
+ commands={commandMenuItems}
364
+ selectedIndex={commandMenuIndex}
365
+ />
366
+ )}
367
+
368
+ {/* Input */}
369
+ <Box marginTop={turnCount === 0 && !commandOutput ? 1 : 0}>
370
+ <Input
371
+ onSubmit={handleSubmit}
372
+ historyValue={historyValue}
373
+ onHistoryNavigate={handleHistoryNavigate}
374
+ onTextChange={handleSlashChange}
375
+ commandMenuVisible={commandMenuVisible}
376
+ onCommandMenuNavigate={handleCommandMenuNavigate}
377
+ getSelectedCommand={getSelectedCommand}
378
+ />
379
+ </Box>
380
+
381
+ {/* Debug Panel */}
382
+ <DebugPanel maxLines={8} show={showDebug} />
383
+ </Box>
384
+ );
385
+ }