attocode 0.1.6 → 0.1.8
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/CHANGELOG.md +23 -2
- package/README.md +47 -0
- package/dist/src/adapters.d.ts +21 -1
- package/dist/src/adapters.d.ts.map +1 -1
- package/dist/src/adapters.js +35 -1
- package/dist/src/adapters.js.map +1 -1
- package/dist/src/agent.d.ts +80 -2
- package/dist/src/agent.d.ts.map +1 -1
- package/dist/src/agent.js +874 -96
- package/dist/src/agent.js.map +1 -1
- package/dist/src/commands/agents-commands.d.ts.map +1 -1
- package/dist/src/commands/agents-commands.js +18 -4
- package/dist/src/commands/agents-commands.js.map +1 -1
- package/dist/src/commands/handler.d.ts.map +1 -1
- package/dist/src/commands/handler.js +126 -5
- package/dist/src/commands/handler.js.map +1 -1
- package/dist/src/defaults.d.ts +33 -1
- package/dist/src/defaults.d.ts.map +1 -1
- package/dist/src/defaults.js +61 -3
- package/dist/src/defaults.js.map +1 -1
- package/dist/src/integrations/agent-registry.d.ts +14 -0
- package/dist/src/integrations/agent-registry.d.ts.map +1 -1
- package/dist/src/integrations/agent-registry.js +4 -4
- package/dist/src/integrations/agent-registry.js.map +1 -1
- package/dist/src/integrations/cancellation.d.ts +62 -0
- package/dist/src/integrations/cancellation.d.ts.map +1 -1
- package/dist/src/integrations/cancellation.js +174 -0
- package/dist/src/integrations/cancellation.js.map +1 -1
- package/dist/src/integrations/dead-letter-queue.js +1 -1
- package/dist/src/integrations/dead-letter-queue.js.map +1 -1
- package/dist/src/integrations/economics.d.ts +41 -0
- package/dist/src/integrations/economics.d.ts.map +1 -1
- package/dist/src/integrations/economics.js +114 -8
- package/dist/src/integrations/economics.js.map +1 -1
- package/dist/src/integrations/history.d.ts +72 -0
- package/dist/src/integrations/history.d.ts.map +1 -0
- package/dist/src/integrations/history.js +165 -0
- package/dist/src/integrations/history.js.map +1 -0
- package/dist/src/integrations/index.d.ts +5 -3
- package/dist/src/integrations/index.d.ts.map +1 -1
- package/dist/src/integrations/index.js +6 -2
- package/dist/src/integrations/index.js.map +1 -1
- package/dist/src/integrations/resources.d.ts +5 -0
- package/dist/src/integrations/resources.d.ts.map +1 -1
- package/dist/src/integrations/resources.js +7 -0
- package/dist/src/integrations/resources.js.map +1 -1
- package/dist/src/integrations/safety.d.ts +3 -1
- package/dist/src/integrations/safety.d.ts.map +1 -1
- package/dist/src/integrations/safety.js +22 -5
- package/dist/src/integrations/safety.js.map +1 -1
- package/dist/src/integrations/sqlite-store.d.ts.map +1 -1
- package/dist/src/integrations/sqlite-store.js +17 -1
- package/dist/src/integrations/sqlite-store.js.map +1 -1
- package/dist/src/integrations/task-manager.d.ts +132 -0
- package/dist/src/integrations/task-manager.d.ts.map +1 -0
- package/dist/src/integrations/task-manager.js +309 -0
- package/dist/src/integrations/task-manager.js.map +1 -0
- package/dist/src/modes/tui.d.ts.map +1 -1
- package/dist/src/modes/tui.js +15 -0
- package/dist/src/modes/tui.js.map +1 -1
- package/dist/src/modes.d.ts +23 -0
- package/dist/src/modes.d.ts.map +1 -1
- package/dist/src/modes.js +61 -0
- package/dist/src/modes.js.map +1 -1
- package/dist/src/providers/adapters/openai.d.ts +46 -2
- package/dist/src/providers/adapters/openai.d.ts.map +1 -1
- package/dist/src/providers/adapters/openai.js +221 -21
- package/dist/src/providers/adapters/openai.js.map +1 -1
- package/dist/src/providers/adapters/openrouter.js +2 -2
- package/dist/src/providers/adapters/openrouter.js.map +1 -1
- package/dist/src/tools/agent.d.ts +18 -1
- package/dist/src/tools/agent.d.ts.map +1 -1
- package/dist/src/tools/agent.js +51 -3
- package/dist/src/tools/agent.js.map +1 -1
- package/dist/src/tools/tasks.d.ts +32 -0
- package/dist/src/tools/tasks.d.ts.map +1 -0
- package/dist/src/tools/tasks.js +334 -0
- package/dist/src/tools/tasks.js.map +1 -0
- package/dist/src/tracing/trace-collector.d.ts +81 -0
- package/dist/src/tracing/trace-collector.d.ts.map +1 -1
- package/dist/src/tracing/trace-collector.js +216 -4
- package/dist/src/tracing/trace-collector.js.map +1 -1
- package/dist/src/tracing/types.d.ts +8 -0
- package/dist/src/tracing/types.d.ts.map +1 -1
- package/dist/src/tracing/types.js.map +1 -1
- package/dist/src/tui/app.d.ts.map +1 -1
- package/dist/src/tui/app.js +459 -114
- package/dist/src/tui/app.js.map +1 -1
- package/dist/src/tui/components/ActiveAgentsPanel.d.ts +45 -0
- package/dist/src/tui/components/ActiveAgentsPanel.d.ts.map +1 -0
- package/dist/src/tui/components/ActiveAgentsPanel.js +121 -0
- package/dist/src/tui/components/ActiveAgentsPanel.js.map +1 -0
- package/dist/src/tui/components/DebugPanel.d.ts +41 -0
- package/dist/src/tui/components/DebugPanel.d.ts.map +1 -0
- package/dist/src/tui/components/DebugPanel.js +104 -0
- package/dist/src/tui/components/DebugPanel.js.map +1 -0
- package/dist/src/tui/components/ErrorBoundary.d.ts +63 -0
- package/dist/src/tui/components/ErrorBoundary.d.ts.map +1 -0
- package/dist/src/tui/components/ErrorBoundary.js +88 -0
- package/dist/src/tui/components/ErrorBoundary.js.map +1 -0
- package/dist/src/tui/components/ErrorDetailPanel.d.ts +49 -0
- package/dist/src/tui/components/ErrorDetailPanel.d.ts.map +1 -0
- package/dist/src/tui/components/ErrorDetailPanel.js +109 -0
- package/dist/src/tui/components/ErrorDetailPanel.js.map +1 -0
- package/dist/src/tui/components/TasksPanel.d.ts +25 -0
- package/dist/src/tui/components/TasksPanel.d.ts.map +1 -0
- package/dist/src/tui/components/TasksPanel.js +101 -0
- package/dist/src/tui/components/TasksPanel.js.map +1 -0
- package/dist/src/tui/components/ToolCallItem.d.ts +3 -4
- package/dist/src/tui/components/ToolCallItem.d.ts.map +1 -1
- package/dist/src/tui/components/ToolCallItem.js +51 -15
- package/dist/src/tui/components/ToolCallItem.js.map +1 -1
- package/dist/src/tui/components/index.d.ts +5 -0
- package/dist/src/tui/components/index.d.ts.map +1 -1
- package/dist/src/tui/components/index.js +10 -0
- package/dist/src/tui/components/index.js.map +1 -1
- package/dist/src/tui/hooks/index.d.ts +7 -0
- package/dist/src/tui/hooks/index.d.ts.map +1 -0
- package/dist/src/tui/hooks/index.js +7 -0
- package/dist/src/tui/hooks/index.js.map +1 -0
- package/dist/src/tui/hooks/useMessagePruning.d.ts +114 -0
- package/dist/src/tui/hooks/useMessagePruning.d.ts.map +1 -0
- package/dist/src/tui/hooks/useMessagePruning.js +127 -0
- package/dist/src/tui/hooks/useMessagePruning.js.map +1 -0
- package/dist/src/tui/index.d.ts +3 -0
- package/dist/src/tui/index.d.ts.map +1 -1
- package/dist/src/tui/index.js +9 -0
- package/dist/src/tui/index.js.map +1 -1
- package/dist/src/tui/utils/index.d.ts +7 -0
- package/dist/src/tui/utils/index.d.ts.map +1 -0
- package/dist/src/tui/utils/index.js +7 -0
- package/dist/src/tui/utils/index.js.map +1 -0
- package/dist/src/tui/utils/keyboard.d.ts +123 -0
- package/dist/src/tui/utils/keyboard.d.ts.map +1 -0
- package/dist/src/tui/utils/keyboard.js +185 -0
- package/dist/src/tui/utils/keyboard.js.map +1 -0
- package/dist/src/types.d.ts +94 -0
- package/dist/src/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/src/tui/app.js
CHANGED
|
@@ -10,7 +10,10 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
10
10
|
*/
|
|
11
11
|
import { useState, useCallback, useEffect, memo, useRef, useMemo } from 'react';
|
|
12
12
|
import { Box, Text, useApp, useInput, Static } from 'ink';
|
|
13
|
-
import {
|
|
13
|
+
import { ActiveAgentsPanel } from './components/ActiveAgentsPanel.js';
|
|
14
|
+
import { TasksPanel } from './components/TasksPanel.js';
|
|
15
|
+
import { ToolCallItem } from './components/ToolCallItem.js';
|
|
16
|
+
import { DebugPanel, useDebugBuffer } from './components/DebugPanel.js';
|
|
14
17
|
import { getTheme, getThemeNames } from './theme/index.js';
|
|
15
18
|
import { ControlledCommandPalette } from './input/CommandPalette.js';
|
|
16
19
|
import { ApprovalDialog } from './components/ApprovalDialog.js';
|
|
@@ -18,6 +21,7 @@ import { TransparencyAggregator } from './transparency-aggregator.js';
|
|
|
18
21
|
import { handleSkillsCommand } from '../commands/skills-commands.js';
|
|
19
22
|
import { handleAgentsCommand } from '../commands/agents-commands.js';
|
|
20
23
|
import { handleInitCommand } from '../commands/init-commands.js';
|
|
24
|
+
import { createHistoryManager } from '../integrations/history.js';
|
|
21
25
|
// =============================================================================
|
|
22
26
|
// PATTERN GENERATION FOR ALWAYS-ALLOW
|
|
23
27
|
// =============================================================================
|
|
@@ -57,98 +61,34 @@ const MessageItem = memo(function MessageItem({ msg, colors }) {
|
|
|
57
61
|
const label = isUser ? 'You' : isAssistant ? 'Assistant' : isError ? 'Error' : 'System';
|
|
58
62
|
return (_jsxs(Box, { marginBottom: 1, flexDirection: "column", children: [_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: roleColor, bold: true, children: icon }), _jsx(Text, { color: roleColor, bold: true, children: label }), _jsx(Text, { color: colors.textMuted, dimColor: true, children: ` ${msg.ts.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' })}` })] }), _jsx(Box, { marginLeft: 2, children: _jsx(Text, { wrap: "wrap", color: isError ? colors.error : colors.text, children: msg.content }) })] }));
|
|
59
63
|
});
|
|
60
|
-
const
|
|
61
|
-
const icon = tc.status === 'success' ? '[OK]' : tc.status === 'error' ? '[X]' : tc.status === 'running' ? '[~]' : '[ ]';
|
|
62
|
-
const statusColor = tc.status === 'success' ? '#98FB98' : tc.status === 'error' ? '#FF6B6B' : tc.status === 'running' ? '#87CEEB' : colors.textMuted;
|
|
63
|
-
// Compact formatting for collapsed view
|
|
64
|
-
const formatToolArgsCompact = (args) => {
|
|
65
|
-
const entries = Object.entries(args);
|
|
66
|
-
if (entries.length === 0)
|
|
67
|
-
return '';
|
|
68
|
-
if (entries.length === 1) {
|
|
69
|
-
const [key, val] = entries[0];
|
|
70
|
-
const valStr = typeof val === 'string' ? val : JSON.stringify(val);
|
|
71
|
-
return valStr.length > 50 ? `${key}: ${valStr.slice(0, 47)}...` : `${key}: ${valStr}`;
|
|
72
|
-
}
|
|
73
|
-
return `{${entries.length} args}`;
|
|
74
|
-
};
|
|
75
|
-
// Expanded formatting - each arg on its own line with proper handling
|
|
76
|
-
const formatToolArgsExpanded = (args) => {
|
|
77
|
-
const entries = Object.entries(args);
|
|
78
|
-
if (entries.length === 0)
|
|
79
|
-
return [];
|
|
80
|
-
return entries.map(([key, val]) => {
|
|
81
|
-
let valStr;
|
|
82
|
-
if (typeof val === 'string') {
|
|
83
|
-
// For strings, show with quotes, handle multiline
|
|
84
|
-
if (val.includes('\n')) {
|
|
85
|
-
const lines = val.split('\n');
|
|
86
|
-
if (lines.length > 3) {
|
|
87
|
-
valStr = `"${lines.slice(0, 3).join('\\n')}..." (${lines.length} lines)`;
|
|
88
|
-
}
|
|
89
|
-
else {
|
|
90
|
-
valStr = `"${val.replace(/\n/g, '\\n')}"`;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
else if (val.length > 100) {
|
|
94
|
-
valStr = `"${val.slice(0, 97)}..."`;
|
|
95
|
-
}
|
|
96
|
-
else {
|
|
97
|
-
valStr = `"${val}"`;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
else if (typeof val === 'object' && val !== null) {
|
|
101
|
-
const json = JSON.stringify(val, null, 2);
|
|
102
|
-
if (json.length > 200) {
|
|
103
|
-
valStr = JSON.stringify(val).slice(0, 197) + '...';
|
|
104
|
-
}
|
|
105
|
-
else {
|
|
106
|
-
valStr = json;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
else {
|
|
110
|
-
valStr = String(val);
|
|
111
|
-
}
|
|
112
|
-
return `${key}: ${valStr}`;
|
|
113
|
-
});
|
|
114
|
-
};
|
|
115
|
-
const argsStr = formatToolArgsCompact(tc.args);
|
|
116
|
-
if (expanded) {
|
|
117
|
-
const expandedArgs = formatToolArgsExpanded(tc.args);
|
|
118
|
-
return (_jsxs(Box, { marginLeft: 2, flexDirection: "column", children: [_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: statusColor, children: icon }), _jsx(Text, { color: "#DDA0DD", bold: true, children: tc.name }), tc.duration ? _jsxs(Text, { color: colors.textMuted, dimColor: true, children: ["(", tc.duration, "ms)"] }) : null] }), expandedArgs.map((argLine, i) => (_jsx(Box, { marginLeft: 3, children: _jsx(Text, { color: "#87CEEB", dimColor: true, children: argLine }) }, i))), tc.status === 'success' && tc.result !== undefined && tc.result !== null ? (_jsx(Box, { marginLeft: 3, flexDirection: "column", children: (tc.name === 'edit_file' || tc.name === 'write_file') &&
|
|
119
|
-
typeof tc.result === 'object' && tc.result !== null &&
|
|
120
|
-
'metadata' in tc.result &&
|
|
121
|
-
typeof tc.result.metadata?.diff === 'string' ? (_jsx(DiffView, { diff: tc.result.metadata.diff, expanded: true, maxLines: 15 })) : (_jsx(Text, { color: "#98FB98", dimColor: true, children: `-> ${String(tc.result).slice(0, 150)}${String(tc.result).length > 150 ? '...' : ''}` })) })) : null, tc.status === 'error' && tc.error && (_jsx(Box, { marginLeft: 3, children: _jsx(Text, { color: "#FF6B6B", children: `x ${tc.error}` }) }))] }));
|
|
122
|
-
}
|
|
123
|
-
// Check if result has diff metadata for collapsed summary
|
|
124
|
-
const hasDiff = tc.status === 'success' &&
|
|
125
|
-
(tc.name === 'edit_file' || tc.name === 'write_file') &&
|
|
126
|
-
typeof tc.result === 'object' && tc.result !== null &&
|
|
127
|
-
'metadata' in tc.result &&
|
|
128
|
-
typeof tc.result.metadata?.diff === 'string';
|
|
129
|
-
return (_jsxs(Box, { marginLeft: 2, gap: 1, children: [_jsx(Text, { color: statusColor, children: icon }), _jsx(Text, { color: "#DDA0DD", bold: true, children: tc.name }), argsStr ? _jsx(Text, { color: colors.textMuted, dimColor: true, children: argsStr }) : null, hasDiff && (_jsx(DiffView, { diff: tc.result.metadata.diff, expanded: false })), tc.duration ? _jsxs(Text, { color: colors.textMuted, dimColor: true, children: ["(", tc.duration, "ms)"] }) : null] }));
|
|
130
|
-
});
|
|
131
|
-
const MemoizedInputArea = memo(function MemoizedInputArea({ onSubmit, disabled, borderColor, textColor, cursorColor, onCtrlC, onCtrlL, onCtrlP, onEscape, onToggleToolExpand, onToggleThinking, onToggleTransparency, onPageUp, onPageDown, onHome, onEnd, commandPaletteOpen, onCommandPaletteInput, approvalDialogOpen, approvalDenyReasonMode, onApprovalApprove, onApprovalAlwaysAllow, onApprovalDeny, onApprovalDenyWithReason, onApprovalCancelDenyReason, onApprovalDenyReasonInput, }) {
|
|
64
|
+
const MemoizedInputArea = memo(function MemoizedInputArea({ onSubmit, disabled, borderColor, textColor, cursorColor, onCtrlC, onCtrlL, onCtrlP, onEscape, onToggleToolExpand, onToggleThinking, onToggleTransparency, onToggleActiveAgents, onToggleTasks, onToggleDebug, onPageUp, onPageDown, onHome, onEnd, commandPaletteOpen, onCommandPaletteInput, approvalDialogOpen, approvalDenyReasonMode, onApprovalApprove, onApprovalAlwaysAllow, onApprovalDeny, onApprovalDenyWithReason, onApprovalCancelDenyReason, onApprovalDenyReasonInput, history = [], onHistorySearch, }) {
|
|
132
65
|
const [value, setValue] = useState('');
|
|
133
66
|
const [cursorPos, setCursorPos] = useState(0);
|
|
67
|
+
// History navigation state
|
|
68
|
+
const [historyIndex, setHistoryIndex] = useState(-1); // -1 = current input (not browsing history)
|
|
69
|
+
const [savedInput, setSavedInput] = useState(''); // Preserve current input when browsing
|
|
70
|
+
const historyRef = useRef(history);
|
|
71
|
+
historyRef.current = history;
|
|
134
72
|
// Store callbacks in refs so useInput doesn't re-subscribe on prop changes
|
|
135
73
|
const callbacksRef = useRef({
|
|
136
74
|
onSubmit, onCtrlC, onCtrlL, onCtrlP, onEscape,
|
|
137
|
-
onToggleToolExpand, onToggleThinking, onToggleTransparency,
|
|
75
|
+
onToggleToolExpand, onToggleThinking, onToggleTransparency, onToggleActiveAgents, onToggleTasks, onToggleDebug,
|
|
138
76
|
onPageUp, onPageDown, onHome, onEnd,
|
|
139
77
|
commandPaletteOpen, onCommandPaletteInput,
|
|
140
78
|
approvalDialogOpen, approvalDenyReasonMode,
|
|
141
79
|
onApprovalApprove, onApprovalAlwaysAllow, onApprovalDeny, onApprovalDenyWithReason,
|
|
142
80
|
onApprovalCancelDenyReason, onApprovalDenyReasonInput,
|
|
81
|
+
onHistorySearch,
|
|
143
82
|
});
|
|
144
83
|
callbacksRef.current = {
|
|
145
84
|
onSubmit, onCtrlC, onCtrlL, onCtrlP, onEscape,
|
|
146
|
-
onToggleToolExpand, onToggleThinking, onToggleTransparency,
|
|
85
|
+
onToggleToolExpand, onToggleThinking, onToggleTransparency, onToggleActiveAgents, onToggleTasks, onToggleDebug,
|
|
147
86
|
onPageUp, onPageDown, onHome, onEnd,
|
|
148
87
|
commandPaletteOpen, onCommandPaletteInput,
|
|
149
88
|
approvalDialogOpen, approvalDenyReasonMode,
|
|
150
89
|
onApprovalApprove, onApprovalAlwaysAllow, onApprovalDeny, onApprovalDenyWithReason,
|
|
151
90
|
onApprovalCancelDenyReason, onApprovalDenyReasonInput,
|
|
91
|
+
onHistorySearch,
|
|
152
92
|
};
|
|
153
93
|
const disabledRef = useRef(disabled);
|
|
154
94
|
disabledRef.current = disabled;
|
|
@@ -186,6 +126,21 @@ const MemoizedInputArea = memo(function MemoizedInputArea({ onSubmit, disabled,
|
|
|
186
126
|
cb.onToggleTransparency?.();
|
|
187
127
|
return;
|
|
188
128
|
}
|
|
129
|
+
// Alt+A / Option+A - Toggle active agents panel
|
|
130
|
+
if (input === '\u00e5' || (key.meta && input === 'a')) {
|
|
131
|
+
cb.onToggleActiveAgents?.();
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
// Alt+K / Option+K - Toggle tasks panel
|
|
135
|
+
if (input === '\u02da' || (key.meta && input === 'k')) {
|
|
136
|
+
cb.onToggleTasks?.();
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
// Alt+D / Option+D - Toggle debug panel
|
|
140
|
+
if (input === '\u2202' || (key.meta && input === 'd')) {
|
|
141
|
+
cb.onToggleDebug?.();
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
189
144
|
// Command palette keyboard handling (when open)
|
|
190
145
|
if (cb.commandPaletteOpen && cb.onCommandPaletteInput) {
|
|
191
146
|
cb.onCommandPaletteInput(input, key);
|
|
@@ -249,10 +204,18 @@ const MemoizedInputArea = memo(function MemoizedInputArea({ onSubmit, disabled,
|
|
|
249
204
|
// Input handling (only when not disabled)
|
|
250
205
|
if (disabledRef.current)
|
|
251
206
|
return;
|
|
207
|
+
// Shift+Enter for multiline input (insert newline)
|
|
208
|
+
if (key.return && key.shift) {
|
|
209
|
+
setValue(v => v.slice(0, cursorPos) + '\n' + v.slice(cursorPos));
|
|
210
|
+
setCursorPos(p => p + 1);
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
252
213
|
if (key.return && value.trim()) {
|
|
253
214
|
cb.onSubmit(value);
|
|
254
215
|
setValue('');
|
|
255
216
|
setCursorPos(0);
|
|
217
|
+
setHistoryIndex(-1); // Reset history navigation
|
|
218
|
+
setSavedInput('');
|
|
256
219
|
return;
|
|
257
220
|
}
|
|
258
221
|
if (key.backspace || key.delete) {
|
|
@@ -270,6 +233,54 @@ const MemoizedInputArea = memo(function MemoizedInputArea({ onSubmit, disabled,
|
|
|
270
233
|
setCursorPos(p => Math.min(value.length, p + 1));
|
|
271
234
|
return;
|
|
272
235
|
}
|
|
236
|
+
// History navigation with up/down arrows
|
|
237
|
+
if (key.upArrow && historyRef.current.length > 0) {
|
|
238
|
+
setHistoryIndex(prevIndex => {
|
|
239
|
+
const maxIndex = historyRef.current.length - 1;
|
|
240
|
+
if (prevIndex === -1) {
|
|
241
|
+
// First press - save current input and go to most recent history
|
|
242
|
+
setSavedInput(value);
|
|
243
|
+
const newValue = historyRef.current[maxIndex] || '';
|
|
244
|
+
setValue(newValue);
|
|
245
|
+
setCursorPos(newValue.length);
|
|
246
|
+
return maxIndex;
|
|
247
|
+
}
|
|
248
|
+
else if (prevIndex > 0) {
|
|
249
|
+
// Go to older entry
|
|
250
|
+
const newIndex = prevIndex - 1;
|
|
251
|
+
const newValue = historyRef.current[newIndex] || '';
|
|
252
|
+
setValue(newValue);
|
|
253
|
+
setCursorPos(newValue.length);
|
|
254
|
+
return newIndex;
|
|
255
|
+
}
|
|
256
|
+
return prevIndex; // Already at oldest
|
|
257
|
+
});
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
if (key.downArrow && historyRef.current.length > 0) {
|
|
261
|
+
setHistoryIndex(prevIndex => {
|
|
262
|
+
if (prevIndex === -1) {
|
|
263
|
+
// Not browsing history, do nothing
|
|
264
|
+
return -1;
|
|
265
|
+
}
|
|
266
|
+
else if (prevIndex < historyRef.current.length - 1) {
|
|
267
|
+
// Go to newer entry
|
|
268
|
+
const newIndex = prevIndex + 1;
|
|
269
|
+
const newValue = historyRef.current[newIndex] || '';
|
|
270
|
+
setValue(newValue);
|
|
271
|
+
setCursorPos(newValue.length);
|
|
272
|
+
return newIndex;
|
|
273
|
+
}
|
|
274
|
+
else {
|
|
275
|
+
// At most recent - restore saved input
|
|
276
|
+
setValue(savedInput);
|
|
277
|
+
setCursorPos(savedInput.length);
|
|
278
|
+
setSavedInput('');
|
|
279
|
+
return -1;
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
273
284
|
if (key.ctrl && input === 'a') {
|
|
274
285
|
setCursorPos(0);
|
|
275
286
|
return;
|
|
@@ -288,7 +299,10 @@ const MemoizedInputArea = memo(function MemoizedInputArea({ onSubmit, disabled,
|
|
|
288
299
|
setCursorPos(p => p + input.length);
|
|
289
300
|
}
|
|
290
301
|
});
|
|
291
|
-
|
|
302
|
+
// Check if multiline (for visual indicator)
|
|
303
|
+
const isMultiline = value.includes('\n');
|
|
304
|
+
const lineCount = value.split('\n').length;
|
|
305
|
+
return (_jsx(Box, { borderStyle: "round", borderColor: disabledRef.current ? '#666' : borderColor, paddingX: 1, flexDirection: "column", children: _jsxs(Box, { children: [_jsxs(Text, { color: textColor, bold: true, children: [isMultiline ? '»' : '>', " "] }), _jsx(Text, { children: value.slice(0, cursorPos).replace(/\n/g, '⏎') }), !disabled && (_jsx(Text, { backgroundColor: cursorColor, color: "#1a1a2e", children: value[cursorPos] === '\n' ? '⏎' : (value[cursorPos] ?? ' ') })), _jsx(Text, { children: value.slice(cursorPos + 1).replace(/\n/g, '⏎') }), isMultiline && (_jsxs(Text, { color: "#666", dimColor: true, children: [" (", lineCount, " lines)"] }))] }) }));
|
|
292
306
|
}, (prevProps, nextProps) => {
|
|
293
307
|
// Custom comparison: only re-render if visual props change
|
|
294
308
|
return prevProps.disabled === nextProps.disabled &&
|
|
@@ -314,12 +328,27 @@ export function TUIApp({ agent, sessionStore, mcpClient, compactor, lspManager,
|
|
|
314
328
|
const [contextTokens, setContextTokens] = useState(0);
|
|
315
329
|
const [elapsedTime, setElapsedTime] = useState(0);
|
|
316
330
|
const processingStartRef = useRef(null);
|
|
331
|
+
// Command history manager (persistent)
|
|
332
|
+
const historyManagerRef = useRef(null);
|
|
333
|
+
if (!historyManagerRef.current) {
|
|
334
|
+
historyManagerRef.current = createHistoryManager();
|
|
335
|
+
}
|
|
336
|
+
const [historyEntries, setHistoryEntries] = useState(() => historyManagerRef.current?.getHistory() || []);
|
|
337
|
+
// Debug buffer for debug panel
|
|
338
|
+
const debugBuffer = useDebugBuffer(100);
|
|
317
339
|
const [executionMode, setExecutionMode] = useState('idle');
|
|
318
340
|
const executionModeRef = useRef('idle');
|
|
319
341
|
// Display toggles
|
|
320
342
|
const [toolCallsExpanded, setToolCallsExpanded] = useState(false);
|
|
321
343
|
const [showThinking, setShowThinking] = useState(true);
|
|
322
344
|
const [transparencyExpanded, setTransparencyExpanded] = useState(false);
|
|
345
|
+
const [activeAgentsExpanded, setActiveAgentsExpanded] = useState(true);
|
|
346
|
+
const [tasksExpanded, setTasksExpanded] = useState(true);
|
|
347
|
+
const [debugExpanded, setDebugExpanded] = useState(false);
|
|
348
|
+
// Active agents tracking (for Active Agents Panel)
|
|
349
|
+
const [activeAgents, setActiveAgents] = useState([]);
|
|
350
|
+
// Tasks tracking (for Tasks Panel)
|
|
351
|
+
const [tasks, setTasks] = useState([]);
|
|
323
352
|
// Command palette state
|
|
324
353
|
const [commandPaletteOpen, setCommandPaletteOpen] = useState(false);
|
|
325
354
|
const [commandPaletteQuery, setCommandPaletteQuery] = useState('');
|
|
@@ -333,6 +362,9 @@ export function TUIApp({ agent, sessionStore, mcpClient, compactor, lspManager,
|
|
|
333
362
|
// Transparency state
|
|
334
363
|
const [transparencyState, setTransparencyState] = useState(null);
|
|
335
364
|
const transparencyAggregatorRef = useRef(null);
|
|
365
|
+
// Consecutive Ctrl+C tracking for force exit
|
|
366
|
+
const [ctrlCCount, setCtrlCCount] = useState(0);
|
|
367
|
+
const ctrlCTimerRef = useRef(null);
|
|
336
368
|
// Refs for stable callbacks
|
|
337
369
|
const isProcessingRef = useRef(isProcessing);
|
|
338
370
|
const messagesLengthRef = useRef(messages.length);
|
|
@@ -348,6 +380,22 @@ export function TUIApp({ agent, sessionStore, mcpClient, compactor, lspManager,
|
|
|
348
380
|
const uniqueId = `${role}-${Date.now()}-${++messageIdCounter.current}`;
|
|
349
381
|
setMessages(prev => [...prev, { id: uniqueId, role, content, ts: new Date() }]);
|
|
350
382
|
}, []);
|
|
383
|
+
const persistPendingPlanToStore = useCallback(() => {
|
|
384
|
+
if (!agent.hasPendingPlan())
|
|
385
|
+
return;
|
|
386
|
+
if (!('savePendingPlan' in sessionStore) || typeof sessionStore.savePendingPlan !== 'function') {
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
const pendingPlan = agent.getPendingPlan();
|
|
390
|
+
if (!pendingPlan)
|
|
391
|
+
return;
|
|
392
|
+
sessionStore.savePendingPlan(pendingPlan, currentSessionId);
|
|
393
|
+
persistenceDebug.log('Pending plan saved', {
|
|
394
|
+
planId: pendingPlan.id,
|
|
395
|
+
changes: pendingPlan.proposedChanges.length,
|
|
396
|
+
sessionId: currentSessionId,
|
|
397
|
+
});
|
|
398
|
+
}, [agent, sessionStore, currentSessionId, persistenceDebug]);
|
|
351
399
|
// =========================================================================
|
|
352
400
|
// APPROVAL DIALOG HANDLERS
|
|
353
401
|
// =========================================================================
|
|
@@ -420,6 +468,10 @@ export function TUIApp({ agent, sessionStore, mcpClient, compactor, lspManager,
|
|
|
420
468
|
const mode = executionModeRef.current;
|
|
421
469
|
if (mode === 'idle')
|
|
422
470
|
return; // No active execution, ignore events
|
|
471
|
+
// Log event to debug buffer
|
|
472
|
+
if (debugExpanded) {
|
|
473
|
+
debugBuffer.debug(`Event: ${event.type}`, event);
|
|
474
|
+
}
|
|
423
475
|
// Extract subagent from event if present (not all events have it)
|
|
424
476
|
const eventWithSubagent = event;
|
|
425
477
|
const subagentPrefix = eventWithSubagent.subagent ? `[${eventWithSubagent.subagent}] ` : '';
|
|
@@ -441,26 +493,66 @@ export function TUIApp({ agent, sessionStore, mcpClient, compactor, lspManager,
|
|
|
441
493
|
// -------------------------------------------------------------------------
|
|
442
494
|
// Shared events (both processing and approving modes)
|
|
443
495
|
// -------------------------------------------------------------------------
|
|
444
|
-
// Subagent lifecycle events
|
|
496
|
+
// Subagent lifecycle events - also update Active Agents Panel
|
|
445
497
|
if (event.type === 'agent.spawn') {
|
|
446
498
|
const e = event;
|
|
499
|
+
const agentId = e.agentId || `spawn-${Date.now()}`;
|
|
447
500
|
addMessage('system', `[AGENT] Spawning ${e.name}: ${e.task.slice(0, 100)}${e.task.length > 100 ? '...' : ''}`);
|
|
501
|
+
// Add to active agents panel
|
|
502
|
+
setActiveAgents(prev => [...prev, {
|
|
503
|
+
id: agentId,
|
|
504
|
+
type: e.name,
|
|
505
|
+
task: e.task,
|
|
506
|
+
status: 'running',
|
|
507
|
+
tokens: 0,
|
|
508
|
+
startTime: Date.now(),
|
|
509
|
+
}]);
|
|
448
510
|
return;
|
|
449
511
|
}
|
|
450
512
|
if (event.type === 'agent.complete') {
|
|
451
513
|
const e = event;
|
|
452
514
|
const statusText = e.success ? 'completed' : 'failed';
|
|
453
|
-
|
|
454
|
-
|
|
515
|
+
const displayName = e.agentType || e.agentId;
|
|
516
|
+
addMessage('system', `[AGENT] ${displayName} ${statusText}`);
|
|
517
|
+
// Show output preview if substantive (increased from 300 to 1000 chars)
|
|
455
518
|
if (e.output && e.output.length > 50) {
|
|
456
|
-
const preview = e.output.slice(0,
|
|
457
|
-
|
|
519
|
+
const preview = e.output.slice(0, 1000);
|
|
520
|
+
const truncated = e.output.length > 1000;
|
|
521
|
+
addMessage('system', `[AGENT OUTPUT]\n${preview}${truncated ? `\n...(full output: ${e.output.length} chars)` : ''}`);
|
|
458
522
|
}
|
|
523
|
+
// Update active agents panel - use strict ID matching
|
|
524
|
+
setActiveAgents(prev => prev.map(a => a.id === e.agentId
|
|
525
|
+
? { ...a, status: e.success ? 'completed' : 'error' }
|
|
526
|
+
: a));
|
|
459
527
|
return;
|
|
460
528
|
}
|
|
461
529
|
if (event.type === 'agent.error') {
|
|
462
530
|
const e = event;
|
|
463
|
-
|
|
531
|
+
const displayName = e.agentType || e.agentId;
|
|
532
|
+
addMessage('system', `[AGENT] ${displayName} error: ${e.error}`);
|
|
533
|
+
// For timeout errors, use 'timing_out' status first to indicate the agent
|
|
534
|
+
// is in the process of stopping. Then transition to 'timeout' after a delay.
|
|
535
|
+
// This provides better UX than immediately showing "failed" while tokens accumulate.
|
|
536
|
+
const isTimeout = e.error.includes('timed out') || e.error.includes('Timed out');
|
|
537
|
+
if (isTimeout) {
|
|
538
|
+
// First, mark as timing_out - use strict ID matching
|
|
539
|
+
setActiveAgents(prev => prev.map(a => a.id === e.agentId
|
|
540
|
+
? { ...a, status: 'timing_out' }
|
|
541
|
+
: a));
|
|
542
|
+
// After 3 seconds, transition to final timeout status
|
|
543
|
+
// (agent should have stopped by then due to cancellation token check)
|
|
544
|
+
setTimeout(() => {
|
|
545
|
+
setActiveAgents(prev => prev.map(a => a.id === e.agentId && a.status === 'timing_out'
|
|
546
|
+
? { ...a, status: 'timeout' }
|
|
547
|
+
: a));
|
|
548
|
+
}, 3000);
|
|
549
|
+
}
|
|
550
|
+
else {
|
|
551
|
+
// Regular error - set immediately with strict ID matching
|
|
552
|
+
setActiveAgents(prev => prev.map(a => a.id === e.agentId
|
|
553
|
+
? { ...a, status: 'error' }
|
|
554
|
+
: a));
|
|
555
|
+
}
|
|
464
556
|
return;
|
|
465
557
|
}
|
|
466
558
|
if (event.type === 'agent.pending_plan') {
|
|
@@ -529,10 +621,33 @@ export function TUIApp({ agent, sessionStore, mcpClient, compactor, lspManager,
|
|
|
529
621
|
addMessage('error', `${prefix} ${errorMsg}`);
|
|
530
622
|
return;
|
|
531
623
|
}
|
|
532
|
-
// Insight events
|
|
533
|
-
if (event.type === 'insight.tokens'
|
|
624
|
+
// Insight events - also track tokens for active agents
|
|
625
|
+
if (event.type === 'insight.tokens') {
|
|
534
626
|
const e = event;
|
|
535
|
-
|
|
627
|
+
if (showThinking) {
|
|
628
|
+
addMessage('system', `${subagentPrefix}* ${e.inputTokens.toLocaleString()} in, ${e.outputTokens.toLocaleString()} out${e.cost ? ` $${e.cost.toFixed(6)}` : ''}`);
|
|
629
|
+
}
|
|
630
|
+
// Update tokens for active agent if this event is from a subagent
|
|
631
|
+
// IMPORTANT: Don't update tokens for agents that are timing_out/timeout/error
|
|
632
|
+
// These agents should have stopped, and any lingering events are from
|
|
633
|
+
// zombie processes that we don't want to count.
|
|
634
|
+
if (e.subagent || eventWithSubagent.subagent) {
|
|
635
|
+
const subagentId = e.subagentId || eventWithSubagent.subagentId;
|
|
636
|
+
const agentName = e.subagent || eventWithSubagent.subagent;
|
|
637
|
+
setActiveAgents(prev => prev.map(a => {
|
|
638
|
+
// Use strict ID matching when subagentId is available (prevents duplicate counting
|
|
639
|
+
// when multiple agents of the same type run in parallel)
|
|
640
|
+
const matchesAgent = subagentId
|
|
641
|
+
? a.id === subagentId
|
|
642
|
+
: (a.type === agentName || a.id.includes(agentName || ''));
|
|
643
|
+
const isStillRunning = a.status === 'running';
|
|
644
|
+
// Only update tokens if agent is still running
|
|
645
|
+
if (matchesAgent && isStillRunning) {
|
|
646
|
+
return { ...a, tokens: a.tokens + (e.inputTokens || 0) + (e.outputTokens || 0) };
|
|
647
|
+
}
|
|
648
|
+
return a;
|
|
649
|
+
}));
|
|
650
|
+
}
|
|
536
651
|
return;
|
|
537
652
|
}
|
|
538
653
|
// Resilience events
|
|
@@ -546,15 +661,45 @@ export function TUIApp({ agent, sessionStore, mcpClient, compactor, lspManager,
|
|
|
546
661
|
addMessage('system', `[RECOVERED] ${e.reason} after ${e.attempts} attempt(s)`);
|
|
547
662
|
return;
|
|
548
663
|
}
|
|
549
|
-
// Subagent visibility events
|
|
664
|
+
// Subagent visibility events - also update Active Agents Panel
|
|
550
665
|
if (event.type === 'subagent.iteration') {
|
|
551
666
|
const e = event;
|
|
552
667
|
setStatus(s => ({ ...s, mode: `${e.agentId} iter ${e.iteration}/${e.maxIterations}` }));
|
|
668
|
+
// Update active agents panel with iteration info
|
|
669
|
+
// Use subagentId for strict matching when available (parallel same-type agents)
|
|
670
|
+
setActiveAgents(prev => prev.map(a => {
|
|
671
|
+
const matches = e.subagentId
|
|
672
|
+
? a.id === e.subagentId
|
|
673
|
+
: (a.type === e.agentId || a.id.includes(e.agentId));
|
|
674
|
+
return matches ? { ...a, iteration: e.iteration, maxIterations: e.maxIterations } : a;
|
|
675
|
+
}));
|
|
553
676
|
return;
|
|
554
677
|
}
|
|
555
678
|
if (event.type === 'subagent.phase') {
|
|
556
679
|
const e = event;
|
|
557
680
|
setStatus(s => ({ ...s, mode: `${e.agentId} ${e.phase}` }));
|
|
681
|
+
// Update active agents panel with phase info
|
|
682
|
+
// Use subagentId for strict matching when available (parallel same-type agents)
|
|
683
|
+
setActiveAgents(prev => prev.map(a => {
|
|
684
|
+
const matches = e.subagentId
|
|
685
|
+
? a.id === e.subagentId
|
|
686
|
+
: (a.type === e.agentId || a.id.includes(e.agentId));
|
|
687
|
+
return matches ? { ...a, currentPhase: e.phase } : a;
|
|
688
|
+
}));
|
|
689
|
+
return;
|
|
690
|
+
}
|
|
691
|
+
// Task events - update Tasks Panel
|
|
692
|
+
if (event.type === 'task.created') {
|
|
693
|
+
const e = event;
|
|
694
|
+
setTasks(prev => [...prev, e.task]);
|
|
695
|
+
addMessage('system', `[TASK] Created: ${e.task.subject}`);
|
|
696
|
+
return;
|
|
697
|
+
}
|
|
698
|
+
if (event.type === 'task.updated') {
|
|
699
|
+
const e = event;
|
|
700
|
+
setTasks(prev => prev.map(t => t.id === e.task.id ? e.task : t));
|
|
701
|
+
// Only log status changes
|
|
702
|
+
addMessage('system', `[TASK] ${e.task.subject}: ${e.task.status}`);
|
|
558
703
|
return;
|
|
559
704
|
}
|
|
560
705
|
// -------------------------------------------------------------------------
|
|
@@ -673,9 +818,11 @@ export function TUIApp({ agent, sessionStore, mcpClient, compactor, lspManager,
|
|
|
673
818
|
' /theme [name] Show/change theme',
|
|
674
819
|
' /tools List tools',
|
|
675
820
|
'',
|
|
676
|
-
'> SESSIONS',
|
|
821
|
+
'> SESSIONS & TASKS',
|
|
677
822
|
' /save Save session',
|
|
678
823
|
' /sessions List sessions',
|
|
824
|
+
' /load <id> Load session by ID',
|
|
825
|
+
' /tasks List tracked tasks',
|
|
679
826
|
' /checkpoint Create checkpoint',
|
|
680
827
|
' /checkpoints List checkpoints',
|
|
681
828
|
' /restore <id> Restore checkpoint',
|
|
@@ -727,6 +874,8 @@ export function TUIApp({ agent, sessionStore, mcpClient, compactor, lspManager,
|
|
|
727
874
|
' Alt+T Toggle tool details',
|
|
728
875
|
' Alt+O Toggle thinking',
|
|
729
876
|
' Alt+I Toggle transparency panel',
|
|
877
|
+
' Alt+K Toggle tasks panel',
|
|
878
|
+
' Alt+D Toggle debug panel',
|
|
730
879
|
'========================',
|
|
731
880
|
].join('\n'));
|
|
732
881
|
return;
|
|
@@ -750,6 +899,7 @@ export function TUIApp({ agent, sessionStore, mcpClient, compactor, lspManager,
|
|
|
750
899
|
plan: agentState.plan,
|
|
751
900
|
memoryContext: agentState.memoryContext,
|
|
752
901
|
});
|
|
902
|
+
persistPendingPlanToStore();
|
|
753
903
|
addMessage('system', `Session saved: ${currentSessionId} (checkpoint: ${ckptId})`);
|
|
754
904
|
}
|
|
755
905
|
catch (e) {
|
|
@@ -765,6 +915,98 @@ export function TUIApp({ agent, sessionStore, mcpClient, compactor, lspManager,
|
|
|
765
915
|
addMessage('error', e.message);
|
|
766
916
|
}
|
|
767
917
|
return;
|
|
918
|
+
case 'load': {
|
|
919
|
+
const targetSessionId = args[0];
|
|
920
|
+
if (!targetSessionId) {
|
|
921
|
+
addMessage('system', 'Usage: /load <session-id>\n Use /sessions to list available sessions');
|
|
922
|
+
return;
|
|
923
|
+
}
|
|
924
|
+
// Check if session exists
|
|
925
|
+
const targetSession = sessionStore.getSessionMetadata(targetSessionId);
|
|
926
|
+
if (!targetSession) {
|
|
927
|
+
addMessage('error', `Session not found: ${targetSessionId}\n Use /sessions to list available sessions`);
|
|
928
|
+
return;
|
|
929
|
+
}
|
|
930
|
+
try {
|
|
931
|
+
addMessage('system', `Loading session: ${targetSession.id}\n Created: ${new Date(targetSession.createdAt).toLocaleString()}\n Messages: ${targetSession.messageCount}`);
|
|
932
|
+
// Try to load from checkpoint first
|
|
933
|
+
let loadCheckpointData;
|
|
934
|
+
if ('loadLatestCheckpoint' in sessionStore && typeof sessionStore.loadLatestCheckpoint === 'function') {
|
|
935
|
+
const sqliteCheckpoint = sessionStore.loadLatestCheckpoint(targetSession.id);
|
|
936
|
+
if (sqliteCheckpoint?.state) {
|
|
937
|
+
loadCheckpointData = sqliteCheckpoint.state;
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
// Fall back to loading from entries if no checkpoint
|
|
941
|
+
if (!loadCheckpointData) {
|
|
942
|
+
const entriesResult = sessionStore.loadSession(targetSession.id);
|
|
943
|
+
const entries = Array.isArray(entriesResult) ? entriesResult : await entriesResult;
|
|
944
|
+
const checkpoint = [...entries].reverse().find((e) => e.type === 'checkpoint');
|
|
945
|
+
if (checkpoint?.data) {
|
|
946
|
+
loadCheckpointData = checkpoint.data;
|
|
947
|
+
}
|
|
948
|
+
else {
|
|
949
|
+
const messages = entries
|
|
950
|
+
.filter((e) => e.type === 'message')
|
|
951
|
+
.map((e) => e.data);
|
|
952
|
+
if (messages.length > 0) {
|
|
953
|
+
agent.loadState({ messages });
|
|
954
|
+
addMessage('system', `+ Loaded ${messages.length} messages from session`);
|
|
955
|
+
}
|
|
956
|
+
else {
|
|
957
|
+
addMessage('system', 'No messages found in session');
|
|
958
|
+
}
|
|
959
|
+
return;
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
// Load from checkpoint data
|
|
963
|
+
if (loadCheckpointData?.messages) {
|
|
964
|
+
agent.loadState({
|
|
965
|
+
messages: loadCheckpointData.messages,
|
|
966
|
+
iteration: loadCheckpointData.iteration,
|
|
967
|
+
metrics: loadCheckpointData.metrics,
|
|
968
|
+
plan: loadCheckpointData.plan,
|
|
969
|
+
memoryContext: loadCheckpointData.memoryContext,
|
|
970
|
+
});
|
|
971
|
+
addMessage('system', `+ Loaded ${loadCheckpointData.messages.length} messages from session${loadCheckpointData.iteration ? `\n Iteration: ${loadCheckpointData.iteration}` : ''}${loadCheckpointData.plan ? '\n Plan restored' : ''}`);
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
catch (e) {
|
|
975
|
+
addMessage('error', `Error loading session: ${e.message}`);
|
|
976
|
+
}
|
|
977
|
+
return;
|
|
978
|
+
}
|
|
979
|
+
case 'tasks': {
|
|
980
|
+
// Filter out deleted tasks
|
|
981
|
+
const visibleTasks = tasks.filter(t => t.status !== 'deleted');
|
|
982
|
+
if (visibleTasks.length === 0) {
|
|
983
|
+
addMessage('system', 'No tasks. Tasks are created when the agent uses task_create tool.');
|
|
984
|
+
return;
|
|
985
|
+
}
|
|
986
|
+
// Count by status
|
|
987
|
+
const pending = visibleTasks.filter(t => t.status === 'pending').length;
|
|
988
|
+
const inProgress = visibleTasks.filter(t => t.status === 'in_progress').length;
|
|
989
|
+
const completed = visibleTasks.filter(t => t.status === 'completed').length;
|
|
990
|
+
// Format task list
|
|
991
|
+
const taskLines = visibleTasks.map(t => {
|
|
992
|
+
const isBlocked = t.blockedBy.some(id => {
|
|
993
|
+
const blocker = visibleTasks.find(bt => bt.id === id);
|
|
994
|
+
return blocker && blocker.status !== 'completed';
|
|
995
|
+
});
|
|
996
|
+
const icon = isBlocked ? '◌' : t.status === 'completed' ? '✓' : t.status === 'in_progress' ? '●' : '○';
|
|
997
|
+
const blockedInfo = isBlocked ? ` (blocked by: ${t.blockedBy.slice(0, 2).join(', ')})` : '';
|
|
998
|
+
const activeInfo = t.status === 'in_progress' && t.activeForm ? `\n └ ${t.activeForm}...` : '';
|
|
999
|
+
return ` ${icon} ${t.id} ${t.subject}${blockedInfo}${activeInfo}`;
|
|
1000
|
+
});
|
|
1001
|
+
addMessage('system', [
|
|
1002
|
+
`TASKS [${pending} pending, ${inProgress} in_progress, ${completed} completed]`,
|
|
1003
|
+
'',
|
|
1004
|
+
...taskLines,
|
|
1005
|
+
'',
|
|
1006
|
+
'Toggle panel: Alt+K',
|
|
1007
|
+
].join('\n'));
|
|
1008
|
+
return;
|
|
1009
|
+
}
|
|
768
1010
|
case 'context':
|
|
769
1011
|
case 'ctx': {
|
|
770
1012
|
const agentState = agent.getState();
|
|
@@ -1188,7 +1430,7 @@ export function TUIApp({ agent, sessionStore, mcpClient, compactor, lspManager,
|
|
|
1188
1430
|
case 'trace': {
|
|
1189
1431
|
const traceCollector = agent.getTraceCollector();
|
|
1190
1432
|
if (args.length === 0) {
|
|
1191
|
-
// Show current session trace summary
|
|
1433
|
+
// Show current session trace summary with subagent hierarchy
|
|
1192
1434
|
if (!traceCollector) {
|
|
1193
1435
|
addMessage('system', 'Tracing is not enabled. Start agent with --trace to enable.');
|
|
1194
1436
|
return;
|
|
@@ -1198,24 +1440,56 @@ export function TUIApp({ agent, sessionStore, mcpClient, compactor, lspManager,
|
|
|
1198
1440
|
addMessage('system', 'No trace data collected yet.');
|
|
1199
1441
|
return;
|
|
1200
1442
|
}
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1443
|
+
// Get subagent hierarchy from JSONL file
|
|
1444
|
+
const hierarchy = await traceCollector.getSubagentHierarchy();
|
|
1445
|
+
if (hierarchy && hierarchy.subagents.length > 0) {
|
|
1446
|
+
// Show hierarchy view with subagents
|
|
1447
|
+
const lines = [
|
|
1448
|
+
'Trace Summary:',
|
|
1449
|
+
` Session ID: ${data.sessionId}`,
|
|
1450
|
+
` Status: ${data.status}`,
|
|
1451
|
+
` Duration: ${data.durationMs ? `${Math.round(data.durationMs / 1000)}s` : 'ongoing'}`,
|
|
1452
|
+
'',
|
|
1453
|
+
'Main Agent:',
|
|
1454
|
+
` Iterations: ${hierarchy.mainAgent.llmCalls}`,
|
|
1455
|
+
` Input tokens: ${hierarchy.mainAgent.inputTokens.toLocaleString()}`,
|
|
1456
|
+
` Output tokens: ${hierarchy.mainAgent.outputTokens.toLocaleString()}`,
|
|
1457
|
+
` Tool calls: ${hierarchy.mainAgent.toolCalls}`,
|
|
1458
|
+
'',
|
|
1459
|
+
'Subagent Tree:',
|
|
1460
|
+
];
|
|
1461
|
+
// Sort subagents by spawn time
|
|
1462
|
+
const sortedSubagents = hierarchy.subagents.sort((a, b) => (a.spawnedAtIteration || 0) - (b.spawnedAtIteration || 0));
|
|
1463
|
+
for (const sub of sortedSubagents) {
|
|
1464
|
+
const durationSec = Math.round(sub.duration / 1000);
|
|
1465
|
+
lines.push(` └─ ${sub.agentId} (spawned iter ${sub.spawnedAtIteration || '?'})`);
|
|
1466
|
+
lines.push(` ├─ ${sub.inputTokens.toLocaleString()} in / ${sub.outputTokens.toLocaleString()} out tokens`);
|
|
1467
|
+
lines.push(` ├─ ${sub.toolCalls} tools | ${durationSec}s`);
|
|
1468
|
+
}
|
|
1469
|
+
lines.push('', 'TOTALS (all agents):', ` Input tokens: ${hierarchy.totals.inputTokens.toLocaleString()}`, ` Output tokens: ${hierarchy.totals.outputTokens.toLocaleString()}`, ` Tool calls: ${hierarchy.totals.toolCalls}`, ` LLM calls: ${hierarchy.totals.llmCalls}`, ` Est. Cost: $${hierarchy.totals.estimatedCost.toFixed(4)}`, ` Duration: ${Math.round(hierarchy.totals.duration / 1000)}s`, '', 'Use: /trace --analyze for efficiency analysis', ' /trace issues to see detected inefficiencies');
|
|
1470
|
+
addMessage('system', lines.join('\n'));
|
|
1471
|
+
}
|
|
1472
|
+
else {
|
|
1473
|
+
// Original simple view (no subagents)
|
|
1474
|
+
addMessage('system', [
|
|
1475
|
+
'Trace Summary:',
|
|
1476
|
+
` Session ID: ${data.sessionId}`,
|
|
1477
|
+
` Status: ${data.status}`,
|
|
1478
|
+
` Iterations: ${data.iterations.length}`,
|
|
1479
|
+
` Duration: ${data.durationMs ? `${Math.round(data.durationMs / 1000)}s` : 'ongoing'}`,
|
|
1480
|
+
'',
|
|
1481
|
+
'Metrics:',
|
|
1482
|
+
` Input tokens: ${data.metrics.inputTokens.toLocaleString()}`,
|
|
1483
|
+
` Output tokens: ${data.metrics.outputTokens.toLocaleString()}`,
|
|
1484
|
+
` Cache hit: ${Math.round(data.metrics.avgCacheHitRate * 100)}%`,
|
|
1485
|
+
` Tool calls: ${data.metrics.toolCalls}`,
|
|
1486
|
+
` Errors: ${data.metrics.errors}`,
|
|
1487
|
+
` Est. Cost: $${data.metrics.estimatedCost.toFixed(4)}`,
|
|
1488
|
+
'',
|
|
1489
|
+
'Use: /trace --analyze for efficiency analysis',
|
|
1490
|
+
' /trace issues to see detected inefficiencies',
|
|
1491
|
+
].join('\n'));
|
|
1492
|
+
}
|
|
1219
1493
|
}
|
|
1220
1494
|
else if (args[0] === '--analyze' || args[0] === 'analyze') {
|
|
1221
1495
|
if (!traceCollector) {
|
|
@@ -1337,7 +1611,7 @@ export function TUIApp({ agent, sessionStore, mcpClient, compactor, lspManager,
|
|
|
1337
1611
|
default:
|
|
1338
1612
|
addMessage('system', `Unknown: /${cmd}. Try /help`);
|
|
1339
1613
|
}
|
|
1340
|
-
}, [addMessage, exit, agent, mcpClient, lspManager, sessionStore, compactor, model, currentThemeName, currentSessionId, formatSessionsTable, saveCheckpointToStore, showThinking]);
|
|
1614
|
+
}, [addMessage, exit, agent, mcpClient, lspManager, sessionStore, compactor, model, currentThemeName, currentSessionId, formatSessionsTable, saveCheckpointToStore, showThinking, persistPendingPlanToStore]);
|
|
1341
1615
|
// =========================================================================
|
|
1342
1616
|
// SUBMIT HANDLER
|
|
1343
1617
|
// =========================================================================
|
|
@@ -1345,6 +1619,11 @@ export function TUIApp({ agent, sessionStore, mcpClient, compactor, lspManager,
|
|
|
1345
1619
|
const trimmed = input.trim();
|
|
1346
1620
|
if (!trimmed)
|
|
1347
1621
|
return;
|
|
1622
|
+
// Add to history (persistent)
|
|
1623
|
+
if (historyManagerRef.current) {
|
|
1624
|
+
historyManagerRef.current.addEntry(trimmed);
|
|
1625
|
+
setHistoryEntries(historyManagerRef.current.getHistory());
|
|
1626
|
+
}
|
|
1348
1627
|
addMessage('user', trimmed);
|
|
1349
1628
|
if (trimmed.startsWith('/')) {
|
|
1350
1629
|
const parts = trimmed.slice(1).split(/\s+/);
|
|
@@ -1356,6 +1635,8 @@ export function TUIApp({ agent, sessionStore, mcpClient, compactor, lspManager,
|
|
|
1356
1635
|
executionModeRef.current = 'processing';
|
|
1357
1636
|
setExecutionMode('processing');
|
|
1358
1637
|
setStatus(s => ({ ...s, mode: 'thinking' }));
|
|
1638
|
+
// Reset CPU time counter for per-prompt resource limits (prevents session-wide timeout)
|
|
1639
|
+
agent.resetResourceTimer();
|
|
1359
1640
|
try {
|
|
1360
1641
|
const result = await agent.run(trimmed);
|
|
1361
1642
|
const metrics = agent.getMetrics();
|
|
@@ -1398,6 +1679,7 @@ export function TUIApp({ agent, sessionStore, mcpClient, compactor, lspManager,
|
|
|
1398
1679
|
plan: checkpoint.state.plan,
|
|
1399
1680
|
memoryContext: checkpoint.state.memoryContext,
|
|
1400
1681
|
});
|
|
1682
|
+
persistPendingPlanToStore();
|
|
1401
1683
|
}
|
|
1402
1684
|
catch (e) {
|
|
1403
1685
|
persistenceDebug.error('[TUI] Checkpoint failed', e);
|
|
@@ -1413,7 +1695,7 @@ export function TUIApp({ agent, sessionStore, mcpClient, compactor, lspManager,
|
|
|
1413
1695
|
setIsProcessing(false);
|
|
1414
1696
|
setToolCalls([]);
|
|
1415
1697
|
}
|
|
1416
|
-
}, [addMessage, handleCommand, agent, sessionStore, saveCheckpointToStore, persistenceDebug]);
|
|
1698
|
+
}, [addMessage, handleCommand, agent, sessionStore, saveCheckpointToStore, persistenceDebug, persistPendingPlanToStore]);
|
|
1417
1699
|
// =========================================================================
|
|
1418
1700
|
// COMMAND PALETTE ITEMS
|
|
1419
1701
|
// =========================================================================
|
|
@@ -1423,6 +1705,7 @@ export function TUIApp({ agent, sessionStore, mcpClient, compactor, lspManager,
|
|
|
1423
1705
|
{ id: 'clear', label: 'Clear Screen', shortcut: 'Ctrl+L', category: 'General', action: () => { setMessages([]); setToolCalls([]); } },
|
|
1424
1706
|
{ id: 'save', label: 'Save Session', shortcut: '/save', category: 'Sessions', action: () => handleCommand('save', []) },
|
|
1425
1707
|
{ id: 'sessions', label: 'List Sessions', shortcut: '/sessions', category: 'Sessions', action: () => handleCommand('sessions', []) },
|
|
1708
|
+
{ id: 'load', label: 'Load Session', shortcut: '/load <id>', category: 'Sessions', action: () => handleCommand('sessions', []) }, // Shows sessions, user types /load <id>
|
|
1426
1709
|
{ id: 'context', label: 'Context Info', shortcut: '/context', category: 'Context', action: () => handleCommand('context', []) },
|
|
1427
1710
|
{ id: 'compact', label: 'Compact Context', shortcut: '/compact', category: 'Context', action: () => handleCommand('compact', []) },
|
|
1428
1711
|
{ id: 'mcp', label: 'MCP Servers', shortcut: '/mcp', category: 'MCP', action: () => handleCommand('mcp', []) },
|
|
@@ -1506,8 +1789,33 @@ export function TUIApp({ agent, sessionStore, mcpClient, compactor, lspManager,
|
|
|
1506
1789
|
// KEYBOARD CALLBACKS
|
|
1507
1790
|
// =========================================================================
|
|
1508
1791
|
const handleCtrlC = useCallback(() => {
|
|
1509
|
-
|
|
1510
|
-
|
|
1792
|
+
// Clear any existing timer
|
|
1793
|
+
if (ctrlCTimerRef.current) {
|
|
1794
|
+
clearTimeout(ctrlCTimerRef.current);
|
|
1795
|
+
ctrlCTimerRef.current = null;
|
|
1796
|
+
}
|
|
1797
|
+
setCtrlCCount(prevCount => {
|
|
1798
|
+
const newCount = prevCount + 1;
|
|
1799
|
+
if (newCount >= 2) {
|
|
1800
|
+
// Second Ctrl+C within timeout window - force exit immediately
|
|
1801
|
+
process.exit(1);
|
|
1802
|
+
}
|
|
1803
|
+
// First Ctrl+C - show warning and start graceful cleanup
|
|
1804
|
+
addMessage('system', '[CTRL+C] Press again within 1s to force exit...');
|
|
1805
|
+
// Start graceful cleanup in background
|
|
1806
|
+
agent.cleanup()
|
|
1807
|
+
.then(() => mcpClient.cleanup())
|
|
1808
|
+
.then(() => lspManager.cleanup())
|
|
1809
|
+
.then(() => exit())
|
|
1810
|
+
.catch(() => exit()); // Exit even if cleanup fails
|
|
1811
|
+
// Reset counter after 1 second
|
|
1812
|
+
ctrlCTimerRef.current = setTimeout(() => {
|
|
1813
|
+
setCtrlCCount(0);
|
|
1814
|
+
ctrlCTimerRef.current = null;
|
|
1815
|
+
}, 1000);
|
|
1816
|
+
return newCount;
|
|
1817
|
+
});
|
|
1818
|
+
}, [agent, mcpClient, lspManager, exit, addMessage]);
|
|
1511
1819
|
const handleCtrlL = useCallback(() => {
|
|
1512
1820
|
setMessages([]);
|
|
1513
1821
|
setToolCalls([]);
|
|
@@ -1517,7 +1825,7 @@ export function TUIApp({ agent, sessionStore, mcpClient, compactor, lspManager,
|
|
|
1517
1825
|
setCommandPaletteQuery('');
|
|
1518
1826
|
setCommandPaletteIndex(0);
|
|
1519
1827
|
}, []);
|
|
1520
|
-
const handleEscape = useCallback(() => {
|
|
1828
|
+
const handleEscape = useCallback(async () => {
|
|
1521
1829
|
// Close command palette first if open
|
|
1522
1830
|
if (commandPaletteOpen) {
|
|
1523
1831
|
setCommandPaletteOpen(false);
|
|
@@ -1527,11 +1835,30 @@ export function TUIApp({ agent, sessionStore, mcpClient, compactor, lspManager,
|
|
|
1527
1835
|
}
|
|
1528
1836
|
// Otherwise cancel processing
|
|
1529
1837
|
if (isProcessingRef.current) {
|
|
1838
|
+
// Immediate visual feedback
|
|
1839
|
+
addMessage('system', '[ESC] Stopping agent...');
|
|
1840
|
+
// Autosave checkpoint before cancel (async, don't block)
|
|
1841
|
+
try {
|
|
1842
|
+
const agentState = agent.getState();
|
|
1843
|
+
saveCheckpointToStore(sessionStore, {
|
|
1844
|
+
sessionId: currentSessionId,
|
|
1845
|
+
reason: 'user_cancel',
|
|
1846
|
+
messages: agentState.messages,
|
|
1847
|
+
iteration: agentState.iteration,
|
|
1848
|
+
timestamp: Date.now(),
|
|
1849
|
+
});
|
|
1850
|
+
persistPendingPlanToStore();
|
|
1851
|
+
persistenceDebug.log('Checkpoint saved before cancel');
|
|
1852
|
+
}
|
|
1853
|
+
catch (e) {
|
|
1854
|
+
persistenceDebug.error('Failed to save checkpoint before cancel', e);
|
|
1855
|
+
}
|
|
1856
|
+
// Cancel the agent
|
|
1530
1857
|
agent.cancel('Cancelled by ESC');
|
|
1531
1858
|
setIsProcessing(false);
|
|
1532
|
-
addMessage('system', '[STOP] Cancelled');
|
|
1859
|
+
addMessage('system', '[STOP] Cancelled (checkpoint saved)');
|
|
1533
1860
|
}
|
|
1534
|
-
}, [agent, addMessage, commandPaletteOpen]);
|
|
1861
|
+
}, [agent, addMessage, commandPaletteOpen, sessionStore, currentSessionId, saveCheckpointToStore, persistenceDebug, persistPendingPlanToStore]);
|
|
1535
1862
|
const handleToggleToolExpand = useCallback(() => {
|
|
1536
1863
|
setToolCallsExpanded(prev => {
|
|
1537
1864
|
addMessage('system', !prev ? '[*] Tool details: expanded' : '[ ] Tool details: collapsed');
|
|
@@ -1550,6 +1877,24 @@ export function TUIApp({ agent, sessionStore, mcpClient, compactor, lspManager,
|
|
|
1550
1877
|
return !prev;
|
|
1551
1878
|
});
|
|
1552
1879
|
}, [addMessage]);
|
|
1880
|
+
const handleToggleActiveAgents = useCallback(() => {
|
|
1881
|
+
setActiveAgentsExpanded(prev => {
|
|
1882
|
+
addMessage('system', !prev ? '[v] Active agents: visible' : '[^] Active agents: hidden');
|
|
1883
|
+
return !prev;
|
|
1884
|
+
});
|
|
1885
|
+
}, [addMessage]);
|
|
1886
|
+
const handleToggleTasks = useCallback(() => {
|
|
1887
|
+
setTasksExpanded(prev => {
|
|
1888
|
+
addMessage('system', !prev ? '[v] Tasks: visible' : '[^] Tasks: hidden');
|
|
1889
|
+
return !prev;
|
|
1890
|
+
});
|
|
1891
|
+
}, [addMessage]);
|
|
1892
|
+
const handleToggleDebug = useCallback(() => {
|
|
1893
|
+
setDebugExpanded(prev => {
|
|
1894
|
+
addMessage('system', !prev ? '[v] Debug panel: visible (Alt+D)' : '[^] Debug panel: hidden');
|
|
1895
|
+
return !prev;
|
|
1896
|
+
});
|
|
1897
|
+
}, [addMessage]);
|
|
1553
1898
|
// Update context tokens
|
|
1554
1899
|
useEffect(() => {
|
|
1555
1900
|
const agentState = agent.getState();
|
|
@@ -1591,7 +1936,7 @@ export function TUIApp({ agent, sessionStore, mcpClient, compactor, lspManager,
|
|
|
1591
1936
|
args: pendingApproval.args || {},
|
|
1592
1937
|
risk: pendingApproval.risk,
|
|
1593
1938
|
context: pendingApproval.context,
|
|
1594
|
-
}, onApprove: handleApprove, onDeny: handleDeny, colors: colors, denyReasonMode: denyReasonMode, denyReason: denyReason })), _jsx(MemoizedInputArea, { onSubmit: handleSubmit, disabled: isProcessing || !!pendingApproval, borderColor: pendingApproval ? '#FFD700' : '#87CEEB', textColor: "#98FB98", cursorColor: "#87CEEB", onCtrlC: handleCtrlC, onCtrlL: handleCtrlL, onCtrlP: handleCtrlP, onEscape: handleEscape, onToggleToolExpand: handleToggleToolExpand, onToggleThinking: handleToggleThinking, onToggleTransparency: handleToggleTransparency, commandPaletteOpen: commandPaletteOpen, onCommandPaletteInput: handleCommandPaletteInput, approvalDialogOpen: !!pendingApproval, approvalDenyReasonMode: denyReasonMode, onApprovalApprove: handleApprove, onApprovalAlwaysAllow: handleAlwaysAllow, onApprovalDeny: handleDeny, onApprovalDenyWithReason: handleDenyWithReason, onApprovalCancelDenyReason: handleCancelDenyReason, onApprovalDenyReasonInput: handleApprovalDenyReasonInput }), commandPaletteOpen && (_jsx(ControlledCommandPalette, { theme: selectedTheme, items: filteredCommandItems, visible: commandPaletteOpen, query: commandPaletteQuery, selectedIndex: commandPaletteIndex, onQueryChange: setCommandPaletteQuery, onSelectItem: (item) => {
|
|
1939
|
+
}, onApprove: handleApprove, onDeny: handleDeny, colors: colors, denyReasonMode: denyReasonMode, denyReason: denyReason })), _jsx(DebugPanel, { entries: debugBuffer.entries, expanded: debugExpanded, colors: colors }), _jsx(TasksPanel, { tasks: tasks, colors: colors, expanded: tasksExpanded }), _jsx(ActiveAgentsPanel, { agents: activeAgents, colors: colors, expanded: activeAgentsExpanded }), _jsx(MemoizedInputArea, { onSubmit: handleSubmit, disabled: isProcessing || !!pendingApproval, borderColor: pendingApproval ? '#FFD700' : '#87CEEB', textColor: "#98FB98", cursorColor: "#87CEEB", onCtrlC: handleCtrlC, onCtrlL: handleCtrlL, onCtrlP: handleCtrlP, onEscape: handleEscape, onToggleToolExpand: handleToggleToolExpand, onToggleThinking: handleToggleThinking, onToggleTransparency: handleToggleTransparency, onToggleActiveAgents: handleToggleActiveAgents, onToggleTasks: handleToggleTasks, onToggleDebug: handleToggleDebug, commandPaletteOpen: commandPaletteOpen, onCommandPaletteInput: handleCommandPaletteInput, approvalDialogOpen: !!pendingApproval, approvalDenyReasonMode: denyReasonMode, onApprovalApprove: handleApprove, onApprovalAlwaysAllow: handleAlwaysAllow, onApprovalDeny: handleDeny, onApprovalDenyWithReason: handleDenyWithReason, onApprovalCancelDenyReason: handleCancelDenyReason, onApprovalDenyReasonInput: handleApprovalDenyReasonInput, history: historyEntries }), commandPaletteOpen && (_jsx(ControlledCommandPalette, { theme: selectedTheme, items: filteredCommandItems, visible: commandPaletteOpen, query: commandPaletteQuery, selectedIndex: commandPaletteIndex, onQueryChange: setCommandPaletteQuery, onSelectItem: (item) => {
|
|
1595
1940
|
setCommandPaletteOpen(false);
|
|
1596
1941
|
setCommandPaletteQuery('');
|
|
1597
1942
|
setCommandPaletteIndex(0);
|