@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.
Files changed (162) hide show
  1. package/.ax-cli/checkpoints/2025-11-20/checkpoint-11e9e0ba-c39d-4fd2-aa77-bc818811c921.json +69 -0
  2. package/.ax-cli/checkpoints/2025-11-20/checkpoint-2b260b98-b418-4c7c-9694-e2b94967e662.json +24 -0
  3. package/.ax-cli/checkpoints/2025-11-20/checkpoint-7e03601e-e8ab-4cd7-9841-a74b66adf78f.json +69 -0
  4. package/.ax-cli/checkpoints/2025-11-20/checkpoint-7f9c6562-771f-4fd0-adcf-9e7e9ac34ae8.json +44 -0
  5. package/.ax-cli/checkpoints/2025-11-20/checkpoint-e1ebe666-4c3a-4367-ba5c-27fe512a9c70.json +24 -0
  6. package/.ax-cli/checkpoints/2025-11-21/checkpoint-15743e7d-430c-4d76-b6fc-955d7a5c250c.json +44 -0
  7. package/.ax-cli/checkpoints/2025-11-21/checkpoint-25cf7679-0b3f-4988-83d7-704548fbba91.json +69 -0
  8. package/.ax-cli/checkpoints/2025-11-21/checkpoint-54aedbac-6db0-464e-8ebb-dbb3979e6dca.json +24 -0
  9. package/.ax-cli/checkpoints/2025-11-21/checkpoint-7658aed8-fe5d-4222-903f-1a7c63717ea7.json +24 -0
  10. package/.ax-cli/checkpoints/2025-11-21/checkpoint-c9c13497-40dc-4294-a327-6a5fc854eaa1.json +69 -0
  11. package/.ax-cli/memory.json +15 -8
  12. package/README.md +423 -82
  13. package/ax.config.json +333 -0
  14. package/config-defaults/messages.yaml +75 -0
  15. package/config-defaults/models.yaml +66 -0
  16. package/config-defaults/prompts.yaml +156 -0
  17. package/config-defaults/settings.yaml +86 -0
  18. package/dist/agent/chat-history-manager.d.ts +56 -0
  19. package/dist/agent/chat-history-manager.js +150 -0
  20. package/dist/agent/chat-history-manager.js.map +1 -0
  21. package/dist/agent/llm-agent.js +1 -1
  22. package/dist/agent/llm-agent.js.map +1 -1
  23. package/dist/agent/tool-manager.d.ts +39 -0
  24. package/dist/agent/tool-manager.js +76 -0
  25. package/dist/agent/tool-manager.js.map +1 -0
  26. package/dist/analyzers/code-smells/detectors/data-clumps-detector.js +7 -9
  27. package/dist/analyzers/code-smells/detectors/data-clumps-detector.js.map +1 -1
  28. package/dist/analyzers/code-smells/detectors/dead-code-detector.js +1 -1
  29. package/dist/analyzers/code-smells/detectors/dead-code-detector.js.map +1 -1
  30. package/dist/analyzers/code-smells/detectors/duplicate-code-detector.js +22 -10
  31. package/dist/analyzers/code-smells/detectors/duplicate-code-detector.js.map +1 -1
  32. package/dist/analyzers/code-smells/detectors/feature-envy-detector.js +1 -1
  33. package/dist/analyzers/code-smells/detectors/feature-envy-detector.js.map +1 -1
  34. package/dist/analyzers/code-smells/detectors/inappropriate-intimacy-detector.js +1 -1
  35. package/dist/analyzers/code-smells/detectors/inappropriate-intimacy-detector.js.map +1 -1
  36. package/dist/analyzers/code-smells/detectors/large-class-detector.js +4 -1
  37. package/dist/analyzers/code-smells/detectors/large-class-detector.js.map +1 -1
  38. package/dist/analyzers/code-smells/detectors/long-method-detector.js +4 -1
  39. package/dist/analyzers/code-smells/detectors/long-method-detector.js.map +1 -1
  40. package/dist/analyzers/code-smells/detectors/long-parameter-list-detector.js +4 -1
  41. package/dist/analyzers/code-smells/detectors/long-parameter-list-detector.js.map +1 -1
  42. package/dist/analyzers/code-smells/detectors/magic-numbers-detector.js +4 -5
  43. package/dist/analyzers/code-smells/detectors/magic-numbers-detector.js.map +1 -1
  44. package/dist/analyzers/code-smells/detectors/nested-conditionals-detector.js +4 -1
  45. package/dist/analyzers/code-smells/detectors/nested-conditionals-detector.js.map +1 -1
  46. package/dist/commands/memory.js +1 -1
  47. package/dist/commands/memory.js.map +1 -1
  48. package/dist/commands/setup.js +19 -6
  49. package/dist/commands/setup.js.map +1 -1
  50. package/dist/index.js +7 -0
  51. package/dist/index.js.bak +664 -0
  52. package/dist/index.js.map +1 -1
  53. package/dist/llm/client.d.ts +1 -0
  54. package/dist/llm/client.js +44 -0
  55. package/dist/llm/client.js.map +1 -1
  56. package/dist/mcp/health.js +4 -2
  57. package/dist/mcp/health.js.map +1 -1
  58. package/dist/mcp/ssrf-protection.d.ts +86 -0
  59. package/dist/mcp/ssrf-protection.js +313 -0
  60. package/dist/mcp/ssrf-protection.js.map +1 -0
  61. package/dist/mcp/validation.d.ts +4 -0
  62. package/dist/mcp/validation.js +122 -11
  63. package/dist/mcp/validation.js.map +1 -1
  64. package/dist/schemas/settings-schemas.d.ts +53 -0
  65. package/dist/schemas/settings-schemas.js +47 -0
  66. package/dist/schemas/settings-schemas.js.map +1 -1
  67. package/dist/tools/bash.d.ts +3 -2
  68. package/dist/tools/bash.js +31 -2
  69. package/dist/tools/bash.js.map +1 -1
  70. package/dist/tools/search.d.ts +1 -1
  71. package/dist/tools/search.js +121 -128
  72. package/dist/tools/search.js.map +1 -1
  73. package/dist/tools/text-editor.js +52 -15
  74. package/dist/tools/text-editor.js.map +1 -1
  75. package/dist/tools/web-search/index.d.ts +0 -2
  76. package/dist/tools/web-search/index.js +0 -2
  77. package/dist/tools/web-search/index.js.map +1 -1
  78. package/dist/tools/web-search/router.d.ts +0 -2
  79. package/dist/tools/web-search/router.js +2 -37
  80. package/dist/tools/web-search/router.js.map +1 -1
  81. package/dist/tools/web-search/web-search-tool.js +2 -12
  82. package/dist/tools/web-search/web-search-tool.js.map +1 -1
  83. package/dist/ui/components/chat-history.js +1 -1
  84. package/dist/ui/components/chat-history.js.map +1 -1
  85. package/dist/ui/components/chat-input.d.ts +4 -1
  86. package/dist/ui/components/chat-input.js +133 -52
  87. package/dist/ui/components/chat-input.js.map +1 -1
  88. package/dist/ui/components/chat-interface.js +5 -4
  89. package/dist/ui/components/chat-interface.js.map +1 -1
  90. package/dist/ui/components/confirmation-dialog.js +1 -1
  91. package/dist/ui/components/confirmation-dialog.js.map +1 -1
  92. package/dist/ui/components/keyboard-hints.js +2 -0
  93. package/dist/ui/components/keyboard-hints.js.map +1 -1
  94. package/dist/ui/components/status-bar.js +3 -13
  95. package/dist/ui/components/status-bar.js.map +1 -1
  96. package/dist/ui/components/welcome-panel.js +4 -0
  97. package/dist/ui/components/welcome-panel.js.map +1 -1
  98. package/dist/ui/hooks/use-chat-reducer.d.ts +61 -0
  99. package/dist/ui/hooks/use-chat-reducer.js +118 -0
  100. package/dist/ui/hooks/use-chat-reducer.js.map +1 -0
  101. package/dist/ui/hooks/use-enhanced-input.d.ts +44 -0
  102. package/dist/ui/hooks/use-enhanced-input.js +364 -0
  103. package/dist/ui/hooks/use-enhanced-input.js.map +1 -0
  104. package/dist/ui/hooks/use-input-handler.d.ts +48 -0
  105. package/dist/ui/hooks/use-input-handler.js +1446 -0
  106. package/dist/ui/hooks/use-input-handler.js.map +1 -0
  107. package/dist/utils/audit-logger.d.ts +205 -0
  108. package/dist/utils/audit-logger.js +269 -0
  109. package/dist/utils/audit-logger.js.map +1 -0
  110. package/dist/utils/command-security.d.ts +85 -0
  111. package/dist/utils/command-security.js +200 -0
  112. package/dist/utils/command-security.js.map +1 -0
  113. package/dist/utils/config-loader.js +3 -3
  114. package/dist/utils/config-loader.js.map +1 -1
  115. package/dist/utils/encryption.d.ts +78 -0
  116. package/dist/utils/encryption.js +216 -0
  117. package/dist/utils/encryption.js.map +1 -0
  118. package/dist/utils/error-sanitizer.d.ts +119 -0
  119. package/dist/utils/error-sanitizer.js +253 -0
  120. package/dist/utils/error-sanitizer.js.map +1 -0
  121. package/dist/utils/input-sanitizer.d.ts +210 -0
  122. package/dist/utils/input-sanitizer.js +362 -0
  123. package/dist/utils/input-sanitizer.js.map +1 -0
  124. package/dist/utils/json-utils.d.ts +13 -0
  125. package/dist/utils/json-utils.js +55 -1
  126. package/dist/utils/json-utils.js.map +1 -1
  127. package/dist/utils/paste-collapse.d.ts +46 -0
  128. package/dist/utils/paste-collapse.js +77 -0
  129. package/dist/utils/paste-collapse.js.map +1 -0
  130. package/dist/utils/paste-utils.d.ts +99 -0
  131. package/dist/utils/paste-utils.js +239 -0
  132. package/dist/utils/paste-utils.js.map +1 -0
  133. package/dist/utils/path-security.d.ts +90 -0
  134. package/dist/utils/path-security.js +328 -0
  135. package/dist/utils/path-security.js.map +1 -0
  136. package/dist/utils/process-pool.d.ts +105 -0
  137. package/dist/utils/process-pool.js +326 -0
  138. package/dist/utils/process-pool.js.map +1 -0
  139. package/dist/utils/rate-limiter.d.ts +221 -0
  140. package/dist/utils/rate-limiter.js +317 -0
  141. package/dist/utils/rate-limiter.js.map +1 -0
  142. package/dist/utils/settings-manager.js +99 -6
  143. package/dist/utils/settings-manager.js.map +1 -1
  144. package/dist/utils/streaming-analyzer.js +9 -21
  145. package/dist/utils/streaming-analyzer.js.map +1 -1
  146. package/package.json +3 -7
  147. package/packages/schemas/dist/index.d.ts +14 -0
  148. package/packages/schemas/dist/index.d.ts.map +1 -0
  149. package/packages/schemas/dist/index.js +19 -0
  150. package/packages/schemas/dist/index.js.map +1 -0
  151. package/packages/schemas/dist/public/core/brand-types.d.ts +308 -0
  152. package/packages/schemas/dist/public/core/brand-types.d.ts.map +1 -0
  153. package/packages/schemas/dist/public/core/brand-types.js +243 -0
  154. package/packages/schemas/dist/public/core/brand-types.js.map +1 -0
  155. package/packages/schemas/dist/public/core/enums.d.ts +227 -0
  156. package/packages/schemas/dist/public/core/enums.d.ts.map +1 -0
  157. package/packages/schemas/dist/public/core/enums.js +222 -0
  158. package/packages/schemas/dist/public/core/enums.js.map +1 -0
  159. package/packages/schemas/dist/public/core/id-types.d.ts +286 -0
  160. package/packages/schemas/dist/public/core/id-types.d.ts.map +1 -0
  161. package/packages/schemas/dist/public/core/id-types.js +136 -0
  162. 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