@defai.digital/ax-cli 3.5.4 → 3.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.ax-cli/checkpoints/2025-11-20/checkpoint-11e9e0ba-c39d-4fd2-aa77-bc818811c921.json +69 -0
- package/.ax-cli/checkpoints/2025-11-20/checkpoint-2b260b98-b418-4c7c-9694-e2b94967e662.json +24 -0
- package/.ax-cli/checkpoints/2025-11-20/checkpoint-7e03601e-e8ab-4cd7-9841-a74b66adf78f.json +69 -0
- package/.ax-cli/checkpoints/2025-11-20/checkpoint-7f9c6562-771f-4fd0-adcf-9e7e9ac34ae8.json +44 -0
- package/.ax-cli/checkpoints/2025-11-20/checkpoint-e1ebe666-4c3a-4367-ba5c-27fe512a9c70.json +24 -0
- package/.ax-cli/checkpoints/2025-11-21/checkpoint-15743e7d-430c-4d76-b6fc-955d7a5c250c.json +44 -0
- package/.ax-cli/checkpoints/2025-11-21/checkpoint-25cf7679-0b3f-4988-83d7-704548fbba91.json +69 -0
- package/.ax-cli/checkpoints/2025-11-21/checkpoint-54aedbac-6db0-464e-8ebb-dbb3979e6dca.json +24 -0
- package/.ax-cli/checkpoints/2025-11-21/checkpoint-7658aed8-fe5d-4222-903f-1a7c63717ea7.json +24 -0
- package/.ax-cli/checkpoints/2025-11-21/checkpoint-c9c13497-40dc-4294-a327-6a5fc854eaa1.json +69 -0
- package/.ax-cli/memory.json +15 -8
- package/README.md +423 -82
- package/ax.config.json +333 -0
- package/config-defaults/messages.yaml +75 -0
- package/config-defaults/models.yaml +66 -0
- package/config-defaults/prompts.yaml +156 -0
- package/config-defaults/settings.yaml +86 -0
- package/dist/agent/chat-history-manager.d.ts +56 -0
- package/dist/agent/chat-history-manager.js +150 -0
- package/dist/agent/chat-history-manager.js.map +1 -0
- package/dist/agent/llm-agent.js +1 -1
- package/dist/agent/llm-agent.js.map +1 -1
- package/dist/agent/tool-manager.d.ts +39 -0
- package/dist/agent/tool-manager.js +76 -0
- package/dist/agent/tool-manager.js.map +1 -0
- package/dist/analyzers/code-smells/detectors/data-clumps-detector.js +7 -9
- package/dist/analyzers/code-smells/detectors/data-clumps-detector.js.map +1 -1
- package/dist/analyzers/code-smells/detectors/dead-code-detector.js +1 -1
- package/dist/analyzers/code-smells/detectors/dead-code-detector.js.map +1 -1
- package/dist/analyzers/code-smells/detectors/duplicate-code-detector.js +22 -10
- package/dist/analyzers/code-smells/detectors/duplicate-code-detector.js.map +1 -1
- package/dist/analyzers/code-smells/detectors/feature-envy-detector.js +1 -1
- package/dist/analyzers/code-smells/detectors/feature-envy-detector.js.map +1 -1
- package/dist/analyzers/code-smells/detectors/inappropriate-intimacy-detector.js +1 -1
- package/dist/analyzers/code-smells/detectors/inappropriate-intimacy-detector.js.map +1 -1
- package/dist/analyzers/code-smells/detectors/large-class-detector.js +4 -1
- package/dist/analyzers/code-smells/detectors/large-class-detector.js.map +1 -1
- package/dist/analyzers/code-smells/detectors/long-method-detector.js +4 -1
- package/dist/analyzers/code-smells/detectors/long-method-detector.js.map +1 -1
- package/dist/analyzers/code-smells/detectors/long-parameter-list-detector.js +4 -1
- package/dist/analyzers/code-smells/detectors/long-parameter-list-detector.js.map +1 -1
- package/dist/analyzers/code-smells/detectors/magic-numbers-detector.js +4 -5
- package/dist/analyzers/code-smells/detectors/magic-numbers-detector.js.map +1 -1
- package/dist/analyzers/code-smells/detectors/nested-conditionals-detector.js +4 -1
- package/dist/analyzers/code-smells/detectors/nested-conditionals-detector.js.map +1 -1
- package/dist/commands/memory.js +1 -1
- package/dist/commands/memory.js.map +1 -1
- package/dist/commands/setup.js +19 -6
- package/dist/commands/setup.js.map +1 -1
- package/dist/index.js +7 -0
- package/dist/index.js.bak +664 -0
- package/dist/index.js.map +1 -1
- package/dist/llm/client.d.ts +1 -0
- package/dist/llm/client.js +44 -0
- package/dist/llm/client.js.map +1 -1
- package/dist/mcp/health.js +4 -2
- package/dist/mcp/health.js.map +1 -1
- package/dist/mcp/ssrf-protection.d.ts +86 -0
- package/dist/mcp/ssrf-protection.js +313 -0
- package/dist/mcp/ssrf-protection.js.map +1 -0
- package/dist/mcp/validation.d.ts +4 -0
- package/dist/mcp/validation.js +122 -11
- package/dist/mcp/validation.js.map +1 -1
- package/dist/schemas/settings-schemas.d.ts +53 -0
- package/dist/schemas/settings-schemas.js +47 -0
- package/dist/schemas/settings-schemas.js.map +1 -1
- package/dist/tools/bash.d.ts +3 -2
- package/dist/tools/bash.js +31 -2
- package/dist/tools/bash.js.map +1 -1
- package/dist/tools/search.d.ts +1 -1
- package/dist/tools/search.js +121 -128
- package/dist/tools/search.js.map +1 -1
- package/dist/tools/text-editor.js +52 -15
- package/dist/tools/text-editor.js.map +1 -1
- package/dist/tools/web-search/index.d.ts +0 -2
- package/dist/tools/web-search/index.js +0 -2
- package/dist/tools/web-search/index.js.map +1 -1
- package/dist/tools/web-search/router.d.ts +0 -2
- package/dist/tools/web-search/router.js +2 -37
- package/dist/tools/web-search/router.js.map +1 -1
- package/dist/tools/web-search/web-search-tool.js +2 -12
- package/dist/tools/web-search/web-search-tool.js.map +1 -1
- package/dist/ui/components/chat-history.js +1 -1
- package/dist/ui/components/chat-history.js.map +1 -1
- package/dist/ui/components/chat-input.d.ts +4 -1
- package/dist/ui/components/chat-input.js +133 -52
- package/dist/ui/components/chat-input.js.map +1 -1
- package/dist/ui/components/chat-interface.js +5 -4
- package/dist/ui/components/chat-interface.js.map +1 -1
- package/dist/ui/components/confirmation-dialog.js +1 -1
- package/dist/ui/components/confirmation-dialog.js.map +1 -1
- package/dist/ui/components/keyboard-hints.js +2 -0
- package/dist/ui/components/keyboard-hints.js.map +1 -1
- package/dist/ui/components/status-bar.js +3 -13
- package/dist/ui/components/status-bar.js.map +1 -1
- package/dist/ui/components/welcome-panel.js +4 -0
- package/dist/ui/components/welcome-panel.js.map +1 -1
- package/dist/ui/hooks/use-chat-reducer.d.ts +61 -0
- package/dist/ui/hooks/use-chat-reducer.js +118 -0
- package/dist/ui/hooks/use-chat-reducer.js.map +1 -0
- package/dist/ui/hooks/use-enhanced-input.d.ts +44 -0
- package/dist/ui/hooks/use-enhanced-input.js +364 -0
- package/dist/ui/hooks/use-enhanced-input.js.map +1 -0
- package/dist/ui/hooks/use-input-handler.d.ts +48 -0
- package/dist/ui/hooks/use-input-handler.js +1446 -0
- package/dist/ui/hooks/use-input-handler.js.map +1 -0
- package/dist/utils/audit-logger.d.ts +205 -0
- package/dist/utils/audit-logger.js +269 -0
- package/dist/utils/audit-logger.js.map +1 -0
- package/dist/utils/command-security.d.ts +85 -0
- package/dist/utils/command-security.js +200 -0
- package/dist/utils/command-security.js.map +1 -0
- package/dist/utils/config-loader.js +3 -3
- package/dist/utils/config-loader.js.map +1 -1
- package/dist/utils/encryption.d.ts +78 -0
- package/dist/utils/encryption.js +216 -0
- package/dist/utils/encryption.js.map +1 -0
- package/dist/utils/error-sanitizer.d.ts +119 -0
- package/dist/utils/error-sanitizer.js +253 -0
- package/dist/utils/error-sanitizer.js.map +1 -0
- package/dist/utils/input-sanitizer.d.ts +210 -0
- package/dist/utils/input-sanitizer.js +362 -0
- package/dist/utils/input-sanitizer.js.map +1 -0
- package/dist/utils/json-utils.d.ts +13 -0
- package/dist/utils/json-utils.js +55 -1
- package/dist/utils/json-utils.js.map +1 -1
- package/dist/utils/paste-collapse.d.ts +46 -0
- package/dist/utils/paste-collapse.js +77 -0
- package/dist/utils/paste-collapse.js.map +1 -0
- package/dist/utils/paste-utils.d.ts +99 -0
- package/dist/utils/paste-utils.js +239 -0
- package/dist/utils/paste-utils.js.map +1 -0
- package/dist/utils/path-security.d.ts +90 -0
- package/dist/utils/path-security.js +328 -0
- package/dist/utils/path-security.js.map +1 -0
- package/dist/utils/process-pool.d.ts +105 -0
- package/dist/utils/process-pool.js +326 -0
- package/dist/utils/process-pool.js.map +1 -0
- package/dist/utils/rate-limiter.d.ts +221 -0
- package/dist/utils/rate-limiter.js +317 -0
- package/dist/utils/rate-limiter.js.map +1 -0
- package/dist/utils/settings-manager.js +99 -6
- package/dist/utils/settings-manager.js.map +1 -1
- package/dist/utils/streaming-analyzer.js +9 -21
- package/dist/utils/streaming-analyzer.js.map +1 -1
- package/package.json +3 -7
- package/packages/schemas/dist/index.d.ts +14 -0
- package/packages/schemas/dist/index.d.ts.map +1 -0
- package/packages/schemas/dist/index.js +19 -0
- package/packages/schemas/dist/index.js.map +1 -0
- package/packages/schemas/dist/public/core/brand-types.d.ts +308 -0
- package/packages/schemas/dist/public/core/brand-types.d.ts.map +1 -0
- package/packages/schemas/dist/public/core/brand-types.js +243 -0
- package/packages/schemas/dist/public/core/brand-types.js.map +1 -0
- package/packages/schemas/dist/public/core/enums.d.ts +227 -0
- package/packages/schemas/dist/public/core/enums.d.ts.map +1 -0
- package/packages/schemas/dist/public/core/enums.js +222 -0
- package/packages/schemas/dist/public/core/enums.js.map +1 -0
- package/packages/schemas/dist/public/core/id-types.d.ts +286 -0
- package/packages/schemas/dist/public/core/id-types.d.ts.map +1 -0
- package/packages/schemas/dist/public/core/id-types.js +136 -0
- package/packages/schemas/dist/public/core/id-types.js.map +1 -0
|
@@ -0,0 +1,1446 @@
|
|
|
1
|
+
import { useState, useMemo, useEffect, useCallback, useRef } from "react";
|
|
2
|
+
import { useInput } from "ink";
|
|
3
|
+
import { ConfirmationService } from "../../utils/confirmation-service.js";
|
|
4
|
+
import { useEnhancedInput } from "./use-enhanced-input.js";
|
|
5
|
+
import { escapeShellArg } from "../../tools/bash.js";
|
|
6
|
+
import { filterCommandSuggestions } from "../components/command-suggestions.js";
|
|
7
|
+
import { getSettingsManager } from "../../utils/settings-manager.js";
|
|
8
|
+
import { ProjectAnalyzer } from "../../utils/project-analyzer.js";
|
|
9
|
+
import { InstructionGenerator } from "../../utils/instruction-generator.js";
|
|
10
|
+
import { getUsageTracker } from "../../utils/usage-tracker.js";
|
|
11
|
+
import { getHistoryManager } from "../../utils/history-manager.js";
|
|
12
|
+
import { handleRewindCommand, handleCheckpointsCommand, handleCheckpointCleanCommand } from "../../commands/rewind.js";
|
|
13
|
+
import { handlePlansCommand, handlePlanCommand, handlePhasesCommand, handlePauseCommand, handleResumeCommand, handleSkipPhaseCommand, handleAbandonCommand, handleResumableCommand, } from "../../commands/plan.js";
|
|
14
|
+
import { BashOutputTool } from "../../tools/bash-output.js";
|
|
15
|
+
import { getKeyboardShortcutGuideText } from "../components/keyboard-hints.js";
|
|
16
|
+
import { getContextStore, ContextGenerator, getStatsCollector, } from "../../memory/index.js";
|
|
17
|
+
import * as fs from "fs";
|
|
18
|
+
import * as path from "path";
|
|
19
|
+
export function useInputHandler({ agent, chatHistory, setChatHistory, setIsProcessing, setIsStreaming, setTokenCount, setProcessingTime, processingStartTime, isProcessing, isStreaming, isConfirmationActive = false, onQuickActionsToggle, onVerboseModeChange, onBackgroundModeChange, onAutoEditModeChange, onTaskMovedToBackground, onOperationInterrupted, onChatCleared, onCopyLastResponse, onMemoryWarmed, onMemoryRefreshed, onCheckpointCreated: _onCheckpointCreated, onCheckpointRestored: _onCheckpointRestored, }) {
|
|
20
|
+
const [showCommandSuggestions, setShowCommandSuggestions] = useState(false);
|
|
21
|
+
const [selectedCommandIndex, setSelectedCommandIndex] = useState(0);
|
|
22
|
+
const retryTimeoutRef = useRef(null);
|
|
23
|
+
const [autoEditEnabled, setAutoEditEnabled] = useState(() => {
|
|
24
|
+
const confirmationService = ConfirmationService.getInstance();
|
|
25
|
+
const sessionFlags = confirmationService.getSessionFlags();
|
|
26
|
+
// Default to true (auto-apply enabled by default)
|
|
27
|
+
return sessionFlags.allOperations !== undefined ? sessionFlags.allOperations : true;
|
|
28
|
+
});
|
|
29
|
+
const [verboseMode, setVerboseMode] = useState(false);
|
|
30
|
+
const [backgroundMode, setBackgroundMode] = useState(false);
|
|
31
|
+
const handleSpecialKey = (key) => {
|
|
32
|
+
// Don't handle input if confirmation dialog is active
|
|
33
|
+
if (isConfirmationActive) {
|
|
34
|
+
return true; // Prevent default handling
|
|
35
|
+
}
|
|
36
|
+
// Handle shift+tab to toggle auto-edit mode
|
|
37
|
+
if (key.shift && key.tab) {
|
|
38
|
+
const newAutoEditState = !autoEditEnabled;
|
|
39
|
+
setAutoEditEnabled(newAutoEditState);
|
|
40
|
+
const confirmationService = ConfirmationService.getInstance();
|
|
41
|
+
if (newAutoEditState) {
|
|
42
|
+
// Enable auto-edit: set all operations to be accepted
|
|
43
|
+
confirmationService.setSessionFlag("allOperations", true);
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
// Disable auto-edit: reset session flags
|
|
47
|
+
confirmationService.resetSession();
|
|
48
|
+
}
|
|
49
|
+
// Notify parent for toast/flash feedback
|
|
50
|
+
onAutoEditModeChange?.(newAutoEditState);
|
|
51
|
+
return true; // Handled
|
|
52
|
+
}
|
|
53
|
+
// Handle escape key for closing menus
|
|
54
|
+
if (key.escape) {
|
|
55
|
+
if (showCommandSuggestions) {
|
|
56
|
+
setShowCommandSuggestions(false);
|
|
57
|
+
setSelectedCommandIndex(0);
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
if (isProcessing || isStreaming) {
|
|
61
|
+
agent.abortCurrentOperation();
|
|
62
|
+
setIsProcessing(false);
|
|
63
|
+
setIsStreaming(false);
|
|
64
|
+
setTokenCount(0);
|
|
65
|
+
setProcessingTime(0);
|
|
66
|
+
processingStartTime.current = 0;
|
|
67
|
+
// Notify parent for toast feedback
|
|
68
|
+
onOperationInterrupted?.();
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
return false; // Let default escape handling work
|
|
72
|
+
}
|
|
73
|
+
// Handle command suggestions navigation
|
|
74
|
+
if (showCommandSuggestions) {
|
|
75
|
+
const filteredSuggestions = filterCommandSuggestions(commandSuggestions, input);
|
|
76
|
+
if (filteredSuggestions.length === 0) {
|
|
77
|
+
setShowCommandSuggestions(false);
|
|
78
|
+
setSelectedCommandIndex(0);
|
|
79
|
+
return false; // Continue processing
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
if (key.upArrow) {
|
|
83
|
+
setSelectedCommandIndex((prev) => prev === 0 ? filteredSuggestions.length - 1 : prev - 1);
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
if (key.downArrow) {
|
|
87
|
+
setSelectedCommandIndex((prev) => (prev + 1) % filteredSuggestions.length);
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
if (key.tab || key.return) {
|
|
91
|
+
// Check if there are any suggestions available
|
|
92
|
+
if (filteredSuggestions.length === 0) {
|
|
93
|
+
return true; // No suggestions, do nothing
|
|
94
|
+
}
|
|
95
|
+
const safeIndex = Math.min(selectedCommandIndex, filteredSuggestions.length - 1);
|
|
96
|
+
const selectedCommand = filteredSuggestions[safeIndex];
|
|
97
|
+
const newInput = selectedCommand.command + " ";
|
|
98
|
+
setInput(newInput);
|
|
99
|
+
setCursorPosition(newInput.length);
|
|
100
|
+
setShowCommandSuggestions(false);
|
|
101
|
+
setSelectedCommandIndex(0);
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return false; // Let default handling proceed
|
|
107
|
+
};
|
|
108
|
+
const handleInputSubmit = async (userInput) => {
|
|
109
|
+
if (userInput === "exit" || userInput === "quit") {
|
|
110
|
+
process.exit(0);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
if (userInput.trim()) {
|
|
114
|
+
const directCommandResult = await handleDirectCommand(userInput);
|
|
115
|
+
if (!directCommandResult) {
|
|
116
|
+
await processUserMessage(userInput);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
const handleInputChange = useCallback((newInput) => {
|
|
121
|
+
// Update command suggestions based on input
|
|
122
|
+
if (newInput.startsWith("/")) {
|
|
123
|
+
setShowCommandSuggestions(true);
|
|
124
|
+
setSelectedCommandIndex(0);
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
setShowCommandSuggestions(false);
|
|
128
|
+
setSelectedCommandIndex(0);
|
|
129
|
+
}
|
|
130
|
+
}, []);
|
|
131
|
+
const handleVerboseToggle = useCallback(() => {
|
|
132
|
+
setVerboseMode((prev) => {
|
|
133
|
+
const newState = !prev;
|
|
134
|
+
// Notify parent for toast/flash feedback instead of polluting chat history
|
|
135
|
+
onVerboseModeChange?.(newState);
|
|
136
|
+
return newState;
|
|
137
|
+
});
|
|
138
|
+
}, [onVerboseModeChange]);
|
|
139
|
+
const handleBackgroundModeToggle = useCallback(() => {
|
|
140
|
+
// Check if a bash command is currently executing
|
|
141
|
+
if (agent.isBashExecuting()) {
|
|
142
|
+
const taskId = agent.moveBashToBackground();
|
|
143
|
+
if (taskId) {
|
|
144
|
+
// Notify parent for toast feedback
|
|
145
|
+
onTaskMovedToBackground?.(taskId);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
// Otherwise toggle background mode preference
|
|
150
|
+
setBackgroundMode((prev) => {
|
|
151
|
+
const newState = !prev;
|
|
152
|
+
// Notify parent for toast/flash feedback instead of polluting chat history
|
|
153
|
+
onBackgroundModeChange?.(newState);
|
|
154
|
+
return newState;
|
|
155
|
+
});
|
|
156
|
+
}, [agent, onBackgroundModeChange, onTaskMovedToBackground]);
|
|
157
|
+
const { input, cursorPosition, pastedBlocks, currentBlockAtCursor, setInput, setCursorPosition, clearInput, resetHistory, handleInput, expandPlaceholdersForSubmit: _expandPlaceholdersForSubmit, } = useEnhancedInput({
|
|
158
|
+
onSubmit: handleInputSubmit,
|
|
159
|
+
onSpecialKey: handleSpecialKey,
|
|
160
|
+
onVerboseToggle: handleVerboseToggle,
|
|
161
|
+
onQuickActions: onQuickActionsToggle,
|
|
162
|
+
onBackgroundModeToggle: handleBackgroundModeToggle,
|
|
163
|
+
onCopyLastResponse,
|
|
164
|
+
disabled: isConfirmationActive,
|
|
165
|
+
});
|
|
166
|
+
// Suppress unused variable warning - expandPlaceholdersForSubmit is part of the hook interface but not yet used
|
|
167
|
+
void _expandPlaceholdersForSubmit;
|
|
168
|
+
// Hook up the actual input handling
|
|
169
|
+
useInput((inputChar, key) => {
|
|
170
|
+
handleInput(inputChar, key);
|
|
171
|
+
});
|
|
172
|
+
// Update command suggestions when input changes
|
|
173
|
+
useEffect(() => {
|
|
174
|
+
handleInputChange(input);
|
|
175
|
+
}, [input, handleInputChange]);
|
|
176
|
+
// Cleanup retry timeout on unmount
|
|
177
|
+
useEffect(() => {
|
|
178
|
+
return () => {
|
|
179
|
+
if (retryTimeoutRef.current) {
|
|
180
|
+
clearTimeout(retryTimeoutRef.current);
|
|
181
|
+
retryTimeoutRef.current = null; // Clear ref to prevent dangling reference
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
}, []);
|
|
185
|
+
const commandSuggestions = [
|
|
186
|
+
{ command: "/help", description: "Show help information" },
|
|
187
|
+
{ command: "/shortcuts", description: "Show keyboard shortcuts guide" },
|
|
188
|
+
{ command: "/continue", description: "Continue incomplete response" },
|
|
189
|
+
{ command: "/retry", description: "Re-send the last message" },
|
|
190
|
+
{ command: "/clear", description: "Clear chat history" },
|
|
191
|
+
{ command: "/init", description: "Initialize project with smart analysis" },
|
|
192
|
+
{ command: "/usage", description: "Show API usage statistics" },
|
|
193
|
+
{ command: "/doctor", description: "Run health check diagnostics" },
|
|
194
|
+
{ command: "/tasks", description: "List background tasks" },
|
|
195
|
+
{ command: "/task", description: "View output of a background task" },
|
|
196
|
+
{ command: "/kill", description: "Kill a background task" },
|
|
197
|
+
{ command: "/rewind", description: "Rewind to previous checkpoint" },
|
|
198
|
+
{ command: "/checkpoints", description: "List checkpoint statistics" },
|
|
199
|
+
{ command: "/checkpoint-clean", description: "Clean old checkpoints" },
|
|
200
|
+
{ command: "/commit-and-push", description: "AI commit & push to remote" },
|
|
201
|
+
{ command: "/plans", description: "List all task plans" },
|
|
202
|
+
{ command: "/plan", description: "Show current plan details" },
|
|
203
|
+
{ command: "/phases", description: "Show phases of current plan" },
|
|
204
|
+
{ command: "/pause", description: "Pause current plan execution" },
|
|
205
|
+
{ command: "/resume", description: "Resume paused plan" },
|
|
206
|
+
{ command: "/skip", description: "Skip current phase" },
|
|
207
|
+
{ command: "/abandon", description: "Abandon current plan" },
|
|
208
|
+
{ command: "/memory", description: "Show project memory status" },
|
|
209
|
+
{ command: "/memory warmup", description: "Generate project memory" },
|
|
210
|
+
{ command: "/memory refresh", description: "Update project memory" },
|
|
211
|
+
{ command: "/exit", description: "Exit the application" },
|
|
212
|
+
];
|
|
213
|
+
// Load models from configuration with fallback to defaults
|
|
214
|
+
const availableModels = useMemo(() => {
|
|
215
|
+
const settingsManager = getSettingsManager();
|
|
216
|
+
const models = settingsManager.getAvailableModels();
|
|
217
|
+
return models.map(m => ({ model: m }));
|
|
218
|
+
}, []);
|
|
219
|
+
const handleDirectCommand = async (input) => {
|
|
220
|
+
const trimmedInput = input.trim();
|
|
221
|
+
if (trimmedInput === "/continue") {
|
|
222
|
+
// Send a shorter, more focused continuation prompt to avoid timeout
|
|
223
|
+
// Using a brief prompt reduces token overhead for large contexts
|
|
224
|
+
const continuePrompt = "Continue from where you left off.";
|
|
225
|
+
// Add user continue command to history (showing the actual command for clarity)
|
|
226
|
+
const userEntry = {
|
|
227
|
+
type: "user",
|
|
228
|
+
content: "/continue",
|
|
229
|
+
timestamp: new Date(),
|
|
230
|
+
};
|
|
231
|
+
setChatHistory((prev) => [...prev, userEntry]);
|
|
232
|
+
// Process through agent
|
|
233
|
+
setIsProcessing(true);
|
|
234
|
+
setIsStreaming(true);
|
|
235
|
+
processingStartTime.current = Date.now();
|
|
236
|
+
// Fire async operation with proper error handling
|
|
237
|
+
(async () => {
|
|
238
|
+
try {
|
|
239
|
+
let streamingEntry = null;
|
|
240
|
+
for await (const chunk of agent.processUserMessageStream(continuePrompt)) {
|
|
241
|
+
switch (chunk.type) {
|
|
242
|
+
case "reasoning":
|
|
243
|
+
if (chunk.reasoningContent) {
|
|
244
|
+
if (!streamingEntry) {
|
|
245
|
+
const newStreamingEntry = {
|
|
246
|
+
type: "assistant",
|
|
247
|
+
content: "",
|
|
248
|
+
timestamp: new Date(),
|
|
249
|
+
isStreaming: true,
|
|
250
|
+
reasoningContent: chunk.reasoningContent,
|
|
251
|
+
isReasoningStreaming: true,
|
|
252
|
+
};
|
|
253
|
+
setChatHistory((prev) => [...prev, newStreamingEntry]);
|
|
254
|
+
streamingEntry = newStreamingEntry;
|
|
255
|
+
}
|
|
256
|
+
else {
|
|
257
|
+
setChatHistory((prev) => prev.map((entry, idx) => idx === prev.length - 1 && entry.isStreaming
|
|
258
|
+
? {
|
|
259
|
+
...entry,
|
|
260
|
+
reasoningContent: (entry.reasoningContent || "") +
|
|
261
|
+
chunk.reasoningContent,
|
|
262
|
+
isReasoningStreaming: true,
|
|
263
|
+
}
|
|
264
|
+
: entry));
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
break;
|
|
268
|
+
case "content":
|
|
269
|
+
if (chunk.content) {
|
|
270
|
+
if (!streamingEntry) {
|
|
271
|
+
const newStreamingEntry = {
|
|
272
|
+
type: "assistant",
|
|
273
|
+
content: chunk.content,
|
|
274
|
+
timestamp: new Date(),
|
|
275
|
+
isStreaming: true,
|
|
276
|
+
};
|
|
277
|
+
setChatHistory((prev) => [...prev, newStreamingEntry]);
|
|
278
|
+
streamingEntry = newStreamingEntry;
|
|
279
|
+
}
|
|
280
|
+
else {
|
|
281
|
+
setChatHistory((prev) => prev.map((entry, idx) => idx === prev.length - 1 && entry.isStreaming
|
|
282
|
+
? {
|
|
283
|
+
...entry,
|
|
284
|
+
content: entry.content + chunk.content,
|
|
285
|
+
isReasoningStreaming: false,
|
|
286
|
+
}
|
|
287
|
+
: entry));
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
break;
|
|
291
|
+
case "token_count":
|
|
292
|
+
if (chunk.tokenCount !== undefined) {
|
|
293
|
+
setTokenCount(chunk.tokenCount);
|
|
294
|
+
}
|
|
295
|
+
break;
|
|
296
|
+
case "tool_calls":
|
|
297
|
+
if (chunk.toolCalls) {
|
|
298
|
+
setChatHistory((prev) => prev.map((entry) => entry.isStreaming
|
|
299
|
+
? {
|
|
300
|
+
...entry,
|
|
301
|
+
isStreaming: false,
|
|
302
|
+
toolCalls: chunk.toolCalls,
|
|
303
|
+
}
|
|
304
|
+
: entry));
|
|
305
|
+
streamingEntry = null;
|
|
306
|
+
chunk.toolCalls.forEach((toolCall) => {
|
|
307
|
+
const toolCallEntry = {
|
|
308
|
+
type: "tool_call",
|
|
309
|
+
content: "Executing...",
|
|
310
|
+
timestamp: new Date(),
|
|
311
|
+
toolCall: toolCall,
|
|
312
|
+
};
|
|
313
|
+
setChatHistory((prev) => [...prev, toolCallEntry]);
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
break;
|
|
317
|
+
case "tool_result":
|
|
318
|
+
if (chunk.toolCall && chunk.toolResult) {
|
|
319
|
+
setChatHistory((prev) => prev.map((entry) => {
|
|
320
|
+
if (entry.isStreaming) {
|
|
321
|
+
return { ...entry, isStreaming: false };
|
|
322
|
+
}
|
|
323
|
+
if (entry.type === "tool_call" &&
|
|
324
|
+
entry.toolCall?.id === chunk.toolCall?.id) {
|
|
325
|
+
return {
|
|
326
|
+
...entry,
|
|
327
|
+
type: "tool_result",
|
|
328
|
+
content: chunk.toolResult?.success
|
|
329
|
+
? chunk.toolResult?.output || "Success"
|
|
330
|
+
: chunk.toolResult?.error || "Error occurred",
|
|
331
|
+
toolResult: chunk.toolResult,
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
return entry;
|
|
335
|
+
}));
|
|
336
|
+
streamingEntry = null;
|
|
337
|
+
}
|
|
338
|
+
break;
|
|
339
|
+
case "done":
|
|
340
|
+
if (streamingEntry) {
|
|
341
|
+
// Calculate response duration
|
|
342
|
+
const durationMs = processingStartTime.current > 0
|
|
343
|
+
? Date.now() - processingStartTime.current
|
|
344
|
+
: undefined;
|
|
345
|
+
setChatHistory((prev) => prev.map((entry) => entry.isStreaming ? { ...entry, isStreaming: false, durationMs } : entry));
|
|
346
|
+
}
|
|
347
|
+
setIsStreaming(false);
|
|
348
|
+
break;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
catch (error) {
|
|
353
|
+
const errorObj = error instanceof Error ? error : new Error(String(error));
|
|
354
|
+
let errorMessage = `Error: ${errorObj.message}`;
|
|
355
|
+
// Provide helpful guidance for timeout errors during /continue
|
|
356
|
+
if (errorObj.message?.includes('timeout')) {
|
|
357
|
+
errorMessage += `\n\n💡 Tip: For very long conversations, try:\n`;
|
|
358
|
+
errorMessage += ` • Use /clear to start fresh and ask a more focused question\n`;
|
|
359
|
+
errorMessage += ` • Break down your request into smaller parts\n`;
|
|
360
|
+
errorMessage += ` • Use --continue flag to start a new session with history`;
|
|
361
|
+
}
|
|
362
|
+
const errorEntry = {
|
|
363
|
+
type: "assistant",
|
|
364
|
+
content: errorMessage,
|
|
365
|
+
timestamp: new Date(),
|
|
366
|
+
};
|
|
367
|
+
setChatHistory((prev) => [...prev, errorEntry]);
|
|
368
|
+
setIsStreaming(false);
|
|
369
|
+
}
|
|
370
|
+
setIsProcessing(false);
|
|
371
|
+
processingStartTime.current = 0;
|
|
372
|
+
})().catch((error) => {
|
|
373
|
+
// Safety net: handle any uncaught errors from the async IIFE
|
|
374
|
+
const errorObj = error instanceof Error ? error : new Error(String(error));
|
|
375
|
+
const errorEntry = {
|
|
376
|
+
type: "assistant",
|
|
377
|
+
content: `Unexpected error: ${errorObj.message}`,
|
|
378
|
+
timestamp: new Date(),
|
|
379
|
+
};
|
|
380
|
+
setChatHistory((prev) => [...prev, errorEntry]);
|
|
381
|
+
setIsStreaming(false);
|
|
382
|
+
setIsProcessing(false);
|
|
383
|
+
processingStartTime.current = 0;
|
|
384
|
+
});
|
|
385
|
+
clearInput();
|
|
386
|
+
resetHistory();
|
|
387
|
+
return true;
|
|
388
|
+
}
|
|
389
|
+
if (trimmedInput === "/retry") {
|
|
390
|
+
// Find the last user message index and re-send it
|
|
391
|
+
// Use findLastIndex instead of reverse().find() + lastIndexOf() to avoid object reference issues
|
|
392
|
+
const lastUserIndex = chatHistory.findLastIndex(entry => entry.type === "user");
|
|
393
|
+
if (lastUserIndex >= 0 && chatHistory[lastUserIndex]?.content) {
|
|
394
|
+
// Store the message content and history state before clearing
|
|
395
|
+
const messageToRetry = chatHistory[lastUserIndex].content;
|
|
396
|
+
const historyBackup = [...chatHistory];
|
|
397
|
+
// Remove the last user message and any assistant responses after it
|
|
398
|
+
setChatHistory(prev => prev.slice(0, lastUserIndex));
|
|
399
|
+
clearInput();
|
|
400
|
+
// Trigger submit after a brief delay to allow state update
|
|
401
|
+
// Track timeout for cleanup on unmount
|
|
402
|
+
retryTimeoutRef.current = setTimeout(() => {
|
|
403
|
+
retryTimeoutRef.current = null;
|
|
404
|
+
// Call async function and handle promise rejection
|
|
405
|
+
handleInputSubmit(messageToRetry).catch(() => {
|
|
406
|
+
// Restore history if retry fails
|
|
407
|
+
setChatHistory(historyBackup);
|
|
408
|
+
});
|
|
409
|
+
}, 50);
|
|
410
|
+
}
|
|
411
|
+
else {
|
|
412
|
+
clearInput();
|
|
413
|
+
}
|
|
414
|
+
return true;
|
|
415
|
+
}
|
|
416
|
+
if (trimmedInput === "/clear") {
|
|
417
|
+
// Reset chat history
|
|
418
|
+
setChatHistory([]);
|
|
419
|
+
// Clear saved history from disk
|
|
420
|
+
const historyManager = getHistoryManager();
|
|
421
|
+
historyManager.clearHistory();
|
|
422
|
+
// Reset processing states
|
|
423
|
+
setIsProcessing(false);
|
|
424
|
+
setIsStreaming(false);
|
|
425
|
+
setTokenCount(0);
|
|
426
|
+
setProcessingTime(0);
|
|
427
|
+
processingStartTime.current = 0;
|
|
428
|
+
// Reset confirmation service session flags
|
|
429
|
+
const confirmationService = ConfirmationService.getInstance();
|
|
430
|
+
confirmationService.resetSession();
|
|
431
|
+
// Notify parent for toast feedback
|
|
432
|
+
onChatCleared?.();
|
|
433
|
+
clearInput();
|
|
434
|
+
resetHistory();
|
|
435
|
+
return true;
|
|
436
|
+
}
|
|
437
|
+
if (trimmedInput === "/init") {
|
|
438
|
+
const userEntry = {
|
|
439
|
+
type: "user",
|
|
440
|
+
content: "/init",
|
|
441
|
+
timestamp: new Date(),
|
|
442
|
+
};
|
|
443
|
+
setChatHistory((prev) => [...prev, userEntry]);
|
|
444
|
+
setIsProcessing(true);
|
|
445
|
+
try {
|
|
446
|
+
const projectRoot = process.cwd();
|
|
447
|
+
// Add analysis message
|
|
448
|
+
const analyzingEntry = {
|
|
449
|
+
type: "assistant",
|
|
450
|
+
content: "🔍 Analyzing project...\n",
|
|
451
|
+
timestamp: new Date(),
|
|
452
|
+
};
|
|
453
|
+
setChatHistory((prev) => [...prev, analyzingEntry]);
|
|
454
|
+
// Check if already initialized
|
|
455
|
+
const axCliDir = path.join(projectRoot, ".ax-cli");
|
|
456
|
+
const customMdPath = path.join(axCliDir, "CUSTOM.md");
|
|
457
|
+
const indexPath = path.join(axCliDir, "index.json");
|
|
458
|
+
if (fs.existsSync(customMdPath)) {
|
|
459
|
+
const alreadyInitEntry = {
|
|
460
|
+
type: "assistant",
|
|
461
|
+
content: `✅ Project already initialized!\n📝 Custom instructions: ${customMdPath}\n📊 Project index: ${indexPath}\n\n💡 Run 'ax-cli init --force' from terminal to regenerate`,
|
|
462
|
+
timestamp: new Date(),
|
|
463
|
+
};
|
|
464
|
+
setChatHistory((prev) => [...prev, alreadyInitEntry]);
|
|
465
|
+
setIsProcessing(false);
|
|
466
|
+
clearInput();
|
|
467
|
+
return true;
|
|
468
|
+
}
|
|
469
|
+
// Analyze project
|
|
470
|
+
const analyzer = new ProjectAnalyzer(projectRoot);
|
|
471
|
+
const result = await analyzer.analyze();
|
|
472
|
+
if (!result.success || !result.projectInfo) {
|
|
473
|
+
const errorEntry = {
|
|
474
|
+
type: "assistant",
|
|
475
|
+
content: `❌ Failed to analyze project: ${result.error || "Unknown error"}`,
|
|
476
|
+
timestamp: new Date(),
|
|
477
|
+
};
|
|
478
|
+
setChatHistory((prev) => [...prev, errorEntry]);
|
|
479
|
+
setIsProcessing(false);
|
|
480
|
+
clearInput();
|
|
481
|
+
return true;
|
|
482
|
+
}
|
|
483
|
+
const projectInfo = result.projectInfo;
|
|
484
|
+
// Generate instructions
|
|
485
|
+
const generator = new InstructionGenerator();
|
|
486
|
+
const instructions = generator.generateInstructions(projectInfo);
|
|
487
|
+
const index = generator.generateIndex(projectInfo);
|
|
488
|
+
// Create .ax-cli directory
|
|
489
|
+
if (!fs.existsSync(axCliDir)) {
|
|
490
|
+
fs.mkdirSync(axCliDir, { recursive: true });
|
|
491
|
+
}
|
|
492
|
+
// Write custom instructions
|
|
493
|
+
fs.writeFileSync(customMdPath, instructions, "utf-8");
|
|
494
|
+
// Write project index
|
|
495
|
+
fs.writeFileSync(indexPath, index, "utf-8");
|
|
496
|
+
// Display success
|
|
497
|
+
let successMessage = `🎉 Project initialized successfully!\n\n`;
|
|
498
|
+
successMessage += `📋 Analysis Results:\n`;
|
|
499
|
+
successMessage += ` Name: ${projectInfo.name}\n`;
|
|
500
|
+
successMessage += ` Type: ${projectInfo.projectType}\n`;
|
|
501
|
+
successMessage += ` Language: ${projectInfo.primaryLanguage}\n`;
|
|
502
|
+
if (projectInfo.techStack.length > 0) {
|
|
503
|
+
successMessage += ` Stack: ${projectInfo.techStack.join(", ")}\n`;
|
|
504
|
+
}
|
|
505
|
+
successMessage += `\n✅ Generated custom instructions: ${customMdPath}\n`;
|
|
506
|
+
successMessage += `✅ Generated project index: ${indexPath}\n\n`;
|
|
507
|
+
successMessage += `💡 Next steps:\n`;
|
|
508
|
+
successMessage += ` 1. Review and customize the instructions if needed\n`;
|
|
509
|
+
successMessage += ` 2. Run AX CLI - it will automatically use these instructions\n`;
|
|
510
|
+
successMessage += ` 3. Use 'ax-cli init --force' to regenerate after project changes`;
|
|
511
|
+
const successEntry = {
|
|
512
|
+
type: "assistant",
|
|
513
|
+
content: successMessage,
|
|
514
|
+
timestamp: new Date(),
|
|
515
|
+
};
|
|
516
|
+
setChatHistory((prev) => [...prev, successEntry]);
|
|
517
|
+
}
|
|
518
|
+
catch (error) {
|
|
519
|
+
const errorEntry = {
|
|
520
|
+
type: "assistant",
|
|
521
|
+
content: `❌ Error during initialization: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
522
|
+
timestamp: new Date(),
|
|
523
|
+
};
|
|
524
|
+
setChatHistory((prev) => [...prev, errorEntry]);
|
|
525
|
+
}
|
|
526
|
+
setIsProcessing(false);
|
|
527
|
+
clearInput();
|
|
528
|
+
return true;
|
|
529
|
+
}
|
|
530
|
+
if (trimmedInput === "/help") {
|
|
531
|
+
const helpEntry = {
|
|
532
|
+
type: "assistant",
|
|
533
|
+
content: `AX CLI Help:
|
|
534
|
+
|
|
535
|
+
Built-in Commands:
|
|
536
|
+
/continue - Continue incomplete response from where it left off
|
|
537
|
+
/clear - Clear chat history
|
|
538
|
+
/init - Initialize project with smart analysis
|
|
539
|
+
/help - Show this help
|
|
540
|
+
/shortcuts - Show keyboard shortcuts guide
|
|
541
|
+
/usage - Show API usage statistics
|
|
542
|
+
/doctor - Run health check diagnostics
|
|
543
|
+
/exit - Exit application
|
|
544
|
+
exit, quit - Exit application
|
|
545
|
+
|
|
546
|
+
Background Task Commands:
|
|
547
|
+
/tasks - List all background tasks
|
|
548
|
+
/task <id> - View output of a background task
|
|
549
|
+
/kill <id> - Kill a running background task
|
|
550
|
+
|
|
551
|
+
Tip: Append ' &' to any bash command to run it in background
|
|
552
|
+
Example: npm run dev &
|
|
553
|
+
|
|
554
|
+
Checkpoint Commands:
|
|
555
|
+
/rewind - Rewind to a previous checkpoint (interactive)
|
|
556
|
+
/checkpoints - Show checkpoint statistics
|
|
557
|
+
/checkpoint-clean - Clean old checkpoints (compress and prune)
|
|
558
|
+
|
|
559
|
+
Plan Commands (Multi-Phase Task Planning):
|
|
560
|
+
/plans - List all task plans
|
|
561
|
+
/plan [id] - Show current or specific plan details
|
|
562
|
+
/phases - Show phases of current plan
|
|
563
|
+
/pause - Pause current plan execution
|
|
564
|
+
/resume [id] - Resume current or specific plan
|
|
565
|
+
/skip - Skip current phase
|
|
566
|
+
/abandon - Abandon current plan
|
|
567
|
+
|
|
568
|
+
Git Commands:
|
|
569
|
+
/commit-and-push - AI-generated commit + push to remote
|
|
570
|
+
|
|
571
|
+
Memory Commands (z.ai GLM-4.6 caching):
|
|
572
|
+
/memory - Show project memory status
|
|
573
|
+
/memory warmup - Generate project memory context
|
|
574
|
+
/memory refresh - Update memory after changes
|
|
575
|
+
|
|
576
|
+
Enhanced Input Features:
|
|
577
|
+
↑/↓ Arrow - Navigate command history
|
|
578
|
+
Ctrl+C - Clear input (press twice to exit)
|
|
579
|
+
Ctrl+X - Clear entire input line
|
|
580
|
+
Ctrl+←/→ - Move by word
|
|
581
|
+
Ctrl+A/E - Move to line start/end
|
|
582
|
+
Ctrl+W - Delete word before cursor
|
|
583
|
+
Ctrl+K - Delete to end of line
|
|
584
|
+
Ctrl+U - Delete to start of line
|
|
585
|
+
Ctrl+O - Toggle verbose mode (show full output, default: concise)
|
|
586
|
+
Ctrl+B - Toggle background mode (run all commands in background)
|
|
587
|
+
Ctrl+P - Expand/collapse pasted text at cursor
|
|
588
|
+
Ctrl+Y - Copy last assistant response to clipboard
|
|
589
|
+
Shift+Tab - Toggle auto-edit mode (bypass confirmations)
|
|
590
|
+
1-4 keys - Quick select in confirmation dialogs
|
|
591
|
+
|
|
592
|
+
Paste Handling:
|
|
593
|
+
When you paste 5+ lines, it's automatically collapsed to a preview.
|
|
594
|
+
Position cursor on collapsed text and press Ctrl+P to expand/collapse.
|
|
595
|
+
Full content is always sent to AI (display-only feature).
|
|
596
|
+
|
|
597
|
+
Direct Commands (executed immediately):
|
|
598
|
+
ls [path] - List directory contents
|
|
599
|
+
pwd - Show current directory
|
|
600
|
+
cd <path> - Change directory
|
|
601
|
+
cat <file> - View file contents
|
|
602
|
+
mkdir <dir> - Create directory
|
|
603
|
+
touch <file>- Create empty file
|
|
604
|
+
|
|
605
|
+
Model Configuration:
|
|
606
|
+
Edit ~/.ax-cli/config.json to configure default model and settings
|
|
607
|
+
|
|
608
|
+
For complex operations, just describe what you want in natural language.
|
|
609
|
+
Examples:
|
|
610
|
+
"edit package.json and add a new script"
|
|
611
|
+
"create a new React component called Header"
|
|
612
|
+
"show me all TypeScript files in this project"`,
|
|
613
|
+
timestamp: new Date(),
|
|
614
|
+
};
|
|
615
|
+
setChatHistory((prev) => [...prev, helpEntry]);
|
|
616
|
+
clearInput();
|
|
617
|
+
return true;
|
|
618
|
+
}
|
|
619
|
+
if (trimmedInput === "/shortcuts") {
|
|
620
|
+
const shortcutsEntry = {
|
|
621
|
+
type: "assistant",
|
|
622
|
+
content: getKeyboardShortcutGuideText(),
|
|
623
|
+
timestamp: new Date(),
|
|
624
|
+
};
|
|
625
|
+
setChatHistory((prev) => [...prev, shortcutsEntry]);
|
|
626
|
+
clearInput();
|
|
627
|
+
return true;
|
|
628
|
+
}
|
|
629
|
+
if (trimmedInput === "/usage") {
|
|
630
|
+
const tracker = getUsageTracker();
|
|
631
|
+
const stats = tracker.getSessionStats();
|
|
632
|
+
let usageContent = "📊 **API Usage & Limits (Z.AI)**\n\n";
|
|
633
|
+
// Session statistics
|
|
634
|
+
usageContent += "**📱 Current Session:**\n";
|
|
635
|
+
if (stats.totalRequests === 0) {
|
|
636
|
+
usageContent += " No API requests made yet. Ask me something to start tracking!\n";
|
|
637
|
+
}
|
|
638
|
+
else {
|
|
639
|
+
usageContent += ` • Requests: ${stats.totalRequests.toLocaleString()}\n`;
|
|
640
|
+
usageContent += ` • Prompt Tokens: ${stats.totalPromptTokens.toLocaleString()}\n`;
|
|
641
|
+
usageContent += ` • Completion Tokens: ${stats.totalCompletionTokens.toLocaleString()}\n`;
|
|
642
|
+
usageContent += ` • Total Tokens: ${stats.totalTokens.toLocaleString()}\n`;
|
|
643
|
+
if (stats.totalReasoningTokens > 0) {
|
|
644
|
+
usageContent += ` • Reasoning Tokens: ${stats.totalReasoningTokens.toLocaleString()}\n`;
|
|
645
|
+
}
|
|
646
|
+
if (stats.byModel.size > 0) {
|
|
647
|
+
usageContent += `\n **Models Used:**\n`;
|
|
648
|
+
for (const [model, modelStats] of stats.byModel.entries()) {
|
|
649
|
+
usageContent += ` - ${model}: ${modelStats.totalTokens.toLocaleString()} tokens (${modelStats.requests} requests)\n`;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
// Z.AI account information
|
|
654
|
+
usageContent += `\n**🔑 Z.AI Account Usage & Limits:**\n`;
|
|
655
|
+
usageContent += ` ⚠️ API does not provide programmatic access to usage data\n`;
|
|
656
|
+
usageContent += `\n **Check your account:**\n`;
|
|
657
|
+
usageContent += ` • Billing & Usage: https://z.ai/manage-apikey/billing\n`;
|
|
658
|
+
usageContent += ` • Rate Limits: https://z.ai/manage-apikey/rate-limits\n`;
|
|
659
|
+
usageContent += ` • API Keys: https://z.ai/manage-apikey/apikey-list\n`;
|
|
660
|
+
usageContent += `\n**ℹ️ Notes:**\n`;
|
|
661
|
+
usageContent += ` • Billing reflects previous day (n-1) consumption\n`;
|
|
662
|
+
usageContent += ` • Current day usage may not be immediately visible\n`;
|
|
663
|
+
usageContent += ` • Cached content: 1/5 of original price\n`;
|
|
664
|
+
usageContent += `\n**💰 GLM-4.6 Pricing:**\n`;
|
|
665
|
+
usageContent += ` • Input: $0.11 per 1M tokens\n`;
|
|
666
|
+
usageContent += ` • Output: $0.28 per 1M tokens\n`;
|
|
667
|
+
if (stats.totalRequests > 0) {
|
|
668
|
+
// Calculate estimated cost for this session
|
|
669
|
+
const inputCost = (stats.totalPromptTokens / 1000000) * 0.11;
|
|
670
|
+
const outputCost = (stats.totalCompletionTokens / 1000000) * 0.28;
|
|
671
|
+
const totalCost = inputCost + outputCost;
|
|
672
|
+
usageContent += `\n**💵 Estimated Session Cost:**\n`;
|
|
673
|
+
usageContent += ` • Input: $${inputCost.toFixed(6)} (${stats.totalPromptTokens.toLocaleString()} tokens)\n`;
|
|
674
|
+
usageContent += ` • Output: $${outputCost.toFixed(6)} (${stats.totalCompletionTokens.toLocaleString()} tokens)\n`;
|
|
675
|
+
usageContent += ` • **Total: ~$${totalCost.toFixed(6)}**\n`;
|
|
676
|
+
}
|
|
677
|
+
const usageEntry = {
|
|
678
|
+
type: "assistant",
|
|
679
|
+
content: usageContent,
|
|
680
|
+
timestamp: new Date(),
|
|
681
|
+
};
|
|
682
|
+
setChatHistory((prev) => [...prev, usageEntry]);
|
|
683
|
+
clearInput();
|
|
684
|
+
return true;
|
|
685
|
+
}
|
|
686
|
+
if (trimmedInput === "/doctor") {
|
|
687
|
+
// Run doctor diagnostics
|
|
688
|
+
const doctorContent = "🏥 **Running AX CLI Diagnostics...**\n\n";
|
|
689
|
+
const doctorEntry = {
|
|
690
|
+
type: "assistant",
|
|
691
|
+
content: doctorContent,
|
|
692
|
+
timestamp: new Date(),
|
|
693
|
+
};
|
|
694
|
+
setChatHistory((prev) => [...prev, doctorEntry]);
|
|
695
|
+
// Execute doctor command asynchronously (non-blocking)
|
|
696
|
+
(async () => {
|
|
697
|
+
try {
|
|
698
|
+
const { exec } = await import("child_process");
|
|
699
|
+
const { promisify } = await import("util");
|
|
700
|
+
const execAsync = promisify(exec);
|
|
701
|
+
// Use 'ax-cli doctor' command which will use the globally installed binary
|
|
702
|
+
// This works whether installed globally or linked locally
|
|
703
|
+
const { stdout, stderr } = await execAsync("ax-cli doctor", {
|
|
704
|
+
encoding: "utf-8",
|
|
705
|
+
timeout: 30000, // 30 second timeout
|
|
706
|
+
maxBuffer: 10 * 1024 * 1024, // 10MB buffer
|
|
707
|
+
});
|
|
708
|
+
const output = stdout || stderr;
|
|
709
|
+
const resultEntry = {
|
|
710
|
+
type: "assistant",
|
|
711
|
+
content: `\`\`\`\n${output}\n\`\`\``,
|
|
712
|
+
timestamp: new Date(),
|
|
713
|
+
};
|
|
714
|
+
setChatHistory((prev) => [...prev, resultEntry]);
|
|
715
|
+
}
|
|
716
|
+
catch (error) {
|
|
717
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
718
|
+
const errorEntry = {
|
|
719
|
+
type: "assistant",
|
|
720
|
+
content: `❌ **Doctor diagnostics failed:**\n\n\`\`\`\n${errorMessage}\n\`\`\``,
|
|
721
|
+
timestamp: new Date(),
|
|
722
|
+
};
|
|
723
|
+
setChatHistory((prev) => [...prev, errorEntry]);
|
|
724
|
+
}
|
|
725
|
+
})();
|
|
726
|
+
clearInput();
|
|
727
|
+
return true;
|
|
728
|
+
}
|
|
729
|
+
if (trimmedInput === "/exit") {
|
|
730
|
+
process.exit(0);
|
|
731
|
+
return true;
|
|
732
|
+
}
|
|
733
|
+
// Memory commands
|
|
734
|
+
if (trimmedInput === "/memory" || trimmedInput === "/memory status") {
|
|
735
|
+
const store = getContextStore();
|
|
736
|
+
const metadata = store.getMetadata();
|
|
737
|
+
let memoryContent = "🧠 **Project Memory Status**\n\n";
|
|
738
|
+
if (!metadata.exists) {
|
|
739
|
+
memoryContent += "❌ No project memory found.\n\n";
|
|
740
|
+
memoryContent += "Run `/memory warmup` to generate project memory for z.ai caching.\n";
|
|
741
|
+
}
|
|
742
|
+
else {
|
|
743
|
+
memoryContent += `✅ Memory initialized\n\n`;
|
|
744
|
+
memoryContent += `**Token Estimate:** ${metadata.tokenEstimate?.toLocaleString() || 'N/A'} tokens\n`;
|
|
745
|
+
memoryContent += `**Last Updated:** ${metadata.updatedAt ? new Date(metadata.updatedAt).toLocaleString() : 'N/A'}\n`;
|
|
746
|
+
memoryContent += `**Usage Count:** ${metadata.usageCount || 0}\n`;
|
|
747
|
+
// Try to get section breakdown
|
|
748
|
+
const loadResult = store.load();
|
|
749
|
+
if (loadResult.success) {
|
|
750
|
+
const sections = loadResult.data.context.sections;
|
|
751
|
+
memoryContent += `\n**📊 Token Distribution:**\n`;
|
|
752
|
+
const total = Object.values(sections).reduce((a, b) => a + b, 0);
|
|
753
|
+
for (const [name, tokens] of Object.entries(sections)) {
|
|
754
|
+
const pct = total > 0 ? Math.round((tokens / total) * 100) : 0;
|
|
755
|
+
const bar = '█'.repeat(Math.round(pct / 5)) + '░'.repeat(20 - Math.round(pct / 5));
|
|
756
|
+
memoryContent += ` ${bar} ${name.charAt(0).toUpperCase() + name.slice(1)} (${pct}%)\n`;
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
// Show cache stats if available
|
|
760
|
+
const statsCollector = getStatsCollector();
|
|
761
|
+
const formattedStats = statsCollector.getFormattedStats();
|
|
762
|
+
if (formattedStats && formattedStats.usageCount > 0) {
|
|
763
|
+
memoryContent += `\n**💾 Cache Statistics:**\n`;
|
|
764
|
+
memoryContent += ` • Usage Count: ${formattedStats.usageCount}\n`;
|
|
765
|
+
memoryContent += ` • Tokens Saved: ${formattedStats.tokensSaved.toLocaleString()}\n`;
|
|
766
|
+
memoryContent += ` • Cache Rate: ${formattedStats.cacheRate}%\n`;
|
|
767
|
+
memoryContent += ` • Est. Savings: $${formattedStats.estimatedSavings.toFixed(4)}\n`;
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
const memoryEntry = {
|
|
771
|
+
type: "assistant",
|
|
772
|
+
content: memoryContent,
|
|
773
|
+
timestamp: new Date(),
|
|
774
|
+
};
|
|
775
|
+
setChatHistory((prev) => [...prev, memoryEntry]);
|
|
776
|
+
clearInput();
|
|
777
|
+
return true;
|
|
778
|
+
}
|
|
779
|
+
if (trimmedInput === "/memory warmup") {
|
|
780
|
+
setIsProcessing(true);
|
|
781
|
+
const warmupEntry = {
|
|
782
|
+
type: "assistant",
|
|
783
|
+
content: "🔄 Generating project memory...",
|
|
784
|
+
timestamp: new Date(),
|
|
785
|
+
};
|
|
786
|
+
// Track the entry index BEFORE any async operations to prevent race conditions
|
|
787
|
+
let entryIndex = -1;
|
|
788
|
+
setChatHistory((prev) => {
|
|
789
|
+
entryIndex = prev.length; // Store index before pushing
|
|
790
|
+
return [...prev, warmupEntry];
|
|
791
|
+
});
|
|
792
|
+
try {
|
|
793
|
+
const generator = new ContextGenerator();
|
|
794
|
+
const result = await generator.generate();
|
|
795
|
+
if (result.success) {
|
|
796
|
+
const store = getContextStore();
|
|
797
|
+
const saveResult = store.save(result.memory);
|
|
798
|
+
if (saveResult.success) {
|
|
799
|
+
const sections = result.memory.context.sections;
|
|
800
|
+
const tokenEstimate = result.memory.context.token_estimate;
|
|
801
|
+
let resultContent = `✅ Project memory generated (${tokenEstimate.toLocaleString()} tokens)\n\n`;
|
|
802
|
+
resultContent += `**📊 Context breakdown:**\n`;
|
|
803
|
+
for (const [name, tokens] of Object.entries(sections)) {
|
|
804
|
+
if (tokens !== undefined) {
|
|
805
|
+
const tokenCount = tokens;
|
|
806
|
+
const pct = Math.round((tokenCount / tokenEstimate) * 100);
|
|
807
|
+
resultContent += ` ${name.charAt(0).toUpperCase() + name.slice(1)}: ${tokenCount.toLocaleString()} tokens (${pct}%)\n`;
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
resultContent += `\n💾 Saved to .ax-cli/memory.json`;
|
|
811
|
+
// Update the specific entry by index to avoid race conditions
|
|
812
|
+
setChatHistory((prev) => {
|
|
813
|
+
const updated = [...prev];
|
|
814
|
+
if (entryIndex >= 0 && entryIndex < updated.length) {
|
|
815
|
+
updated[entryIndex] = {
|
|
816
|
+
type: "assistant",
|
|
817
|
+
content: resultContent,
|
|
818
|
+
timestamp: new Date(),
|
|
819
|
+
};
|
|
820
|
+
}
|
|
821
|
+
return updated;
|
|
822
|
+
});
|
|
823
|
+
// Trigger toast notification
|
|
824
|
+
onMemoryWarmed?.(tokenEstimate);
|
|
825
|
+
}
|
|
826
|
+
else {
|
|
827
|
+
throw new Error(saveResult.error);
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
else {
|
|
831
|
+
throw new Error(result.error);
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
catch (error) {
|
|
835
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
836
|
+
// Update the specific entry by index to avoid race conditions
|
|
837
|
+
setChatHistory((prev) => {
|
|
838
|
+
const updated = [...prev];
|
|
839
|
+
if (entryIndex >= 0 && entryIndex < updated.length) {
|
|
840
|
+
updated[entryIndex] = {
|
|
841
|
+
type: "assistant",
|
|
842
|
+
content: `❌ Failed to generate memory: ${errorMessage}`,
|
|
843
|
+
timestamp: new Date(),
|
|
844
|
+
};
|
|
845
|
+
}
|
|
846
|
+
return updated;
|
|
847
|
+
});
|
|
848
|
+
}
|
|
849
|
+
setIsProcessing(false);
|
|
850
|
+
clearInput();
|
|
851
|
+
return true;
|
|
852
|
+
}
|
|
853
|
+
if (trimmedInput === "/memory refresh") {
|
|
854
|
+
setIsProcessing(true);
|
|
855
|
+
const refreshEntry = {
|
|
856
|
+
type: "assistant",
|
|
857
|
+
content: "🔄 Refreshing project memory...",
|
|
858
|
+
timestamp: new Date(),
|
|
859
|
+
};
|
|
860
|
+
// Track the entry index BEFORE any async operations to prevent race conditions
|
|
861
|
+
let entryIndex = -1;
|
|
862
|
+
setChatHistory((prev) => {
|
|
863
|
+
entryIndex = prev.length; // Store index before pushing
|
|
864
|
+
return [...prev, refreshEntry];
|
|
865
|
+
});
|
|
866
|
+
try {
|
|
867
|
+
const store = getContextStore();
|
|
868
|
+
const existing = store.load();
|
|
869
|
+
const generator = new ContextGenerator();
|
|
870
|
+
const result = await generator.generate();
|
|
871
|
+
if (result.success) {
|
|
872
|
+
const hasChanges = !existing.success ||
|
|
873
|
+
existing.data.content_hash !== result.memory.content_hash;
|
|
874
|
+
if (hasChanges) {
|
|
875
|
+
const saveResult = store.save(result.memory);
|
|
876
|
+
if (saveResult.success) {
|
|
877
|
+
// Update the specific entry by index to avoid race conditions
|
|
878
|
+
setChatHistory((prev) => {
|
|
879
|
+
const updated = [...prev];
|
|
880
|
+
if (entryIndex >= 0 && entryIndex < updated.length) {
|
|
881
|
+
updated[entryIndex] = {
|
|
882
|
+
type: "assistant",
|
|
883
|
+
content: `✅ Memory updated (${result.memory.context.token_estimate.toLocaleString()} tokens)`,
|
|
884
|
+
timestamp: new Date(),
|
|
885
|
+
};
|
|
886
|
+
}
|
|
887
|
+
return updated;
|
|
888
|
+
});
|
|
889
|
+
// Trigger toast notification
|
|
890
|
+
onMemoryRefreshed?.();
|
|
891
|
+
}
|
|
892
|
+
else {
|
|
893
|
+
throw new Error(saveResult.error);
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
else {
|
|
897
|
+
// Update the specific entry by index to avoid race conditions
|
|
898
|
+
setChatHistory((prev) => {
|
|
899
|
+
const updated = [...prev];
|
|
900
|
+
if (entryIndex >= 0 && entryIndex < updated.length) {
|
|
901
|
+
updated[entryIndex] = {
|
|
902
|
+
type: "assistant",
|
|
903
|
+
content: `✅ No changes detected - memory is up to date`,
|
|
904
|
+
timestamp: new Date(),
|
|
905
|
+
};
|
|
906
|
+
}
|
|
907
|
+
return updated;
|
|
908
|
+
});
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
else {
|
|
912
|
+
throw new Error(result.error);
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
catch (error) {
|
|
916
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
917
|
+
// Update the specific entry by index to avoid race conditions
|
|
918
|
+
setChatHistory((prev) => {
|
|
919
|
+
const updated = [...prev];
|
|
920
|
+
if (entryIndex >= 0 && entryIndex < updated.length) {
|
|
921
|
+
updated[entryIndex] = {
|
|
922
|
+
type: "assistant",
|
|
923
|
+
content: `❌ Failed to refresh memory: ${errorMessage}`,
|
|
924
|
+
timestamp: new Date(),
|
|
925
|
+
};
|
|
926
|
+
}
|
|
927
|
+
return updated;
|
|
928
|
+
});
|
|
929
|
+
}
|
|
930
|
+
setIsProcessing(false);
|
|
931
|
+
clearInput();
|
|
932
|
+
return true;
|
|
933
|
+
}
|
|
934
|
+
// Background task commands
|
|
935
|
+
if (trimmedInput === "/tasks") {
|
|
936
|
+
const bashOutputTool = new BashOutputTool();
|
|
937
|
+
const result = bashOutputTool.listTasks();
|
|
938
|
+
const tasksEntry = {
|
|
939
|
+
type: "assistant",
|
|
940
|
+
content: result.output || "No background tasks",
|
|
941
|
+
timestamp: new Date(),
|
|
942
|
+
};
|
|
943
|
+
setChatHistory((prev) => [...prev, tasksEntry]);
|
|
944
|
+
clearInput();
|
|
945
|
+
return true;
|
|
946
|
+
}
|
|
947
|
+
if (trimmedInput.startsWith("/task ")) {
|
|
948
|
+
const taskId = trimmedInput.substring(6).trim();
|
|
949
|
+
if (!taskId) {
|
|
950
|
+
const errorEntry = {
|
|
951
|
+
type: "assistant",
|
|
952
|
+
content: "Usage: /task <task_id>",
|
|
953
|
+
timestamp: new Date(),
|
|
954
|
+
};
|
|
955
|
+
setChatHistory((prev) => [...prev, errorEntry]);
|
|
956
|
+
clearInput();
|
|
957
|
+
return true;
|
|
958
|
+
}
|
|
959
|
+
const bashOutputTool = new BashOutputTool();
|
|
960
|
+
const result = await bashOutputTool.execute(taskId);
|
|
961
|
+
const taskEntry = {
|
|
962
|
+
type: "assistant",
|
|
963
|
+
content: result.success ? result.output || "No output" : result.error || "Task not found",
|
|
964
|
+
timestamp: new Date(),
|
|
965
|
+
};
|
|
966
|
+
setChatHistory((prev) => [...prev, taskEntry]);
|
|
967
|
+
clearInput();
|
|
968
|
+
return true;
|
|
969
|
+
}
|
|
970
|
+
if (trimmedInput.startsWith("/kill ")) {
|
|
971
|
+
const taskId = trimmedInput.substring(6).trim();
|
|
972
|
+
if (!taskId) {
|
|
973
|
+
const errorEntry = {
|
|
974
|
+
type: "assistant",
|
|
975
|
+
content: "Usage: /kill <task_id>",
|
|
976
|
+
timestamp: new Date(),
|
|
977
|
+
};
|
|
978
|
+
setChatHistory((prev) => [...prev, errorEntry]);
|
|
979
|
+
clearInput();
|
|
980
|
+
return true;
|
|
981
|
+
}
|
|
982
|
+
const bashOutputTool = new BashOutputTool();
|
|
983
|
+
const result = bashOutputTool.killTask(taskId);
|
|
984
|
+
const killEntry = {
|
|
985
|
+
type: "assistant",
|
|
986
|
+
content: result.success ? result.output || "Task killed" : result.error || "Failed to kill task",
|
|
987
|
+
timestamp: new Date(),
|
|
988
|
+
};
|
|
989
|
+
setChatHistory((prev) => [...prev, killEntry]);
|
|
990
|
+
clearInput();
|
|
991
|
+
return true;
|
|
992
|
+
}
|
|
993
|
+
if (trimmedInput === "/rewind") {
|
|
994
|
+
await handleRewindCommand(agent);
|
|
995
|
+
clearInput();
|
|
996
|
+
return true;
|
|
997
|
+
}
|
|
998
|
+
if (trimmedInput === "/checkpoints") {
|
|
999
|
+
await handleCheckpointsCommand();
|
|
1000
|
+
clearInput();
|
|
1001
|
+
return true;
|
|
1002
|
+
}
|
|
1003
|
+
if (trimmedInput === "/checkpoint-clean") {
|
|
1004
|
+
await handleCheckpointCleanCommand();
|
|
1005
|
+
clearInput();
|
|
1006
|
+
return true;
|
|
1007
|
+
}
|
|
1008
|
+
// Plan commands
|
|
1009
|
+
if (trimmedInput === "/plans") {
|
|
1010
|
+
const result = await handlePlansCommand();
|
|
1011
|
+
const plansEntry = {
|
|
1012
|
+
type: "assistant",
|
|
1013
|
+
content: result.output,
|
|
1014
|
+
timestamp: new Date(),
|
|
1015
|
+
};
|
|
1016
|
+
setChatHistory((prev) => [...prev, plansEntry]);
|
|
1017
|
+
clearInput();
|
|
1018
|
+
return true;
|
|
1019
|
+
}
|
|
1020
|
+
if (trimmedInput === "/plan" || trimmedInput.startsWith("/plan ")) {
|
|
1021
|
+
const planId = trimmedInput === "/plan" ? undefined : trimmedInput.substring(6).trim();
|
|
1022
|
+
const result = await handlePlanCommand(planId || undefined);
|
|
1023
|
+
const planEntry = {
|
|
1024
|
+
type: "assistant",
|
|
1025
|
+
content: result.output,
|
|
1026
|
+
timestamp: new Date(),
|
|
1027
|
+
};
|
|
1028
|
+
setChatHistory((prev) => [...prev, planEntry]);
|
|
1029
|
+
clearInput();
|
|
1030
|
+
return true;
|
|
1031
|
+
}
|
|
1032
|
+
if (trimmedInput === "/phases") {
|
|
1033
|
+
const result = await handlePhasesCommand();
|
|
1034
|
+
const phasesEntry = {
|
|
1035
|
+
type: "assistant",
|
|
1036
|
+
content: result.output,
|
|
1037
|
+
timestamp: new Date(),
|
|
1038
|
+
};
|
|
1039
|
+
setChatHistory((prev) => [...prev, phasesEntry]);
|
|
1040
|
+
clearInput();
|
|
1041
|
+
return true;
|
|
1042
|
+
}
|
|
1043
|
+
if (trimmedInput === "/pause") {
|
|
1044
|
+
const result = await handlePauseCommand();
|
|
1045
|
+
const pauseEntry = {
|
|
1046
|
+
type: "assistant",
|
|
1047
|
+
content: result.output,
|
|
1048
|
+
timestamp: new Date(),
|
|
1049
|
+
};
|
|
1050
|
+
setChatHistory((prev) => [...prev, pauseEntry]);
|
|
1051
|
+
clearInput();
|
|
1052
|
+
return true;
|
|
1053
|
+
}
|
|
1054
|
+
if (trimmedInput === "/resume" || trimmedInput.startsWith("/resume ")) {
|
|
1055
|
+
const planId = trimmedInput === "/resume" ? undefined : trimmedInput.substring(8).trim();
|
|
1056
|
+
const result = await handleResumeCommand(planId || undefined);
|
|
1057
|
+
const resumeEntry = {
|
|
1058
|
+
type: "assistant",
|
|
1059
|
+
content: result.output,
|
|
1060
|
+
timestamp: new Date(),
|
|
1061
|
+
};
|
|
1062
|
+
setChatHistory((prev) => [...prev, resumeEntry]);
|
|
1063
|
+
clearInput();
|
|
1064
|
+
return true;
|
|
1065
|
+
}
|
|
1066
|
+
if (trimmedInput === "/skip") {
|
|
1067
|
+
const result = await handleSkipPhaseCommand();
|
|
1068
|
+
const skipEntry = {
|
|
1069
|
+
type: "assistant",
|
|
1070
|
+
content: result.output,
|
|
1071
|
+
timestamp: new Date(),
|
|
1072
|
+
};
|
|
1073
|
+
setChatHistory((prev) => [...prev, skipEntry]);
|
|
1074
|
+
clearInput();
|
|
1075
|
+
return true;
|
|
1076
|
+
}
|
|
1077
|
+
if (trimmedInput === "/abandon") {
|
|
1078
|
+
const result = await handleAbandonCommand();
|
|
1079
|
+
const abandonEntry = {
|
|
1080
|
+
type: "assistant",
|
|
1081
|
+
content: result.output,
|
|
1082
|
+
timestamp: new Date(),
|
|
1083
|
+
};
|
|
1084
|
+
setChatHistory((prev) => [...prev, abandonEntry]);
|
|
1085
|
+
clearInput();
|
|
1086
|
+
return true;
|
|
1087
|
+
}
|
|
1088
|
+
if (trimmedInput === "/resumable") {
|
|
1089
|
+
const result = await handleResumableCommand();
|
|
1090
|
+
const resumableEntry = {
|
|
1091
|
+
type: "assistant",
|
|
1092
|
+
content: result.output,
|
|
1093
|
+
timestamp: new Date(),
|
|
1094
|
+
};
|
|
1095
|
+
setChatHistory((prev) => [...prev, resumableEntry]);
|
|
1096
|
+
clearInput();
|
|
1097
|
+
return true;
|
|
1098
|
+
}
|
|
1099
|
+
if (trimmedInput === "/commit-and-push") {
|
|
1100
|
+
const userEntry = {
|
|
1101
|
+
type: "user",
|
|
1102
|
+
content: "/commit-and-push",
|
|
1103
|
+
timestamp: new Date(),
|
|
1104
|
+
};
|
|
1105
|
+
setChatHistory((prev) => [...prev, userEntry]);
|
|
1106
|
+
setIsProcessing(true);
|
|
1107
|
+
setIsStreaming(true);
|
|
1108
|
+
try {
|
|
1109
|
+
// First check if there are any changes at all
|
|
1110
|
+
const initialStatusResult = await agent.executeBashCommand("git status --porcelain");
|
|
1111
|
+
if (!initialStatusResult.success ||
|
|
1112
|
+
!initialStatusResult.output?.trim()) {
|
|
1113
|
+
const noChangesEntry = {
|
|
1114
|
+
type: "assistant",
|
|
1115
|
+
content: "No changes to commit. Working directory is clean.",
|
|
1116
|
+
timestamp: new Date(),
|
|
1117
|
+
};
|
|
1118
|
+
setChatHistory((prev) => [...prev, noChangesEntry]);
|
|
1119
|
+
setIsProcessing(false);
|
|
1120
|
+
setIsStreaming(false);
|
|
1121
|
+
setInput("");
|
|
1122
|
+
return true;
|
|
1123
|
+
}
|
|
1124
|
+
// Add all changes
|
|
1125
|
+
const addResult = await agent.executeBashCommand("git add .");
|
|
1126
|
+
if (!addResult.success) {
|
|
1127
|
+
const addErrorEntry = {
|
|
1128
|
+
type: "assistant",
|
|
1129
|
+
content: `Failed to stage changes: ${addResult.error || "Unknown error"}`,
|
|
1130
|
+
timestamp: new Date(),
|
|
1131
|
+
};
|
|
1132
|
+
setChatHistory((prev) => [...prev, addErrorEntry]);
|
|
1133
|
+
setIsProcessing(false);
|
|
1134
|
+
setIsStreaming(false);
|
|
1135
|
+
setInput("");
|
|
1136
|
+
return true;
|
|
1137
|
+
}
|
|
1138
|
+
// Show that changes were staged
|
|
1139
|
+
const addEntry = {
|
|
1140
|
+
type: "tool_result",
|
|
1141
|
+
content: "Changes staged successfully",
|
|
1142
|
+
timestamp: new Date(),
|
|
1143
|
+
toolCall: {
|
|
1144
|
+
id: `git_add_${Date.now()}`,
|
|
1145
|
+
type: "function",
|
|
1146
|
+
function: {
|
|
1147
|
+
name: "bash",
|
|
1148
|
+
arguments: JSON.stringify({ command: "git add ." }),
|
|
1149
|
+
},
|
|
1150
|
+
},
|
|
1151
|
+
toolResult: addResult,
|
|
1152
|
+
};
|
|
1153
|
+
setChatHistory((prev) => [...prev, addEntry]);
|
|
1154
|
+
// Get staged changes for commit message generation
|
|
1155
|
+
const diffResult = await agent.executeBashCommand("git diff --cached");
|
|
1156
|
+
// Generate commit message using AI
|
|
1157
|
+
const commitPrompt = `Generate a concise, professional git commit message for these changes:
|
|
1158
|
+
|
|
1159
|
+
Git Status:
|
|
1160
|
+
${initialStatusResult.output}
|
|
1161
|
+
|
|
1162
|
+
Git Diff (staged changes):
|
|
1163
|
+
${diffResult.output || "No staged changes shown"}
|
|
1164
|
+
|
|
1165
|
+
Follow conventional commit format (feat:, fix:, docs:, etc.) and keep it under 72 characters.
|
|
1166
|
+
Respond with ONLY the commit message, no additional text.`;
|
|
1167
|
+
let commitMessage = "";
|
|
1168
|
+
let streamingEntry = null;
|
|
1169
|
+
for await (const chunk of agent.processUserMessageStream(commitPrompt)) {
|
|
1170
|
+
if (chunk.type === "content" && chunk.content) {
|
|
1171
|
+
if (!streamingEntry) {
|
|
1172
|
+
const newEntry = {
|
|
1173
|
+
type: "assistant",
|
|
1174
|
+
content: `Generating commit message...\n\n${chunk.content}`,
|
|
1175
|
+
timestamp: new Date(),
|
|
1176
|
+
isStreaming: true,
|
|
1177
|
+
};
|
|
1178
|
+
setChatHistory((prev) => [...prev, newEntry]);
|
|
1179
|
+
streamingEntry = newEntry;
|
|
1180
|
+
commitMessage = chunk.content;
|
|
1181
|
+
}
|
|
1182
|
+
else {
|
|
1183
|
+
commitMessage += chunk.content;
|
|
1184
|
+
setChatHistory((prev) => prev.map((entry, idx) => idx === prev.length - 1 && entry.isStreaming
|
|
1185
|
+
? {
|
|
1186
|
+
...entry,
|
|
1187
|
+
content: `Generating commit message...\n\n${commitMessage}`,
|
|
1188
|
+
}
|
|
1189
|
+
: entry));
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
else if (chunk.type === "done") {
|
|
1193
|
+
if (streamingEntry) {
|
|
1194
|
+
setChatHistory((prev) => prev.map((entry) => entry.isStreaming
|
|
1195
|
+
? {
|
|
1196
|
+
...entry,
|
|
1197
|
+
content: `Generated commit message: "${commitMessage.trim()}"`,
|
|
1198
|
+
isStreaming: false,
|
|
1199
|
+
}
|
|
1200
|
+
: entry));
|
|
1201
|
+
}
|
|
1202
|
+
break;
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
// Execute the commit with properly escaped message to prevent command injection
|
|
1206
|
+
const cleanCommitMessage = commitMessage.trim();
|
|
1207
|
+
const commitCommand = `git commit -m ${escapeShellArg(cleanCommitMessage)}`;
|
|
1208
|
+
const commitResult = await agent.executeBashCommand(commitCommand);
|
|
1209
|
+
const commitEntry = {
|
|
1210
|
+
type: "tool_result",
|
|
1211
|
+
content: commitResult.success
|
|
1212
|
+
? commitResult.output || "Commit successful"
|
|
1213
|
+
: commitResult.error || "Commit failed",
|
|
1214
|
+
timestamp: new Date(),
|
|
1215
|
+
toolCall: {
|
|
1216
|
+
id: `git_commit_${Date.now()}`,
|
|
1217
|
+
type: "function",
|
|
1218
|
+
function: {
|
|
1219
|
+
name: "bash",
|
|
1220
|
+
arguments: JSON.stringify({ command: commitCommand }),
|
|
1221
|
+
},
|
|
1222
|
+
},
|
|
1223
|
+
toolResult: commitResult,
|
|
1224
|
+
};
|
|
1225
|
+
setChatHistory((prev) => [...prev, commitEntry]);
|
|
1226
|
+
// If commit was successful, push to remote
|
|
1227
|
+
if (commitResult.success) {
|
|
1228
|
+
// First try regular push, if it fails try with upstream setup
|
|
1229
|
+
let pushResult = await agent.executeBashCommand("git push");
|
|
1230
|
+
let pushCommand = "git push";
|
|
1231
|
+
if (!pushResult.success &&
|
|
1232
|
+
pushResult.error?.includes("no upstream branch")) {
|
|
1233
|
+
pushCommand = "git push -u origin HEAD";
|
|
1234
|
+
pushResult = await agent.executeBashCommand(pushCommand);
|
|
1235
|
+
}
|
|
1236
|
+
const pushEntry = {
|
|
1237
|
+
type: "tool_result",
|
|
1238
|
+
content: pushResult.success
|
|
1239
|
+
? pushResult.output || "Push successful"
|
|
1240
|
+
: pushResult.error || "Push failed",
|
|
1241
|
+
timestamp: new Date(),
|
|
1242
|
+
toolCall: {
|
|
1243
|
+
id: `git_push_${Date.now()}`,
|
|
1244
|
+
type: "function",
|
|
1245
|
+
function: {
|
|
1246
|
+
name: "bash",
|
|
1247
|
+
arguments: JSON.stringify({ command: pushCommand }),
|
|
1248
|
+
},
|
|
1249
|
+
},
|
|
1250
|
+
toolResult: pushResult,
|
|
1251
|
+
};
|
|
1252
|
+
setChatHistory((prev) => [...prev, pushEntry]);
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
catch (error) {
|
|
1256
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1257
|
+
const errorEntry = {
|
|
1258
|
+
type: "assistant",
|
|
1259
|
+
content: `Error during commit and push: ${errorMessage}`,
|
|
1260
|
+
timestamp: new Date(),
|
|
1261
|
+
};
|
|
1262
|
+
setChatHistory((prev) => [...prev, errorEntry]);
|
|
1263
|
+
}
|
|
1264
|
+
setIsProcessing(false);
|
|
1265
|
+
setIsStreaming(false);
|
|
1266
|
+
clearInput();
|
|
1267
|
+
return true;
|
|
1268
|
+
}
|
|
1269
|
+
const directBashCommands = [
|
|
1270
|
+
"ls",
|
|
1271
|
+
"pwd",
|
|
1272
|
+
"cd",
|
|
1273
|
+
"cat",
|
|
1274
|
+
"mkdir",
|
|
1275
|
+
"touch",
|
|
1276
|
+
"echo",
|
|
1277
|
+
"grep",
|
|
1278
|
+
"find",
|
|
1279
|
+
"cp",
|
|
1280
|
+
"mv",
|
|
1281
|
+
"rm",
|
|
1282
|
+
];
|
|
1283
|
+
const firstWord = trimmedInput.split(" ")[0];
|
|
1284
|
+
if (directBashCommands.includes(firstWord)) {
|
|
1285
|
+
const userEntry = {
|
|
1286
|
+
type: "user",
|
|
1287
|
+
content: trimmedInput,
|
|
1288
|
+
timestamp: new Date(),
|
|
1289
|
+
};
|
|
1290
|
+
setChatHistory((prev) => [...prev, userEntry]);
|
|
1291
|
+
try {
|
|
1292
|
+
const result = await agent.executeBashCommand(trimmedInput);
|
|
1293
|
+
const commandEntry = {
|
|
1294
|
+
type: "tool_result",
|
|
1295
|
+
content: result.success
|
|
1296
|
+
? result.output || "Command completed"
|
|
1297
|
+
: result.error || "Command failed",
|
|
1298
|
+
timestamp: new Date(),
|
|
1299
|
+
toolCall: {
|
|
1300
|
+
id: `bash_${Date.now()}`,
|
|
1301
|
+
type: "function",
|
|
1302
|
+
function: {
|
|
1303
|
+
name: "bash",
|
|
1304
|
+
arguments: JSON.stringify({ command: trimmedInput }),
|
|
1305
|
+
},
|
|
1306
|
+
},
|
|
1307
|
+
toolResult: result,
|
|
1308
|
+
};
|
|
1309
|
+
setChatHistory((prev) => [...prev, commandEntry]);
|
|
1310
|
+
}
|
|
1311
|
+
catch (error) {
|
|
1312
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1313
|
+
const errorEntry = {
|
|
1314
|
+
type: "assistant",
|
|
1315
|
+
content: `Error executing command: ${errorMessage}`,
|
|
1316
|
+
timestamp: new Date(),
|
|
1317
|
+
};
|
|
1318
|
+
setChatHistory((prev) => [...prev, errorEntry]);
|
|
1319
|
+
}
|
|
1320
|
+
clearInput();
|
|
1321
|
+
return true;
|
|
1322
|
+
}
|
|
1323
|
+
return false;
|
|
1324
|
+
};
|
|
1325
|
+
const processUserMessage = async (userInput) => {
|
|
1326
|
+
const userEntry = {
|
|
1327
|
+
type: "user",
|
|
1328
|
+
content: userInput,
|
|
1329
|
+
timestamp: new Date(),
|
|
1330
|
+
};
|
|
1331
|
+
setChatHistory((prev) => [...prev, userEntry]);
|
|
1332
|
+
setIsProcessing(true);
|
|
1333
|
+
clearInput();
|
|
1334
|
+
try {
|
|
1335
|
+
setIsStreaming(true);
|
|
1336
|
+
let streamingEntry = null;
|
|
1337
|
+
for await (const chunk of agent.processUserMessageStream(userInput)) {
|
|
1338
|
+
switch (chunk.type) {
|
|
1339
|
+
case "content":
|
|
1340
|
+
if (chunk.content) {
|
|
1341
|
+
if (!streamingEntry) {
|
|
1342
|
+
const newStreamingEntry = {
|
|
1343
|
+
type: "assistant",
|
|
1344
|
+
content: chunk.content,
|
|
1345
|
+
timestamp: new Date(),
|
|
1346
|
+
isStreaming: true,
|
|
1347
|
+
};
|
|
1348
|
+
setChatHistory((prev) => [...prev, newStreamingEntry]);
|
|
1349
|
+
streamingEntry = newStreamingEntry;
|
|
1350
|
+
}
|
|
1351
|
+
else {
|
|
1352
|
+
setChatHistory((prev) => prev.map((entry, idx) => idx === prev.length - 1 && entry.isStreaming
|
|
1353
|
+
? { ...entry, content: entry.content + chunk.content }
|
|
1354
|
+
: entry));
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
break;
|
|
1358
|
+
case "token_count":
|
|
1359
|
+
if (chunk.tokenCount !== undefined) {
|
|
1360
|
+
setTokenCount(chunk.tokenCount);
|
|
1361
|
+
}
|
|
1362
|
+
break;
|
|
1363
|
+
case "tool_calls":
|
|
1364
|
+
if (chunk.toolCalls) {
|
|
1365
|
+
// Stop streaming for the current assistant message
|
|
1366
|
+
setChatHistory((prev) => prev.map((entry) => entry.isStreaming
|
|
1367
|
+
? {
|
|
1368
|
+
...entry,
|
|
1369
|
+
isStreaming: false,
|
|
1370
|
+
toolCalls: chunk.toolCalls,
|
|
1371
|
+
}
|
|
1372
|
+
: entry));
|
|
1373
|
+
streamingEntry = null;
|
|
1374
|
+
// Add individual tool call entries to show tools are being executed
|
|
1375
|
+
chunk.toolCalls.forEach((toolCall) => {
|
|
1376
|
+
const toolCallEntry = {
|
|
1377
|
+
type: "tool_call",
|
|
1378
|
+
content: "Executing...",
|
|
1379
|
+
timestamp: new Date(),
|
|
1380
|
+
toolCall: toolCall,
|
|
1381
|
+
};
|
|
1382
|
+
setChatHistory((prev) => [...prev, toolCallEntry]);
|
|
1383
|
+
});
|
|
1384
|
+
}
|
|
1385
|
+
break;
|
|
1386
|
+
case "tool_result":
|
|
1387
|
+
if (chunk.toolCall && chunk.toolResult) {
|
|
1388
|
+
setChatHistory((prev) => prev.map((entry) => {
|
|
1389
|
+
if (entry.isStreaming) {
|
|
1390
|
+
return { ...entry, isStreaming: false };
|
|
1391
|
+
}
|
|
1392
|
+
// Update the existing tool_call entry with the result
|
|
1393
|
+
if (entry.type === "tool_call" &&
|
|
1394
|
+
entry.toolCall?.id === chunk.toolCall?.id) {
|
|
1395
|
+
return {
|
|
1396
|
+
...entry,
|
|
1397
|
+
type: "tool_result",
|
|
1398
|
+
content: chunk.toolResult?.success
|
|
1399
|
+
? chunk.toolResult?.output || "Success"
|
|
1400
|
+
: chunk.toolResult?.error || "Error occurred",
|
|
1401
|
+
toolResult: chunk.toolResult,
|
|
1402
|
+
};
|
|
1403
|
+
}
|
|
1404
|
+
return entry;
|
|
1405
|
+
}));
|
|
1406
|
+
streamingEntry = null;
|
|
1407
|
+
}
|
|
1408
|
+
break;
|
|
1409
|
+
case "done":
|
|
1410
|
+
if (streamingEntry) {
|
|
1411
|
+
setChatHistory((prev) => prev.map((entry) => entry.isStreaming ? { ...entry, isStreaming: false } : entry));
|
|
1412
|
+
}
|
|
1413
|
+
setIsStreaming(false);
|
|
1414
|
+
break;
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
catch (error) {
|
|
1419
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1420
|
+
const errorEntry = {
|
|
1421
|
+
type: "assistant",
|
|
1422
|
+
content: `Error: ${errorMessage}`,
|
|
1423
|
+
timestamp: new Date(),
|
|
1424
|
+
};
|
|
1425
|
+
setChatHistory((prev) => [...prev, errorEntry]);
|
|
1426
|
+
setIsStreaming(false);
|
|
1427
|
+
}
|
|
1428
|
+
setIsProcessing(false);
|
|
1429
|
+
processingStartTime.current = 0;
|
|
1430
|
+
};
|
|
1431
|
+
return {
|
|
1432
|
+
input,
|
|
1433
|
+
cursorPosition,
|
|
1434
|
+
showCommandSuggestions,
|
|
1435
|
+
selectedCommandIndex,
|
|
1436
|
+
commandSuggestions,
|
|
1437
|
+
availableModels,
|
|
1438
|
+
agent,
|
|
1439
|
+
autoEditEnabled,
|
|
1440
|
+
verboseMode,
|
|
1441
|
+
backgroundMode,
|
|
1442
|
+
pastedBlocks,
|
|
1443
|
+
currentBlockAtCursor,
|
|
1444
|
+
};
|
|
1445
|
+
}
|
|
1446
|
+
//# sourceMappingURL=use-input-handler.js.map
|