@calliopelabs/cli 0.6.10 → 0.7.0
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 +451 -156
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +165 -1
- package/dist/cli.js.map +1 -1
- package/dist/storage.d.ts +25 -0
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +77 -0
- package/dist/storage.js.map +1 -1
- package/dist/ui-cli.d.ts.map +1 -1
- package/dist/ui-cli.js +635 -23
- package/dist/ui-cli.js.map +1 -1
- package/package.json +2 -2
- package/dist/context-warnings.d.ts +0 -47
- package/dist/context-warnings.d.ts.map +0 -1
- package/dist/context-warnings.js +0 -123
- package/dist/context-warnings.js.map +0 -1
package/dist/ui-cli.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
/**
|
|
3
3
|
* Calliope CLI - Ink UI
|
|
4
4
|
*
|
|
@@ -21,7 +21,7 @@ import { getSystemPrompt, DEFAULT_MODELS, MODE_CONFIG, RISK_CONFIG, supportsVisi
|
|
|
21
21
|
import { getVersion, getLatestVersion, performUpgrade } from './version-check.js';
|
|
22
22
|
import { getAvailableModels } from './model-detection.js';
|
|
23
23
|
import { assessToolRisk, detectComplexity } from './risk.js';
|
|
24
|
-
import { formatError } from './errors.js';
|
|
24
|
+
import { formatError, classifyError } from './errors.js';
|
|
25
25
|
import * as storage from './storage.js';
|
|
26
26
|
import { parseFileReferences, processFilesForMessage, formatFileInfo } from './files.js';
|
|
27
27
|
import { renderMarkdown } from './markdown.js';
|
|
@@ -37,6 +37,14 @@ import { addToScope, removeFromScope, getScopeSummary, getScopeDetails, resetSco
|
|
|
37
37
|
import { getAgentStatusReport } from './agterm/index.js';
|
|
38
38
|
// Module-level state for agterm mode
|
|
39
39
|
let moduleAgtermEnabled = false;
|
|
40
|
+
// Debug logging for flow control issues
|
|
41
|
+
let debugEnabled = process.env.CALLIOPE_DEBUG === '1';
|
|
42
|
+
const debugLog = (label, ...args) => {
|
|
43
|
+
if (debugEnabled) {
|
|
44
|
+
const timestamp = new Date().toISOString().split('T')[1].slice(0, 12);
|
|
45
|
+
console.error(`[${timestamp}] ${label}:`, ...args);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
40
48
|
class ErrorBoundary extends React.Component {
|
|
41
49
|
constructor(props) {
|
|
42
50
|
super(props);
|
|
@@ -120,7 +128,7 @@ function ThinkingDisplay({ state }) {
|
|
|
120
128
|
}, 80);
|
|
121
129
|
return () => clearInterval(timer);
|
|
122
130
|
}, []);
|
|
123
|
-
return (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsxs(Box, { children: [_jsx(Text, { color: "cyan", children: SPINNER_FRAMES[frame] }), _jsxs(Text, { children: [" ", state.status] }), state.iteration && state.maxIterations && (_jsxs(Text, { dimColor: true, children: [" (", state.iteration, "/", state.maxIterations, ")"] }))] }), state.detail && (_jsx(Box, { marginLeft: 2, children: _jsxs(Text, { dimColor: true, children: ["\u21B3 ", state.detail] }) })), state.thinking && (_jsxs(Box, { flexDirection: "column", marginLeft: 2, marginTop: 1, children: [_jsx(Text, { color: "magenta", children: "\uD83D\uDCAD Thinking:" }), state.thinking.split('\n').slice(0, 5).map((line, i) => (_jsxs(Text, { dimColor: true, children: [" ", line.substring(0, 80)] }, i))), state.thinking.split('\n').length > 5 && (_jsx(Text, { dimColor: true, children: " ..." }))] }))] }));
|
|
131
|
+
return (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsxs(Box, { children: [_jsx(Text, { color: "cyan", children: SPINNER_FRAMES[frame] }), _jsxs(Text, { children: [" ", state.status] }), state.iteration != null && state.maxIterations && (_jsxs(Text, { dimColor: true, children: [" (", state.iteration, "/", state.maxIterations, ")"] }))] }), state.detail && (_jsx(Box, { marginLeft: 2, children: _jsxs(Text, { dimColor: true, children: ["\u21B3 ", state.detail] }) })), state.thinking && (_jsxs(Box, { flexDirection: "column", marginLeft: 2, marginTop: 1, children: [_jsx(Text, { color: "magenta", children: "\uD83D\uDCAD Thinking:" }), state.thinking.split('\n').slice(0, 5).map((line, i) => (_jsxs(Text, { dimColor: true, children: [" ", line.substring(0, 80)] }, i))), state.thinking.split('\n').length > 5 && (_jsx(Text, { dimColor: true, children: " ..." }))] }))] }));
|
|
124
132
|
}
|
|
125
133
|
// Legacy simple indicator for non-agent operations
|
|
126
134
|
function ProcessingIndicator({ label }) {
|
|
@@ -133,6 +141,18 @@ function ProcessingIndicator({ label }) {
|
|
|
133
141
|
}, []);
|
|
134
142
|
return (_jsxs(Box, { marginY: 1, children: [_jsx(Text, { color: "cyan", children: SPINNER_FRAMES[frame] }), _jsxs(Text, { dimColor: true, children: [" ", label] })] }));
|
|
135
143
|
}
|
|
144
|
+
// Minimal indicator shown during streaming to show we're still receiving
|
|
145
|
+
function StreamingIndicator() {
|
|
146
|
+
const [frame, setFrame] = useState(0);
|
|
147
|
+
const pulseFrames = ['·', '•', '●', '•'];
|
|
148
|
+
useEffect(() => {
|
|
149
|
+
const timer = setInterval(() => {
|
|
150
|
+
setFrame(f => (f + 1) % pulseFrames.length);
|
|
151
|
+
}, 200);
|
|
152
|
+
return () => clearInterval(timer);
|
|
153
|
+
}, []);
|
|
154
|
+
return (_jsxs(Box, { children: [_jsx(Text, { color: "green", children: pulseFrames[frame] }), _jsx(Text, { dimColor: true, children: " receiving..." })] }));
|
|
155
|
+
}
|
|
136
156
|
// ============================================================================
|
|
137
157
|
// Message Components
|
|
138
158
|
// ============================================================================
|
|
@@ -252,7 +272,7 @@ function UpgradePrompt({ currentVersion, latestVersion, onConfirm, onCancel }) {
|
|
|
252
272
|
});
|
|
253
273
|
return (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsxs(Text, { color: "yellow", children: ["Update available: v", currentVersion, " \u2192 ", _jsxs(Text, { color: "green", children: ["v", latestVersion] })] }), _jsxs(Text, { children: ["Upgrade now? ", _jsx(Text, { color: "cyan", children: "(y/N)" })] })] }));
|
|
254
274
|
}
|
|
255
|
-
function ComplexityWarning({ reason, onProceed, onPlan, onCancel, }) {
|
|
275
|
+
function ComplexityWarning({ reason, prompt, onProceed, onPlan, onCancel, }) {
|
|
256
276
|
useInput((input, key) => {
|
|
257
277
|
if (input === 'p' || input === 'P')
|
|
258
278
|
onProceed();
|
|
@@ -261,7 +281,53 @@ function ComplexityWarning({ reason, onProceed, onPlan, onCancel, }) {
|
|
|
261
281
|
else if (key.escape || input === 'c' || input === 'C')
|
|
262
282
|
onCancel();
|
|
263
283
|
});
|
|
264
|
-
|
|
284
|
+
// Analyze the prompt for operation preview
|
|
285
|
+
const analysis = React.useMemo(() => {
|
|
286
|
+
if (!prompt)
|
|
287
|
+
return null;
|
|
288
|
+
const lower = prompt.toLowerCase();
|
|
289
|
+
const cwd = process.cwd();
|
|
290
|
+
// Parse file references
|
|
291
|
+
const fileRefs = parseFileReferences(prompt, cwd);
|
|
292
|
+
// Detect operation types
|
|
293
|
+
const operations = [];
|
|
294
|
+
if (lower.includes('delete') || lower.includes('remove') || lower.includes('rm ')) {
|
|
295
|
+
operations.push('Delete files');
|
|
296
|
+
}
|
|
297
|
+
if (lower.includes('create') || lower.includes('add') || lower.includes('new ')) {
|
|
298
|
+
operations.push('Create files');
|
|
299
|
+
}
|
|
300
|
+
if (lower.includes('modify') || lower.includes('change') || lower.includes('update') || lower.includes('edit')) {
|
|
301
|
+
operations.push('Modify files');
|
|
302
|
+
}
|
|
303
|
+
if (lower.includes('refactor') || lower.includes('restructure') || lower.includes('reorganize')) {
|
|
304
|
+
operations.push('Refactor code');
|
|
305
|
+
}
|
|
306
|
+
if (lower.includes('install') || lower.includes('npm') || lower.includes('yarn') || lower.includes('pip')) {
|
|
307
|
+
operations.push('Install packages');
|
|
308
|
+
}
|
|
309
|
+
if (lower.includes('git ') || lower.includes('commit') || lower.includes('push') || lower.includes('merge')) {
|
|
310
|
+
operations.push('Git operations');
|
|
311
|
+
}
|
|
312
|
+
if (lower.includes('test') || lower.includes('build') || lower.includes('compile')) {
|
|
313
|
+
operations.push('Build/Test');
|
|
314
|
+
}
|
|
315
|
+
// Estimate risk level based on keywords
|
|
316
|
+
let riskLevel = 'medium';
|
|
317
|
+
if (lower.includes('delete') || lower.includes('remove') || lower.includes('force') || lower.includes('--hard')) {
|
|
318
|
+
riskLevel = 'high';
|
|
319
|
+
}
|
|
320
|
+
else if (lower.includes('read') || lower.includes('show') || lower.includes('list') || lower.includes('find')) {
|
|
321
|
+
riskLevel = 'low';
|
|
322
|
+
}
|
|
323
|
+
return {
|
|
324
|
+
files: fileRefs.files,
|
|
325
|
+
operations,
|
|
326
|
+
riskLevel,
|
|
327
|
+
};
|
|
328
|
+
}, [prompt]);
|
|
329
|
+
const riskColors = { low: 'green', medium: 'yellow', high: 'red' };
|
|
330
|
+
return (_jsxs(Box, { flexDirection: "column", marginY: 1, borderStyle: "round", borderColor: "yellow", paddingX: 1, children: [_jsx(Text, { color: "yellow", bold: true, children: "\uD83D\uDD0D Operation Preview" }), _jsx(Text, { children: " " }), _jsx(Text, { dimColor: true, children: reason }), analysis && (_jsxs(_Fragment, { children: [_jsx(Text, { children: " " }), analysis.operations.length > 0 && (_jsxs(Text, { children: ["Operations: ", _jsx(Text, { color: "cyan", children: analysis.operations.join(', ') })] })), analysis.files.length > 0 && (_jsxs(Text, { children: ["Files referenced: ", _jsx(Text, { color: "cyan", children: analysis.files.length }), analysis.files.length <= 3 && (_jsxs(Text, { dimColor: true, children: [" (", analysis.files.map(f => f.split('/').pop()).join(', '), ")"] }))] })), _jsxs(Text, { children: ["Risk level: ", _jsx(Text, { color: riskColors[analysis.riskLevel], children: analysis.riskLevel.toUpperCase() })] })] })), _jsx(Text, { children: " " }), _jsx(Text, { children: "This operation may affect multiple files or require careful planning." }), _jsx(Text, { children: " " }), _jsx(Text, { color: "cyan", children: "How would you like to proceed?" }), _jsx(Text, { children: " " }), _jsxs(Text, { children: [_jsx(Text, { color: "green", children: "[P]" }), _jsx(Text, { children: "roceed directly " }), _jsx(Text, { color: "yellow", children: "[L]" }), _jsx(Text, { children: "et me plan first " }), _jsx(Text, { color: "red", children: "[C]" }), _jsx(Text, { children: "ancel" })] })] }));
|
|
265
331
|
}
|
|
266
332
|
function SessionResumePrompt({ session, onResume, onNew, }) {
|
|
267
333
|
useInput((input, key) => {
|
|
@@ -296,6 +362,14 @@ function ToolConfirmation({ toolCall, riskLevel, reason, onConfirm, onDeny }) {
|
|
|
296
362
|
const riskIcon = riskLevel === 'critical' ? '⚠️' : '⚡';
|
|
297
363
|
return (_jsxs(Box, { flexDirection: "column", marginY: 1, borderStyle: "round", borderColor: riskColor, paddingX: 1, children: [_jsxs(Text, { color: riskColor, bold: true, children: [riskIcon, " ", riskLevel.toUpperCase(), " RISK OPERATION"] }), _jsx(Text, { children: " " }), _jsxs(Text, { children: ["Tool: ", _jsx(Text, { color: "cyan", children: toolCall.name })] }), _jsxs(Text, { children: ["Command: ", _jsx(Text, { dimColor: true, children: preview.substring(0, 60) })] }), _jsxs(Text, { children: ["Reason: ", _jsx(Text, { dimColor: true, children: reason })] }), _jsx(Text, { children: " " }), _jsxs(Text, { children: ["Execute this operation? ", _jsx(Text, { color: "cyan", children: "(y/N)" })] })] }));
|
|
298
364
|
}
|
|
365
|
+
// Keybindings modal component
|
|
366
|
+
function KeybindingsModal({ onClose }) {
|
|
367
|
+
useInput((input, key) => {
|
|
368
|
+
if (key.escape || key.return || input === 'q')
|
|
369
|
+
onClose();
|
|
370
|
+
});
|
|
371
|
+
return (_jsxs(Box, { flexDirection: "column", marginY: 1, borderStyle: "round", borderColor: "cyan", paddingX: 2, paddingY: 1, children: [_jsx(Text, { color: "cyan", bold: true, children: "\u2328\uFE0F Keyboard Shortcuts" }), _jsx(Text, { children: " " }), _jsx(Text, { bold: true, color: "yellow", children: "General:" }), _jsx(Text, { children: " Enter Submit message" }), _jsx(Text, { children: " Alt/Ctrl+Enter Insert newline (multiline)" }), _jsx(Text, { children: " Shift+Tab Cycle modes (plan/hybrid/work)" }), _jsx(Text, { children: " Esc Cancel operation / show hint" }), _jsx(Text, { children: " Ctrl+C Exit" }), _jsx(Text, { children: " \u2191/\u2193 Navigate input history" }), _jsx(Text, { children: " Tab Auto-complete commands/paths" }), _jsx(Text, { children: " Ctrl+U Clear input line" }), _jsx(Text, { children: " " }), _jsx(Text, { bold: true, color: "yellow", children: "During Processing (queue mode):" }), _jsx(Text, { children: " Enter Queue message for later" }), _jsx(Text, { children: " Shift+Enter Send directly (interrupt)" }), _jsx(Text, { children: " \u2191/\u2193 Edit queued messages" }), _jsx(Text, { children: " Ctrl+D Delete queued message" }), _jsx(Text, { children: " " }), _jsx(Text, { bold: true, color: "yellow", children: "Quick Commands:" }), _jsx(Text, { children: " /keys This help" }), _jsx(Text, { children: " /work Switch to work mode" }), _jsx(Text, { children: " /plan Switch to plan mode" }), _jsx(Text, { children: " /flush Force-process queue" }), _jsx(Text, { children: " /unstick Reset stuck state" }), _jsx(Text, { children: " /debug on/off Toggle debug mode" }), _jsx(Text, { children: " " }), _jsx(Text, { dimColor: true, children: "Press any key to close..." })] }));
|
|
372
|
+
}
|
|
299
373
|
// ============================================================================
|
|
300
374
|
// Slash Commands (for tab completion)
|
|
301
375
|
// ============================================================================
|
|
@@ -341,6 +415,15 @@ const SLASH_COMMANDS = [
|
|
|
341
415
|
'/loop',
|
|
342
416
|
'/cancel-loop',
|
|
343
417
|
'/exit',
|
|
418
|
+
'/keys',
|
|
419
|
+
'/?',
|
|
420
|
+
'/queue',
|
|
421
|
+
'/flush',
|
|
422
|
+
'/debug',
|
|
423
|
+
'/unstick',
|
|
424
|
+
'/work',
|
|
425
|
+
'/plan',
|
|
426
|
+
'/resume',
|
|
344
427
|
];
|
|
345
428
|
// Commands that take a path argument (for file tab completion)
|
|
346
429
|
const PATH_COMMANDS = ['/add-dir', '/remove-dir', '/export', '/find'];
|
|
@@ -410,7 +493,9 @@ function getPathCompletions(partial, cwd) {
|
|
|
410
493
|
// ============================================================================
|
|
411
494
|
// Input Components
|
|
412
495
|
// ============================================================================
|
|
413
|
-
function ChatInput({ value, onChange, onSubmit, onEscape, onCycleMode, disabled, isProcessing, queuedCount, onQueueMessage, cwd, suggestions, onSuggestionsChange, onNavigateHistory,
|
|
496
|
+
function ChatInput({ value, onChange, onSubmit, onEscape, onCycleMode, disabled, isProcessing, queuedCount, queuedMessages, editingQueueIndex, onQueueMessage, onEditQueuedMessage, onSetEditingQueueIndex, onDirectSend, cwd, suggestions, onSuggestionsChange, onNavigateHistory,
|
|
497
|
+
// Smart suggestion context
|
|
498
|
+
currentMode, contextPercentage, recentCommands, hasGitRepo, }) {
|
|
414
499
|
const workingDir = cwd || process.cwd();
|
|
415
500
|
// Handle ALL keyboard input here - single source of input handling
|
|
416
501
|
useInput((input, key) => {
|
|
@@ -436,11 +521,70 @@ function ChatInput({ value, onChange, onSubmit, onEscape, onCycleMode, disabled,
|
|
|
436
521
|
}
|
|
437
522
|
if (key.ctrl && input === 'u') {
|
|
438
523
|
onChange('');
|
|
524
|
+
onSetEditingQueueIndex?.(null); // Clear editing state
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
// Up/Down arrows to navigate queued messages for editing
|
|
528
|
+
if (key.upArrow && queuedMessages && queuedMessages.length > 0) {
|
|
529
|
+
if (editingQueueIndex === null || editingQueueIndex === undefined) {
|
|
530
|
+
// Start editing the last queued message
|
|
531
|
+
const idx = queuedMessages.length - 1;
|
|
532
|
+
onSetEditingQueueIndex?.(idx);
|
|
533
|
+
onChange(queuedMessages[idx]);
|
|
534
|
+
}
|
|
535
|
+
else if (editingQueueIndex > 0) {
|
|
536
|
+
// Move to previous message
|
|
537
|
+
const idx = editingQueueIndex - 1;
|
|
538
|
+
onSetEditingQueueIndex?.(idx);
|
|
539
|
+
onChange(queuedMessages[idx]);
|
|
540
|
+
}
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
543
|
+
if (key.downArrow && queuedMessages && editingQueueIndex !== null && editingQueueIndex !== undefined) {
|
|
544
|
+
if (editingQueueIndex < queuedMessages.length - 1) {
|
|
545
|
+
// Move to next message
|
|
546
|
+
const idx = editingQueueIndex + 1;
|
|
547
|
+
onSetEditingQueueIndex?.(idx);
|
|
548
|
+
onChange(queuedMessages[idx]);
|
|
549
|
+
}
|
|
550
|
+
else {
|
|
551
|
+
// At the end, clear to new input
|
|
552
|
+
onSetEditingQueueIndex?.(null);
|
|
553
|
+
onChange('');
|
|
554
|
+
}
|
|
439
555
|
return;
|
|
440
556
|
}
|
|
441
|
-
// Enter
|
|
442
|
-
if (key.return &&
|
|
443
|
-
|
|
557
|
+
// Alt+Enter or Ctrl+Enter to insert newline (multiline input)
|
|
558
|
+
if (key.return && (key.meta || key.ctrl)) {
|
|
559
|
+
onChange(value + '\n');
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
// Shift+Enter sends directly (interrupts current operation)
|
|
563
|
+
if (key.return && key.shift && value.trim() && onDirectSend) {
|
|
564
|
+
onDirectSend(value.trim());
|
|
565
|
+
onSetEditingQueueIndex?.(null);
|
|
566
|
+
onChange('');
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
569
|
+
// Enter queues or updates the message
|
|
570
|
+
if (key.return && value.trim()) {
|
|
571
|
+
if (editingQueueIndex !== null && editingQueueIndex !== undefined && onEditQueuedMessage) {
|
|
572
|
+
// Update existing queued message
|
|
573
|
+
onEditQueuedMessage(editingQueueIndex, value.trim());
|
|
574
|
+
onSetEditingQueueIndex?.(null);
|
|
575
|
+
onChange('');
|
|
576
|
+
}
|
|
577
|
+
else if (onQueueMessage) {
|
|
578
|
+
// Add new queued message
|
|
579
|
+
onQueueMessage(value.trim());
|
|
580
|
+
onChange('');
|
|
581
|
+
}
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
584
|
+
// Ctrl+D to delete currently editing queued message
|
|
585
|
+
if (key.ctrl && input === 'd' && editingQueueIndex !== null && editingQueueIndex !== undefined && onEditQueuedMessage) {
|
|
586
|
+
onEditQueuedMessage(editingQueueIndex, ''); // Empty string signals deletion
|
|
587
|
+
onSetEditingQueueIndex?.(null);
|
|
444
588
|
onChange('');
|
|
445
589
|
return;
|
|
446
590
|
}
|
|
@@ -455,6 +599,11 @@ function ChatInput({ value, onChange, onSubmit, onEscape, onCycleMode, disabled,
|
|
|
455
599
|
onCycleMode();
|
|
456
600
|
return;
|
|
457
601
|
}
|
|
602
|
+
// Alt+Enter or Ctrl+Enter to insert newline (multiline input)
|
|
603
|
+
if (key.return && (key.meta || key.ctrl)) {
|
|
604
|
+
onChange(value + '\n');
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
458
607
|
// Enter to submit
|
|
459
608
|
if (key.return) {
|
|
460
609
|
if (value.trim()) {
|
|
@@ -500,10 +649,20 @@ function ChatInput({ value, onChange, onSubmit, onEscape, onCycleMode, disabled,
|
|
|
500
649
|
}
|
|
501
650
|
return;
|
|
502
651
|
}
|
|
503
|
-
// Slash command completion
|
|
652
|
+
// Slash command completion with smart suggestions
|
|
504
653
|
if (value.startsWith('/')) {
|
|
654
|
+
// Use smart suggestions if context is available
|
|
655
|
+
const smartMatches = getSmartCommandSuggestions({
|
|
656
|
+
input: value,
|
|
657
|
+
hasGitRepo: hasGitRepo ?? false,
|
|
658
|
+
contextPercentage: contextPercentage ?? 0,
|
|
659
|
+
currentMode: currentMode ?? 'hybrid',
|
|
660
|
+
recentCommands: recentCommands ?? [],
|
|
661
|
+
isProcessing: isProcessing ?? false,
|
|
662
|
+
});
|
|
663
|
+
// Fall back to basic matching if smart suggestions didn't find anything
|
|
505
664
|
const partial = value.toLowerCase();
|
|
506
|
-
const matches = SLASH_COMMANDS.filter(cmdName => cmdName.startsWith(partial) && cmdName !== partial);
|
|
665
|
+
const matches = smartMatches.length > 0 ? smartMatches : SLASH_COMMANDS.filter(cmdName => cmdName.startsWith(partial) && cmdName !== partial);
|
|
507
666
|
if (matches.length === 1) {
|
|
508
667
|
onChange(matches[0] + ' ');
|
|
509
668
|
onSuggestionsChange?.([]);
|
|
@@ -543,7 +702,10 @@ function ChatInput({ value, onChange, onSubmit, onEscape, onCycleMode, disabled,
|
|
|
543
702
|
});
|
|
544
703
|
// Determine prompt style based on state
|
|
545
704
|
const promptColor = disabled ? 'gray' : isProcessing ? 'yellow' : 'cyan';
|
|
546
|
-
const
|
|
705
|
+
const isEditing = editingQueueIndex !== null && editingQueueIndex !== undefined;
|
|
706
|
+
const promptText = isProcessing
|
|
707
|
+
? (isEditing ? `edit[${editingQueueIndex + 1}]>` : 'queue>')
|
|
708
|
+
: 'calliope>';
|
|
547
709
|
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Separator, {}), suggestions && suggestions.length > 0 && (_jsxs(Box, { children: [_jsx(Text, { dimColor: true, children: "Tab: " }), _jsx(Text, { color: "cyan", children: suggestions.slice(0, 5).join(' ') }), suggestions.length > 5 && _jsxs(Text, { dimColor: true, children: [" (+", suggestions.length - 5, " more)"] })] })), (queuedCount ?? 0) > 0 && (_jsx(Box, { children: _jsxs(Text, { color: "yellow", children: ["\uD83D\uDCE8 ", queuedCount, " message", queuedCount > 1 ? 's' : '', " queued (will be sent after current task)"] }) })), _jsxs(Box, { children: [_jsxs(Text, { color: promptColor, children: [promptText, " "] }), _jsx(Text, { children: value }), _jsx(Text, { color: promptColor, children: "\u258C" })] })] }));
|
|
548
710
|
}
|
|
549
711
|
// Context window limits by model (approximate)
|
|
@@ -570,6 +732,142 @@ function getContextLimit(model) {
|
|
|
570
732
|
}
|
|
571
733
|
return CONTEXT_LIMITS.default;
|
|
572
734
|
}
|
|
735
|
+
// Module-level state for context tracking (persists across renders)
|
|
736
|
+
const contextState = {
|
|
737
|
+
lastLevel: 'healthy',
|
|
738
|
+
warningCounts: { healthy: 0, caution: 0, warning: 0, critical: 0, emergency: 0 },
|
|
739
|
+
lastWarningTime: 0,
|
|
740
|
+
};
|
|
741
|
+
function getContextLevel(percentage) {
|
|
742
|
+
if (percentage >= 98)
|
|
743
|
+
return 'emergency';
|
|
744
|
+
if (percentage >= 95)
|
|
745
|
+
return 'critical';
|
|
746
|
+
if (percentage >= 85)
|
|
747
|
+
return 'warning';
|
|
748
|
+
if (percentage >= 70)
|
|
749
|
+
return 'caution';
|
|
750
|
+
return 'healthy';
|
|
751
|
+
}
|
|
752
|
+
function getContextLevelIndex(level) {
|
|
753
|
+
const order = ['healthy', 'caution', 'warning', 'critical', 'emergency'];
|
|
754
|
+
return order.indexOf(level);
|
|
755
|
+
}
|
|
756
|
+
function shouldShowContextWarning(level) {
|
|
757
|
+
if (level === 'healthy')
|
|
758
|
+
return false;
|
|
759
|
+
const now = Date.now();
|
|
760
|
+
const timeSinceLastWarning = now - contextState.lastWarningTime;
|
|
761
|
+
const minInterval = level === 'emergency' ? 30000 : 60000; // 30s for emergency, 60s otherwise
|
|
762
|
+
// Always warn on level increase
|
|
763
|
+
if (getContextLevelIndex(level) > getContextLevelIndex(contextState.lastLevel)) {
|
|
764
|
+
return true;
|
|
765
|
+
}
|
|
766
|
+
// Warn again if enough time has passed and we're at critical/emergency
|
|
767
|
+
if ((level === 'critical' || level === 'emergency') && timeSinceLastWarning > minInterval) {
|
|
768
|
+
return true;
|
|
769
|
+
}
|
|
770
|
+
return false;
|
|
771
|
+
}
|
|
772
|
+
function checkAndWarnContextLimit(model, tokens, addMessage) {
|
|
773
|
+
const limit = getContextLimit(model);
|
|
774
|
+
const percentage = (tokens / limit) * 100;
|
|
775
|
+
const level = getContextLevel(percentage);
|
|
776
|
+
const used = Math.round(tokens / 1000);
|
|
777
|
+
const limitK = Math.round(limit / 1000);
|
|
778
|
+
if (!shouldShowContextWarning(level))
|
|
779
|
+
return;
|
|
780
|
+
// Update state
|
|
781
|
+
contextState.lastLevel = level;
|
|
782
|
+
contextState.warningCounts[level]++;
|
|
783
|
+
contextState.lastWarningTime = Date.now();
|
|
784
|
+
// Generate warning message based on level
|
|
785
|
+
let message;
|
|
786
|
+
switch (level) {
|
|
787
|
+
case 'emergency':
|
|
788
|
+
message = `\x1b[31m\x1b[1m🚨 EMERGENCY: Context at ${Math.round(percentage)}% (${used}K/${limitK}K)\x1b[0m
|
|
789
|
+
\x1b[31m Responses WILL be truncated. Take action NOW:\x1b[0m
|
|
790
|
+
\x1b[2m /summarize compact - Auto-compress (recommended)
|
|
791
|
+
/clear - Fresh start
|
|
792
|
+
/branch new "save" - Save and branch\x1b[0m`;
|
|
793
|
+
break;
|
|
794
|
+
case 'critical':
|
|
795
|
+
message = `\x1b[31m🔴 CRITICAL: Context at ${Math.round(percentage)}% (${used}K/${limitK}K)\x1b[0m
|
|
796
|
+
\x1b[2m Approaching limits. Action recommended:
|
|
797
|
+
/summarize compact | /clear | shorter messages\x1b[0m`;
|
|
798
|
+
break;
|
|
799
|
+
case 'warning':
|
|
800
|
+
message = `\x1b[33m⚠️ WARNING: Context at ${Math.round(percentage)}% (${used}K/${limitK}K)\x1b[0m
|
|
801
|
+
\x1b[2m Consider: /summarize compact | /clear\x1b[0m`;
|
|
802
|
+
break;
|
|
803
|
+
case 'caution':
|
|
804
|
+
message = `\x1b[36m💡 Context at ${Math.round(percentage)}% (${used}K/${limitK}K)\x1b[0m
|
|
805
|
+
\x1b[2m Monitor usage. /context summary for details\x1b[0m`;
|
|
806
|
+
break;
|
|
807
|
+
default:
|
|
808
|
+
return;
|
|
809
|
+
}
|
|
810
|
+
console.log(message + '\n');
|
|
811
|
+
// Also add to UI messages if callback provided (for critical+)
|
|
812
|
+
if (addMessage && (level === 'critical' || level === 'emergency')) {
|
|
813
|
+
const uiMessage = level === 'emergency'
|
|
814
|
+
? `🚨 EMERGENCY: Context at ${Math.round(percentage)}% - responses will be truncated! Use /summarize compact NOW`
|
|
815
|
+
: `🔴 Context at ${Math.round(percentage)}% - consider /summarize compact`;
|
|
816
|
+
addMessage('system', uiMessage);
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
function resetContextWarnings() {
|
|
820
|
+
contextState.lastLevel = 'healthy';
|
|
821
|
+
contextState.warningCounts = { healthy: 0, caution: 0, warning: 0, critical: 0, emergency: 0 };
|
|
822
|
+
contextState.lastWarningTime = 0;
|
|
823
|
+
}
|
|
824
|
+
function getSmartCommandSuggestions(ctx) {
|
|
825
|
+
const { input, hasGitRepo, contextPercentage, currentMode, recentCommands } = ctx;
|
|
826
|
+
if (!input.startsWith('/'))
|
|
827
|
+
return [];
|
|
828
|
+
const suggestions = [];
|
|
829
|
+
const inputLower = input.toLowerCase();
|
|
830
|
+
// All available commands for matching
|
|
831
|
+
const allCommands = [
|
|
832
|
+
'/help', '/clear', '/exit', '/quit',
|
|
833
|
+
'/mode', '/work', '/plan',
|
|
834
|
+
'/provider', '/model', '/models', '/config',
|
|
835
|
+
'/scope', '/add-dir', '/remove-dir', '/find',
|
|
836
|
+
'/summarize', '/context', '/cost', '/session',
|
|
837
|
+
'/debug', '/keys', '/unstick', '/flush',
|
|
838
|
+
'/branch', '/branches', '/switch',
|
|
839
|
+
'/save', '/load', '/sessions',
|
|
840
|
+
'/git', '/run', '/set', '/confirm',
|
|
841
|
+
];
|
|
842
|
+
// Context-aware prioritization
|
|
843
|
+
const prioritized = [];
|
|
844
|
+
// High context? Suggest compaction commands first
|
|
845
|
+
if (contextPercentage > 70) {
|
|
846
|
+
prioritized.push('/summarize compact', '/clear', '/branch new');
|
|
847
|
+
}
|
|
848
|
+
// Mode-specific suggestions
|
|
849
|
+
if (currentMode === 'plan') {
|
|
850
|
+
prioritized.push('/mode hybrid', '/work');
|
|
851
|
+
}
|
|
852
|
+
else if (currentMode === 'work') {
|
|
853
|
+
prioritized.push('/mode hybrid', '/plan');
|
|
854
|
+
}
|
|
855
|
+
// Git repo? Suggest git commands
|
|
856
|
+
if (hasGitRepo) {
|
|
857
|
+
prioritized.push('/git status', '/git diff', '/git add', '/git commit');
|
|
858
|
+
}
|
|
859
|
+
// Add recent commands (deduplicated)
|
|
860
|
+
for (const cmd of recentCommands.slice(-5)) {
|
|
861
|
+
if (cmd.startsWith('/') && !prioritized.includes(cmd)) {
|
|
862
|
+
prioritized.push(cmd);
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
// Filter by what user is typing
|
|
866
|
+
const matchingPrioritized = prioritized.filter(cmd => cmd.toLowerCase().startsWith(inputLower));
|
|
867
|
+
const matchingAll = allCommands.filter(cmd => cmd.toLowerCase().startsWith(inputLower) && !matchingPrioritized.includes(cmd));
|
|
868
|
+
suggestions.push(...matchingPrioritized, ...matchingAll);
|
|
869
|
+
return suggestions.slice(0, 6);
|
|
870
|
+
}
|
|
573
871
|
function StatusBar({ provider, model, stats, mode, contextTokens, }) {
|
|
574
872
|
const formatTokens = (n) => n >= 1000 ? `${(n / 1000).toFixed(1)}K` : String(n);
|
|
575
873
|
const formatCost = (c) => c < 0.01 ? '<$0.01' : `$${c.toFixed(2)}`;
|
|
@@ -599,6 +897,16 @@ function TerminalChat() {
|
|
|
599
897
|
const [inputHistory, setInputHistory] = useState([]);
|
|
600
898
|
const [historyIndex, setHistoryIndex] = useState(-1);
|
|
601
899
|
const [savedInput, setSavedInput] = useState(''); // Save current input when navigating
|
|
900
|
+
// Smart suggestions context
|
|
901
|
+
const [hasGitRepo] = useState(() => {
|
|
902
|
+
try {
|
|
903
|
+
return fs.existsSync('.git') || fs.existsSync('../.git');
|
|
904
|
+
}
|
|
905
|
+
catch {
|
|
906
|
+
return false;
|
|
907
|
+
}
|
|
908
|
+
});
|
|
909
|
+
const recentCommands = React.useMemo(() => inputHistory.filter(cmd => cmd.startsWith('/')).slice(-10), [inputHistory]);
|
|
602
910
|
// Clear suggestions when input changes significantly
|
|
603
911
|
const handleInputChange = useCallback((newValue) => {
|
|
604
912
|
setInput(newValue);
|
|
@@ -671,6 +979,7 @@ function TerminalChat() {
|
|
|
671
979
|
// Message queue for human-in-the-loop feedback during processing
|
|
672
980
|
const [queuedMessages, setQueuedMessages] = useState([]);
|
|
673
981
|
const [queueInput, setQueueInput] = useState('');
|
|
982
|
+
const [editingQueueIndex, setEditingQueueIndex] = useState(null);
|
|
674
983
|
const undoStack = useRef([]);
|
|
675
984
|
const redoStack = useRef([]);
|
|
676
985
|
const MAX_UNDO_HISTORY = 10;
|
|
@@ -765,6 +1074,15 @@ function TerminalChat() {
|
|
|
765
1074
|
setMemoryLoaded(true);
|
|
766
1075
|
// Execute session start hooks
|
|
767
1076
|
hooks.executeHooks('session-start', {}).catch(() => { });
|
|
1077
|
+
// Load templates from storage
|
|
1078
|
+
const savedTemplates = storage.getTemplates();
|
|
1079
|
+
if (savedTemplates.length > 0) {
|
|
1080
|
+
setTemplates(savedTemplates.map(t => ({
|
|
1081
|
+
name: t.name,
|
|
1082
|
+
prompt: t.prompt,
|
|
1083
|
+
createdAt: new Date(t.createdAt),
|
|
1084
|
+
})));
|
|
1085
|
+
}
|
|
768
1086
|
}
|
|
769
1087
|
}, [memoryLoaded]);
|
|
770
1088
|
// Derived values
|
|
@@ -779,6 +1097,19 @@ function TerminalChat() {
|
|
|
779
1097
|
content
|
|
780
1098
|
}]);
|
|
781
1099
|
}, []);
|
|
1100
|
+
// Handler to edit or delete a queued message
|
|
1101
|
+
const handleEditQueuedMessage = useCallback((index, newMsg) => {
|
|
1102
|
+
if (newMsg === '') {
|
|
1103
|
+
// Delete the message
|
|
1104
|
+
setQueuedMessages(prev => prev.filter((_, i) => i !== index));
|
|
1105
|
+
addMessage('system', `🗑️ Deleted queued message #${index + 1}`);
|
|
1106
|
+
}
|
|
1107
|
+
else {
|
|
1108
|
+
// Update the message
|
|
1109
|
+
setQueuedMessages(prev => prev.map((msg, i) => i === index ? newMsg : msg));
|
|
1110
|
+
addMessage('system', `✏️ Updated queued message #${index + 1}`);
|
|
1111
|
+
}
|
|
1112
|
+
}, [addMessage]);
|
|
782
1113
|
// Handle slash commands
|
|
783
1114
|
const handleCommand = useCallback(async (cmd) => {
|
|
784
1115
|
const parts = cmd.split(/\s+/);
|
|
@@ -827,12 +1158,19 @@ function TerminalChat() {
|
|
|
827
1158
|
/bookmark [name] - Create bookmark at current point
|
|
828
1159
|
/bookmark list - List all bookmarks
|
|
829
1160
|
/bookmark goto <n> - Jump to bookmark
|
|
830
|
-
/queue [show|clear]
|
|
1161
|
+
/queue [show|clear|flush] - Manage queued messages
|
|
1162
|
+
/flush - Force-process queued msgs (unstick)
|
|
1163
|
+
/debug [on|off] - Show state / toggle debug logging
|
|
1164
|
+
/unstick - Emergency reset of processing state
|
|
1165
|
+
/work - Quick switch to work mode
|
|
1166
|
+
/plan - Quick switch to plan mode
|
|
1167
|
+
/keys or /? - Show keyboard shortcuts
|
|
831
1168
|
/resume [n] - Resume previous session (load n messages)
|
|
832
1169
|
/exit - Exit
|
|
833
1170
|
|
|
834
1171
|
File references: @filename, ./path, /absolute/path
|
|
835
1172
|
Modes: 📋 Plan | 🔄 Hybrid | 🔧 Work
|
|
1173
|
+
Queue: ↑/↓ edit, Ctrl+D delete, Shift+Enter send directly
|
|
836
1174
|
Auto-route: ${autoRoute ? 'ON' : 'OFF'}${moduleAgtermEnabled ? '\nAGTerm: ON (spawn_agent, check_agent tools available)' : ''}`);
|
|
837
1175
|
break;
|
|
838
1176
|
case '/provider':
|
|
@@ -912,6 +1250,7 @@ Auto-route: ${autoRoute ? 'ON' : 'OFF'}${moduleAgtermEnabled ? '\nAGTerm: ON (sp
|
|
|
912
1250
|
setMessages([]);
|
|
913
1251
|
llmMessages.current = [{ role: 'system', content: getSystemPrompt(persona) }];
|
|
914
1252
|
setStats({ inputTokens: 0, outputTokens: 0, cost: 0, messageCount: 0 });
|
|
1253
|
+
resetContextWarnings(); // Reset context warning state
|
|
915
1254
|
break;
|
|
916
1255
|
case '/copy': {
|
|
917
1256
|
// Copy last assistant response to clipboard
|
|
@@ -1737,8 +2076,25 @@ Example: /loop "Build a REST API" --max-iterations 50 --completion-promise "DONE
|
|
|
1737
2076
|
addMessage('system', output);
|
|
1738
2077
|
}
|
|
1739
2078
|
}
|
|
2079
|
+
else if (subCommand === 'work' && parts[2]) {
|
|
2080
|
+
const id = parts[2];
|
|
2081
|
+
const todos = [...storage.getSessionTodos(), ...storage.getGlobalTodos()];
|
|
2082
|
+
const todo = todos.find(t => t.id.endsWith(id) || t.id === id);
|
|
2083
|
+
if (todo) {
|
|
2084
|
+
storage.setActiveTodo(todo.id);
|
|
2085
|
+
storage.updateTodo(todo.id, { status: 'in_progress' });
|
|
2086
|
+
addMessage('system', `✓ Working on: ${todo.content}\n\nTip: I'll help you complete this task. Describe what you need.`);
|
|
2087
|
+
}
|
|
2088
|
+
else {
|
|
2089
|
+
addMessage('error', `TODO #${id} not found`);
|
|
2090
|
+
}
|
|
2091
|
+
}
|
|
2092
|
+
else if (subCommand === 'clear') {
|
|
2093
|
+
storage.setActiveTodo(null);
|
|
2094
|
+
addMessage('system', '✓ Active TODO cleared');
|
|
2095
|
+
}
|
|
1740
2096
|
else {
|
|
1741
|
-
addMessage('system', 'Usage: /todo [add <task>|done <id>|list]');
|
|
2097
|
+
addMessage('system', 'Usage: /todo [add <task>|done <id>|work <id>|clear|list]');
|
|
1742
2098
|
}
|
|
1743
2099
|
break;
|
|
1744
2100
|
}
|
|
@@ -1765,8 +2121,27 @@ Example: /loop "Build a REST API" --max-iterations 50 --completion-promise "DONE
|
|
|
1765
2121
|
addMessage('error', `Plan #${parts[2]} not found`);
|
|
1766
2122
|
}
|
|
1767
2123
|
}
|
|
2124
|
+
else if (subCommand === 'rerun' && parts[2]) {
|
|
2125
|
+
const plans = storage.getPlans();
|
|
2126
|
+
const plan = plans.find(p => p.id.endsWith(parts[2]) || p.id === parts[2]);
|
|
2127
|
+
if (plan) {
|
|
2128
|
+
// Reset plan status and activate
|
|
2129
|
+
plan.status = 'in_progress';
|
|
2130
|
+
plan.phases.forEach(ph => ph.status = 'pending');
|
|
2131
|
+
storage.savePlan(plan);
|
|
2132
|
+
storage.setActivePlan(plan);
|
|
2133
|
+
// Generate prompt for re-execution
|
|
2134
|
+
const phaseList = plan.phases.map(ph => `- ${ph.name}`).join('\n');
|
|
2135
|
+
const prompt = `Please help me execute this plan:\n\n**${plan.title}**\n\nPhases:\n${phaseList}\n\nStart with the first phase.`;
|
|
2136
|
+
setInput(prompt);
|
|
2137
|
+
addMessage('system', `✓ Plan loaded: ${plan.title}\nPress Enter to start execution.`);
|
|
2138
|
+
}
|
|
2139
|
+
else {
|
|
2140
|
+
addMessage('error', `Plan #${parts[2]} not found`);
|
|
2141
|
+
}
|
|
2142
|
+
}
|
|
1768
2143
|
else {
|
|
1769
|
-
addMessage('system', 'Usage: /plans [list|view <id>]');
|
|
2144
|
+
addMessage('system', 'Usage: /plans [list|view <id>|rerun <id>]');
|
|
1770
2145
|
}
|
|
1771
2146
|
break;
|
|
1772
2147
|
}
|
|
@@ -1895,6 +2270,7 @@ Example: /loop "Build a REST API" --max-iterations 50 --completion-promise "DONE
|
|
|
1895
2270
|
addMessage('error', 'Usage: /template save <name> "<prompt>"');
|
|
1896
2271
|
}
|
|
1897
2272
|
else {
|
|
2273
|
+
storage.saveTemplate(name, prompt);
|
|
1898
2274
|
setTemplates(prev => {
|
|
1899
2275
|
const filtered = prev.filter(t => t.name !== name);
|
|
1900
2276
|
return [...filtered, { name, prompt, createdAt: new Date() }];
|
|
@@ -1917,6 +2293,7 @@ Example: /loop "Build a REST API" --max-iterations 50 --completion-promise "DONE
|
|
|
1917
2293
|
const name = parts[2];
|
|
1918
2294
|
const found = templates.find(t => t.name === name);
|
|
1919
2295
|
if (found) {
|
|
2296
|
+
storage.deleteTemplate(name);
|
|
1920
2297
|
setTemplates(prev => prev.filter(t => t.name !== name));
|
|
1921
2298
|
addMessage('system', `✓ Template deleted: ${name}`);
|
|
1922
2299
|
}
|
|
@@ -2025,11 +2402,126 @@ Example: /loop "Build a REST API" --max-iterations 50 --completion-promise "DONE
|
|
|
2025
2402
|
addMessage('system', `📨 Queued messages:\n${list}`);
|
|
2026
2403
|
}
|
|
2027
2404
|
}
|
|
2405
|
+
else if (subCmd === 'flush') {
|
|
2406
|
+
// Force-process queued messages even if stuck
|
|
2407
|
+
if (queuedMessages.length === 0) {
|
|
2408
|
+
addMessage('system', 'No messages to flush.');
|
|
2409
|
+
}
|
|
2410
|
+
else {
|
|
2411
|
+
const queued = [...queuedMessages];
|
|
2412
|
+
setQueuedMessages([]);
|
|
2413
|
+
setIsProcessing(false); // Force reset processing state
|
|
2414
|
+
setThinkingState(null);
|
|
2415
|
+
setStreamingResponse('');
|
|
2416
|
+
addMessage('system', `🔄 Flushing ${queued.length} queued message(s)...`);
|
|
2417
|
+
const followUp = queued.length === 1
|
|
2418
|
+
? queued[0]
|
|
2419
|
+
: `[Multiple follow-up messages:]\n${queued.map((m, i) => `${i + 1}. ${m}`).join('\n')}`;
|
|
2420
|
+
setTimeout(() => {
|
|
2421
|
+
setIsProcessing(true);
|
|
2422
|
+
runAgent(followUp).finally(() => {
|
|
2423
|
+
setIsProcessing(false);
|
|
2424
|
+
setThinkingState(null);
|
|
2425
|
+
setStreamingResponse('');
|
|
2426
|
+
});
|
|
2427
|
+
}, 50);
|
|
2428
|
+
}
|
|
2429
|
+
}
|
|
2430
|
+
else {
|
|
2431
|
+
addMessage('system', 'Usage: /queue [show|clear|flush]\n\nTip: Type while agent is processing to queue follow-up messages.');
|
|
2432
|
+
}
|
|
2433
|
+
break;
|
|
2434
|
+
}
|
|
2435
|
+
case '/flush': {
|
|
2436
|
+
// Shortcut for /queue flush - force-process queued messages
|
|
2437
|
+
if (queuedMessages.length === 0) {
|
|
2438
|
+
addMessage('system', 'No messages to flush. Use /debug to see current state.');
|
|
2439
|
+
}
|
|
2440
|
+
else {
|
|
2441
|
+
const queued = [...queuedMessages];
|
|
2442
|
+
setQueuedMessages([]);
|
|
2443
|
+
setIsProcessing(false); // Force reset processing state
|
|
2444
|
+
setThinkingState(null);
|
|
2445
|
+
setStreamingResponse('');
|
|
2446
|
+
addMessage('system', `🔄 Flushing ${queued.length} queued message(s)...`);
|
|
2447
|
+
const followUp = queued.length === 1
|
|
2448
|
+
? queued[0]
|
|
2449
|
+
: `[Multiple follow-up messages:]\n${queued.map((m, i) => `${i + 1}. ${m}`).join('\n')}`;
|
|
2450
|
+
setTimeout(() => {
|
|
2451
|
+
setIsProcessing(true);
|
|
2452
|
+
runAgent(followUp).finally(() => {
|
|
2453
|
+
setIsProcessing(false);
|
|
2454
|
+
setThinkingState(null);
|
|
2455
|
+
setStreamingResponse('');
|
|
2456
|
+
});
|
|
2457
|
+
}, 50);
|
|
2458
|
+
}
|
|
2459
|
+
break;
|
|
2460
|
+
}
|
|
2461
|
+
case '/debug': {
|
|
2462
|
+
const subCmd = parts[1];
|
|
2463
|
+
if (subCmd === 'on') {
|
|
2464
|
+
debugEnabled = true;
|
|
2465
|
+
addMessage('system', '🔍 Debug logging ON (output to stderr). Use /debug off to disable.');
|
|
2466
|
+
}
|
|
2467
|
+
else if (subCmd === 'off') {
|
|
2468
|
+
debugEnabled = false;
|
|
2469
|
+
addMessage('system', '🔍 Debug logging OFF');
|
|
2470
|
+
}
|
|
2471
|
+
else {
|
|
2472
|
+
// Show internal state for debugging stuck issues
|
|
2473
|
+
const debugInfo = [
|
|
2474
|
+
`isProcessing: ${isProcessing}`,
|
|
2475
|
+
`queuedMessages: ${queuedMessages.length}`,
|
|
2476
|
+
`modalMode: ${modalMode}`,
|
|
2477
|
+
`confirmMode: ${confirmMode}`,
|
|
2478
|
+
`loopActive: ${loopActive}`,
|
|
2479
|
+
`thinkingState: ${thinkingState ? JSON.stringify(thinkingState) : 'null'}`,
|
|
2480
|
+
`streamingResponse length: ${streamingResponse.length}`,
|
|
2481
|
+
`llmMessages count: ${llmMessages.current.length}`,
|
|
2482
|
+
`mode: ${mode}`,
|
|
2483
|
+
`debugEnabled: ${debugEnabled}`,
|
|
2484
|
+
];
|
|
2485
|
+
addMessage('system', `🔍 Debug State:\n${debugInfo.join('\n')}\n\nUse /debug on|off to toggle logging.`);
|
|
2486
|
+
}
|
|
2487
|
+
break;
|
|
2488
|
+
}
|
|
2489
|
+
case '/unstick': {
|
|
2490
|
+
// Emergency reset of processing state
|
|
2491
|
+
setIsProcessing(false);
|
|
2492
|
+
setThinkingState(null);
|
|
2493
|
+
setStreamingResponse('');
|
|
2494
|
+
setLoopActive(false);
|
|
2495
|
+
setModalMode('none');
|
|
2496
|
+
setPendingComplexPrompt(null);
|
|
2497
|
+
// Also reset to hybrid mode if stuck in plan mode
|
|
2498
|
+
if (mode === 'plan') {
|
|
2499
|
+
setMode('hybrid');
|
|
2500
|
+
addMessage('system', '🔧 Reset processing state + switched from plan to hybrid mode.');
|
|
2501
|
+
}
|
|
2028
2502
|
else {
|
|
2029
|
-
addMessage('system', '
|
|
2503
|
+
addMessage('system', '🔧 Reset processing state. You can now submit new messages.');
|
|
2030
2504
|
}
|
|
2031
2505
|
break;
|
|
2032
2506
|
}
|
|
2507
|
+
case '/keys':
|
|
2508
|
+
case '/?': {
|
|
2509
|
+
// Show keybindings modal
|
|
2510
|
+
setModalMode('keys');
|
|
2511
|
+
break;
|
|
2512
|
+
}
|
|
2513
|
+
case '/work': {
|
|
2514
|
+
// Quick shortcut to enter work mode
|
|
2515
|
+
setMode('work');
|
|
2516
|
+
addMessage('system', `Mode: ${MODE_CONFIG['work'].icon} ${MODE_CONFIG['work'].label} - ${MODE_CONFIG['work'].description}`);
|
|
2517
|
+
break;
|
|
2518
|
+
}
|
|
2519
|
+
case '/plan': {
|
|
2520
|
+
// Quick shortcut to enter plan mode
|
|
2521
|
+
setMode('plan');
|
|
2522
|
+
addMessage('system', `Mode: ${MODE_CONFIG['plan'].icon} ${MODE_CONFIG['plan'].label} - ${MODE_CONFIG['plan'].description}`);
|
|
2523
|
+
break;
|
|
2524
|
+
}
|
|
2033
2525
|
case '/resume': {
|
|
2034
2526
|
// Resume previous session manually
|
|
2035
2527
|
const history = storage.getChatHistory(parseInt(parts[1]) || 20);
|
|
@@ -2058,8 +2550,44 @@ Example: /loop "Build a REST API" --max-iterations 50 --completion-promise "DONE
|
|
|
2058
2550
|
addMessage('error', `Unknown command: ${command}. Type /help for help.`);
|
|
2059
2551
|
}
|
|
2060
2552
|
}, [actualProvider, actualModel, persona, stats, addMessage, exit]);
|
|
2553
|
+
// Validate and repair message history to ensure tool_use always has tool_result
|
|
2554
|
+
const validateAndRepairMessages = useCallback(() => {
|
|
2555
|
+
const messages = llmMessages.current;
|
|
2556
|
+
let repaired = false;
|
|
2557
|
+
for (let i = 0; i < messages.length; i++) {
|
|
2558
|
+
const msg = messages[i];
|
|
2559
|
+
if (msg.role === 'assistant' && msg.toolCalls && msg.toolCalls.length > 0) {
|
|
2560
|
+
// Check that each tool_use has a corresponding tool_result
|
|
2561
|
+
for (const toolCall of msg.toolCalls) {
|
|
2562
|
+
const hasResult = messages.slice(i + 1).some(m => m.role === 'tool' && m.toolCallId === toolCall.id);
|
|
2563
|
+
if (!hasResult) {
|
|
2564
|
+
// Add a placeholder tool_result for the missing tool call
|
|
2565
|
+
debugLog('repair', 'Adding missing tool_result for', toolCall.id);
|
|
2566
|
+
// Find the right position to insert (right after this assistant message or after existing tool results)
|
|
2567
|
+
let insertPos = i + 1;
|
|
2568
|
+
while (insertPos < messages.length && messages[insertPos].role === 'tool') {
|
|
2569
|
+
insertPos++;
|
|
2570
|
+
}
|
|
2571
|
+
messages.splice(insertPos, 0, {
|
|
2572
|
+
role: 'tool',
|
|
2573
|
+
content: '[Error: Tool execution was interrupted. Please retry.]',
|
|
2574
|
+
toolCallId: toolCall.id,
|
|
2575
|
+
});
|
|
2576
|
+
repaired = true;
|
|
2577
|
+
}
|
|
2578
|
+
}
|
|
2579
|
+
}
|
|
2580
|
+
}
|
|
2581
|
+
if (repaired) {
|
|
2582
|
+
addMessage('system', '🔧 Repaired corrupted message history (missing tool results).');
|
|
2583
|
+
}
|
|
2584
|
+
return repaired;
|
|
2585
|
+
}, [addMessage]);
|
|
2061
2586
|
// Run agent with user prompt
|
|
2062
2587
|
const runAgent = useCallback(async (content) => {
|
|
2588
|
+
debugLog('runAgent', 'ENTER', typeof content === 'string' ? content.substring(0, 50) : '[complex]');
|
|
2589
|
+
// Validate message history before adding new content
|
|
2590
|
+
validateAndRepairMessages();
|
|
2063
2591
|
llmMessages.current.push({ role: 'user', content });
|
|
2064
2592
|
setStats(s => ({ ...s, messageCount: s.messageCount + 1 }));
|
|
2065
2593
|
setStreamingResponse('');
|
|
@@ -2111,7 +2639,9 @@ Example: /loop "Build a REST API" --max-iterations 50 --completion-promise "DONE
|
|
|
2111
2639
|
maxIterations,
|
|
2112
2640
|
});
|
|
2113
2641
|
};
|
|
2642
|
+
debugLog('chat', 'WAITING for LLM response', `iteration=${i + 1}`);
|
|
2114
2643
|
const response = await chat(provider, llmMessages.current, getTools(moduleAgtermEnabled), effectiveModel, onToken, onRetry);
|
|
2644
|
+
debugLog('chat', 'GOT response', `toolCalls=${response.toolCalls?.length ?? 0}`);
|
|
2115
2645
|
// Update token stats and cost
|
|
2116
2646
|
if (response.usage) {
|
|
2117
2647
|
const usageCost = calculateCost(model || DEFAULT_MODELS[provider], response.usage.inputTokens, response.usage.outputTokens);
|
|
@@ -2205,6 +2735,7 @@ Example: /loop "Build a REST API" --max-iterations 50 --completion-promise "DONE
|
|
|
2205
2735
|
maxIterations,
|
|
2206
2736
|
});
|
|
2207
2737
|
// Execute in parallel using dependency-aware staging
|
|
2738
|
+
debugLog('tools', 'PARALLEL exec start', `count=${executableTools.length}`);
|
|
2208
2739
|
const results = await executeParallel(executableTools, async (call) => {
|
|
2209
2740
|
const result = await executeTool(call, process.cwd());
|
|
2210
2741
|
return result.result;
|
|
@@ -2216,6 +2747,7 @@ Example: /loop "Build a REST API" --max-iterations 50 --completion-promise "DONE
|
|
|
2216
2747
|
maxIterations,
|
|
2217
2748
|
});
|
|
2218
2749
|
});
|
|
2750
|
+
debugLog('tools', 'PARALLEL exec done', `results=${results.length}`);
|
|
2219
2751
|
// Process results sequentially for UI and LLM messages
|
|
2220
2752
|
for (const result of results) {
|
|
2221
2753
|
const toolCall = result.toolCall;
|
|
@@ -2247,6 +2779,7 @@ Example: /loop "Build a REST API" --max-iterations 50 --completion-promise "DONE
|
|
|
2247
2779
|
}
|
|
2248
2780
|
else {
|
|
2249
2781
|
// Sequential execution (single tool or dependencies prevent parallelization)
|
|
2782
|
+
debugLog('tools', 'SEQUENTIAL exec start', `count=${executableTools.length}`);
|
|
2250
2783
|
for (const toolCall of executableTools) {
|
|
2251
2784
|
const args = toolCall.arguments;
|
|
2252
2785
|
const toolPreview = String(args.command || args.path || '...');
|
|
@@ -2270,7 +2803,9 @@ Example: /loop "Build a REST API" --max-iterations 50 --completion-promise "DONE
|
|
|
2270
2803
|
maxIterations,
|
|
2271
2804
|
});
|
|
2272
2805
|
}
|
|
2806
|
+
debugLog('tools', 'EXEC', toolCall.name, toolPreview.substring(0, 30));
|
|
2273
2807
|
const result = await executeTool(toolCall, process.cwd());
|
|
2808
|
+
debugLog('tools', 'DONE', toolCall.name);
|
|
2274
2809
|
// Execute post-tool hooks
|
|
2275
2810
|
hooks.executeHooks('post-tool', {
|
|
2276
2811
|
tool: toolCall.name,
|
|
@@ -2302,6 +2837,7 @@ Example: /loop "Build a REST API" --max-iterations 50 --completion-promise "DONE
|
|
|
2302
2837
|
addMessage('assistant', response.content);
|
|
2303
2838
|
setStreamingResponse('');
|
|
2304
2839
|
setContextTokens(estimateContextTokens());
|
|
2840
|
+
checkAndWarnContextLimit(actualModel, estimateContextTokens(), addMessage);
|
|
2305
2841
|
// Auto-continue if response was truncated due to length
|
|
2306
2842
|
if (response.finishReason === 'length') {
|
|
2307
2843
|
addMessage('system', '(auto-continuing...)');
|
|
@@ -2314,9 +2850,32 @@ Example: /loop "Build a REST API" --max-iterations 50 --completion-promise "DONE
|
|
|
2314
2850
|
catch (error) {
|
|
2315
2851
|
setThinkingState(null);
|
|
2316
2852
|
setStreamingResponse('');
|
|
2317
|
-
|
|
2853
|
+
// Format error with provider context for better suggestions
|
|
2854
|
+
const errorMsg = formatError(error, { provider: actualProvider });
|
|
2855
|
+
addMessage('error', errorMsg);
|
|
2856
|
+
// Classify error to provide additional recovery suggestions
|
|
2857
|
+
const classified = classifyError(error);
|
|
2858
|
+
const availableProviders = getAvailableProviders();
|
|
2859
|
+
const otherProviders = availableProviders.filter(p => p !== actualProvider);
|
|
2860
|
+
// Suggest alternatives based on error type
|
|
2861
|
+
if (classified.category === 'rate_limit' || classified.category === 'server') {
|
|
2862
|
+
if (otherProviders.length > 0) {
|
|
2863
|
+
addMessage('system', `💡 Try switching providers: /provider ${otherProviders[0]} or /models to see alternatives`);
|
|
2864
|
+
}
|
|
2865
|
+
}
|
|
2866
|
+
else if (classified.category === 'timeout' || classified.category === 'network') {
|
|
2867
|
+
addMessage('system', `💡 Network issue detected. Check connection and try again, or use /provider to switch.`);
|
|
2868
|
+
}
|
|
2869
|
+
else if (classified.category === 'auth') {
|
|
2870
|
+
addMessage('system', `💡 Run 'calliope --setup' to reconfigure API keys.`);
|
|
2871
|
+
}
|
|
2318
2872
|
completedNaturally = true; // Error counts as "done" - don't show iteration warning
|
|
2319
|
-
|
|
2873
|
+
// On error, clear queued messages to prevent infinite retry loop
|
|
2874
|
+
if (queuedMessages.length > 0) {
|
|
2875
|
+
addMessage('system', `⚠️ Cleared ${queuedMessages.length} queued message(s) due to error. Use /clear to reset conversation.`);
|
|
2876
|
+
setQueuedMessages([]);
|
|
2877
|
+
}
|
|
2878
|
+
return; // Exit early on error - don't process queued messages
|
|
2320
2879
|
}
|
|
2321
2880
|
}
|
|
2322
2881
|
// Only show warning if we actually hit the iteration limit (not errors or natural completion)
|
|
@@ -2326,6 +2885,7 @@ Example: /loop "Build a REST API" --max-iterations 50 --completion-promise "DONE
|
|
|
2326
2885
|
// Update context tokens after agent run
|
|
2327
2886
|
setContextTokens(estimateContextTokens());
|
|
2328
2887
|
// Process any queued messages (human-in-the-loop feedback)
|
|
2888
|
+
debugLog('runAgent', 'EXIT loop', `queued=${queuedMessages.length}`);
|
|
2329
2889
|
if (queuedMessages.length > 0) {
|
|
2330
2890
|
const queued = [...queuedMessages];
|
|
2331
2891
|
setQueuedMessages([]); // Clear the queue
|
|
@@ -2336,10 +2896,20 @@ Example: /loop "Build a REST API" --max-iterations 50 --completion-promise "DONE
|
|
|
2336
2896
|
addMessage('system', `📨 Processing ${queued.length} queued message${queued.length > 1 ? 's' : ''}...`);
|
|
2337
2897
|
// Recursively run agent with follow-up
|
|
2338
2898
|
// Use setTimeout to avoid stack overflow and allow UI to update
|
|
2899
|
+
// Note: handleSubmit's finally will set isProcessing=false, so we need to re-enable it
|
|
2900
|
+
debugLog('runAgent', 'SCHEDULING recursive call for queued messages');
|
|
2339
2901
|
setTimeout(() => {
|
|
2340
|
-
runAgent
|
|
2902
|
+
debugLog('runAgent', 'RECURSIVE call starting');
|
|
2903
|
+
setIsProcessing(true);
|
|
2904
|
+
runAgent(followUp).finally(() => {
|
|
2905
|
+
setIsProcessing(false);
|
|
2906
|
+
setThinkingState(null);
|
|
2907
|
+
setStreamingResponse('');
|
|
2908
|
+
setEditingQueueIndex(null);
|
|
2909
|
+
});
|
|
2341
2910
|
}, 100);
|
|
2342
2911
|
}
|
|
2912
|
+
debugLog('runAgent', 'RETURN');
|
|
2343
2913
|
}, [provider, model, addMessage, mode, estimateContextTokens, queuedMessages]);
|
|
2344
2914
|
// Ralph Wiggum loop - runs prompt repeatedly until completion promise or max iterations
|
|
2345
2915
|
const runLoop = useCallback(async (prompt, maxIter, completionPromise) => {
|
|
@@ -2492,8 +3062,48 @@ Example: /loop "Build a REST API" --max-iterations 50 --completion-promise "DONE
|
|
|
2492
3062
|
return next;
|
|
2493
3063
|
});
|
|
2494
3064
|
}, []);
|
|
3065
|
+
// Handle Escape key - cancel operation if processing, otherwise show hint
|
|
3066
|
+
const handleEscape = useCallback(() => {
|
|
3067
|
+
if (isProcessing) {
|
|
3068
|
+
// Cancel current operation
|
|
3069
|
+
setIsProcessing(false);
|
|
3070
|
+
setThinkingState(null);
|
|
3071
|
+
setStreamingResponse('');
|
|
3072
|
+
setLoopActive(false);
|
|
3073
|
+
setEditingQueueIndex(null);
|
|
3074
|
+
addMessage('system', '⏹ Operation cancelled. Use /exit to quit.');
|
|
3075
|
+
}
|
|
3076
|
+
else if (modalMode !== 'none') {
|
|
3077
|
+
// Close any open modal
|
|
3078
|
+
setModalMode('none');
|
|
3079
|
+
setPendingComplexPrompt(null);
|
|
3080
|
+
}
|
|
3081
|
+
else {
|
|
3082
|
+
// Not processing - show hint instead of exiting
|
|
3083
|
+
addMessage('system', '💡 Use /exit to quit, or Ctrl+C.');
|
|
3084
|
+
}
|
|
3085
|
+
}, [isProcessing, modalMode, addMessage]);
|
|
3086
|
+
// Handle direct send (Shift+Enter) - interrupts current operation and sends immediately
|
|
3087
|
+
const handleDirectSend = useCallback((msg) => {
|
|
3088
|
+
// Stop current processing
|
|
3089
|
+
setIsProcessing(false);
|
|
3090
|
+
setThinkingState(null);
|
|
3091
|
+
setStreamingResponse('');
|
|
3092
|
+
setEditingQueueIndex(null);
|
|
3093
|
+
// Show what happened
|
|
3094
|
+
addMessage('system', '⚡ Direct send - interrupting current operation');
|
|
3095
|
+
addMessage('user', msg);
|
|
3096
|
+
// Start new agent run with this message
|
|
3097
|
+
setIsProcessing(true);
|
|
3098
|
+
runAgent(msg).finally(() => {
|
|
3099
|
+
setIsProcessing(false);
|
|
3100
|
+
setThinkingState(null);
|
|
3101
|
+
setStreamingResponse('');
|
|
3102
|
+
setEditingQueueIndex(null);
|
|
3103
|
+
});
|
|
3104
|
+
}, [addMessage, runAgent]);
|
|
2495
3105
|
// Render
|
|
2496
|
-
return (_jsxs(Box, { flexDirection: "column", width: width, children: [_jsx(MessageHistory, { messages: messages }), streamingResponse && (_jsxs(Box, { flexDirection: "column", marginTop: 1, marginBottom: 1, children: [_jsx(Text, { color: "cyan", children: "\u2727 Calliope:" }), _jsx(Text, { children: " " }), streamingResponse.split('\n').map((line, i) => (_jsxs(Text, { children: [_jsx(Text, { color: "blue", children: "\u2502" }), " ", line] }, i))), _jsx(Text, { color: "blue", children: "\u2502" }), _jsx(Text, { color: "cyan", children: "\u258C" })] })), isProcessing && thinkingState && !streamingResponse && _jsx(ThinkingDisplay, { state: thinkingState }), isProcessing && !thinkingState && !streamingResponse && _jsx(ProcessingIndicator, { label: "
|
|
3106
|
+
return (_jsxs(Box, { flexDirection: "column", width: width, children: [_jsx(MessageHistory, { messages: messages }), streamingResponse && (_jsxs(Box, { flexDirection: "column", marginTop: 1, marginBottom: 1, children: [_jsx(Text, { color: "cyan", children: "\u2727 Calliope:" }), _jsx(Text, { children: " " }), streamingResponse.split('\n').map((line, i) => (_jsxs(Text, { children: [_jsx(Text, { color: "blue", children: "\u2502" }), " ", line] }, i))), _jsx(Text, { color: "blue", children: "\u2502" }), _jsx(Text, { color: "cyan", children: "\u258C" })] })), isProcessing && thinkingState && !streamingResponse && _jsx(ThinkingDisplay, { state: thinkingState }), isProcessing && !thinkingState && !streamingResponse && _jsx(ProcessingIndicator, { label: "Waiting for response..." }), isProcessing && streamingResponse && _jsx(StreamingIndicator, {}), debugEnabled && (_jsx(Box, { marginY: 0, children: _jsxs(Text, { dimColor: true, children: ["[dbg] proc=", isProcessing ? 'Y' : 'N', " think=", thinkingState ? 'Y' : 'N', " stream=", streamingResponse.length, " mode=", mode, " queue=", queuedMessages.length] }) })), modalMode === 'model' && availableModels.length > 0 && (_jsx(ModelSelector, { models: availableModels, onSelect: handleModelSelect, onCancel: handleModalCancel })), modalMode === 'upgrade' && latestVersion && (_jsx(UpgradePrompt, { currentVersion: getVersion(), latestVersion: latestVersion, onConfirm: handleUpgradeConfirm, onCancel: handleModalCancel })), modalMode === 'session-resume' && previousSession && (_jsx(SessionResumePrompt, { session: previousSession, onResume: () => {
|
|
2497
3107
|
// Load chat history into context
|
|
2498
3108
|
const history = storage.getChatHistory(20);
|
|
2499
3109
|
if (history.length > 0) {
|
|
@@ -2514,7 +3124,7 @@ Example: /loop "Build a REST API" --max-iterations 50 --completion-promise "DONE
|
|
|
2514
3124
|
addMessage('system', '✓ Starting fresh session');
|
|
2515
3125
|
setModalMode('none');
|
|
2516
3126
|
setPreviousSession(null);
|
|
2517
|
-
} })), modalMode === 'complexity-warning' && pendingComplexPrompt && (_jsx(ComplexityWarning, { reason: pendingComplexPrompt.complexity.reason || 'Complex operation detected', onProceed: async () => {
|
|
3127
|
+
} })), modalMode === 'complexity-warning' && pendingComplexPrompt && (_jsx(ComplexityWarning, { reason: pendingComplexPrompt.complexity.reason || 'Complex operation detected', prompt: typeof pendingComplexPrompt.prompt === 'string' ? pendingComplexPrompt.prompt : undefined, onProceed: async () => {
|
|
2518
3128
|
setModalMode('none');
|
|
2519
3129
|
const prompt = pendingComplexPrompt.prompt;
|
|
2520
3130
|
setPendingComplexPrompt(null);
|
|
@@ -2543,10 +3153,12 @@ Example: /loop "Build a REST API" --max-iterations 50 --completion-promise "DONE
|
|
|
2543
3153
|
setModalMode('none');
|
|
2544
3154
|
setPendingComplexPrompt(null);
|
|
2545
3155
|
addMessage('system', 'Operation cancelled.');
|
|
2546
|
-
} })), _jsx(ChatInput, { value: input, onChange: handleInputChange, onSubmit: handleSubmit, onEscape:
|
|
3156
|
+
} })), modalMode === 'keys' && (_jsx(KeybindingsModal, { onClose: () => setModalMode('none') })), _jsx(ChatInput, { value: input, onChange: handleInputChange, onSubmit: handleSubmit, onEscape: handleEscape, onCycleMode: cycleMode, disabled: isModalActive, isProcessing: isProcessing, queuedCount: queuedMessages.length, queuedMessages: queuedMessages, editingQueueIndex: editingQueueIndex, onQueueMessage: (msg) => {
|
|
2547
3157
|
setQueuedMessages(prev => [...prev, msg]);
|
|
2548
3158
|
addMessage('system', `📨 Queued: "${msg.substring(0, 50)}${msg.length > 50 ? '...' : ''}"`);
|
|
2549
|
-
},
|
|
3159
|
+
}, onEditQueuedMessage: handleEditQueuedMessage, onSetEditingQueueIndex: setEditingQueueIndex, onDirectSend: handleDirectSend, cwd: process.cwd(), suggestions: suggestions, onSuggestionsChange: setSuggestions, onNavigateHistory: navigateHistory,
|
|
3160
|
+
// Smart suggestions context
|
|
3161
|
+
currentMode: mode, contextPercentage: Math.round((contextTokens / getContextLimit(actualModel)) * 100), recentCommands: recentCommands, hasGitRepo: hasGitRepo }), _jsx(StatusBar, { provider: actualProvider, model: actualModel, mode: mode, stats: stats, contextTokens: contextTokens })] }));
|
|
2550
3162
|
}
|
|
2551
3163
|
// ============================================================================
|
|
2552
3164
|
// App Wrapper & Entry Point
|