centaurus-cli 3.1.2 → 3.1.4
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/dist/cli-adapter.js +689 -155
- package/dist/cli-adapter.js.map +1 -1
- package/dist/config/defaultConfig.js +1 -4
- package/dist/config/defaultConfig.js.map +1 -1
- package/dist/config/models.js +6 -0
- package/dist/config/models.js.map +1 -1
- package/dist/config/slash-commands.js +66 -2
- package/dist/config/slash-commands.js.map +1 -1
- package/dist/config/types.js +4 -4
- package/dist/config/types.js.map +1 -1
- package/dist/index.js +36 -0
- package/dist/index.js.map +1 -1
- package/dist/services/ai-context-injector.js +109 -0
- package/dist/services/ai-context-injector.js.map +1 -1
- package/dist/services/ai-service-client.js +3 -2
- package/dist/services/ai-service-client.js.map +1 -1
- package/dist/services/api-client.js.map +1 -1
- package/dist/services/background-task-manager.js +59 -0
- package/dist/services/background-task-manager.js.map +1 -1
- package/dist/services/local-chat-storage.js +2 -0
- package/dist/services/local-chat-storage.js.map +1 -1
- package/dist/services/skill-storage.js +141 -0
- package/dist/services/skill-storage.js.map +1 -0
- package/dist/services/sub-agent-manager.js +49 -8
- package/dist/services/sub-agent-manager.js.map +1 -1
- package/dist/services/warpify-detector.js +17 -5
- package/dist/services/warpify-detector.js.map +1 -1
- package/dist/tools/background-command.js +5 -2
- package/dist/tools/background-command.js.map +1 -1
- package/dist/tools/command.js +367 -109
- package/dist/tools/command.js.map +1 -1
- package/dist/tools/file-ops.js +23 -6
- package/dist/tools/file-ops.js.map +1 -1
- package/dist/tools/plan-mode.js +184 -336
- package/dist/tools/plan-mode.js.map +1 -1
- package/dist/tools/sub-agent.js +24 -5
- package/dist/tools/sub-agent.js.map +1 -1
- package/dist/tools/todo-list.js +157 -0
- package/dist/tools/todo-list.js.map +1 -0
- package/dist/types/skill.js +30 -0
- package/dist/types/skill.js.map +1 -0
- package/dist/ui/components/App.js +956 -162
- package/dist/ui/components/App.js.map +1 -1
- package/dist/ui/components/AuthScreen.js +3 -1
- package/dist/ui/components/AuthScreen.js.map +1 -1
- package/dist/ui/components/AuthWelcomeScreen.js +3 -1
- package/dist/ui/components/AuthWelcomeScreen.js.map +1 -1
- package/dist/ui/components/CodeBlock.js +3 -1
- package/dist/ui/components/CodeBlock.js.map +1 -1
- package/dist/ui/components/CompactShellPreview.js +44 -0
- package/dist/ui/components/CompactShellPreview.js.map +1 -0
- package/dist/ui/components/ConfigViewer.js +3 -1
- package/dist/ui/components/ConfigViewer.js.map +1 -1
- package/dist/ui/components/ConfirmPrompt.js +3 -1
- package/dist/ui/components/ConfirmPrompt.js.map +1 -1
- package/dist/ui/components/ConnectionStatusMessage.js +3 -1
- package/dist/ui/components/ConnectionStatusMessage.js.map +1 -1
- package/dist/ui/components/DetailedPlanReviewScreen.js +84 -74
- package/dist/ui/components/DetailedPlanReviewScreen.js.map +1 -1
- package/dist/ui/components/DiffViewer.js +6 -3
- package/dist/ui/components/DiffViewer.js.map +1 -1
- package/dist/ui/components/FileCreationPreview.js.map +1 -1
- package/dist/ui/components/FileTagAutocomplete.js +4 -2
- package/dist/ui/components/FileTagAutocomplete.js.map +1 -1
- package/dist/ui/components/InputBox.js +243 -40
- package/dist/ui/components/InputBox.js.map +1 -1
- package/dist/ui/components/InteractiveShell.js +5 -3
- package/dist/ui/components/InteractiveShell.js.map +1 -1
- package/dist/ui/components/KeyboardHelp.js +4 -1
- package/dist/ui/components/KeyboardHelp.js.map +1 -1
- package/dist/ui/components/LoadingIndicator.js +3 -1
- package/dist/ui/components/LoadingIndicator.js.map +1 -1
- package/dist/ui/components/MCPAddScreen.js +63 -13
- package/dist/ui/components/MCPAddScreen.js.map +1 -1
- package/dist/ui/components/MarkdownRenderer.js +3 -1
- package/dist/ui/components/MarkdownRenderer.js.map +1 -1
- package/dist/ui/components/MessageDisplay.js +9 -7
- package/dist/ui/components/MessageDisplay.js.map +1 -1
- package/dist/ui/components/ModelPicker.js +170 -0
- package/dist/ui/components/ModelPicker.js.map +1 -0
- package/dist/ui/components/MonitorModeAIPanel.js +3 -1
- package/dist/ui/components/MonitorModeAIPanel.js.map +1 -1
- package/dist/ui/components/PlanAcceptedMessage.js +12 -6
- package/dist/ui/components/PlanAcceptedMessage.js.map +1 -1
- package/dist/ui/components/PlanQuestionMessage.js +37 -0
- package/dist/ui/components/PlanQuestionMessage.js.map +1 -0
- package/dist/ui/components/PlanQuestionScreen.js +138 -0
- package/dist/ui/components/PlanQuestionScreen.js.map +1 -0
- package/dist/ui/components/PlanReviewScreen.js +7 -9
- package/dist/ui/components/PlanReviewScreen.js.map +1 -1
- package/dist/ui/components/RulesEditorScreen.js +65 -28
- package/dist/ui/components/RulesEditorScreen.js.map +1 -1
- package/dist/ui/components/SelectPrompt.js +3 -1
- package/dist/ui/components/SelectPrompt.js.map +1 -1
- package/dist/ui/components/SkillCreatorScreen.js +217 -0
- package/dist/ui/components/SkillCreatorScreen.js.map +1 -0
- package/dist/ui/components/SlashCommandAutocomplete.js +4 -2
- package/dist/ui/components/SlashCommandAutocomplete.js.map +1 -1
- package/dist/ui/components/StatusBar.js +4 -2
- package/dist/ui/components/StatusBar.js.map +1 -1
- package/dist/ui/components/StreamingMessageDisplay.js +5 -3
- package/dist/ui/components/StreamingMessageDisplay.js.map +1 -1
- package/dist/ui/components/SubAgentListScreen.js +65 -0
- package/dist/ui/components/SubAgentListScreen.js.map +1 -0
- package/dist/ui/components/SubAgentViewScreen.js +123 -0
- package/dist/ui/components/SubAgentViewScreen.js.map +1 -0
- package/dist/ui/components/TaskCompletedMessage.js +40 -8
- package/dist/ui/components/TaskCompletedMessage.js.map +1 -1
- package/dist/ui/components/TaskProgressIndicator.js +6 -4
- package/dist/ui/components/TaskProgressIndicator.js.map +1 -1
- package/dist/ui/components/TextEditor.js +297 -0
- package/dist/ui/components/TextEditor.js.map +1 -0
- package/dist/ui/components/TodoListMessage.js +59 -0
- package/dist/ui/components/TodoListMessage.js.map +1 -0
- package/dist/ui/components/ToolExecutionMessage.js +134 -84
- package/dist/ui/components/ToolExecutionMessage.js.map +1 -1
- package/dist/ui/components/ToolExecutionStatus.js +3 -1
- package/dist/ui/components/ToolExecutionStatus.js.map +1 -1
- package/dist/ui/components/WelcomeBanner.js +33 -33
- package/dist/ui/components/WelcomeBanner.js.map +1 -1
- package/dist/ui/components/WorkflowCreatorScreen.js +5 -3
- package/dist/ui/components/WorkflowCreatorScreen.js.map +1 -1
- package/dist/ui/theme.js +97 -0
- package/dist/ui/theme.js.map +1 -0
- package/dist/ui/utils/chat-history-limit.js +247 -0
- package/dist/ui/utils/chat-history-limit.js.map +1 -0
- package/dist/utils/chat-formatter.js +22 -9
- package/dist/utils/chat-formatter.js.map +1 -1
- package/dist/utils/git-stats.js +7 -5
- package/dist/utils/git-stats.js.map +1 -1
- package/dist/utils/input-classifier.js +11 -1
- package/dist/utils/input-classifier.js.map +1 -1
- package/dist/utils/output-truncation.js +175 -0
- package/dist/utils/output-truncation.js.map +1 -0
- package/dist/utils/rule-reference-resolver.js +3 -3
- package/dist/utils/rule-reference-resolver.js.map +1 -1
- package/dist/utils/tunnel-commands-manager.js +134 -0
- package/dist/utils/tunnel-commands-manager.js.map +1 -0
- package/package.json +91 -90
- package/postinstall.js +4 -11
- package/dist/ui/components/MultiLineInput.js +0 -255
- package/dist/ui/components/MultiLineInput.js.map +0 -1
|
@@ -3,6 +3,7 @@ import { Box, Text, useApp, useInput, Static } from "ink";
|
|
|
3
3
|
import SelectInput from "ink-select-input";
|
|
4
4
|
import { CircularSelectInput } from "./CircularSelectInput.js";
|
|
5
5
|
import stripAnsi from "strip-ansi";
|
|
6
|
+
import { ThemeContext, deriveThemeColors, setGlobalAccentColor, DEFAULT_ACCENT_COLOR } from "../theme.js";
|
|
6
7
|
import TextInput from "ink-text-input";
|
|
7
8
|
import * as path from "path";
|
|
8
9
|
import { quickLog } from "../../utils/conversation-logger.js";
|
|
@@ -11,6 +12,7 @@ import { InputBox } from "./InputBox.js";
|
|
|
11
12
|
import { MessageDisplay, countFilesInMessage } from "./MessageDisplay.js";
|
|
12
13
|
import { StreamingMessageDisplay } from "./StreamingMessageDisplay.js";
|
|
13
14
|
import { LoadingIndicator } from "./LoadingIndicator.js";
|
|
15
|
+
import { ModelPicker } from "./ModelPicker.js";
|
|
14
16
|
import { AgentTimer } from "./AgentTimer.js";
|
|
15
17
|
import { SelectPrompt } from "./SelectPrompt.js";
|
|
16
18
|
import { ConfirmPrompt } from "./ConfirmPrompt.js";
|
|
@@ -20,16 +22,24 @@ import { DiffViewer } from "./DiffViewer.js";
|
|
|
20
22
|
import { PasswordPrompt } from "./PasswordPrompt.js";
|
|
21
23
|
import { VersionUpdatePrompt } from "./VersionUpdatePrompt.js";
|
|
22
24
|
import { InteractiveShell } from "./InteractiveShell.js";
|
|
25
|
+
import { CompactShellPreview } from "./CompactShellPreview.js";
|
|
23
26
|
import { checkForUpdates } from "../../utils/version-checker.js";
|
|
24
27
|
import { runInteractiveEditor, runWSLEditor, runDockerEditor, runSSHEditor, runNestedDockerSSHEditor } from "../../utils/editor-utils.js";
|
|
25
28
|
import { DetailedPlanReviewScreen } from "./DetailedPlanReviewScreen.js";
|
|
26
29
|
import { TaskCompletedMessage } from "./TaskCompletedMessage.js";
|
|
30
|
+
import { TodoListMessage } from "./TodoListMessage.js";
|
|
27
31
|
import { PlanAcceptedMessage } from "./PlanAcceptedMessage.js";
|
|
32
|
+
import { PlanQuestionScreen } from "./PlanQuestionScreen.js";
|
|
33
|
+
import { PlanQuestionMessage } from "./PlanQuestionMessage.js";
|
|
28
34
|
import { MCPAddScreen } from "./MCPAddScreen.js";
|
|
29
35
|
import { MCPServerListScreen } from "./MCPServerListScreen.js";
|
|
30
36
|
import { MCPListScreen } from "./MCPListScreen.js";
|
|
31
37
|
import { WorkflowCreatorScreen } from "./WorkflowCreatorScreen.js";
|
|
32
38
|
import { RulesEditorScreen } from "./RulesEditorScreen.js";
|
|
39
|
+
import { SkillCreatorScreen } from "./SkillCreatorScreen.js";
|
|
40
|
+
import { SubAgentListScreen } from "./SubAgentListScreen.js";
|
|
41
|
+
import { SubAgentViewScreen } from "./SubAgentViewScreen.js";
|
|
42
|
+
import { SubAgentManager } from "../../services/sub-agent-manager.js";
|
|
33
43
|
import { processTerminalOutput } from "../../utils/terminal-output.js";
|
|
34
44
|
import { BackgroundTaskManager } from "../../services/background-task-manager.js";
|
|
35
45
|
import { MonitorModeAIPanel } from "./MonitorModeAIPanel.js";
|
|
@@ -44,6 +54,9 @@ import { logDebug, logError } from "../../utils/logger.js";
|
|
|
44
54
|
import { getTerminalDimensions } from "../../hooks/useTerminalDimensions.js";
|
|
45
55
|
import { ConfigManager } from "../../config/manager.js";
|
|
46
56
|
import { getGitDiffStats, getRemoteGitDiffStats } from "../../utils/git-stats.js";
|
|
57
|
+
import { limitChatHistoryForDisplay } from "../utils/chat-history-limit.js";
|
|
58
|
+
import { TunnelCommandsManager } from "../../utils/tunnel-commands-manager.js";
|
|
59
|
+
import { detectIntent } from "../../utils/input-classifier.js";
|
|
47
60
|
function isOutsideCwd(filePath, cwd) {
|
|
48
61
|
if (!filePath || !cwd) return false;
|
|
49
62
|
try {
|
|
@@ -54,6 +67,11 @@ function isOutsideCwd(filePath, cwd) {
|
|
|
54
67
|
return false;
|
|
55
68
|
}
|
|
56
69
|
}
|
|
70
|
+
const ScreenContainer = ({ children }) => {
|
|
71
|
+
const rows = process.stdout.rows || 24;
|
|
72
|
+
const maxContentHeight = Math.max(3, rows - 2);
|
|
73
|
+
return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column" }, /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", height: maxContentHeight, overflow: "hidden" }, children), /* @__PURE__ */ React.createElement(Box, { justifyContent: "flex-end" }, /* @__PURE__ */ React.createElement(Text, { color: "#666666", dimColor: true }, "[truncated]")));
|
|
74
|
+
};
|
|
57
75
|
const BANNER_ITEM = { id: "__banner__", role: "__banner__", content: "", timestamp: /* @__PURE__ */ new Date(0) };
|
|
58
76
|
const MessageList = React.memo(({ history, current, showBanner }) => {
|
|
59
77
|
const staticItems = React.useMemo(() => {
|
|
@@ -83,10 +101,23 @@ const MessageList = React.memo(({ history, current, showBanner }) => {
|
|
|
83
101
|
TaskCompletedMessage,
|
|
84
102
|
{
|
|
85
103
|
key: item.id,
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
104
|
+
completedStepNumber: msg.taskCompletion.completedStepNumber,
|
|
105
|
+
completedStepDescription: msg.taskCompletion.completedStepDescription,
|
|
106
|
+
completedCount: msg.taskCompletion.completedCount,
|
|
107
|
+
totalCount: msg.taskCompletion.totalCount,
|
|
108
|
+
completionNote: msg.taskCompletion.completionNote,
|
|
109
|
+
allSteps: msg.taskCompletion.allSteps
|
|
110
|
+
}
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
if (msg.todoList) {
|
|
114
|
+
return /* @__PURE__ */ React.createElement(
|
|
115
|
+
TodoListMessage,
|
|
116
|
+
{
|
|
117
|
+
key: item.id,
|
|
118
|
+
todos: msg.todoList.todos,
|
|
119
|
+
completedCount: msg.todoList.completedCount,
|
|
120
|
+
totalCount: msg.todoList.totalCount
|
|
90
121
|
}
|
|
91
122
|
);
|
|
92
123
|
}
|
|
@@ -96,22 +127,35 @@ const MessageList = React.memo(({ history, current, showBanner }) => {
|
|
|
96
127
|
{
|
|
97
128
|
key: item.id,
|
|
98
129
|
planTitle: msg.planAccepted.planTitle,
|
|
99
|
-
|
|
100
|
-
|
|
130
|
+
totalSteps: msg.planAccepted.totalSteps,
|
|
131
|
+
steps: msg.planAccepted.steps
|
|
132
|
+
}
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
if (msg.planQuestion) {
|
|
136
|
+
return /* @__PURE__ */ React.createElement(
|
|
137
|
+
PlanQuestionMessage,
|
|
138
|
+
{
|
|
139
|
+
key: item.id,
|
|
140
|
+
question: msg.planQuestion.question,
|
|
141
|
+
options: msg.planQuestion.options,
|
|
142
|
+
userAnswer: msg.planQuestion.userAnswer,
|
|
143
|
+
wasSkipped: msg.planQuestion.wasSkipped
|
|
101
144
|
}
|
|
102
145
|
);
|
|
103
146
|
}
|
|
104
147
|
return /* @__PURE__ */ React.createElement(MessageDisplay, { key: item.id, message: item, fileCountBefore });
|
|
105
148
|
}), current && !history.some((msg) => msg.id === current.id) && (() => {
|
|
106
149
|
const dimensions = getTerminalDimensions();
|
|
107
|
-
const
|
|
150
|
+
const hasActiveThoughts = current.thoughts && current.thoughts.length > 0 && current.thinkingDuration === void 0;
|
|
151
|
+
const canStream = current.role === "assistant" && current.shouldStream !== false && (dimensions.shouldEnableStreaming || hasActiveThoughts);
|
|
108
152
|
if (canStream) {
|
|
109
153
|
return /* @__PURE__ */ React.createElement(
|
|
110
154
|
StreamingMessageDisplay,
|
|
111
155
|
{
|
|
112
156
|
key: current.id,
|
|
113
157
|
message: current,
|
|
114
|
-
maxLines: dimensions.maxStreamingLines
|
|
158
|
+
maxLines: dimensions.shouldEnableStreaming ? dimensions.maxStreamingLines : 1
|
|
115
159
|
}
|
|
116
160
|
);
|
|
117
161
|
} else {
|
|
@@ -155,12 +199,13 @@ const ApprovalSection = React.memo(({ approvalRequest, onApprove }) => {
|
|
|
155
199
|
});
|
|
156
200
|
const RenameInputScreen = ({ currentTitle, onRename, onCancel }) => {
|
|
157
201
|
const [value, setValue] = React.useState(currentTitle);
|
|
202
|
+
const _theme = React.useContext(ThemeContext);
|
|
158
203
|
useInput((input, key) => {
|
|
159
204
|
if (key.escape) {
|
|
160
205
|
onCancel();
|
|
161
206
|
}
|
|
162
207
|
});
|
|
163
|
-
return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", borderStyle: "round", borderColor:
|
|
208
|
+
return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", borderStyle: "round", borderColor: _theme.accent, paddingX: 1 }, /* @__PURE__ */ React.createElement(Text, { color: _theme.accent, bold: true }, "\u270F\uFE0F Rename Chat"), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, null, "Current name: ", /* @__PURE__ */ React.createElement(Text, { color: "#00cc66" }, currentTitle))), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, null, "New name: "), /* @__PURE__ */ React.createElement(
|
|
164
209
|
TextInput,
|
|
165
210
|
{
|
|
166
211
|
value,
|
|
@@ -200,10 +245,13 @@ const App = ({
|
|
|
200
245
|
onPlanModeChange,
|
|
201
246
|
onPlanApprovalRequest,
|
|
202
247
|
onPlanCreated,
|
|
248
|
+
onPlanQuestionRequest,
|
|
203
249
|
onTaskCompleted,
|
|
250
|
+
onTodoListUpdate,
|
|
204
251
|
onFileChangeSummary,
|
|
205
252
|
onCommandModeChange,
|
|
206
253
|
onToggleCommandMode,
|
|
254
|
+
onTogglePlanMode,
|
|
207
255
|
onBackgroundModeChange,
|
|
208
256
|
onToggleBackgroundMode,
|
|
209
257
|
onBackgroundTaskCountChange,
|
|
@@ -248,7 +296,16 @@ const App = ({
|
|
|
248
296
|
onPromptAnswered,
|
|
249
297
|
getMainConversation,
|
|
250
298
|
onWarpifySession,
|
|
299
|
+
onCustomTunnelStateChange,
|
|
251
300
|
onAiAutoSuggestChange,
|
|
301
|
+
onLimitChatHistoryChange,
|
|
302
|
+
onThemeColorChange,
|
|
303
|
+
onSubAgentListScreenSetup,
|
|
304
|
+
onSubAgentViewScreenSetup,
|
|
305
|
+
onSubAgentTerminateScreenSetup,
|
|
306
|
+
onSkillEditorScreenSetup,
|
|
307
|
+
onSkillSave,
|
|
308
|
+
onSkillExecute,
|
|
252
309
|
onWorkflowCreatorSetup,
|
|
253
310
|
onWorkflowSave,
|
|
254
311
|
onRulesEditorSetup,
|
|
@@ -283,6 +340,10 @@ const App = ({
|
|
|
283
340
|
return getModelContextWindowSync(model);
|
|
284
341
|
} catch (error) {
|
|
285
342
|
if (model.toLowerCase().includes("kimi")) return 128e3;
|
|
343
|
+
if (model.toLowerCase().includes("qwen")) return 131072;
|
|
344
|
+
if (model.toLowerCase().includes("gpt-oss")) return 128e3;
|
|
345
|
+
if (model.toLowerCase().includes("nemotron")) return 131072;
|
|
346
|
+
if (model.toLowerCase().includes("deepseek")) return 131072;
|
|
286
347
|
if (model.toLowerCase().includes("gemini")) return 1e6;
|
|
287
348
|
return 1e6;
|
|
288
349
|
}
|
|
@@ -320,6 +381,7 @@ const App = ({
|
|
|
320
381
|
maxTokens: getMaxTokensForModel(initialModel || "gemini-2.5-flash"),
|
|
321
382
|
contextLimitReached: false,
|
|
322
383
|
shellState: void 0,
|
|
384
|
+
customTunnel: void 0,
|
|
323
385
|
isInteractiveEditorMode: false,
|
|
324
386
|
pickerOptions: void 0,
|
|
325
387
|
approvalRequest: void 0,
|
|
@@ -331,6 +393,8 @@ const App = ({
|
|
|
331
393
|
sessionQuotaExhausted: false,
|
|
332
394
|
sessionQuotaTimeRemaining: "",
|
|
333
395
|
aiAutoSuggest: false,
|
|
396
|
+
limitChatHistory: true,
|
|
397
|
+
themeColor: "#00ccff",
|
|
334
398
|
rulesEditorRequest: void 0,
|
|
335
399
|
interruptQueue: [],
|
|
336
400
|
commandQueue: [],
|
|
@@ -340,22 +404,44 @@ const App = ({
|
|
|
340
404
|
lastFileChangeSummary: null
|
|
341
405
|
});
|
|
342
406
|
const lastTerminalWidthRef = React.useRef(process.stdout.columns || 80);
|
|
407
|
+
const lastTerminalHeightRef = React.useRef(process.stdout.rows || 24);
|
|
408
|
+
const shellFocusedRef = React.useRef(false);
|
|
343
409
|
const isCleaningRef = React.useRef(false);
|
|
344
410
|
const savedHistoryRef = React.useRef([]);
|
|
345
411
|
const savedCurrentRef = React.useRef(null);
|
|
412
|
+
const previousHiddenMessageCountRef = React.useRef(null);
|
|
346
413
|
const resizeDebounceRef = React.useRef(null);
|
|
347
414
|
const restoreMessagesTimeoutRef = React.useRef(null);
|
|
348
415
|
const preservedInputTextRef = React.useRef("");
|
|
416
|
+
React.useEffect(() => {
|
|
417
|
+
shellFocusedRef.current = !!state.shellState?.isFocused;
|
|
418
|
+
}, [state.shellState?.isFocused]);
|
|
349
419
|
React.useEffect(() => {
|
|
350
420
|
const DEBOUNCE_MS = 300;
|
|
351
421
|
const handleResize = () => {
|
|
352
422
|
const newWidth = process.stdout.columns || 80;
|
|
423
|
+
const newHeight = process.stdout.rows || 24;
|
|
353
424
|
if (resizeDebounceRef.current) {
|
|
354
425
|
clearTimeout(resizeDebounceRef.current);
|
|
355
426
|
}
|
|
356
427
|
resizeDebounceRef.current = setTimeout(() => {
|
|
357
428
|
const oldWidth = lastTerminalWidthRef.current;
|
|
358
|
-
|
|
429
|
+
const oldHeight = lastTerminalHeightRef.current;
|
|
430
|
+
const heightChanged = newHeight !== oldHeight;
|
|
431
|
+
const widthChanged = newWidth !== oldWidth;
|
|
432
|
+
lastTerminalHeightRef.current = newHeight;
|
|
433
|
+
if (shellFocusedRef.current && (heightChanged || widthChanged)) {
|
|
434
|
+
lastTerminalWidthRef.current = newWidth;
|
|
435
|
+
clearScreen();
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
if (altScreenActiveRef.current && (heightChanged || widthChanged)) {
|
|
439
|
+
lastTerminalWidthRef.current = newWidth;
|
|
440
|
+
process.stdout.write("\x1B[2J\x1B[H");
|
|
441
|
+
setState((prev) => ({ ...prev, messageListKey: (prev.messageListKey ?? 0) + 1 }));
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
if (widthChanged && !isCleaningRef.current) {
|
|
359
445
|
lastTerminalWidthRef.current = newWidth;
|
|
360
446
|
isCleaningRef.current = true;
|
|
361
447
|
clearScreen();
|
|
@@ -425,8 +511,13 @@ const App = ({
|
|
|
425
511
|
const generalConfig = configManager.load();
|
|
426
512
|
setState((prev) => ({
|
|
427
513
|
...prev,
|
|
428
|
-
aiAutoSuggest: generalConfig.aiAutoSuggest === true
|
|
514
|
+
aiAutoSuggest: generalConfig.aiAutoSuggest === true,
|
|
515
|
+
limitChatHistory: generalConfig.limitChatHistory !== false,
|
|
516
|
+
themeColor: generalConfig.themeColor || DEFAULT_ACCENT_COLOR
|
|
429
517
|
}));
|
|
518
|
+
if (generalConfig.themeColor) {
|
|
519
|
+
setGlobalAccentColor(generalConfig.themeColor);
|
|
520
|
+
}
|
|
430
521
|
const { quickLog: quickLog2 } = await import("../../utils/conversation-logger.js");
|
|
431
522
|
quickLog2(`[${(/* @__PURE__ */ new Date()).toISOString()}] [App] loadModelsConfig effect starting, currentModel: ${initialModel}
|
|
432
523
|
`);
|
|
@@ -567,6 +658,11 @@ const App = ({
|
|
|
567
658
|
const ignoreIncomingAIUpdatesRef = React.useRef(false);
|
|
568
659
|
const warpifyInProgressRef = React.useRef(false);
|
|
569
660
|
const warpifyTerminatingCommandRef = React.useRef(null);
|
|
661
|
+
const tunnelOutputPositionRef = React.useRef(0);
|
|
662
|
+
const tunnelOutputFlushTimerRef = React.useRef(null);
|
|
663
|
+
const latestShellOutputRef = React.useRef("");
|
|
664
|
+
const customTunnelRef = React.useRef(void 0);
|
|
665
|
+
const shellRunningRef = React.useRef(false);
|
|
570
666
|
const isStreamingRef = React.useRef(false);
|
|
571
667
|
const bufferedContentRef = React.useRef("");
|
|
572
668
|
const bufferedMessageIdRef = React.useRef(null);
|
|
@@ -579,7 +675,7 @@ const App = ({
|
|
|
579
675
|
if (!enableStreaming) {
|
|
580
676
|
bufferedContentRef.current += chunk;
|
|
581
677
|
setState((prev) => {
|
|
582
|
-
if (prev.currentMessage && prev.currentMessage.role === "assistant"
|
|
678
|
+
if (prev.currentMessage && prev.currentMessage.role === "assistant") {
|
|
583
679
|
return {
|
|
584
680
|
...prev,
|
|
585
681
|
isAiWorking: true
|
|
@@ -666,6 +762,8 @@ const App = ({
|
|
|
666
762
|
React.useEffect(() => {
|
|
667
763
|
onThoughtStream((thought) => {
|
|
668
764
|
if (ignoreIncomingAIUpdatesRef.current) return;
|
|
765
|
+
const thoughtDimensions = getTerminalDimensions();
|
|
766
|
+
const maxThoughtLines = thoughtDimensions.shouldEnableStreaming ? 3 : 1;
|
|
669
767
|
try {
|
|
670
768
|
quickLog(`[${(/* @__PURE__ */ new Date()).toISOString()}] [App] Received thought chunk (${thought.length} chars): ${thought.substring(0, 100)}
|
|
671
769
|
`);
|
|
@@ -682,7 +780,7 @@ const App = ({
|
|
|
682
780
|
}
|
|
683
781
|
thoughtAccumulatorRef.current = thought;
|
|
684
782
|
const allLines2 = thoughtAccumulatorRef.current.split("\n").filter((line) => line.trim());
|
|
685
|
-
const
|
|
783
|
+
const lastNLines2 = allLines2.slice(-maxThoughtLines);
|
|
686
784
|
const now = /* @__PURE__ */ new Date();
|
|
687
785
|
const newMessage = {
|
|
688
786
|
id: `assistant-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`,
|
|
@@ -691,7 +789,7 @@ const App = ({
|
|
|
691
789
|
// Empty content initially, will be filled when text arrives
|
|
692
790
|
timestamp: now,
|
|
693
791
|
shouldStream: true,
|
|
694
|
-
thoughts:
|
|
792
|
+
thoughts: lastNLines2
|
|
695
793
|
};
|
|
696
794
|
let newHistory = prev.messageHistory;
|
|
697
795
|
if (prev.currentMessage && prev.currentMessage.role === "assistant" && prev.currentMessage.thinkingDuration !== void 0 && prev.currentMessage.content.trim() !== "") {
|
|
@@ -715,9 +813,9 @@ const App = ({
|
|
|
715
813
|
}
|
|
716
814
|
thoughtAccumulatorRef.current += thought;
|
|
717
815
|
const allLines = thoughtAccumulatorRef.current.split("\n").filter((line) => line.trim());
|
|
718
|
-
const
|
|
816
|
+
const lastNLines = allLines.slice(-maxThoughtLines);
|
|
719
817
|
try {
|
|
720
|
-
quickLog(`[${(/* @__PURE__ */ new Date()).toISOString()}] [App] Accumulated ${allLines.length} total lines, showing last
|
|
818
|
+
quickLog(`[${(/* @__PURE__ */ new Date()).toISOString()}] [App] Accumulated ${allLines.length} total lines, showing last ${maxThoughtLines}: ${JSON.stringify(lastNLines)}
|
|
721
819
|
`);
|
|
722
820
|
} catch (e) {
|
|
723
821
|
}
|
|
@@ -725,7 +823,7 @@ const App = ({
|
|
|
725
823
|
...prev,
|
|
726
824
|
currentMessage: {
|
|
727
825
|
...prev.currentMessage,
|
|
728
|
-
thoughts:
|
|
826
|
+
thoughts: lastNLines,
|
|
729
827
|
thinkingDuration: void 0
|
|
730
828
|
// Clear duration while thinking
|
|
731
829
|
}
|
|
@@ -801,6 +899,63 @@ const App = ({
|
|
|
801
899
|
};
|
|
802
900
|
});
|
|
803
901
|
}, [shouldSkipMessage]);
|
|
902
|
+
const getRegisteredTunnelCommands = useCallback(() => {
|
|
903
|
+
const tunnelCommandsManager = TunnelCommandsManager.getInstance();
|
|
904
|
+
tunnelCommandsManager.loadSync();
|
|
905
|
+
return tunnelCommandsManager.listCommands();
|
|
906
|
+
}, []);
|
|
907
|
+
React.useEffect(() => {
|
|
908
|
+
latestShellOutputRef.current = state.shellState?.output || "";
|
|
909
|
+
}, [state.shellState?.output]);
|
|
910
|
+
React.useEffect(() => {
|
|
911
|
+
customTunnelRef.current = state.customTunnel;
|
|
912
|
+
shellRunningRef.current = !!state.shellState?.isRunning;
|
|
913
|
+
}, [state.customTunnel, state.shellState?.isRunning]);
|
|
914
|
+
const flushTunnelOutput = useCallback(() => {
|
|
915
|
+
const currentOutput = latestShellOutputRef.current;
|
|
916
|
+
const lastPos = tunnelOutputPositionRef.current;
|
|
917
|
+
if (currentOutput.length <= lastPos) return;
|
|
918
|
+
const delta = currentOutput.substring(lastPos);
|
|
919
|
+
tunnelOutputPositionRef.current = currentOutput.length;
|
|
920
|
+
const processedDelta = processTerminalOutput(delta);
|
|
921
|
+
if (!processedDelta.trim()) return;
|
|
922
|
+
setState((prev) => {
|
|
923
|
+
if (!prev.customTunnel) return prev;
|
|
924
|
+
return {
|
|
925
|
+
...prev,
|
|
926
|
+
messageHistory: [...prev.messageHistory, {
|
|
927
|
+
id: `tunnel-output-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`,
|
|
928
|
+
role: "tool",
|
|
929
|
+
content: "",
|
|
930
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
931
|
+
toolExecution: {
|
|
932
|
+
toolName: "execute_command",
|
|
933
|
+
status: "completed",
|
|
934
|
+
result: processedDelta,
|
|
935
|
+
arguments: {
|
|
936
|
+
command: prev.customTunnel.shellCommand,
|
|
937
|
+
CommandLine: prev.customTunnel.shellCommand,
|
|
938
|
+
cwd: prev.shellState?.cwd || process.cwd()
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
}]
|
|
942
|
+
};
|
|
943
|
+
});
|
|
944
|
+
}, []);
|
|
945
|
+
React.useEffect(() => {
|
|
946
|
+
if (!state.customTunnel || !state.shellState?.output) return;
|
|
947
|
+
if (tunnelOutputFlushTimerRef.current) {
|
|
948
|
+
clearTimeout(tunnelOutputFlushTimerRef.current);
|
|
949
|
+
}
|
|
950
|
+
tunnelOutputFlushTimerRef.current = setTimeout(() => {
|
|
951
|
+
flushTunnelOutput();
|
|
952
|
+
}, 600);
|
|
953
|
+
return () => {
|
|
954
|
+
if (tunnelOutputFlushTimerRef.current) {
|
|
955
|
+
clearTimeout(tunnelOutputFlushTimerRef.current);
|
|
956
|
+
}
|
|
957
|
+
};
|
|
958
|
+
}, [state.shellState?.output, state.customTunnel, flushTunnelOutput]);
|
|
804
959
|
React.useEffect(() => {
|
|
805
960
|
onDirectMessage((message) => {
|
|
806
961
|
if (ignoreIncomingAIUpdatesRef.current) return;
|
|
@@ -931,7 +1086,7 @@ const App = ({
|
|
|
931
1086
|
const pendingShellRef = React.useRef(null);
|
|
932
1087
|
React.useEffect(() => {
|
|
933
1088
|
onPickerSetup((options) => {
|
|
934
|
-
|
|
1089
|
+
enterAltScreen();
|
|
935
1090
|
setState((prev) => ({
|
|
936
1091
|
...prev,
|
|
937
1092
|
screen: "picker",
|
|
@@ -942,7 +1097,7 @@ const App = ({
|
|
|
942
1097
|
}, []);
|
|
943
1098
|
React.useEffect(() => {
|
|
944
1099
|
onChatPickerSetup((chats, currentChatId) => {
|
|
945
|
-
|
|
1100
|
+
enterAltScreen();
|
|
946
1101
|
setState((prev) => ({
|
|
947
1102
|
...prev,
|
|
948
1103
|
screen: "chat-picker",
|
|
@@ -954,7 +1109,7 @@ const App = ({
|
|
|
954
1109
|
}, []);
|
|
955
1110
|
React.useEffect(() => {
|
|
956
1111
|
onChatDeletePickerSetup((chats, currentChatId) => {
|
|
957
|
-
|
|
1112
|
+
enterAltScreen();
|
|
958
1113
|
setState((prev) => ({
|
|
959
1114
|
...prev,
|
|
960
1115
|
screen: "chat-delete-picker",
|
|
@@ -967,7 +1122,7 @@ const App = ({
|
|
|
967
1122
|
}, []);
|
|
968
1123
|
React.useEffect(() => {
|
|
969
1124
|
onChatListSetup((chats, currentChatId) => {
|
|
970
|
-
|
|
1125
|
+
enterAltScreen();
|
|
971
1126
|
setState((prev) => ({
|
|
972
1127
|
...prev,
|
|
973
1128
|
screen: "chat-list-view",
|
|
@@ -979,7 +1134,7 @@ const App = ({
|
|
|
979
1134
|
}, []);
|
|
980
1135
|
React.useEffect(() => {
|
|
981
1136
|
onChatRenamePickerSetup((chats, currentChatId) => {
|
|
982
|
-
|
|
1137
|
+
enterAltScreen();
|
|
983
1138
|
setState((prev) => ({
|
|
984
1139
|
...prev,
|
|
985
1140
|
screen: "chat-rename-picker",
|
|
@@ -1021,6 +1176,52 @@ const App = ({
|
|
|
1021
1176
|
}));
|
|
1022
1177
|
});
|
|
1023
1178
|
}, [onAiAutoSuggestChange]);
|
|
1179
|
+
React.useEffect(() => {
|
|
1180
|
+
onLimitChatHistoryChange((enabled) => {
|
|
1181
|
+
setState((prev) => ({
|
|
1182
|
+
...prev,
|
|
1183
|
+
limitChatHistory: enabled
|
|
1184
|
+
}));
|
|
1185
|
+
});
|
|
1186
|
+
}, [onLimitChatHistoryChange]);
|
|
1187
|
+
React.useEffect(() => {
|
|
1188
|
+
onThemeColorChange((color) => {
|
|
1189
|
+
setGlobalAccentColor(color);
|
|
1190
|
+
setState((prev) => ({ ...prev, themeColor: color }));
|
|
1191
|
+
});
|
|
1192
|
+
}, [onThemeColorChange]);
|
|
1193
|
+
React.useEffect(() => {
|
|
1194
|
+
onSubAgentListScreenSetup((agents) => {
|
|
1195
|
+
enterAltScreen();
|
|
1196
|
+
setState((prev) => ({
|
|
1197
|
+
...prev,
|
|
1198
|
+
screen: "sub-agent-list",
|
|
1199
|
+
subAgentListData: agents
|
|
1200
|
+
}));
|
|
1201
|
+
});
|
|
1202
|
+
}, [onSubAgentListScreenSetup]);
|
|
1203
|
+
React.useEffect(() => {
|
|
1204
|
+
onSubAgentViewScreenSetup((agentId) => {
|
|
1205
|
+
enterAltScreen();
|
|
1206
|
+
setState((prev) => ({ ...prev, screen: "sub-agent-view", viewingSubAgentId: agentId }));
|
|
1207
|
+
});
|
|
1208
|
+
}, [onSubAgentViewScreenSetup]);
|
|
1209
|
+
React.useEffect(() => {
|
|
1210
|
+
onSubAgentTerminateScreenSetup((agents) => {
|
|
1211
|
+
enterAltScreen();
|
|
1212
|
+
setState((prev) => ({
|
|
1213
|
+
...prev,
|
|
1214
|
+
screen: "sub-agent-terminate",
|
|
1215
|
+
subAgentListData: agents.map((a) => ({ ...a, turnCount: 0, fileOpsCount: 0 }))
|
|
1216
|
+
}));
|
|
1217
|
+
});
|
|
1218
|
+
}, [onSubAgentTerminateScreenSetup]);
|
|
1219
|
+
React.useEffect(() => {
|
|
1220
|
+
onSkillEditorScreenSetup((request) => {
|
|
1221
|
+
enterAltScreen();
|
|
1222
|
+
setState((prev) => ({ ...prev, screen: "skill-editor", skillEditorRequest: request }));
|
|
1223
|
+
});
|
|
1224
|
+
}, [onSkillEditorScreenSetup]);
|
|
1024
1225
|
React.useEffect(() => {
|
|
1025
1226
|
onSetAutoModeSetup((enabled) => {
|
|
1026
1227
|
if (setAutoModeCallbackRef.current) {
|
|
@@ -1112,7 +1313,7 @@ const App = ({
|
|
|
1112
1313
|
}, []);
|
|
1113
1314
|
React.useEffect(() => {
|
|
1114
1315
|
onBackgroundTaskListSetup((tasks) => {
|
|
1115
|
-
|
|
1316
|
+
enterAltScreen();
|
|
1116
1317
|
setState((prev) => ({
|
|
1117
1318
|
...prev,
|
|
1118
1319
|
screen: "background-task-list",
|
|
@@ -1123,7 +1324,7 @@ const App = ({
|
|
|
1123
1324
|
}, []);
|
|
1124
1325
|
React.useEffect(() => {
|
|
1125
1326
|
onBackgroundTaskCancelSetup((tasks) => {
|
|
1126
|
-
|
|
1327
|
+
enterAltScreen();
|
|
1127
1328
|
setState((prev) => ({
|
|
1128
1329
|
...prev,
|
|
1129
1330
|
screen: "background-task-cancel",
|
|
@@ -1220,7 +1421,7 @@ const App = ({
|
|
|
1220
1421
|
}, []);
|
|
1221
1422
|
React.useEffect(() => {
|
|
1222
1423
|
onMCPAddScreenSetup(() => {
|
|
1223
|
-
|
|
1424
|
+
enterAltScreen();
|
|
1224
1425
|
setState((prev) => ({
|
|
1225
1426
|
...prev,
|
|
1226
1427
|
screen: "mcp-add",
|
|
@@ -1230,7 +1431,7 @@ const App = ({
|
|
|
1230
1431
|
}, []);
|
|
1231
1432
|
React.useEffect(() => {
|
|
1232
1433
|
onWorkflowCreatorSetup((initialSteps) => {
|
|
1233
|
-
|
|
1434
|
+
enterAltScreen();
|
|
1234
1435
|
setState((prev) => ({
|
|
1235
1436
|
...prev,
|
|
1236
1437
|
screen: "workflow-creator",
|
|
@@ -1241,7 +1442,7 @@ const App = ({
|
|
|
1241
1442
|
}, []);
|
|
1242
1443
|
React.useEffect(() => {
|
|
1243
1444
|
onRulesEditorSetup((request) => {
|
|
1244
|
-
|
|
1445
|
+
enterAltScreen();
|
|
1245
1446
|
setState((prev) => ({
|
|
1246
1447
|
...prev,
|
|
1247
1448
|
screen: "rules-editor",
|
|
@@ -1252,7 +1453,7 @@ const App = ({
|
|
|
1252
1453
|
}, []);
|
|
1253
1454
|
React.useEffect(() => {
|
|
1254
1455
|
onMCPRemoveScreenSetup((servers) => {
|
|
1255
|
-
|
|
1456
|
+
enterAltScreen();
|
|
1256
1457
|
setState((prev) => ({
|
|
1257
1458
|
...prev,
|
|
1258
1459
|
screen: "mcp-remove",
|
|
@@ -1263,7 +1464,7 @@ const App = ({
|
|
|
1263
1464
|
}, []);
|
|
1264
1465
|
React.useEffect(() => {
|
|
1265
1466
|
onMCPEnableScreenSetup((servers) => {
|
|
1266
|
-
|
|
1467
|
+
enterAltScreen();
|
|
1267
1468
|
setState((prev) => ({
|
|
1268
1469
|
...prev,
|
|
1269
1470
|
screen: "mcp-enable",
|
|
@@ -1274,7 +1475,7 @@ const App = ({
|
|
|
1274
1475
|
}, []);
|
|
1275
1476
|
React.useEffect(() => {
|
|
1276
1477
|
onMCPDisableScreenSetup((servers) => {
|
|
1277
|
-
|
|
1478
|
+
enterAltScreen();
|
|
1278
1479
|
setState((prev) => ({
|
|
1279
1480
|
...prev,
|
|
1280
1481
|
screen: "mcp-disable",
|
|
@@ -1285,7 +1486,7 @@ const App = ({
|
|
|
1285
1486
|
}, []);
|
|
1286
1487
|
React.useEffect(() => {
|
|
1287
1488
|
onMCPListScreenSetup((servers) => {
|
|
1288
|
-
|
|
1489
|
+
enterAltScreen();
|
|
1289
1490
|
setState((prev) => ({
|
|
1290
1491
|
...prev,
|
|
1291
1492
|
screen: "mcp-list",
|
|
@@ -1294,6 +1495,27 @@ const App = ({
|
|
|
1294
1495
|
}));
|
|
1295
1496
|
});
|
|
1296
1497
|
}, []);
|
|
1498
|
+
const ALT_SCREEN_EXCLUDED = ["chat", "version-update", "approval"];
|
|
1499
|
+
const altScreenActiveRef = React.useRef(false);
|
|
1500
|
+
const enterAltScreen = React.useCallback(() => {
|
|
1501
|
+
if (!altScreenActiveRef.current) {
|
|
1502
|
+
process.stdout.write("\x1B[?1049h\x1B[2J\x1B[H");
|
|
1503
|
+
altScreenActiveRef.current = true;
|
|
1504
|
+
}
|
|
1505
|
+
}, []);
|
|
1506
|
+
const exitAltScreen = React.useCallback(() => {
|
|
1507
|
+
if (altScreenActiveRef.current) {
|
|
1508
|
+
process.stdout.write("\x1B[?1049l");
|
|
1509
|
+
altScreenActiveRef.current = false;
|
|
1510
|
+
}
|
|
1511
|
+
}, []);
|
|
1512
|
+
React.useEffect(() => {
|
|
1513
|
+
if (ALT_SCREEN_EXCLUDED.includes(state.screen) && altScreenActiveRef.current) {
|
|
1514
|
+
exitAltScreen();
|
|
1515
|
+
clearScreen();
|
|
1516
|
+
setState((prev) => ({ ...prev, messageListKey: (prev.messageListKey ?? 0) + 1 }));
|
|
1517
|
+
}
|
|
1518
|
+
}, [state.screen, exitAltScreen, clearScreen]);
|
|
1297
1519
|
React.useEffect(() => {
|
|
1298
1520
|
onUIMessageHistoryUpdate(state.messageHistory);
|
|
1299
1521
|
}, [state.messageHistory]);
|
|
@@ -1307,25 +1529,92 @@ const App = ({
|
|
|
1307
1529
|
`);
|
|
1308
1530
|
} catch (e) {
|
|
1309
1531
|
}
|
|
1310
|
-
if (update.
|
|
1311
|
-
if (update.
|
|
1532
|
+
if (update.arguments?._customTunnelExec) {
|
|
1533
|
+
if (update.status === "executing") {
|
|
1312
1534
|
setState((prev) => ({
|
|
1313
1535
|
...prev,
|
|
1314
|
-
// Move current message to history if needed
|
|
1315
1536
|
messageHistory: prev.currentMessage && !shouldSkipMessage(prev.currentMessage) ? [...prev.messageHistory, prev.currentMessage] : prev.messageHistory,
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1537
|
+
currentMessage: null,
|
|
1538
|
+
isLoading: false,
|
|
1539
|
+
isAiWorking: true
|
|
1540
|
+
}));
|
|
1541
|
+
return;
|
|
1542
|
+
}
|
|
1543
|
+
if (update.status === "completed" || update.status === "error") {
|
|
1544
|
+
const tunnelResult = update.result || "(no output)";
|
|
1545
|
+
const tunnelError = update.error;
|
|
1546
|
+
setState((prev) => ({
|
|
1547
|
+
...prev,
|
|
1548
|
+
messageHistory: [...prev.messageHistory, {
|
|
1549
|
+
id: `tunnel-exec-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`,
|
|
1319
1550
|
role: "tool",
|
|
1320
1551
|
content: "",
|
|
1321
1552
|
timestamp: /* @__PURE__ */ new Date(),
|
|
1322
|
-
toolExecution: {
|
|
1323
|
-
|
|
1324
|
-
|
|
1553
|
+
toolExecution: {
|
|
1554
|
+
toolName: "execute_command",
|
|
1555
|
+
status: update.status,
|
|
1556
|
+
result: tunnelResult,
|
|
1557
|
+
error: tunnelError,
|
|
1558
|
+
arguments: update.arguments
|
|
1559
|
+
}
|
|
1560
|
+
}],
|
|
1561
|
+
currentMessage: null,
|
|
1325
1562
|
isAiWorking: true
|
|
1563
|
+
// AI loop continues
|
|
1326
1564
|
}));
|
|
1327
1565
|
return;
|
|
1328
1566
|
}
|
|
1567
|
+
}
|
|
1568
|
+
if (update.status === "executing") {
|
|
1569
|
+
if (bufferedContentRef.current.length > 0) {
|
|
1570
|
+
const flushedContent = bufferedContentRef.current;
|
|
1571
|
+
bufferedContentRef.current = "";
|
|
1572
|
+
bufferedMessageIdRef.current = null;
|
|
1573
|
+
setState((prev) => {
|
|
1574
|
+
if (prev.currentMessage && prev.currentMessage.role === "assistant") {
|
|
1575
|
+
const flushedMessage = {
|
|
1576
|
+
...prev.currentMessage,
|
|
1577
|
+
content: prev.currentMessage.content.trim() === "" ? flushedContent : prev.currentMessage.content + flushedContent,
|
|
1578
|
+
shouldStream: false
|
|
1579
|
+
};
|
|
1580
|
+
return {
|
|
1581
|
+
...prev,
|
|
1582
|
+
messageHistory: shouldSkipMessage(flushedMessage) ? prev.messageHistory : [...prev.messageHistory, flushedMessage],
|
|
1583
|
+
currentMessage: null
|
|
1584
|
+
};
|
|
1585
|
+
}
|
|
1586
|
+
return prev;
|
|
1587
|
+
});
|
|
1588
|
+
}
|
|
1589
|
+
if (update.arguments?.shell_input) {
|
|
1590
|
+
setState((prev) => {
|
|
1591
|
+
if (prev.customTunnel) {
|
|
1592
|
+
return {
|
|
1593
|
+
...prev,
|
|
1594
|
+
messageHistory: prev.currentMessage && !shouldSkipMessage(prev.currentMessage) ? [...prev.messageHistory, prev.currentMessage] : prev.messageHistory,
|
|
1595
|
+
currentMessage: null,
|
|
1596
|
+
isLoading: false,
|
|
1597
|
+
isAiWorking: true
|
|
1598
|
+
};
|
|
1599
|
+
}
|
|
1600
|
+
return {
|
|
1601
|
+
...prev,
|
|
1602
|
+
// Move current message to history if needed
|
|
1603
|
+
messageHistory: prev.currentMessage && !shouldSkipMessage(prev.currentMessage) ? [...prev.messageHistory, prev.currentMessage] : prev.messageHistory,
|
|
1604
|
+
// Update current message to show the tool execution
|
|
1605
|
+
currentMessage: {
|
|
1606
|
+
id: `tool-execute_command-${Date.now()}`,
|
|
1607
|
+
role: "tool",
|
|
1608
|
+
content: "",
|
|
1609
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
1610
|
+
toolExecution: { ...update }
|
|
1611
|
+
},
|
|
1612
|
+
isLoading: false,
|
|
1613
|
+
isAiWorking: true
|
|
1614
|
+
};
|
|
1615
|
+
});
|
|
1616
|
+
return;
|
|
1617
|
+
}
|
|
1329
1618
|
const command = update.arguments?.command || update.arguments?.CommandLine || update.arguments?.commandLine || "";
|
|
1330
1619
|
const timeoutId = setTimeout(() => {
|
|
1331
1620
|
if (pendingShellRef.current) {
|
|
@@ -1459,17 +1748,27 @@ const App = ({
|
|
|
1459
1748
|
ShellInputAgent.terminateSession(prev.shellState.shellId);
|
|
1460
1749
|
}
|
|
1461
1750
|
}
|
|
1751
|
+
const keepShellState = isShellInput && !isDeadShellError;
|
|
1752
|
+
const tunnelClosedMessage = prev.customTunnel && !keepShellState ? {
|
|
1753
|
+
id: `custom-tunnel-closed-${Date.now()}`,
|
|
1754
|
+
role: "assistant",
|
|
1755
|
+
content: `Custom tunnel closed for \`${prev.customTunnel.matchedCommand}\`. Chat input will now go back to the AI.`,
|
|
1756
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
1757
|
+
shouldStream: false
|
|
1758
|
+
} : null;
|
|
1759
|
+
const skipShellMessage = keepShellState && prev.customTunnel;
|
|
1462
1760
|
return {
|
|
1463
1761
|
...prev,
|
|
1464
1762
|
// If it's just shell input, preserve the shell state UNLESS it's a dead shell error
|
|
1465
|
-
shellState:
|
|
1466
|
-
|
|
1763
|
+
shellState: keepShellState ? prev.shellState : void 0,
|
|
1764
|
+
customTunnel: keepShellState ? prev.customTunnel : void 0,
|
|
1765
|
+
messageHistory: skipShellMessage ? prev.messageHistory : tunnelClosedMessage ? [...prev.messageHistory, tunnelClosedMessage, shellMessage] : [...prev.messageHistory, shellMessage],
|
|
1467
1766
|
currentMessage: null,
|
|
1468
1767
|
// Determine if AI should keep working:
|
|
1469
1768
|
// - Normal shell input: preserve state (keep thrusting if in chain)
|
|
1470
1769
|
// - Agent-started shell completed successfully: preserve state (agent loop continues)
|
|
1471
1770
|
// - Dead shell error or manual shell completed: STOP thrusting (reset to false)
|
|
1472
|
-
isAiWorking: isCommandModeExecution ? false :
|
|
1771
|
+
isAiWorking: isCommandModeExecution ? false : keepShellState ? prev.isAiWorking : isAgentStarted && !shouldKill ? prev.isAiWorking : false
|
|
1473
1772
|
};
|
|
1474
1773
|
});
|
|
1475
1774
|
return;
|
|
@@ -1545,9 +1844,18 @@ const App = ({
|
|
|
1545
1844
|
let newHistory2 = prev.messageHistory;
|
|
1546
1845
|
let pendingThinkingDuration = void 0;
|
|
1547
1846
|
if (prev.currentMessage) {
|
|
1548
|
-
|
|
1847
|
+
let messageToCommit = prev.currentMessage;
|
|
1848
|
+
if (bufferedContentRef.current.length > 0 && messageToCommit.role === "assistant" && messageToCommit.content.trim() === "") {
|
|
1849
|
+
messageToCommit = {
|
|
1850
|
+
...messageToCommit,
|
|
1851
|
+
content: bufferedContentRef.current
|
|
1852
|
+
};
|
|
1853
|
+
bufferedContentRef.current = "";
|
|
1854
|
+
bufferedMessageIdRef.current = null;
|
|
1855
|
+
}
|
|
1856
|
+
const isThoughtOnlyMessage = messageToCommit.role === "assistant" && messageToCommit.content.trim() === "" && messageToCommit.thinkingDuration !== void 0;
|
|
1549
1857
|
if (isThoughtOnlyMessage) {
|
|
1550
|
-
pendingThinkingDuration =
|
|
1858
|
+
pendingThinkingDuration = messageToCommit.thinkingDuration;
|
|
1551
1859
|
try {
|
|
1552
1860
|
quickLog(`[${(/* @__PURE__ */ new Date()).toISOString()}] [App] Skipping thought-only message (no content), saving thinkingDuration ${pendingThinkingDuration}s for tool ${update.toolName}
|
|
1553
1861
|
`);
|
|
@@ -1555,11 +1863,11 @@ const App = ({
|
|
|
1555
1863
|
}
|
|
1556
1864
|
} else {
|
|
1557
1865
|
try {
|
|
1558
|
-
quickLog(`[${(/* @__PURE__ */ new Date()).toISOString()}] [App] Moving current message (${
|
|
1866
|
+
quickLog(`[${(/* @__PURE__ */ new Date()).toISOString()}] [App] Moving current message (${messageToCommit.role}) to history for new tool ${update.toolName}
|
|
1559
1867
|
`);
|
|
1560
1868
|
} catch (e) {
|
|
1561
1869
|
}
|
|
1562
|
-
newHistory2 = [...prev.messageHistory,
|
|
1870
|
+
newHistory2 = [...prev.messageHistory, messageToCommit];
|
|
1563
1871
|
}
|
|
1564
1872
|
}
|
|
1565
1873
|
const toolMessageWithDuration = {
|
|
@@ -1581,7 +1889,13 @@ const App = ({
|
|
|
1581
1889
|
}
|
|
1582
1890
|
let newHistory = prev.messageHistory;
|
|
1583
1891
|
if (prev.currentMessage && !shouldSkipMessage(prev.currentMessage)) {
|
|
1584
|
-
|
|
1892
|
+
let msgToCommit = prev.currentMessage;
|
|
1893
|
+
if (bufferedContentRef.current.length > 0 && msgToCommit.role === "assistant" && msgToCommit.content.trim() === "") {
|
|
1894
|
+
msgToCommit = { ...msgToCommit, content: bufferedContentRef.current };
|
|
1895
|
+
bufferedContentRef.current = "";
|
|
1896
|
+
bufferedMessageIdRef.current = null;
|
|
1897
|
+
}
|
|
1898
|
+
newHistory = [...prev.messageHistory, msgToCommit];
|
|
1585
1899
|
}
|
|
1586
1900
|
return {
|
|
1587
1901
|
...prev,
|
|
@@ -1820,7 +2134,7 @@ const App = ({
|
|
|
1820
2134
|
return prev;
|
|
1821
2135
|
});
|
|
1822
2136
|
});
|
|
1823
|
-
}, [onMessage]);
|
|
2137
|
+
}, [appendStaticInfoMessage, onMessage, onShellInput, onSkillExecute, shouldSkipMessage, state]);
|
|
1824
2138
|
React.useEffect(() => {
|
|
1825
2139
|
onPlanModeChange((planMode) => {
|
|
1826
2140
|
setState((prev) => ({
|
|
@@ -1969,29 +2283,52 @@ const App = ({
|
|
|
1969
2283
|
onPlanApprovalRequest(async (plan) => {
|
|
1970
2284
|
return new Promise((resolve) => {
|
|
1971
2285
|
clearScreen();
|
|
1972
|
-
setState((prev) =>
|
|
1973
|
-
...prev
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
2286
|
+
setState((prev) => {
|
|
2287
|
+
savedHistoryRef.current = [...prev.messageHistory];
|
|
2288
|
+
savedCurrentRef.current = prev.currentMessage;
|
|
2289
|
+
return {
|
|
2290
|
+
...prev,
|
|
2291
|
+
screen: "plan-approval",
|
|
2292
|
+
planApprovalRequest: {
|
|
2293
|
+
plan,
|
|
2294
|
+
resolve
|
|
2295
|
+
},
|
|
2296
|
+
isLoading: false,
|
|
2297
|
+
// Clear messages so Ink's Static doesn't re-render chat above the plan screen
|
|
2298
|
+
messageHistory: [],
|
|
2299
|
+
currentMessage: null
|
|
2300
|
+
};
|
|
2301
|
+
});
|
|
1981
2302
|
});
|
|
1982
2303
|
});
|
|
1983
2304
|
}, [clearScreen]);
|
|
1984
2305
|
React.useEffect(() => {
|
|
1985
2306
|
onPlanCreated((plan) => {
|
|
1986
2307
|
try {
|
|
1987
|
-
quickLog(`[${(/* @__PURE__ */ new Date()).toISOString()}] [App] Plan created: ${plan.title} with ${plan.
|
|
2308
|
+
quickLog(`[${(/* @__PURE__ */ new Date()).toISOString()}] [App] Plan created: ${plan.title} with ${plan.implementationSteps.length} steps
|
|
1988
2309
|
`);
|
|
1989
2310
|
} catch (e) {
|
|
1990
2311
|
}
|
|
1991
2312
|
});
|
|
1992
2313
|
}, [onPlanCreated]);
|
|
1993
2314
|
React.useEffect(() => {
|
|
1994
|
-
|
|
2315
|
+
onPlanQuestionRequest(async (question, options) => {
|
|
2316
|
+
return new Promise((resolve) => {
|
|
2317
|
+
setState((prev) => ({
|
|
2318
|
+
...prev,
|
|
2319
|
+
screen: "plan-question",
|
|
2320
|
+
planQuestionRequest: {
|
|
2321
|
+
question,
|
|
2322
|
+
options,
|
|
2323
|
+
resolve
|
|
2324
|
+
},
|
|
2325
|
+
isLoading: false
|
|
2326
|
+
}));
|
|
2327
|
+
});
|
|
2328
|
+
});
|
|
2329
|
+
}, []);
|
|
2330
|
+
React.useEffect(() => {
|
|
2331
|
+
onTaskCompleted((stepNumber, stepDescription, completedCount, totalCount, completionNote, allSteps) => {
|
|
1995
2332
|
setState((prev) => {
|
|
1996
2333
|
const taskCompletedMessage = {
|
|
1997
2334
|
id: `task-complete-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`,
|
|
@@ -2000,11 +2337,16 @@ const App = ({
|
|
|
2000
2337
|
// We'll render this specially
|
|
2001
2338
|
timestamp: /* @__PURE__ */ new Date(),
|
|
2002
2339
|
taskCompletion: {
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
completionNote
|
|
2340
|
+
completedStepNumber: stepNumber,
|
|
2341
|
+
completedStepDescription: stepDescription,
|
|
2342
|
+
completedCount,
|
|
2343
|
+
totalCount,
|
|
2344
|
+
completionNote,
|
|
2345
|
+
allSteps: allSteps?.map((s) => ({
|
|
2346
|
+
id: s.id,
|
|
2347
|
+
description: s.description,
|
|
2348
|
+
status: s.status
|
|
2349
|
+
}))
|
|
2008
2350
|
}
|
|
2009
2351
|
};
|
|
2010
2352
|
const newHistory = prev.currentMessage && !shouldSkipMessage(prev.currentMessage) ? [...prev.messageHistory, prev.currentMessage, taskCompletedMessage] : [...prev.messageHistory, taskCompletedMessage];
|
|
@@ -2016,6 +2358,34 @@ const App = ({
|
|
|
2016
2358
|
});
|
|
2017
2359
|
});
|
|
2018
2360
|
}, [onTaskCompleted]);
|
|
2361
|
+
React.useEffect(() => {
|
|
2362
|
+
onTodoListUpdate((todos, completedCount, totalCount) => {
|
|
2363
|
+
setState((prev) => {
|
|
2364
|
+
const todoListMessage = {
|
|
2365
|
+
id: `todo-list-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`,
|
|
2366
|
+
role: "system",
|
|
2367
|
+
content: "",
|
|
2368
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
2369
|
+
todoList: {
|
|
2370
|
+
todos: todos.map((t) => ({
|
|
2371
|
+
id: t.id,
|
|
2372
|
+
content: t.content,
|
|
2373
|
+
status: t.status
|
|
2374
|
+
})),
|
|
2375
|
+
completedCount,
|
|
2376
|
+
totalCount
|
|
2377
|
+
}
|
|
2378
|
+
};
|
|
2379
|
+
const newHistory = prev.currentMessage && !shouldSkipMessage(prev.currentMessage) ? [...prev.messageHistory, prev.currentMessage, todoListMessage] : [...prev.messageHistory, todoListMessage];
|
|
2380
|
+
return {
|
|
2381
|
+
...prev,
|
|
2382
|
+
messageHistory: newHistory,
|
|
2383
|
+
currentMessage: null,
|
|
2384
|
+
isAiWorking: true
|
|
2385
|
+
};
|
|
2386
|
+
});
|
|
2387
|
+
});
|
|
2388
|
+
}, [onTodoListUpdate]);
|
|
2019
2389
|
React.useEffect(() => {
|
|
2020
2390
|
onFileChangeSummary((data) => {
|
|
2021
2391
|
setState((prev) => ({ ...prev, lastFileChangeSummary: data }));
|
|
@@ -2122,11 +2492,34 @@ const App = ({
|
|
|
2122
2492
|
if (state.shellState?.isFocused) {
|
|
2123
2493
|
return;
|
|
2124
2494
|
}
|
|
2495
|
+
const isCtrlE = key.ctrl && input.toLowerCase() === "e" || input === "";
|
|
2496
|
+
if (key.meta && input.toLowerCase() === "e" || isCtrlE) {
|
|
2497
|
+
if (state.customTunnel && state.shellState?.isRunning) {
|
|
2498
|
+
flushTunnelOutput();
|
|
2499
|
+
clearScreen();
|
|
2500
|
+
onCustomTunnelStateChange(null);
|
|
2501
|
+
setState((prev) => ({
|
|
2502
|
+
...prev,
|
|
2503
|
+
customTunnel: void 0,
|
|
2504
|
+
shellState: prev.shellState ? {
|
|
2505
|
+
...prev.shellState,
|
|
2506
|
+
isFocused: true
|
|
2507
|
+
} : void 0
|
|
2508
|
+
}));
|
|
2509
|
+
return;
|
|
2510
|
+
}
|
|
2511
|
+
}
|
|
2125
2512
|
if (key.ctrl && input === "f") {
|
|
2126
2513
|
if (state.shellState && state.shellState.isRunning) {
|
|
2514
|
+
if (state.customTunnel) {
|
|
2515
|
+
flushTunnelOutput();
|
|
2516
|
+
onCustomTunnelStateChange(null);
|
|
2517
|
+
}
|
|
2127
2518
|
clearScreen();
|
|
2128
2519
|
setState((prev) => ({
|
|
2129
2520
|
...prev,
|
|
2521
|
+
customTunnel: void 0,
|
|
2522
|
+
// Always clear tunnel when toggling focus
|
|
2130
2523
|
shellState: prev.shellState ? {
|
|
2131
2524
|
...prev.shellState,
|
|
2132
2525
|
isFocused: !prev.shellState.isFocused
|
|
@@ -2155,7 +2548,7 @@ const App = ({
|
|
|
2155
2548
|
}));
|
|
2156
2549
|
return;
|
|
2157
2550
|
}
|
|
2158
|
-
if (state.screen === "mcp-
|
|
2551
|
+
if (state.screen === "mcp-remove" || state.screen === "mcp-enable" || state.screen === "mcp-disable" || state.screen === "mcp-list") {
|
|
2159
2552
|
setState((prev) => ({
|
|
2160
2553
|
...prev,
|
|
2161
2554
|
screen: "chat",
|
|
@@ -2163,8 +2556,12 @@ const App = ({
|
|
|
2163
2556
|
}));
|
|
2164
2557
|
return;
|
|
2165
2558
|
}
|
|
2559
|
+
if (state.screen === "sub-agent-list" || state.screen === "sub-agent-view" || state.screen === "sub-agent-terminate" || state.screen === "skill-editor" || state.screen === "mcp-add" || state.screen === "rules-editor" || state.screen === "plan-question") {
|
|
2560
|
+
return;
|
|
2561
|
+
}
|
|
2166
2562
|
if (state.isLoading || state.isAiWorking) {
|
|
2167
2563
|
onCancelRequest();
|
|
2564
|
+
ignoreIncomingAIUpdatesRef.current = true;
|
|
2168
2565
|
setState((prev) => ({
|
|
2169
2566
|
...prev,
|
|
2170
2567
|
isLoading: false,
|
|
@@ -2191,6 +2588,7 @@ const App = ({
|
|
|
2191
2588
|
}
|
|
2192
2589
|
if (state.isLoading || state.isAiWorking) {
|
|
2193
2590
|
onCancelRequest();
|
|
2591
|
+
ignoreIncomingAIUpdatesRef.current = true;
|
|
2194
2592
|
setState((prev) => ({
|
|
2195
2593
|
...prev,
|
|
2196
2594
|
isLoading: false,
|
|
@@ -2242,6 +2640,62 @@ const App = ({
|
|
|
2242
2640
|
const wasAiWorkingBeforeSubmit = isAiWorkingRef.current;
|
|
2243
2641
|
if (!trimmedValue) return;
|
|
2244
2642
|
const isSlashCommand = trimmedValue.startsWith("/");
|
|
2643
|
+
if (customTunnelRef.current && shellRunningRef.current && !isSlashCommand) {
|
|
2644
|
+
const intent = detectIntent(trimmedValue);
|
|
2645
|
+
if (intent === "command") {
|
|
2646
|
+
if (clipboardFiles && clipboardFiles.length > 0) {
|
|
2647
|
+
appendStaticInfoMessage("File attachments are not available while custom tunnel mode is active.");
|
|
2648
|
+
return;
|
|
2649
|
+
}
|
|
2650
|
+
flushTunnelOutput();
|
|
2651
|
+
const tunnelInput = trimmedValue.endsWith("\n") || trimmedValue.endsWith("\r") ? trimmedValue : `${trimmedValue}\r`;
|
|
2652
|
+
try {
|
|
2653
|
+
quickLog(`[${(/* @__PURE__ */ new Date()).toISOString()}] [CustomTunnel] Sending chat input to shell: ${JSON.stringify(trimmedValue)}
|
|
2654
|
+
`);
|
|
2655
|
+
} catch (e) {
|
|
2656
|
+
}
|
|
2657
|
+
setState((prev) => {
|
|
2658
|
+
const userMessage = {
|
|
2659
|
+
id: `user-${Date.now()}`,
|
|
2660
|
+
role: "user",
|
|
2661
|
+
content: trimmedValue,
|
|
2662
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
2663
|
+
};
|
|
2664
|
+
const messageHistory = prev.currentMessage && !shouldSkipMessage(prev.currentMessage) ? [...prev.messageHistory, prev.currentMessage, userMessage] : [...prev.messageHistory, userMessage];
|
|
2665
|
+
return {
|
|
2666
|
+
...prev,
|
|
2667
|
+
messageHistory,
|
|
2668
|
+
currentMessage: null,
|
|
2669
|
+
isLoading: false,
|
|
2670
|
+
isAiWorking: false,
|
|
2671
|
+
commandHistory: prev.commandHistory[prev.commandHistory.length - 1] === trimmedValue ? prev.commandHistory : [...prev.commandHistory, trimmedValue]
|
|
2672
|
+
};
|
|
2673
|
+
});
|
|
2674
|
+
onShellInput(tunnelInput);
|
|
2675
|
+
return;
|
|
2676
|
+
}
|
|
2677
|
+
}
|
|
2678
|
+
if (trimmedValue.startsWith("#")) {
|
|
2679
|
+
const parts = trimmedValue.slice(1).split(/\s+/);
|
|
2680
|
+
const skillName = parts[0];
|
|
2681
|
+
const params = parts.slice(1).join(" ");
|
|
2682
|
+
if (skillName) {
|
|
2683
|
+
const userMessage = {
|
|
2684
|
+
id: `user-${Date.now()}`,
|
|
2685
|
+
role: "user",
|
|
2686
|
+
content: trimmedValue,
|
|
2687
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
2688
|
+
};
|
|
2689
|
+
setState((prev) => ({
|
|
2690
|
+
...prev,
|
|
2691
|
+
messageHistory: [...prev.messageHistory, userMessage],
|
|
2692
|
+
isAiWorking: true
|
|
2693
|
+
}));
|
|
2694
|
+
await onSkillExecute(skillName, params);
|
|
2695
|
+
setState((prev) => ({ ...prev, isAiWorking: false }));
|
|
2696
|
+
return;
|
|
2697
|
+
}
|
|
2698
|
+
}
|
|
2245
2699
|
const lowerValue = trimmedValue.toLowerCase();
|
|
2246
2700
|
if (lowerValue === "/clear" || lowerValue === "clear" || lowerValue === "/cls" || lowerValue === "cls" || lowerValue === "/reset") {
|
|
2247
2701
|
const height = process.stdout.rows || 24;
|
|
@@ -2505,7 +2959,7 @@ const App = ({
|
|
|
2505
2959
|
isAiWorking: wasAiWorkingBeforeSubmit ? prev.isAiWorking : false
|
|
2506
2960
|
}));
|
|
2507
2961
|
}
|
|
2508
|
-
}, [onMessage]);
|
|
2962
|
+
}, [appendStaticInfoMessage, flushTunnelOutput, onMessage, onShellInput, onSkillExecute, shouldSkipMessage]);
|
|
2509
2963
|
const handleToggleAutoAccept = useCallback(() => {
|
|
2510
2964
|
setState((prev) => ({
|
|
2511
2965
|
...prev,
|
|
@@ -2515,21 +2969,59 @@ const App = ({
|
|
|
2515
2969
|
const handleInputValueChange = useCallback((value) => {
|
|
2516
2970
|
preservedInputTextRef.current = value;
|
|
2517
2971
|
}, []);
|
|
2972
|
+
const terminalColumns = process.stdout.columns || 120;
|
|
2973
|
+
const limitedHistory = useMemo(() => limitChatHistoryForDisplay(state.messageHistory, {
|
|
2974
|
+
enabled: state.limitChatHistory,
|
|
2975
|
+
width: terminalColumns
|
|
2976
|
+
}), [state.messageHistory, state.limitChatHistory, terminalColumns]);
|
|
2977
|
+
const displayedMessageHistory = useMemo(() => {
|
|
2978
|
+
if (limitedHistory.hiddenMessageCount === 0) {
|
|
2979
|
+
return limitedHistory.visibleMessages;
|
|
2980
|
+
}
|
|
2981
|
+
const hiddenCount = limitedHistory.hiddenMessageCount;
|
|
2982
|
+
const notice = {
|
|
2983
|
+
id: `__chat-history-limit__-${hiddenCount}`,
|
|
2984
|
+
role: "system",
|
|
2985
|
+
content: `\u2139\uFE0F Showing the most recent chat history only (${hiddenCount} older message${hiddenCount === 1 ? "" : "s"} hidden). Run \`/settings limit-chat-history off\` to show the full chat history in the UI.`,
|
|
2986
|
+
timestamp: /* @__PURE__ */ new Date(0)
|
|
2987
|
+
};
|
|
2988
|
+
return [notice, ...limitedHistory.visibleMessages];
|
|
2989
|
+
}, [limitedHistory]);
|
|
2990
|
+
React.useEffect(() => {
|
|
2991
|
+
const hiddenCount = limitedHistory.hiddenMessageCount;
|
|
2992
|
+
if (previousHiddenMessageCountRef.current === null) {
|
|
2993
|
+
previousHiddenMessageCountRef.current = hiddenCount;
|
|
2994
|
+
return;
|
|
2995
|
+
}
|
|
2996
|
+
if (hiddenCount === previousHiddenMessageCountRef.current) {
|
|
2997
|
+
return;
|
|
2998
|
+
}
|
|
2999
|
+
previousHiddenMessageCountRef.current = hiddenCount;
|
|
3000
|
+
if (state.screen !== "chat" || state.isInteractiveEditorMode || state.shellState?.isFocused) {
|
|
3001
|
+
return;
|
|
3002
|
+
}
|
|
3003
|
+
clearScreen();
|
|
3004
|
+
setState((prev) => ({
|
|
3005
|
+
...prev,
|
|
3006
|
+
messageListKey: (prev.messageListKey ?? 0) + 1
|
|
3007
|
+
}));
|
|
3008
|
+
}, [limitedHistory.hiddenMessageCount, state.screen, state.isInteractiveEditorMode, state.shellState?.isFocused, clearScreen]);
|
|
2518
3009
|
const sessionCommands = useMemo(() => {
|
|
2519
3010
|
return state.messageHistory.filter((m) => m.role === "user").map((m) => m.content);
|
|
2520
3011
|
}, [state.messageHistory]);
|
|
2521
3012
|
if (state.isInteractiveEditorMode) {
|
|
2522
3013
|
return /* @__PURE__ */ React.createElement(Box, null);
|
|
2523
3014
|
}
|
|
2524
|
-
|
|
3015
|
+
const themeColors = useMemo(() => deriveThemeColors(state.themeColor || DEFAULT_ACCENT_COLOR), [state.themeColor]);
|
|
3016
|
+
return /* @__PURE__ */ React.createElement(ThemeContext.Provider, { value: themeColors }, /* @__PURE__ */ React.createElement(Box, { flexDirection: "column" }, state.screen === "chat" && /* @__PURE__ */ React.createElement(React.Fragment, null, !state.shellState?.isFocused && /* @__PURE__ */ React.createElement(
|
|
2525
3017
|
MessageList,
|
|
2526
3018
|
{
|
|
2527
3019
|
key: `msglist-${state.messageListKey}`,
|
|
2528
|
-
history:
|
|
3020
|
+
history: displayedMessageHistory,
|
|
2529
3021
|
current: state.currentMessage,
|
|
2530
3022
|
showBanner: true
|
|
2531
3023
|
}
|
|
2532
|
-
), state.shellState && (getTerminalDimensions().shouldEnableStreaming || !state.shellState.isRunning) && /* @__PURE__ */ React.createElement(
|
|
3024
|
+
), state.shellState && !state.customTunnel && (state.shellState.isFocused || getTerminalDimensions().shouldEnableStreaming || !state.shellState.isRunning) && /* @__PURE__ */ React.createElement(
|
|
2533
3025
|
InteractiveShell,
|
|
2534
3026
|
{
|
|
2535
3027
|
command: state.shellState.command,
|
|
@@ -2622,90 +3114,128 @@ const App = ({
|
|
|
2622
3114
|
`);
|
|
2623
3115
|
}
|
|
2624
3116
|
},
|
|
2625
|
-
onWarpifySession:
|
|
2626
|
-
|
|
2627
|
-
const
|
|
2628
|
-
const
|
|
2629
|
-
|
|
3117
|
+
onWarpifySession: (() => {
|
|
3118
|
+
if (state.shellState?.isBackgroundTask) return void 0;
|
|
3119
|
+
const cmd = state.shellState?.command || "";
|
|
3120
|
+
const out = state.shellState?.output || "";
|
|
3121
|
+
const registered = getRegisteredTunnelCommands();
|
|
3122
|
+
const detected = detectWarpifySession(cmd, out, registered);
|
|
3123
|
+
if (detected.type === "none") return void 0;
|
|
3124
|
+
return async () => {
|
|
3125
|
+
const shellCommand = state.shellState?.command || "";
|
|
3126
|
+
const currentOutput = state.shellState?.output || "";
|
|
3127
|
+
const registeredTunnelCommands = getRegisteredTunnelCommands();
|
|
3128
|
+
const session = detectWarpifySession(shellCommand, currentOutput, registeredTunnelCommands);
|
|
3129
|
+
quickLog(`[${(/* @__PURE__ */ new Date()).toISOString()}] [Warpify] Command: "${shellCommand}", Detected: ${JSON.stringify(session)}
|
|
2630
3130
|
`);
|
|
2631
|
-
|
|
2632
|
-
setState((prev) => ({
|
|
2633
|
-
...prev,
|
|
2634
|
-
connectionStatus: {
|
|
2635
|
-
type: "ssh",
|
|
2636
|
-
status: "error",
|
|
2637
|
-
error: "No remote session detected. Enter an SSH/WSL/Docker session first."
|
|
2638
|
-
}
|
|
2639
|
-
}));
|
|
2640
|
-
setTimeout(() => {
|
|
3131
|
+
if (session.type === "none") {
|
|
2641
3132
|
setState((prev) => ({
|
|
2642
3133
|
...prev,
|
|
2643
|
-
connectionStatus:
|
|
3134
|
+
connectionStatus: {
|
|
3135
|
+
type: "ssh",
|
|
3136
|
+
status: "error",
|
|
3137
|
+
error: "No tunnelable session detected. Enter an SSH/WSL/Docker session first, or register a command with /settings add-tunnel-command."
|
|
3138
|
+
}
|
|
2644
3139
|
}));
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
warpifyTerminatingCommandRef.current = shellCommand;
|
|
2653
|
-
try {
|
|
2654
|
-
onShellSignal("SIGTERM");
|
|
2655
|
-
const historyMessages = [];
|
|
2656
|
-
if (shellCommand) {
|
|
2657
|
-
historyMessages.push({
|
|
2658
|
-
id: `warpify-cmd-${Date.now()}`,
|
|
2659
|
-
role: "user",
|
|
2660
|
-
content: shellCommand,
|
|
2661
|
-
timestamp: /* @__PURE__ */ new Date(),
|
|
2662
|
-
isCommandMode: false
|
|
2663
|
-
// Treat as a normal chat message for history
|
|
2664
|
-
});
|
|
3140
|
+
setTimeout(() => {
|
|
3141
|
+
setState((prev) => ({
|
|
3142
|
+
...prev,
|
|
3143
|
+
connectionStatus: void 0
|
|
3144
|
+
}));
|
|
3145
|
+
}, 3e3);
|
|
3146
|
+
return;
|
|
2665
3147
|
}
|
|
2666
|
-
if (
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
3148
|
+
if (session.type === "custom") {
|
|
3149
|
+
const matchedCommand = session.connectionString || shellCommand;
|
|
3150
|
+
const isAlreadyActive = state.customTunnel?.shellCommand === shellCommand;
|
|
3151
|
+
clearScreen();
|
|
3152
|
+
if (isAlreadyActive) {
|
|
3153
|
+
onCustomTunnelStateChange(null);
|
|
3154
|
+
setState((prev) => ({
|
|
3155
|
+
...prev,
|
|
3156
|
+
customTunnel: void 0
|
|
3157
|
+
}));
|
|
3158
|
+
appendStaticInfoMessage(`Custom tunnel closed for \`${matchedCommand}\`. Chat input goes back to the AI.`);
|
|
3159
|
+
} else {
|
|
3160
|
+
tunnelOutputPositionRef.current = currentOutput.length;
|
|
3161
|
+
onCustomTunnelStateChange(matchedCommand);
|
|
3162
|
+
setState((prev) => ({
|
|
3163
|
+
...prev,
|
|
3164
|
+
customTunnel: {
|
|
3165
|
+
matchedCommand,
|
|
3166
|
+
shellCommand
|
|
3167
|
+
},
|
|
3168
|
+
shellState: prev.shellState ? {
|
|
3169
|
+
...prev.shellState,
|
|
3170
|
+
isFocused: false
|
|
3171
|
+
} : void 0
|
|
3172
|
+
}));
|
|
3173
|
+
appendStaticInfoMessage(`Custom tunnel active for \`${matchedCommand}\`. Chat messages are now sent to the process. Press ${process.platform === "darwin" ? "Cmd+E" : "Alt+E"} from chat to return to shell focus mode.`);
|
|
3174
|
+
}
|
|
3175
|
+
return;
|
|
2684
3176
|
}
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
messageHistory: [...prev.messageHistory, ...historyMessages],
|
|
2688
|
-
shellState: void 0,
|
|
2689
|
-
currentMessage: null,
|
|
2690
|
-
isAiWorking: false
|
|
2691
|
-
}));
|
|
2692
|
-
clearScreen();
|
|
2693
|
-
const success = await onWarpifySession(
|
|
2694
|
-
shellCommand,
|
|
2695
|
-
session.type,
|
|
2696
|
-
session.connectionString
|
|
2697
|
-
);
|
|
2698
|
-
if (!success) {
|
|
2699
|
-
quickLog(`[${(/* @__PURE__ */ new Date()).toISOString()}] [Warpify] Failed to establish ssh2 connection
|
|
3177
|
+
const description = getSessionDescription(session);
|
|
3178
|
+
quickLog(`[${(/* @__PURE__ */ new Date()).toISOString()}] [Warpify] Warpifying: ${description}
|
|
2700
3179
|
`);
|
|
3180
|
+
warpifyInProgressRef.current = true;
|
|
3181
|
+
warpifyTerminatingCommandRef.current = shellCommand;
|
|
3182
|
+
try {
|
|
3183
|
+
onShellSignal("SIGTERM");
|
|
3184
|
+
const historyMessages = [];
|
|
3185
|
+
if (shellCommand) {
|
|
3186
|
+
historyMessages.push({
|
|
3187
|
+
id: `warpify-cmd-${Date.now()}`,
|
|
3188
|
+
role: "user",
|
|
3189
|
+
content: shellCommand,
|
|
3190
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
3191
|
+
isCommandMode: false
|
|
3192
|
+
// Treat as a normal chat message for history
|
|
3193
|
+
});
|
|
3194
|
+
}
|
|
3195
|
+
if (currentOutput && currentOutput.trim().length > 0) {
|
|
3196
|
+
historyMessages.push({
|
|
3197
|
+
id: `warpify-out-${Date.now()}`,
|
|
3198
|
+
role: "tool",
|
|
3199
|
+
// Use 'tool' role to mimic command execution output
|
|
3200
|
+
content: "",
|
|
3201
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
3202
|
+
toolExecution: {
|
|
3203
|
+
toolName: "execute_command",
|
|
3204
|
+
status: "completed",
|
|
3205
|
+
result: currentOutput,
|
|
3206
|
+
// This contains the PTY output including ANSI codes
|
|
3207
|
+
arguments: {
|
|
3208
|
+
command: shellCommand,
|
|
3209
|
+
isPty: true
|
|
3210
|
+
}
|
|
3211
|
+
}
|
|
3212
|
+
});
|
|
3213
|
+
}
|
|
3214
|
+
setState((prev) => ({
|
|
3215
|
+
...prev,
|
|
3216
|
+
messageHistory: [...prev.messageHistory, ...historyMessages],
|
|
3217
|
+
shellState: void 0,
|
|
3218
|
+
currentMessage: null,
|
|
3219
|
+
isAiWorking: false
|
|
3220
|
+
}));
|
|
3221
|
+
clearScreen();
|
|
3222
|
+
const success = await onWarpifySession(
|
|
3223
|
+
shellCommand,
|
|
3224
|
+
session.type,
|
|
3225
|
+
session.connectionString
|
|
3226
|
+
);
|
|
3227
|
+
if (!success) {
|
|
3228
|
+
quickLog(`[${(/* @__PURE__ */ new Date()).toISOString()}] [Warpify] Failed to establish ssh2 connection
|
|
3229
|
+
`);
|
|
3230
|
+
}
|
|
3231
|
+
} finally {
|
|
3232
|
+
setTimeout(() => {
|
|
3233
|
+
warpifyInProgressRef.current = false;
|
|
3234
|
+
warpifyTerminatingCommandRef.current = null;
|
|
3235
|
+
}, 400);
|
|
2701
3236
|
}
|
|
2702
|
-
}
|
|
2703
|
-
|
|
2704
|
-
warpifyInProgressRef.current = false;
|
|
2705
|
-
warpifyTerminatingCommandRef.current = null;
|
|
2706
|
-
}, 400);
|
|
2707
|
-
}
|
|
2708
|
-
}
|
|
3237
|
+
};
|
|
3238
|
+
})()
|
|
2709
3239
|
}
|
|
2710
3240
|
), state.shellState?.isAgentControlled && state.shellState?.isFocused && state.shellState?.isRunning && /* @__PURE__ */ React.createElement(
|
|
2711
3241
|
MonitorModeAIPanel,
|
|
@@ -2724,7 +3254,22 @@ const App = ({
|
|
|
2724
3254
|
maxHeight: Math.floor((process.stdout.rows || 24) / 2) - 5,
|
|
2725
3255
|
isActive: true
|
|
2726
3256
|
}
|
|
2727
|
-
),
|
|
3257
|
+
), state.shellState && !state.customTunnel && !state.shellState.isFocused && !getTerminalDimensions().shouldEnableStreaming && state.shellState.isRunning && (process.stdout.rows || 24) >= 15 && /* @__PURE__ */ React.createElement(
|
|
3258
|
+
CompactShellPreview,
|
|
3259
|
+
{
|
|
3260
|
+
command: state.shellState.command,
|
|
3261
|
+
output: state.shellState.output,
|
|
3262
|
+
isRunning: state.shellState.isRunning,
|
|
3263
|
+
remoteContext: state.shellState.remoteContext
|
|
3264
|
+
}
|
|
3265
|
+
), (() => {
|
|
3266
|
+
const dims = getTerminalDimensions();
|
|
3267
|
+
const isCompact = !dims.shouldEnableStreaming;
|
|
3268
|
+
const showLoading = state.isAiWorking && !state.shellState && !state.approvalRequest && !(state.currentMessage?.toolExecution?.status === "executing");
|
|
3269
|
+
const showFileChanges = !isCompact && state.lastFileChangeSummary && state.lastFileChangeSummary.filesChanged > 0;
|
|
3270
|
+
if ((!showLoading || isCompact) && !showFileChanges) return null;
|
|
3271
|
+
return /* @__PURE__ */ React.createElement(Box, { marginBottom: 1, flexDirection: "row", justifyContent: "space-between", width: "100%" }, /* @__PURE__ */ React.createElement(Box, { paddingLeft: 1, flexDirection: "row" }, showLoading && !isCompact && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(LoadingIndicator, { key: "loading-indicator" }), /* @__PURE__ */ React.createElement(AgentTimer, { key: "agent-timer" }))), showFileChanges && /* @__PURE__ */ React.createElement(Box, { paddingRight: 1, flexDirection: "row", flexShrink: 0 }, /* @__PURE__ */ React.createElement(Text, { color: "#666666" }, state.lastFileChangeSummary.filesChanged, " file", state.lastFileChangeSummary.filesChanged !== 1 ? "s" : "", " changed "), state.lastFileChangeSummary.insertions > 0 && /* @__PURE__ */ React.createElement(Text, { color: "green" }, "+", state.lastFileChangeSummary.insertions), state.lastFileChangeSummary.insertions > 0 && state.lastFileChangeSummary.deletions > 0 && /* @__PURE__ */ React.createElement(Text, { color: "#666666" }, " "), state.lastFileChangeSummary.deletions > 0 && /* @__PURE__ */ React.createElement(Text, { color: "red" }, "-", state.lastFileChangeSummary.deletions)));
|
|
3272
|
+
})(), state.interruptQueue && state.interruptQueue.length > 0 && /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", marginBottom: 1 }, state.interruptQueue.map((msg, i) => /* @__PURE__ */ React.createElement(Text, { key: `interrupt-${i}`, color: "gray", dimColor: true }, "[Queued Interrupt ", i + 1, "/", state.interruptQueue.length, "] ", msg))), state.commandQueue && state.commandQueue.length > 0 && /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", marginBottom: 1 }, state.commandQueue.map((command, i) => /* @__PURE__ */ React.createElement(Text, { key: `queued-command-${i}`, color: "#00cc66" }, "[Queued Command ", i + 1, "/", state.commandQueue.length, "] $ ", command))), !state.shellState?.isFocused && (state.passwordRequest ? /* @__PURE__ */ React.createElement(
|
|
2728
3273
|
PasswordPrompt,
|
|
2729
3274
|
{
|
|
2730
3275
|
message: state.passwordRequest.message,
|
|
@@ -2762,6 +3307,7 @@ const App = ({
|
|
|
2762
3307
|
preservedInputTextRef.current = "";
|
|
2763
3308
|
handleSubmit(value, clipboardImages);
|
|
2764
3309
|
},
|
|
3310
|
+
placeholder: state.customTunnel ? `Send input to ${state.customTunnel.matchedCommand}` : "Ask anything ...",
|
|
2765
3311
|
autoAcceptMode: state.autoAcceptMode,
|
|
2766
3312
|
model: state.currentModel,
|
|
2767
3313
|
planMode: state.planMode,
|
|
@@ -2771,6 +3317,7 @@ const App = ({
|
|
|
2771
3317
|
commandHistory: state.commandHistory,
|
|
2772
3318
|
onToggleAutoAccept: handleToggleAutoAccept,
|
|
2773
3319
|
onToggleCommandMode,
|
|
3320
|
+
onTogglePlanMode,
|
|
2774
3321
|
onToggleBackgroundMode,
|
|
2775
3322
|
isActive: true,
|
|
2776
3323
|
subshellContext: state.subshellContext,
|
|
@@ -2779,6 +3326,8 @@ const App = ({
|
|
|
2779
3326
|
maxTokens: state.maxTokens,
|
|
2780
3327
|
contextLimitReached: state.contextLimitReached,
|
|
2781
3328
|
isShellRunning: state.shellState?.isRunning,
|
|
3329
|
+
allowSubmitWhileShellRunning: !!state.customTunnel,
|
|
3330
|
+
customTunnelCommand: state.customTunnel?.matchedCommand,
|
|
2782
3331
|
backgroundTaskCount: state.backgroundTaskCount,
|
|
2783
3332
|
subAgentCount: state.subAgentCount,
|
|
2784
3333
|
initialValue: preservedInputTextRef.current,
|
|
@@ -2788,13 +3337,15 @@ const App = ({
|
|
|
2788
3337
|
},
|
|
2789
3338
|
sessionQuotaExhausted: state.sessionQuotaExhausted,
|
|
2790
3339
|
sessionQuotaTimeRemaining: state.sessionQuotaTimeRemaining,
|
|
2791
|
-
aiAutoSuggestEnabled: state.aiAutoSuggest,
|
|
3340
|
+
aiAutoSuggestEnabled: state.customTunnel ? false : state.aiAutoSuggest,
|
|
2792
3341
|
sessionCommands,
|
|
2793
3342
|
getCheckpoints,
|
|
2794
3343
|
onSetInputSetup,
|
|
2795
|
-
gitDiffStats: state.gitDiffStats
|
|
3344
|
+
gitDiffStats: state.gitDiffStats,
|
|
3345
|
+
isAiWorking: state.isAiWorking,
|
|
3346
|
+
lastFileChangeSummary: state.lastFileChangeSummary
|
|
2796
3347
|
}
|
|
2797
|
-
)), state.showExitWarning && /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, { color: "#ffaa00", bold: true }, "\u26A0\uFE0F Press Ctrl+C again to exit")), !isConnected && /* @__PURE__ */ React.createElement(Box, { marginTop: 0, marginLeft: state.showExitWarning ? 2 : 0 }, /* @__PURE__ */ React.createElement(Text, { color: "red", bold: true }, "\u26A0\uFE0F No internet connection"))), state.screen === "approval" && state.approvalRequest && /* @__PURE__ */ React.createElement(
|
|
3348
|
+
)), state.showExitWarning && /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, { color: "#ffaa00", bold: true }, "\u26A0\uFE0F Press Ctrl+C again to exit")), !isConnected && /* @__PURE__ */ React.createElement(Box, { marginTop: 0, marginLeft: state.showExitWarning ? 2 : 0 }, /* @__PURE__ */ React.createElement(Text, { color: "red", bold: true }, "\u26A0\uFE0F No internet connection"))), state.screen !== "chat" && /* @__PURE__ */ React.createElement(ScreenContainer, null, state.screen === "approval" && state.approvalRequest && /* @__PURE__ */ React.createElement(
|
|
2798
3349
|
ApprovalSection,
|
|
2799
3350
|
{
|
|
2800
3351
|
key: `approval-${state.approvalRequest.message}`,
|
|
@@ -2806,7 +3357,43 @@ const App = ({
|
|
|
2806
3357
|
}
|
|
2807
3358
|
}
|
|
2808
3359
|
}
|
|
2809
|
-
), state.screen === "help" && /* @__PURE__ */ React.createElement(KeyboardHelp, { onClose: () => setState((prev) => ({ ...prev, screen: "chat" })) }), state.screen === "picker" && state.pickerOptions && /* @__PURE__ */ React.createElement(
|
|
3360
|
+
), state.screen === "help" && /* @__PURE__ */ React.createElement(KeyboardHelp, { onClose: () => setState((prev) => ({ ...prev, screen: "chat" })) }), state.screen === "picker" && state.pickerOptions && (state.pickerOptions.type === "model" && state.pickerOptions.modelConfigs ? /* @__PURE__ */ React.createElement(
|
|
3361
|
+
ModelPicker,
|
|
3362
|
+
{
|
|
3363
|
+
models: state.pickerOptions.modelConfigs,
|
|
3364
|
+
currentModelName: state.pickerOptions.currentModelName || "",
|
|
3365
|
+
onCancel: () => {
|
|
3366
|
+
setState((prev) => ({ ...prev, screen: "chat", isLoading: false, isAiWorking: false }));
|
|
3367
|
+
},
|
|
3368
|
+
onSelect: async (value) => {
|
|
3369
|
+
setState((prev) => ({ ...prev, screen: "chat", isLoading: true }));
|
|
3370
|
+
try {
|
|
3371
|
+
await onPickerSelection(value, state.pickerOptions.type);
|
|
3372
|
+
setState((prev) => ({
|
|
3373
|
+
...prev,
|
|
3374
|
+
isLoading: false,
|
|
3375
|
+
isAiWorking: false
|
|
3376
|
+
}));
|
|
3377
|
+
} catch (error) {
|
|
3378
|
+
setState((prev) => {
|
|
3379
|
+
const errorMessage = {
|
|
3380
|
+
id: `system-error-${Date.now()}`,
|
|
3381
|
+
role: "system",
|
|
3382
|
+
content: `Error: ${error.message || "Unknown error occurred"}`,
|
|
3383
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
3384
|
+
};
|
|
3385
|
+
return {
|
|
3386
|
+
...prev,
|
|
3387
|
+
messageHistory: [...prev.messageHistory, errorMessage],
|
|
3388
|
+
currentMessage: null,
|
|
3389
|
+
isLoading: false,
|
|
3390
|
+
isAiWorking: false
|
|
3391
|
+
};
|
|
3392
|
+
});
|
|
3393
|
+
}
|
|
3394
|
+
}
|
|
3395
|
+
}
|
|
3396
|
+
) : /* @__PURE__ */ React.createElement(
|
|
2810
3397
|
SelectPrompt,
|
|
2811
3398
|
{
|
|
2812
3399
|
message: state.pickerOptions.message,
|
|
@@ -2825,7 +3412,7 @@ const App = ({
|
|
|
2825
3412
|
const errorMessage = {
|
|
2826
3413
|
id: `system-error-${Date.now()}`,
|
|
2827
3414
|
role: "system",
|
|
2828
|
-
content:
|
|
3415
|
+
content: `Error: ${error.message || "Unknown error occurred"}`,
|
|
2829
3416
|
timestamp: /* @__PURE__ */ new Date()
|
|
2830
3417
|
};
|
|
2831
3418
|
return {
|
|
@@ -2839,7 +3426,7 @@ const App = ({
|
|
|
2839
3426
|
}
|
|
2840
3427
|
}
|
|
2841
3428
|
}
|
|
2842
|
-
), state.screen === "chat-picker" && state.chatPickerChats && /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", borderStyle: "round", borderColor:
|
|
3429
|
+
)), state.screen === "chat-picker" && state.chatPickerChats && /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", borderStyle: "round", borderColor: themeColors.accent, paddingX: 1 }, /* @__PURE__ */ React.createElement(Text, { color: themeColors.accent, bold: true }, "\u{1F4DA} Resume a Previous Chat"), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(
|
|
2843
3430
|
CircularSelectInput,
|
|
2844
3431
|
{
|
|
2845
3432
|
limit: listLimit,
|
|
@@ -2887,7 +3474,7 @@ const App = ({
|
|
|
2887
3474
|
}
|
|
2888
3475
|
}
|
|
2889
3476
|
}
|
|
2890
|
-
)), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, { dimColor: true }, "Press ESC to return to chat"))), state.screen === "chat-delete-picker" && state.chatPickerChats && /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", borderStyle: "round", borderColor:
|
|
3477
|
+
)), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, { dimColor: true }, "Press ESC to return to chat"))), state.screen === "chat-delete-picker" && state.chatPickerChats && /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", borderStyle: "round", borderColor: themeColors.accent, paddingX: 1 }, /* @__PURE__ */ React.createElement(Text, { color: themeColors.accent, bold: true }, "\u{1F5D1}\uFE0F Select a Chat to Delete"), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(
|
|
2891
3478
|
CircularSelectInput,
|
|
2892
3479
|
{
|
|
2893
3480
|
key: `chat-delete-picker-${state.chatDeletePickerSession}`,
|
|
@@ -2933,7 +3520,7 @@ const App = ({
|
|
|
2933
3520
|
}
|
|
2934
3521
|
}
|
|
2935
3522
|
}
|
|
2936
|
-
)), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, { dimColor: true }, "Press ESC to return to chat"))), state.screen === "chat-list-view" && state.chatPickerChats && /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", borderStyle: "round", borderColor:
|
|
3523
|
+
)), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, { dimColor: true }, "Press ESC to return to chat"))), state.screen === "chat-list-view" && state.chatPickerChats && /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", borderStyle: "round", borderColor: themeColors.accent, paddingX: 1 }, /* @__PURE__ */ React.createElement(Text, { color: themeColors.accent, bold: true }, "\u{1F4DA} Saved Chats (", state.chatPickerChats.length, ")"), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(
|
|
2937
3524
|
CircularSelectInput,
|
|
2938
3525
|
{
|
|
2939
3526
|
limit: listLimit,
|
|
@@ -2957,7 +3544,7 @@ const App = ({
|
|
|
2957
3544
|
setState((prev) => ({ ...prev, screen: "chat" }));
|
|
2958
3545
|
}
|
|
2959
3546
|
}
|
|
2960
|
-
)), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, { dimColor: true }, "Press ESC to return to chat"))), state.screen === "chat-rename-picker" && state.chatPickerChats && /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", borderStyle: "round", borderColor:
|
|
3547
|
+
)), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, { dimColor: true }, "Press ESC to return to chat"))), state.screen === "chat-rename-picker" && state.chatPickerChats && /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", borderStyle: "round", borderColor: themeColors.accent, paddingX: 1 }, /* @__PURE__ */ React.createElement(Text, { color: themeColors.accent, bold: true }, "\u270F\uFE0F Select a Chat to Rename"), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(
|
|
2961
3548
|
CircularSelectInput,
|
|
2962
3549
|
{
|
|
2963
3550
|
limit: listLimit,
|
|
@@ -3029,18 +3616,23 @@ const App = ({
|
|
|
3029
3616
|
timestamp: /* @__PURE__ */ new Date(),
|
|
3030
3617
|
planAccepted: {
|
|
3031
3618
|
planTitle: plan?.title,
|
|
3032
|
-
|
|
3033
|
-
|
|
3619
|
+
totalSteps: plan?.implementationSteps?.length,
|
|
3620
|
+
steps: plan?.implementationSteps?.map((step) => ({
|
|
3621
|
+
id: step.id,
|
|
3034
3622
|
description: step.description,
|
|
3035
|
-
|
|
3623
|
+
status: step.status
|
|
3036
3624
|
}))
|
|
3037
3625
|
}
|
|
3038
3626
|
};
|
|
3627
|
+
const restoredHistory = savedHistoryRef.current || [];
|
|
3628
|
+
clearScreen();
|
|
3039
3629
|
setState((prev) => ({
|
|
3040
3630
|
...prev,
|
|
3041
3631
|
screen: "chat",
|
|
3042
3632
|
planApprovalRequest: void 0,
|
|
3043
|
-
messageHistory: [...
|
|
3633
|
+
messageHistory: [...restoredHistory, planAcceptedMessage],
|
|
3634
|
+
currentMessage: savedCurrentRef.current,
|
|
3635
|
+
messageListKey: prev.messageListKey + 1,
|
|
3044
3636
|
isLoading: true,
|
|
3045
3637
|
isAiWorking: true
|
|
3046
3638
|
}));
|
|
@@ -3050,10 +3642,77 @@ const App = ({
|
|
|
3050
3642
|
if (resolve) {
|
|
3051
3643
|
resolve(false);
|
|
3052
3644
|
}
|
|
3645
|
+
const restoredHistory = savedHistoryRef.current || [];
|
|
3646
|
+
clearScreen();
|
|
3053
3647
|
setState((prev) => ({
|
|
3054
3648
|
...prev,
|
|
3055
3649
|
screen: "chat",
|
|
3056
|
-
planApprovalRequest: void 0
|
|
3650
|
+
planApprovalRequest: void 0,
|
|
3651
|
+
messageHistory: restoredHistory,
|
|
3652
|
+
currentMessage: savedCurrentRef.current,
|
|
3653
|
+
messageListKey: prev.messageListKey + 1
|
|
3654
|
+
}));
|
|
3655
|
+
}
|
|
3656
|
+
}
|
|
3657
|
+
), state.screen === "plan-question" && state.planQuestionRequest && /* @__PURE__ */ React.createElement(
|
|
3658
|
+
PlanQuestionScreen,
|
|
3659
|
+
{
|
|
3660
|
+
question: state.planQuestionRequest.question,
|
|
3661
|
+
options: state.planQuestionRequest.options,
|
|
3662
|
+
onAnswer: (answer) => {
|
|
3663
|
+
const resolve = state.planQuestionRequest?.resolve;
|
|
3664
|
+
const question = state.planQuestionRequest?.question || "";
|
|
3665
|
+
const options = state.planQuestionRequest?.options || [];
|
|
3666
|
+
if (resolve) {
|
|
3667
|
+
resolve(answer);
|
|
3668
|
+
}
|
|
3669
|
+
const questionMessage = {
|
|
3670
|
+
id: `plan-question-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`,
|
|
3671
|
+
role: "system",
|
|
3672
|
+
content: "",
|
|
3673
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
3674
|
+
planQuestion: {
|
|
3675
|
+
question,
|
|
3676
|
+
options,
|
|
3677
|
+
userAnswer: answer,
|
|
3678
|
+
wasSkipped: false
|
|
3679
|
+
}
|
|
3680
|
+
};
|
|
3681
|
+
setState((prev) => ({
|
|
3682
|
+
...prev,
|
|
3683
|
+
screen: "chat",
|
|
3684
|
+
planQuestionRequest: void 0,
|
|
3685
|
+
messageHistory: [...prev.messageHistory, questionMessage],
|
|
3686
|
+
isLoading: true,
|
|
3687
|
+
isAiWorking: true
|
|
3688
|
+
}));
|
|
3689
|
+
},
|
|
3690
|
+
onSkip: () => {
|
|
3691
|
+
const resolve = state.planQuestionRequest?.resolve;
|
|
3692
|
+
const question = state.planQuestionRequest?.question || "";
|
|
3693
|
+
const options = state.planQuestionRequest?.options || [];
|
|
3694
|
+
if (resolve) {
|
|
3695
|
+
resolve("[User skipped this question. Proceed with the most appropriate and optimal approach as you see fit.]");
|
|
3696
|
+
}
|
|
3697
|
+
const questionMessage = {
|
|
3698
|
+
id: `plan-question-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`,
|
|
3699
|
+
role: "system",
|
|
3700
|
+
content: "",
|
|
3701
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
3702
|
+
planQuestion: {
|
|
3703
|
+
question,
|
|
3704
|
+
options,
|
|
3705
|
+
userAnswer: "",
|
|
3706
|
+
wasSkipped: true
|
|
3707
|
+
}
|
|
3708
|
+
};
|
|
3709
|
+
setState((prev) => ({
|
|
3710
|
+
...prev,
|
|
3711
|
+
screen: "chat",
|
|
3712
|
+
planQuestionRequest: void 0,
|
|
3713
|
+
messageHistory: [...prev.messageHistory, questionMessage],
|
|
3714
|
+
isLoading: true,
|
|
3715
|
+
isAiWorking: true
|
|
3057
3716
|
}));
|
|
3058
3717
|
}
|
|
3059
3718
|
}
|
|
@@ -3334,7 +3993,142 @@ Use it in prompts with: @rules:${savedName}`,
|
|
|
3334
3993
|
}));
|
|
3335
3994
|
}
|
|
3336
3995
|
}
|
|
3337
|
-
)
|
|
3996
|
+
), state.screen === "skill-editor" && state.skillEditorRequest && /* @__PURE__ */ React.createElement(
|
|
3997
|
+
SkillCreatorScreen,
|
|
3998
|
+
{
|
|
3999
|
+
mode: state.skillEditorRequest.mode,
|
|
4000
|
+
initialSkill: state.skillEditorRequest.initialSkill,
|
|
4001
|
+
onSave: (skill, previousName) => {
|
|
4002
|
+
const result = onSkillSave(skill, previousName);
|
|
4003
|
+
if (!result.success) {
|
|
4004
|
+
return result;
|
|
4005
|
+
}
|
|
4006
|
+
const savedName = result.savedName || skill.name;
|
|
4007
|
+
const successMessage = {
|
|
4008
|
+
id: `skill-saved-${Date.now()}`,
|
|
4009
|
+
role: "system",
|
|
4010
|
+
content: `\u2705 Skill "${savedName}" saved successfully.
|
|
4011
|
+
|
|
4012
|
+
Invoke it with: #${savedName} <parameters>`,
|
|
4013
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
4014
|
+
};
|
|
4015
|
+
setState((prev) => ({
|
|
4016
|
+
...prev,
|
|
4017
|
+
screen: "chat",
|
|
4018
|
+
skillEditorRequest: void 0,
|
|
4019
|
+
messageHistory: [...prev.messageHistory, successMessage]
|
|
4020
|
+
}));
|
|
4021
|
+
return result;
|
|
4022
|
+
},
|
|
4023
|
+
onCancel: () => {
|
|
4024
|
+
setState((prev) => ({
|
|
4025
|
+
...prev,
|
|
4026
|
+
screen: "chat",
|
|
4027
|
+
skillEditorRequest: void 0
|
|
4028
|
+
}));
|
|
4029
|
+
}
|
|
4030
|
+
}
|
|
4031
|
+
), state.screen === "sub-agent-list" && state.subAgentListData && /* @__PURE__ */ React.createElement(
|
|
4032
|
+
SubAgentListScreen,
|
|
4033
|
+
{
|
|
4034
|
+
agents: state.subAgentListData,
|
|
4035
|
+
onSelect: (agentId) => {
|
|
4036
|
+
clearScreen();
|
|
4037
|
+
setState((prev) => ({ ...prev, screen: "sub-agent-view", viewingSubAgentId: agentId }));
|
|
4038
|
+
},
|
|
4039
|
+
onCancel: () => {
|
|
4040
|
+
clearScreen();
|
|
4041
|
+
setState((prev) => ({
|
|
4042
|
+
...prev,
|
|
4043
|
+
screen: "chat",
|
|
4044
|
+
subAgentListData: void 0,
|
|
4045
|
+
messageListKey: (prev.messageListKey ?? 0) + 1
|
|
4046
|
+
}));
|
|
4047
|
+
}
|
|
4048
|
+
}
|
|
4049
|
+
), state.screen === "sub-agent-view" && state.viewingSubAgentId && /* @__PURE__ */ React.createElement(
|
|
4050
|
+
SubAgentViewScreen,
|
|
4051
|
+
{
|
|
4052
|
+
agentId: state.viewingSubAgentId,
|
|
4053
|
+
onBack: () => {
|
|
4054
|
+
clearScreen();
|
|
4055
|
+
const allAgents = SubAgentManager.getAllSubAgents();
|
|
4056
|
+
if (allAgents.length > 0) {
|
|
4057
|
+
const agentList = allAgents.map((a) => ({
|
|
4058
|
+
id: a.id,
|
|
4059
|
+
status: a.status,
|
|
4060
|
+
prompt: a.prompt,
|
|
4061
|
+
model: a.model,
|
|
4062
|
+
startTime: a.startTime,
|
|
4063
|
+
turnCount: a.turnCount,
|
|
4064
|
+
fileOpsCount: a.fileOperations.length
|
|
4065
|
+
}));
|
|
4066
|
+
setState((prev) => ({ ...prev, screen: "sub-agent-list", subAgentListData: agentList, viewingSubAgentId: void 0 }));
|
|
4067
|
+
} else {
|
|
4068
|
+
setState((prev) => ({
|
|
4069
|
+
...prev,
|
|
4070
|
+
screen: "chat",
|
|
4071
|
+
viewingSubAgentId: void 0,
|
|
4072
|
+
subAgentListData: void 0,
|
|
4073
|
+
messageListKey: (prev.messageListKey ?? 0) + 1
|
|
4074
|
+
}));
|
|
4075
|
+
}
|
|
4076
|
+
},
|
|
4077
|
+
onTerminate: (agentId) => {
|
|
4078
|
+
SubAgentManager.terminateSubAgent(agentId);
|
|
4079
|
+
},
|
|
4080
|
+
onAutoReturn: () => {
|
|
4081
|
+
clearScreen();
|
|
4082
|
+
setState((prev) => ({
|
|
4083
|
+
...prev,
|
|
4084
|
+
screen: "chat",
|
|
4085
|
+
viewingSubAgentId: void 0,
|
|
4086
|
+
subAgentListData: void 0,
|
|
4087
|
+
messageListKey: (prev.messageListKey ?? 0) + 1
|
|
4088
|
+
}));
|
|
4089
|
+
}
|
|
4090
|
+
}
|
|
4091
|
+
), state.screen === "sub-agent-terminate" && state.subAgentListData && /* @__PURE__ */ React.createElement(
|
|
4092
|
+
SubAgentListScreen,
|
|
4093
|
+
{
|
|
4094
|
+
agents: state.subAgentListData,
|
|
4095
|
+
onSelect: (agentId) => {
|
|
4096
|
+
SubAgentManager.terminateSubAgent(agentId);
|
|
4097
|
+
const runningAgents = SubAgentManager.getRunningSubAgents();
|
|
4098
|
+
if (runningAgents.length === 0) {
|
|
4099
|
+
clearScreen();
|
|
4100
|
+
setState((prev) => ({
|
|
4101
|
+
...prev,
|
|
4102
|
+
screen: "chat",
|
|
4103
|
+
subAgentListData: void 0,
|
|
4104
|
+
messageListKey: (prev.messageListKey ?? 0) + 1
|
|
4105
|
+
}));
|
|
4106
|
+
} else {
|
|
4107
|
+
setState((prev) => ({
|
|
4108
|
+
...prev,
|
|
4109
|
+
subAgentListData: runningAgents.map((a) => ({
|
|
4110
|
+
id: a.id,
|
|
4111
|
+
status: a.status,
|
|
4112
|
+
prompt: a.prompt,
|
|
4113
|
+
model: a.model,
|
|
4114
|
+
startTime: a.startTime,
|
|
4115
|
+
turnCount: a.turnCount,
|
|
4116
|
+
fileOpsCount: a.fileOperations.length
|
|
4117
|
+
}))
|
|
4118
|
+
}));
|
|
4119
|
+
}
|
|
4120
|
+
},
|
|
4121
|
+
onCancel: () => {
|
|
4122
|
+
clearScreen();
|
|
4123
|
+
setState((prev) => ({
|
|
4124
|
+
...prev,
|
|
4125
|
+
screen: "chat",
|
|
4126
|
+
subAgentListData: void 0,
|
|
4127
|
+
messageListKey: (prev.messageListKey ?? 0) + 1
|
|
4128
|
+
}));
|
|
4129
|
+
}
|
|
4130
|
+
}
|
|
4131
|
+
))));
|
|
3338
4132
|
};
|
|
3339
4133
|
export {
|
|
3340
4134
|
App
|