gencode-ai 0.3.0 → 0.4.1
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/RELEASE_NOTES_v0.4.0.md +140 -0
- package/dist/agent/agent.d.ts +17 -2
- package/dist/agent/agent.d.ts.map +1 -1
- package/dist/agent/agent.js +279 -49
- package/dist/agent/agent.js.map +1 -1
- package/dist/agent/types.d.ts +15 -1
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/checkpointing/checkpoint-manager.d.ts +24 -0
- package/dist/checkpointing/checkpoint-manager.d.ts.map +1 -1
- package/dist/checkpointing/checkpoint-manager.js +28 -0
- package/dist/checkpointing/checkpoint-manager.js.map +1 -1
- package/dist/cli/components/App.d.ts +8 -0
- package/dist/cli/components/App.d.ts.map +1 -1
- package/dist/cli/components/App.js +478 -36
- package/dist/cli/components/App.js.map +1 -1
- package/dist/cli/components/CommandSuggestions.d.ts.map +1 -1
- package/dist/cli/components/CommandSuggestions.js +2 -0
- package/dist/cli/components/CommandSuggestions.js.map +1 -1
- package/dist/cli/components/Header.d.ts +6 -1
- package/dist/cli/components/Header.d.ts.map +1 -1
- package/dist/cli/components/Header.js +3 -3
- package/dist/cli/components/Header.js.map +1 -1
- package/dist/cli/components/Messages.d.ts.map +1 -1
- package/dist/cli/components/Messages.js +7 -9
- package/dist/cli/components/Messages.js.map +1 -1
- package/dist/cli/index.js +3 -2
- package/dist/cli/index.js.map +1 -1
- package/dist/config/types.d.ts +20 -1
- package/dist/config/types.d.ts.map +1 -1
- package/dist/config/types.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/input/history-manager.d.ts +78 -0
- package/dist/input/history-manager.d.ts.map +1 -0
- package/dist/input/history-manager.js +224 -0
- package/dist/input/history-manager.js.map +1 -0
- package/dist/input/index.d.ts +6 -0
- package/dist/input/index.d.ts.map +1 -0
- package/dist/input/index.js +5 -0
- package/dist/input/index.js.map +1 -0
- package/dist/prompts/index.js +3 -3
- package/dist/prompts/index.js.map +1 -1
- package/dist/providers/gemini.d.ts.map +1 -1
- package/dist/providers/gemini.js +33 -2
- package/dist/providers/gemini.js.map +1 -1
- package/dist/providers/google.d.ts +22 -0
- package/dist/providers/google.d.ts.map +1 -0
- package/dist/providers/google.js +297 -0
- package/dist/providers/google.js.map +1 -0
- package/dist/providers/index.d.ts +4 -4
- package/dist/providers/index.js +11 -11
- package/dist/providers/index.js.map +1 -1
- package/dist/providers/openai.d.ts.map +1 -1
- package/dist/providers/openai.js +6 -0
- package/dist/providers/openai.js.map +1 -1
- package/dist/providers/registry.js +3 -3
- package/dist/providers/registry.js.map +1 -1
- package/dist/providers/types.d.ts +30 -4
- package/dist/providers/types.d.ts.map +1 -1
- package/dist/session/compression/engine.d.ts +109 -0
- package/dist/session/compression/engine.d.ts.map +1 -0
- package/dist/session/compression/engine.js +311 -0
- package/dist/session/compression/engine.js.map +1 -0
- package/dist/session/compression/index.d.ts +12 -0
- package/dist/session/compression/index.d.ts.map +1 -0
- package/dist/session/compression/index.js +11 -0
- package/dist/session/compression/index.js.map +1 -0
- package/dist/session/compression/types.d.ts +90 -0
- package/dist/session/compression/types.d.ts.map +1 -0
- package/dist/session/compression/types.js +17 -0
- package/dist/session/compression/types.js.map +1 -0
- package/dist/session/manager.d.ts +64 -3
- package/dist/session/manager.d.ts.map +1 -1
- package/dist/session/manager.js +254 -2
- package/dist/session/manager.js.map +1 -1
- package/dist/session/types.d.ts +16 -0
- package/dist/session/types.d.ts.map +1 -1
- package/dist/session/types.js.map +1 -1
- package/docs/README.md +1 -0
- package/docs/diagrams/compression-decision.mmd +30 -0
- package/docs/diagrams/compression-workflow.mmd +54 -0
- package/docs/diagrams/layer1-pruning.mmd +45 -0
- package/docs/diagrams/layer2-compaction.mmd +42 -0
- package/docs/proposals/0007-context-management.md +252 -2
- package/docs/proposals/README.md +4 -3
- package/docs/providers.md +3 -3
- package/docs/session-compression.md +695 -0
- package/examples/agent-demo.ts +23 -1
- package/examples/basic.ts +3 -3
- package/package.json +4 -5
- package/src/agent/agent.ts +314 -52
- package/src/agent/types.ts +19 -1
- package/src/checkpointing/checkpoint-manager.ts +48 -0
- package/src/cli/components/App.tsx +553 -34
- package/src/cli/components/CommandSuggestions.tsx +2 -0
- package/src/cli/components/Header.tsx +16 -1
- package/src/cli/components/Messages.tsx +20 -14
- package/src/cli/index.tsx +3 -2
- package/src/config/types.ts +26 -1
- package/src/index.ts +3 -3
- package/src/input/history-manager.ts +289 -0
- package/src/input/index.ts +6 -0
- package/src/prompts/index.test.ts +2 -1
- package/src/prompts/index.ts +3 -3
- package/src/providers/{gemini.ts → google.ts} +69 -18
- package/src/providers/index.ts +14 -14
- package/src/providers/openai.ts +7 -0
- package/src/providers/registry.ts +3 -3
- package/src/providers/types.ts +33 -3
- package/src/session/compression/engine.ts +406 -0
- package/src/session/compression/index.ts +18 -0
- package/src/session/compression/types.ts +102 -0
- package/src/session/manager.ts +326 -3
- package/src/session/types.ts +21 -0
- package/tests/input-history-manager.test.ts +335 -0
- package/tests/session-checkpoint-persistence.test.ts +198 -0
|
@@ -36,12 +36,15 @@ import { getTodos, formatAnswersForDisplay } from '../../tools/index.js';
|
|
|
36
36
|
import type { Question, QuestionAnswer } from '../../tools/types.js';
|
|
37
37
|
import type { ProviderName } from '../../providers/index.js';
|
|
38
38
|
import type { ApprovalAction, ApprovalSuggestion } from '../../permissions/types.js';
|
|
39
|
+
import type { Message, ToolResultContent, ToolUseContent } from '../../providers/types.js';
|
|
40
|
+
import type { SessionMetadata } from '../../session/types.js';
|
|
39
41
|
import { gatherContextFiles, buildInitPrompt, getContextSummary } from '../../memory/index.js';
|
|
40
42
|
// ModeIndicator kept for potential future use
|
|
41
43
|
import { PlanApproval } from './PlanApproval.js';
|
|
42
44
|
import type { ModeType, PlanApprovalOption, AllowedPrompt } from '../../planning/types.js';
|
|
43
45
|
// Planning utilities kept for potential future use
|
|
44
46
|
import { getCheckpointManager } from '../../checkpointing/index.js';
|
|
47
|
+
import { InputHistoryManager } from '../../input/index.js';
|
|
45
48
|
|
|
46
49
|
// Types
|
|
47
50
|
interface HistoryItem {
|
|
@@ -75,6 +78,7 @@ interface SettingsManager {
|
|
|
75
78
|
save: (settings: { model?: string }) => Promise<void>;
|
|
76
79
|
getCwd?: () => string;
|
|
77
80
|
addPermissionRule?: (pattern: string, type: 'allow' | 'deny', level?: 'global' | 'project' | 'local') => Promise<void>;
|
|
81
|
+
get?: () => { inputHistory?: { enabled?: boolean; maxSize?: number; savePath?: string; deduplicateConsecutive?: boolean } };
|
|
78
82
|
}
|
|
79
83
|
|
|
80
84
|
interface Session {
|
|
@@ -106,17 +110,20 @@ function useAgent(config: AgentConfig) {
|
|
|
106
110
|
// ============================================================================
|
|
107
111
|
// Utils
|
|
108
112
|
// ============================================================================
|
|
109
|
-
|
|
113
|
+
function genId(): string {
|
|
114
|
+
return Math.random().toString(36).slice(2);
|
|
115
|
+
}
|
|
110
116
|
|
|
111
|
-
|
|
117
|
+
function formatRelativeTime(dateStr: string): string {
|
|
112
118
|
const diff = Date.now() - new Date(dateStr).getTime();
|
|
113
119
|
const mins = Math.floor(diff / 60000);
|
|
114
120
|
const hrs = Math.floor(mins / 60);
|
|
115
121
|
const days = Math.floor(hrs / 24);
|
|
122
|
+
|
|
116
123
|
if (mins < 60) return `${mins}m`;
|
|
117
124
|
if (hrs < 24) return `${hrs}h`;
|
|
118
125
|
return `${days}d`;
|
|
119
|
-
}
|
|
126
|
+
}
|
|
120
127
|
|
|
121
128
|
// ============================================================================
|
|
122
129
|
// Help Component
|
|
@@ -137,6 +144,8 @@ function HelpPanel() {
|
|
|
137
144
|
['/memory', 'Show memory files'],
|
|
138
145
|
['/changes', 'List file changes'],
|
|
139
146
|
['/rewind [n|all]', 'Undo file changes'],
|
|
147
|
+
['/context', 'Show context stats'],
|
|
148
|
+
['/compact', 'Compact conversation'],
|
|
140
149
|
];
|
|
141
150
|
|
|
142
151
|
return (
|
|
@@ -224,6 +233,30 @@ function MemoryFilesDisplay({ files }: { files: MemoryFileInfo[] }) {
|
|
|
224
233
|
);
|
|
225
234
|
}
|
|
226
235
|
|
|
236
|
+
// ============================================================================
|
|
237
|
+
// Token Estimation Utilities
|
|
238
|
+
// ============================================================================
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Language-aware token estimation for streaming text
|
|
242
|
+
* Provides better estimates for multilingual content than the simple 4:1 ratio
|
|
243
|
+
*
|
|
244
|
+
* @param text - Text chunk to estimate tokens for
|
|
245
|
+
* @returns Estimated token count
|
|
246
|
+
*/
|
|
247
|
+
function estimateTokenDelta(text: string): number {
|
|
248
|
+
// ASCII/English: ~4 chars per token
|
|
249
|
+
const asciiChars = (text.match(/[\x00-\x7F]/g) || []).length;
|
|
250
|
+
|
|
251
|
+
// CJK (Chinese, Japanese, Korean): ~1.5 chars per token
|
|
252
|
+
const cjkChars = (text.match(/[\u4E00-\u9FFF\u3040-\u309F\u30A0-\u30FF]/g) || []).length;
|
|
253
|
+
|
|
254
|
+
// Other Unicode (including emojis): ~3 chars per token
|
|
255
|
+
const otherChars = text.length - asciiChars - cjkChars;
|
|
256
|
+
|
|
257
|
+
return Math.max(1, Math.ceil(asciiChars / 4 + cjkChars / 1.5 + otherChars / 3));
|
|
258
|
+
}
|
|
259
|
+
|
|
227
260
|
// ============================================================================
|
|
228
261
|
// Main App
|
|
229
262
|
// ============================================================================
|
|
@@ -259,6 +292,14 @@ export function App({ config, settingsManager, resumeLatest, permissionSettings
|
|
|
259
292
|
const [isThinking, setIsThinking] = useState(false);
|
|
260
293
|
const [streamingText, setStreamingText] = useState('');
|
|
261
294
|
const streamingTextRef = useRef(''); // Track current streaming text for closure
|
|
295
|
+
|
|
296
|
+
// Performance optimization: Throttle streaming text updates
|
|
297
|
+
const streamBufferRef = useRef(''); // Buffer for accumulated text
|
|
298
|
+
const lastFlushTimeRef = useRef(0); // Last time we flushed to UI
|
|
299
|
+
const flushTimerRef = useRef<NodeJS.Timeout | null>(null); // Pending flush timer
|
|
300
|
+
const FLUSH_INTERVAL_MS = 16; // ~60 FPS throttling
|
|
301
|
+
|
|
302
|
+
const [messageQueue, setMessageQueue] = useState<string[]>([]);
|
|
262
303
|
const [processingStartTime, setProcessingStartTime] = useState<number | undefined>(undefined);
|
|
263
304
|
const [tokenCount, setTokenCount] = useState(0);
|
|
264
305
|
const [confirmState, setConfirmState] = useState<ConfirmState | null>(null);
|
|
@@ -273,6 +314,10 @@ export function App({ config, settingsManager, resumeLatest, permissionSettings
|
|
|
273
314
|
const pendingToolRef = useRef<{ name: string; input: Record<string, unknown> } | null>(null);
|
|
274
315
|
const [todos, setTodos] = useState<ReturnType<typeof getTodos>>([]);
|
|
275
316
|
|
|
317
|
+
// Input history management
|
|
318
|
+
const historyManagerRef = useRef<InputHistoryManager | null>(null);
|
|
319
|
+
const [historyTempInput, setHistoryTempInput] = useState(''); // Store original input when navigating
|
|
320
|
+
|
|
276
321
|
// Operating mode state (normal → plan → accept → normal)
|
|
277
322
|
const [currentMode, setCurrentMode] = useState<ModeType>('normal');
|
|
278
323
|
const currentModeRef = useRef<ModeType>('normal'); // Track mode for confirm callback
|
|
@@ -292,11 +337,214 @@ export function App({ config, settingsManager, resumeLatest, permissionSettings
|
|
|
292
337
|
setCmdSuggestionIndex(0);
|
|
293
338
|
}, [input]);
|
|
294
339
|
|
|
340
|
+
// Initialize input history manager
|
|
341
|
+
useEffect(() => {
|
|
342
|
+
const initHistory = async () => {
|
|
343
|
+
const settings = settingsManager?.get?.();
|
|
344
|
+
const historyConfig = settings?.inputHistory;
|
|
345
|
+
const manager = new InputHistoryManager(historyConfig);
|
|
346
|
+
await manager.load();
|
|
347
|
+
historyManagerRef.current = manager;
|
|
348
|
+
};
|
|
349
|
+
|
|
350
|
+
initHistory();
|
|
351
|
+
|
|
352
|
+
// Cleanup: flush history on unmount
|
|
353
|
+
return () => {
|
|
354
|
+
if (historyManagerRef.current) {
|
|
355
|
+
void historyManagerRef.current.flush();
|
|
356
|
+
}
|
|
357
|
+
};
|
|
358
|
+
}, [settingsManager]);
|
|
359
|
+
|
|
295
360
|
// Add to history
|
|
296
361
|
const addHistory = useCallback((item: Omit<HistoryItem, 'id'>) => {
|
|
297
362
|
setHistory((prev) => [...prev, { ...item, id: genId() }]);
|
|
298
363
|
}, []);
|
|
299
364
|
|
|
365
|
+
// Track if warning has been shown (to avoid spam)
|
|
366
|
+
const contextWarningShownRef = useRef(false);
|
|
367
|
+
|
|
368
|
+
// Listen to session manager events for context warnings
|
|
369
|
+
useEffect(() => {
|
|
370
|
+
const sessionMgr = agent.getSessionManager();
|
|
371
|
+
|
|
372
|
+
const handleContextWarning = (data: { usagePercent: number }) => {
|
|
373
|
+
if (!contextWarningShownRef.current) {
|
|
374
|
+
addHistory({
|
|
375
|
+
type: 'info',
|
|
376
|
+
content: `⚠️ Context usage at ${Math.round(data.usagePercent)}% - Consider using /compact`,
|
|
377
|
+
});
|
|
378
|
+
contextWarningShownRef.current = true;
|
|
379
|
+
}
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
const handleAutoCompacting = (data: { strategy: string; usagePercent: number }) => {
|
|
383
|
+
addHistory({
|
|
384
|
+
type: 'info',
|
|
385
|
+
content: `📦 Auto-compacting (${Math.round(data.usagePercent)}% usage, strategy: ${data.strategy})...`,
|
|
386
|
+
});
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
const handleCompactionComplete = (data: { strategy: string }) => {
|
|
390
|
+
addHistory({
|
|
391
|
+
type: 'info',
|
|
392
|
+
content: `✓ Compaction complete (${data.strategy})`,
|
|
393
|
+
});
|
|
394
|
+
// Reset warning flag after compaction
|
|
395
|
+
contextWarningShownRef.current = false;
|
|
396
|
+
};
|
|
397
|
+
|
|
398
|
+
sessionMgr.on('context-warning', handleContextWarning);
|
|
399
|
+
sessionMgr.on('auto-compacting', handleAutoCompacting);
|
|
400
|
+
sessionMgr.on('compaction-complete', handleCompactionComplete);
|
|
401
|
+
|
|
402
|
+
return () => {
|
|
403
|
+
sessionMgr.off('context-warning', handleContextWarning);
|
|
404
|
+
sessionMgr.off('auto-compacting', handleAutoCompacting);
|
|
405
|
+
sessionMgr.off('compaction-complete', handleCompactionComplete);
|
|
406
|
+
};
|
|
407
|
+
}, [agent, addHistory]);
|
|
408
|
+
|
|
409
|
+
// Flush buffered streaming text to UI (throttled to ~60 FPS)
|
|
410
|
+
const flushStreamBuffer = useCallback(() => {
|
|
411
|
+
if (streamBufferRef.current) {
|
|
412
|
+
streamingTextRef.current = streamBufferRef.current;
|
|
413
|
+
setStreamingText(streamBufferRef.current);
|
|
414
|
+
lastFlushTimeRef.current = Date.now();
|
|
415
|
+
}
|
|
416
|
+
// Clear pending timer
|
|
417
|
+
if (flushTimerRef.current) {
|
|
418
|
+
clearTimeout(flushTimerRef.current);
|
|
419
|
+
flushTimerRef.current = null;
|
|
420
|
+
}
|
|
421
|
+
}, []);
|
|
422
|
+
|
|
423
|
+
// Add streaming text with throttling for performance
|
|
424
|
+
const addStreamingText = useCallback((text: string) => {
|
|
425
|
+
// Accumulate in buffer
|
|
426
|
+
streamBufferRef.current += text;
|
|
427
|
+
|
|
428
|
+
const now = Date.now();
|
|
429
|
+
const timeSinceLastFlush = now - lastFlushTimeRef.current;
|
|
430
|
+
|
|
431
|
+
if (timeSinceLastFlush >= FLUSH_INTERVAL_MS) {
|
|
432
|
+
// Flush immediately if enough time has passed
|
|
433
|
+
flushStreamBuffer();
|
|
434
|
+
} else if (!flushTimerRef.current) {
|
|
435
|
+
// Schedule a flush for the next interval
|
|
436
|
+
const delay = FLUSH_INTERVAL_MS - timeSinceLastFlush;
|
|
437
|
+
flushTimerRef.current = setTimeout(flushStreamBuffer, delay);
|
|
438
|
+
}
|
|
439
|
+
// Otherwise, wait for the scheduled flush
|
|
440
|
+
}, [flushStreamBuffer, FLUSH_INTERVAL_MS]);
|
|
441
|
+
|
|
442
|
+
// Convert Message[] to HistoryItem[] for displaying session history
|
|
443
|
+
const convertMessagesToHistory = useCallback((
|
|
444
|
+
messages: Message[],
|
|
445
|
+
metadata?: SessionMetadata
|
|
446
|
+
): HistoryItem[] => {
|
|
447
|
+
const items: HistoryItem[] = [];
|
|
448
|
+
|
|
449
|
+
for (let i = 0; i < messages.length; i++) {
|
|
450
|
+
const msg = messages[i];
|
|
451
|
+
|
|
452
|
+
// Skip system messages (they're for the LLM, not for display)
|
|
453
|
+
if (msg.role === 'system') {
|
|
454
|
+
continue;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
if (msg.role === 'user') {
|
|
458
|
+
// User messages can be plain text or contain tool results
|
|
459
|
+
if (typeof msg.content === 'string') {
|
|
460
|
+
items.push({
|
|
461
|
+
id: genId(),
|
|
462
|
+
type: 'user',
|
|
463
|
+
content: msg.content,
|
|
464
|
+
});
|
|
465
|
+
} else {
|
|
466
|
+
// Check for tool results
|
|
467
|
+
const toolResults = msg.content.filter((c) => c.type === 'tool_result');
|
|
468
|
+
const textContent = msg.content.filter((c) => c.type === 'text')
|
|
469
|
+
.map((c) => (c as { text: string }).text)
|
|
470
|
+
.join('\n');
|
|
471
|
+
|
|
472
|
+
if (textContent) {
|
|
473
|
+
items.push({
|
|
474
|
+
id: genId(),
|
|
475
|
+
type: 'user',
|
|
476
|
+
content: textContent,
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// Add tool results
|
|
481
|
+
for (const result of toolResults) {
|
|
482
|
+
const r = result as ToolResultContent;
|
|
483
|
+
items.push({
|
|
484
|
+
id: genId(),
|
|
485
|
+
type: 'tool_result',
|
|
486
|
+
content: r.content,
|
|
487
|
+
meta: { toolUseId: r.toolUseId, isError: r.isError },
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
} else if (msg.role === 'assistant') {
|
|
492
|
+
// Assistant messages can be text or contain tool calls
|
|
493
|
+
if (typeof msg.content === 'string') {
|
|
494
|
+
items.push({
|
|
495
|
+
id: genId(),
|
|
496
|
+
type: 'assistant',
|
|
497
|
+
content: msg.content,
|
|
498
|
+
});
|
|
499
|
+
} else {
|
|
500
|
+
// Separate text and tool calls
|
|
501
|
+
const textContent = msg.content.filter((c) => c.type === 'text')
|
|
502
|
+
.map((c) => (c as { text: string }).text)
|
|
503
|
+
.join('\n');
|
|
504
|
+
const toolCalls = msg.content.filter((c) => c.type === 'tool_use');
|
|
505
|
+
|
|
506
|
+
if (textContent) {
|
|
507
|
+
items.push({
|
|
508
|
+
id: genId(),
|
|
509
|
+
type: 'assistant',
|
|
510
|
+
content: textContent,
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// Add tool calls
|
|
515
|
+
for (const call of toolCalls) {
|
|
516
|
+
const c = call as ToolUseContent;
|
|
517
|
+
items.push({
|
|
518
|
+
id: genId(),
|
|
519
|
+
type: 'tool_call',
|
|
520
|
+
content: c.name,
|
|
521
|
+
meta: { id: c.id, name: c.name, input: c.input },
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// Inject completion message if this message index has a completion
|
|
528
|
+
if (metadata?.completions) {
|
|
529
|
+
const completion = metadata.completions.find((c) => c.afterMessageIndex === i);
|
|
530
|
+
if (completion) {
|
|
531
|
+
items.push({
|
|
532
|
+
id: genId(),
|
|
533
|
+
type: 'completion',
|
|
534
|
+
content: '',
|
|
535
|
+
meta: {
|
|
536
|
+
durationMs: completion.durationMs,
|
|
537
|
+
usage: completion.usage,
|
|
538
|
+
cost: completion.cost,
|
|
539
|
+
},
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
return items;
|
|
546
|
+
}, []);
|
|
547
|
+
|
|
300
548
|
// Initialize
|
|
301
549
|
useEffect(() => {
|
|
302
550
|
const init = async () => {
|
|
@@ -339,12 +587,18 @@ export function App({ config, settingsManager, resumeLatest, permissionSettings
|
|
|
339
587
|
if (resumeLatest) {
|
|
340
588
|
const resumed = await agent.resumeLatest();
|
|
341
589
|
if (resumed) {
|
|
342
|
-
|
|
590
|
+
// Get the restored messages and display them
|
|
591
|
+
const messages = agent.getHistory();
|
|
592
|
+
const session = agent.getSessionManager().getCurrent();
|
|
593
|
+
const historyItems = convertMessagesToHistory(messages, session?.metadata);
|
|
594
|
+
|
|
595
|
+
// Add all historical messages
|
|
596
|
+
setHistory((prev) => [...prev, ...historyItems]);
|
|
343
597
|
}
|
|
344
598
|
}
|
|
345
599
|
};
|
|
346
600
|
init();
|
|
347
|
-
}, [agent, resumeLatest, addHistory, permissionSettings, settingsManager]);
|
|
601
|
+
}, [agent, resumeLatest, addHistory, permissionSettings, settingsManager, convertMessagesToHistory]);
|
|
348
602
|
|
|
349
603
|
// Handle question answers (AskUserQuestion)
|
|
350
604
|
const handleQuestionComplete = useCallback((answers: QuestionAnswer[]) => {
|
|
@@ -432,20 +686,32 @@ export function App({ config, settingsManager, resumeLatest, permissionSettings
|
|
|
432
686
|
|
|
433
687
|
case 'resume': {
|
|
434
688
|
let success = false;
|
|
435
|
-
|
|
689
|
+
|
|
690
|
+
if (!arg) {
|
|
691
|
+
success = await agent.resumeLatest();
|
|
692
|
+
} else {
|
|
436
693
|
const index = parseInt(arg, 10);
|
|
437
|
-
if (
|
|
694
|
+
if (isNaN(index)) {
|
|
695
|
+
success = await agent.resumeSession(arg);
|
|
696
|
+
} else {
|
|
438
697
|
const sessions = await agent.listSessions();
|
|
439
698
|
if (index >= 1 && index <= sessions.length) {
|
|
440
699
|
success = await agent.resumeSession(sessions[index - 1].id);
|
|
441
700
|
}
|
|
442
|
-
} else {
|
|
443
|
-
success = await agent.resumeSession(arg);
|
|
444
701
|
}
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
if (success) {
|
|
705
|
+
// Get the restored messages and display them
|
|
706
|
+
const messages = agent.getHistory();
|
|
707
|
+
const session = agent.getSessionManager().getCurrent();
|
|
708
|
+
const historyItems = convertMessagesToHistory(messages, session?.metadata);
|
|
709
|
+
|
|
710
|
+
// Add all historical messages
|
|
711
|
+
setHistory((prev) => [...prev, ...historyItems]);
|
|
445
712
|
} else {
|
|
446
|
-
|
|
713
|
+
addHistory({ type: 'info', content: 'Failed to restore session' });
|
|
447
714
|
}
|
|
448
|
-
addHistory({ type: 'info', content: success ? 'Restored' : 'Failed' });
|
|
449
715
|
return true;
|
|
450
716
|
}
|
|
451
717
|
|
|
@@ -711,6 +977,127 @@ export function App({ config, settingsManager, resumeLatest, permissionSettings
|
|
|
711
977
|
return true;
|
|
712
978
|
}
|
|
713
979
|
|
|
980
|
+
case 'compact': {
|
|
981
|
+
// Manually trigger conversation compaction
|
|
982
|
+
const sessionMgr = agent.getSessionManager();
|
|
983
|
+
const current = sessionMgr.getCurrent();
|
|
984
|
+
|
|
985
|
+
if (!current || current.messages.length === 0) {
|
|
986
|
+
addHistory({ type: 'info', content: 'No messages to compact' });
|
|
987
|
+
return true;
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
// Perform manual compaction
|
|
991
|
+
const modelInfo = agent.getModelInfo();
|
|
992
|
+
if (modelInfo) {
|
|
993
|
+
try {
|
|
994
|
+
await sessionMgr.performCompaction(modelInfo);
|
|
995
|
+
|
|
996
|
+
const stats = sessionMgr.getCompressionStats();
|
|
997
|
+
const saved = stats?.totalMessages ? stats.totalMessages - stats.activeMessages : 0;
|
|
998
|
+
const savedPercent = stats?.totalMessages
|
|
999
|
+
? ((saved / stats.totalMessages) * 100).toFixed(0)
|
|
1000
|
+
: '0';
|
|
1001
|
+
|
|
1002
|
+
// Create visual bars (ASCII only)
|
|
1003
|
+
const barWidth = 15;
|
|
1004
|
+
const activeBar = stats?.totalMessages
|
|
1005
|
+
? Math.round((stats.activeMessages / stats.totalMessages) * barWidth)
|
|
1006
|
+
: barWidth;
|
|
1007
|
+
const totalBar = barWidth;
|
|
1008
|
+
|
|
1009
|
+
const activeVisual = '#'.repeat(activeBar) + '.'.repeat(barWidth - activeBar);
|
|
1010
|
+
const totalVisual = '#'.repeat(totalBar);
|
|
1011
|
+
|
|
1012
|
+
// Format with simple ASCII box
|
|
1013
|
+
const w = 50;
|
|
1014
|
+
const pad = (text: string) => text + ' '.repeat(Math.max(0, w - text.length - 3));
|
|
1015
|
+
|
|
1016
|
+
const lines = [
|
|
1017
|
+
'+' + '-'.repeat(w - 2) + '+',
|
|
1018
|
+
'| ' + pad('Compaction Complete') + '|',
|
|
1019
|
+
'+' + '-'.repeat(w - 2) + '+',
|
|
1020
|
+
'| ' + pad(`Active Messages ${String(stats?.activeMessages || 0).padStart(3)} [${activeVisual}]`) + '|',
|
|
1021
|
+
'| ' + pad(`Total Messages ${String(stats?.totalMessages || 0).padStart(3)} [${totalVisual}]`) + '|',
|
|
1022
|
+
'| ' + pad(`Summaries ${String(stats?.summaryCount || 0).padStart(3)}`) + '|',
|
|
1023
|
+
'| ' + pad('') + '|',
|
|
1024
|
+
'| ' + pad(`Saved: ${savedPercent}%`) + '|',
|
|
1025
|
+
'+' + '-'.repeat(w - 2) + '+',
|
|
1026
|
+
];
|
|
1027
|
+
|
|
1028
|
+
addHistory({
|
|
1029
|
+
type: 'info',
|
|
1030
|
+
content: '\n' + lines.join('\n'),
|
|
1031
|
+
});
|
|
1032
|
+
} catch (error) {
|
|
1033
|
+
addHistory({
|
|
1034
|
+
type: 'info',
|
|
1035
|
+
content: `Compaction failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
1036
|
+
});
|
|
1037
|
+
}
|
|
1038
|
+
} else {
|
|
1039
|
+
addHistory({ type: 'info', content: 'Model information not available' });
|
|
1040
|
+
}
|
|
1041
|
+
return true;
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
case 'context': {
|
|
1045
|
+
// Show context usage statistics
|
|
1046
|
+
const sessionMgr = agent.getSessionManager();
|
|
1047
|
+
const stats = sessionMgr.getCompressionStats();
|
|
1048
|
+
|
|
1049
|
+
if (!stats) {
|
|
1050
|
+
addHistory({ type: 'info', content: 'No compression statistics available' });
|
|
1051
|
+
return true;
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
const activeRatio = stats.totalMessages > 0
|
|
1055
|
+
? ((stats.activeMessages / stats.totalMessages) * 100).toFixed(0)
|
|
1056
|
+
: '100';
|
|
1057
|
+
|
|
1058
|
+
const isCompressed = stats.activeMessages < stats.totalMessages;
|
|
1059
|
+
|
|
1060
|
+
// Create progress bar (ASCII only)
|
|
1061
|
+
const barWidth = 20;
|
|
1062
|
+
const filledWidth = stats.totalMessages > 0
|
|
1063
|
+
? Math.round((stats.activeMessages / stats.totalMessages) * barWidth)
|
|
1064
|
+
: barWidth;
|
|
1065
|
+
const progressBar = '#'.repeat(filledWidth) + '.'.repeat(barWidth - filledWidth);
|
|
1066
|
+
|
|
1067
|
+
const statusText = isCompressed ? 'Compressed' : 'Uncompressed';
|
|
1068
|
+
const statusColor = isCompressed ? '\x1b[32m' : '\x1b[90m';
|
|
1069
|
+
|
|
1070
|
+
// Format with simple ASCII box
|
|
1071
|
+
const w = 50;
|
|
1072
|
+
const visibleLength = (text: string) => text.replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
1073
|
+
const pad = (text: string) => {
|
|
1074
|
+
const visible = visibleLength(text);
|
|
1075
|
+
return text + ' '.repeat(Math.max(0, w - visible - 3));
|
|
1076
|
+
};
|
|
1077
|
+
|
|
1078
|
+
const statusLine = `Status: ${statusColor}${statusText}\x1b[0m`;
|
|
1079
|
+
|
|
1080
|
+
const lines = [
|
|
1081
|
+
'+' + '-'.repeat(w - 2) + '+',
|
|
1082
|
+
'| ' + pad('Context Usage Statistics') + '|',
|
|
1083
|
+
'+' + '-'.repeat(w - 2) + '+',
|
|
1084
|
+
'| ' + pad(`Active Messages ${String(stats.activeMessages).padStart(3)}`) + '|',
|
|
1085
|
+
'| ' + pad(`Total Messages ${String(stats.totalMessages).padStart(3)}`) + '|',
|
|
1086
|
+
'| ' + pad(`Summaries ${String(stats.summaryCount).padStart(3)}`) + '|',
|
|
1087
|
+
'| ' + pad('') + '|',
|
|
1088
|
+
'| ' + pad(`Usage [${progressBar}] ${activeRatio.padStart(3)}%`) + '|',
|
|
1089
|
+
'| ' + pad('') + '|',
|
|
1090
|
+
'| ' + pad(statusLine) + '|',
|
|
1091
|
+
'+' + '-'.repeat(w - 2) + '+',
|
|
1092
|
+
];
|
|
1093
|
+
|
|
1094
|
+
addHistory({
|
|
1095
|
+
type: 'info',
|
|
1096
|
+
content: '\n' + lines.join('\n'),
|
|
1097
|
+
});
|
|
1098
|
+
return true;
|
|
1099
|
+
}
|
|
1100
|
+
|
|
714
1101
|
default:
|
|
715
1102
|
return false;
|
|
716
1103
|
}
|
|
@@ -718,6 +1105,8 @@ export function App({ config, settingsManager, resumeLatest, permissionSettings
|
|
|
718
1105
|
|
|
719
1106
|
// Interrupt ref for ESC handling
|
|
720
1107
|
const interruptFlagRef = useRef(false);
|
|
1108
|
+
// AbortController for cancellation support
|
|
1109
|
+
const abortControllerRef = useRef<AbortController | null>(null);
|
|
721
1110
|
|
|
722
1111
|
// Run agent
|
|
723
1112
|
const runAgent = async (prompt: string) => {
|
|
@@ -725,25 +1114,36 @@ export function App({ config, settingsManager, resumeLatest, permissionSettings
|
|
|
725
1114
|
setIsThinking(true);
|
|
726
1115
|
setStreamingText('');
|
|
727
1116
|
streamingTextRef.current = '';
|
|
1117
|
+
// Clear streaming buffer and any pending flush timers
|
|
1118
|
+
streamBufferRef.current = '';
|
|
1119
|
+
if (flushTimerRef.current) {
|
|
1120
|
+
clearTimeout(flushTimerRef.current);
|
|
1121
|
+
flushTimerRef.current = null;
|
|
1122
|
+
}
|
|
728
1123
|
interruptFlagRef.current = false;
|
|
1124
|
+
|
|
1125
|
+
// Create AbortController for this run
|
|
1126
|
+
const abortController = new AbortController();
|
|
1127
|
+
abortControllerRef.current = abortController;
|
|
1128
|
+
|
|
729
1129
|
const startTime = Date.now();
|
|
730
1130
|
setProcessingStartTime(startTime);
|
|
731
1131
|
setTokenCount(0);
|
|
732
1132
|
|
|
733
1133
|
try {
|
|
734
|
-
for await (const event of agent.run(prompt)) {
|
|
1134
|
+
for await (const event of agent.run(prompt, abortController.signal)) {
|
|
735
1135
|
// Check for interrupt
|
|
736
|
-
if (interruptFlagRef.current) {
|
|
1136
|
+
if (interruptFlagRef.current || abortController.signal.aborted) {
|
|
737
1137
|
break;
|
|
738
1138
|
}
|
|
739
1139
|
|
|
740
1140
|
switch (event.type) {
|
|
741
1141
|
case 'text':
|
|
742
1142
|
setIsThinking(false);
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
// Estimate token count
|
|
746
|
-
setTokenCount((prev) => prev +
|
|
1143
|
+
// Use throttled streaming text update for better performance
|
|
1144
|
+
addStreamingText(event.text);
|
|
1145
|
+
// Estimate token count with language-aware estimation
|
|
1146
|
+
setTokenCount((prev) => prev + estimateTokenDelta(event.text));
|
|
747
1147
|
break;
|
|
748
1148
|
|
|
749
1149
|
case 'tool_start':
|
|
@@ -796,17 +1196,40 @@ export function App({ config, settingsManager, resumeLatest, permissionSettings
|
|
|
796
1196
|
setIsThinking(true);
|
|
797
1197
|
break;
|
|
798
1198
|
|
|
1199
|
+
case 'reasoning_delta':
|
|
1200
|
+
// Display reasoning content from o1/o3/Gemini 3+ models
|
|
1201
|
+
setIsThinking(false);
|
|
1202
|
+
addHistory({
|
|
1203
|
+
type: 'info',
|
|
1204
|
+
content: `💭 Reasoning: ${event.text}`,
|
|
1205
|
+
});
|
|
1206
|
+
break;
|
|
1207
|
+
|
|
1208
|
+
case 'tool_input_delta':
|
|
1209
|
+
// Progressive display of tool input JSON (optional enhancement)
|
|
1210
|
+
// For now, we just accumulate and display when complete
|
|
1211
|
+
// Could be enhanced to show partial JSON in real-time
|
|
1212
|
+
break;
|
|
1213
|
+
|
|
799
1214
|
case 'error':
|
|
800
1215
|
setIsThinking(false);
|
|
801
1216
|
addHistory({ type: 'info', content: `Error: ${event.error.message}` });
|
|
802
1217
|
break;
|
|
803
1218
|
|
|
804
1219
|
case 'done':
|
|
1220
|
+
// Flush any remaining buffered text immediately
|
|
1221
|
+
flushStreamBuffer();
|
|
1222
|
+
|
|
805
1223
|
if (streamingTextRef.current) {
|
|
806
1224
|
addHistory({ type: 'assistant', content: streamingTextRef.current });
|
|
807
1225
|
streamingTextRef.current = '';
|
|
1226
|
+
streamBufferRef.current = '';
|
|
808
1227
|
setStreamingText('');
|
|
809
1228
|
}
|
|
1229
|
+
// Use real token count from usage if available (overrides estimate)
|
|
1230
|
+
if (event.usage) {
|
|
1231
|
+
setTokenCount(event.usage.outputTokens);
|
|
1232
|
+
}
|
|
810
1233
|
// Add completion message with duration and cost info
|
|
811
1234
|
const durationMs = Date.now() - startTime;
|
|
812
1235
|
addHistory({
|
|
@@ -823,10 +1246,27 @@ export function App({ config, settingsManager, resumeLatest, permissionSettings
|
|
|
823
1246
|
type: 'info',
|
|
824
1247
|
content: `Error: ${error instanceof Error ? error.message : String(error)}`,
|
|
825
1248
|
});
|
|
1249
|
+
} finally {
|
|
1250
|
+
// Clean up AbortController
|
|
1251
|
+
abortControllerRef.current = null;
|
|
826
1252
|
}
|
|
827
1253
|
|
|
828
1254
|
setIsProcessing(false);
|
|
829
1255
|
setIsThinking(false);
|
|
1256
|
+
|
|
1257
|
+
// Process next message in queue if any
|
|
1258
|
+
setMessageQueue((queue) => {
|
|
1259
|
+
if (queue.length > 0) {
|
|
1260
|
+
const [nextMessage, ...rest] = queue;
|
|
1261
|
+
// Schedule next message processing
|
|
1262
|
+
setTimeout(() => {
|
|
1263
|
+
addHistory({ type: 'user', content: nextMessage });
|
|
1264
|
+
runAgent(nextMessage);
|
|
1265
|
+
}, 0);
|
|
1266
|
+
return rest;
|
|
1267
|
+
}
|
|
1268
|
+
return queue;
|
|
1269
|
+
});
|
|
830
1270
|
};
|
|
831
1271
|
|
|
832
1272
|
// Handle submit
|
|
@@ -834,6 +1274,12 @@ export function App({ config, settingsManager, resumeLatest, permissionSettings
|
|
|
834
1274
|
const trimmed = text.trim();
|
|
835
1275
|
if (!trimmed) return;
|
|
836
1276
|
|
|
1277
|
+
// Add to input history
|
|
1278
|
+
if (historyManagerRef.current) {
|
|
1279
|
+
historyManagerRef.current.add(trimmed);
|
|
1280
|
+
historyManagerRef.current.reset(); // Reset navigation state
|
|
1281
|
+
}
|
|
1282
|
+
|
|
837
1283
|
// Auto-complete command on Enter if no exact match
|
|
838
1284
|
if (trimmed.startsWith('/') && cmdSuggestions.length > 0) {
|
|
839
1285
|
const exactMatch = cmdSuggestions.find(
|
|
@@ -905,28 +1351,43 @@ export function App({ config, settingsManager, resumeLatest, permissionSettings
|
|
|
905
1351
|
return;
|
|
906
1352
|
}
|
|
907
1353
|
|
|
1354
|
+
// Queue message if already processing
|
|
1355
|
+
if (isProcessing) {
|
|
1356
|
+
setMessageQueue((queue) => [...queue, trimmed]);
|
|
1357
|
+
// Don't add history item - we'll show queue count in the UI
|
|
1358
|
+
return;
|
|
1359
|
+
}
|
|
1360
|
+
|
|
908
1361
|
addHistory({ type: 'user', content: trimmed });
|
|
909
1362
|
await runAgent(trimmed);
|
|
910
1363
|
};
|
|
911
1364
|
|
|
912
1365
|
// Keyboard shortcuts
|
|
913
1366
|
useInput((inputChar, key) => {
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
1367
|
+
// ESC to interrupt processing or cancel history navigation
|
|
1368
|
+
if (key.escape) {
|
|
1369
|
+
if (isProcessing) {
|
|
1370
|
+
// Abort the operation
|
|
1371
|
+
if (abortControllerRef.current) {
|
|
1372
|
+
abortControllerRef.current.abort();
|
|
1373
|
+
}
|
|
1374
|
+
interruptFlagRef.current = true;
|
|
1375
|
+
setIsProcessing(false);
|
|
1376
|
+
setStreamingText('');
|
|
1377
|
+
streamingTextRef.current = '';
|
|
1378
|
+
streamBufferRef.current = '';
|
|
1379
|
+
// Clear pending tool (stop spinner)
|
|
1380
|
+
pendingToolRef.current = null;
|
|
1381
|
+
setPendingTool(null);
|
|
1382
|
+
// Clean up incomplete tool_use messages to prevent API errors
|
|
1383
|
+
agent.cleanupIncompleteMessages();
|
|
1384
|
+
addHistory({ type: 'info', content: 'Interrupted' });
|
|
1385
|
+
} else if (historyManagerRef.current?.isNavigating()) {
|
|
1386
|
+
// Cancel history navigation - restore original input
|
|
1387
|
+
historyManagerRef.current.reset();
|
|
1388
|
+
setInput(historyTempInput);
|
|
1389
|
+
setHistoryTempInput('');
|
|
1390
|
+
}
|
|
930
1391
|
}
|
|
931
1392
|
|
|
932
1393
|
// Shift+Tab to cycle modes: normal → plan → accept → normal
|
|
@@ -967,19 +1428,58 @@ export function App({ config, settingsManager, resumeLatest, permissionSettings
|
|
|
967
1428
|
}
|
|
968
1429
|
}
|
|
969
1430
|
}
|
|
1431
|
+
// Input history navigation (when NOT showing command suggestions)
|
|
1432
|
+
else if (!isProcessing && !confirmState && !questionState && !planApprovalState && historyManagerRef.current) {
|
|
1433
|
+
if (key.upArrow) {
|
|
1434
|
+
// Save current input on first navigation
|
|
1435
|
+
if (!historyManagerRef.current.isNavigating()) {
|
|
1436
|
+
setHistoryTempInput(input);
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1439
|
+
const prevEntry = historyManagerRef.current.previous();
|
|
1440
|
+
if (prevEntry !== null) {
|
|
1441
|
+
setInput(prevEntry);
|
|
1442
|
+
setInputKey((k) => k + 1); // Force cursor to end
|
|
1443
|
+
}
|
|
1444
|
+
} else if (key.downArrow && historyManagerRef.current.isNavigating()) {
|
|
1445
|
+
const nextEntry = historyManagerRef.current.next();
|
|
1446
|
+
if (nextEntry === null) {
|
|
1447
|
+
// Reached end - restore original input
|
|
1448
|
+
setInput(historyTempInput);
|
|
1449
|
+
setHistoryTempInput('');
|
|
1450
|
+
} else {
|
|
1451
|
+
setInput(nextEntry);
|
|
1452
|
+
}
|
|
1453
|
+
setInputKey((k) => k + 1); // Force cursor to end
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
970
1456
|
});
|
|
971
1457
|
|
|
972
1458
|
// Render history item
|
|
973
1459
|
const renderHistoryItem = (item: HistoryItem) => {
|
|
974
1460
|
switch (item.type) {
|
|
975
|
-
case 'header':
|
|
1461
|
+
case 'header': {
|
|
1462
|
+
// Calculate context stats for header
|
|
1463
|
+
const sessionMgr = agent.getSessionManager();
|
|
1464
|
+
const compressionStats = sessionMgr.getCompressionStats();
|
|
1465
|
+
const tokenUsage = sessionMgr.getTokenUsage();
|
|
1466
|
+
const modelInfo = agent.getModelInfo();
|
|
1467
|
+
|
|
1468
|
+
const contextStats = compressionStats && modelInfo && compressionStats.activeMessages > 0 ? {
|
|
1469
|
+
activeMessages: compressionStats.activeMessages,
|
|
1470
|
+
totalMessages: compressionStats.totalMessages,
|
|
1471
|
+
usagePercent: (tokenUsage.total / modelInfo.contextWindow) * 100,
|
|
1472
|
+
} : undefined;
|
|
1473
|
+
|
|
976
1474
|
return (
|
|
977
1475
|
<Header
|
|
978
1476
|
provider={(item.meta?.provider as string) || ''}
|
|
979
1477
|
model={(item.meta?.model as string) || ''}
|
|
980
1478
|
cwd={(item.meta?.cwd as string) || ''}
|
|
1479
|
+
contextStats={contextStats}
|
|
981
1480
|
/>
|
|
982
1481
|
);
|
|
1482
|
+
}
|
|
983
1483
|
case 'welcome':
|
|
984
1484
|
return <WelcomeMessage model={(item.meta?.model as string) || item.content} />;
|
|
985
1485
|
case 'user':
|
|
@@ -1025,6 +1525,14 @@ export function App({ config, settingsManager, resumeLatest, permissionSettings
|
|
|
1025
1525
|
if (item.content === '__MEMORY__' && item.meta?.files) {
|
|
1026
1526
|
return <MemoryFilesDisplay files={item.meta.files as MemoryFileInfo[]} />;
|
|
1027
1527
|
}
|
|
1528
|
+
// Check if content is a formatted box (starts with box border)
|
|
1529
|
+
if (item.content.trim().startsWith('+---')) {
|
|
1530
|
+
return (
|
|
1531
|
+
<Box marginTop={1}>
|
|
1532
|
+
<Text color={colors.textSecondary}>{item.content}</Text>
|
|
1533
|
+
</Box>
|
|
1534
|
+
);
|
|
1535
|
+
}
|
|
1028
1536
|
return <InfoMessage text={item.content} />;
|
|
1029
1537
|
case 'completion':
|
|
1030
1538
|
return (
|
|
@@ -1113,6 +1621,17 @@ export function App({ config, settingsManager, resumeLatest, permissionSettings
|
|
|
1113
1621
|
|
|
1114
1622
|
{!confirmState && !questionState && !showModelSelector && !showProviderManager && (
|
|
1115
1623
|
<Box flexDirection="column" marginTop={2}>
|
|
1624
|
+
{/* Queue display above input */}
|
|
1625
|
+
{messageQueue.length > 0 && (
|
|
1626
|
+
<Box flexDirection="column" marginBottom={1}>
|
|
1627
|
+
{messageQueue.map((msg, i) => (
|
|
1628
|
+
<Text key={i} color={colors.textMuted}>
|
|
1629
|
+
⏳ <Text color={colors.text}>{msg.length > 60 ? msg.slice(0, 60) + '...' : msg}</Text>
|
|
1630
|
+
</Text>
|
|
1631
|
+
))}
|
|
1632
|
+
</Box>
|
|
1633
|
+
)}
|
|
1634
|
+
|
|
1116
1635
|
<PromptInput
|
|
1117
1636
|
key={inputKey}
|
|
1118
1637
|
value={input}
|