@defai.digital/ax-cli 3.7.2 → 3.8.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/README.md +128 -56
- package/dist/agent/context-manager.d.ts +15 -1
- package/dist/agent/context-manager.js +50 -19
- package/dist/agent/context-manager.js.map +1 -1
- package/dist/agent/dependency-resolver.js +13 -7
- package/dist/agent/dependency-resolver.js.map +1 -1
- package/dist/agent/llm-agent.d.ts +35 -0
- package/dist/agent/llm-agent.js +137 -4
- package/dist/agent/llm-agent.js.map +1 -1
- package/dist/agent/status-reporter.d.ts +114 -0
- package/dist/agent/status-reporter.js +335 -0
- package/dist/agent/status-reporter.js.map +1 -0
- package/dist/analyzers/best-practices/rules/typescript/no-magic-numbers.js +8 -2
- package/dist/analyzers/best-practices/rules/typescript/no-magic-numbers.js.map +1 -1
- package/dist/analyzers/best-practices/rules/typescript/no-unused-vars.js +3 -1
- package/dist/analyzers/best-practices/rules/typescript/no-unused-vars.js.map +1 -1
- package/dist/analyzers/best-practices/rules/typescript/prefer-const.js +3 -1
- package/dist/analyzers/best-practices/rules/typescript/prefer-const.js.map +1 -1
- package/dist/analyzers/best-practices/rules/typescript/prefer-readonly.js +3 -1
- package/dist/analyzers/best-practices/rules/typescript/prefer-readonly.js.map +1 -1
- package/dist/analyzers/code-smells/detectors/duplicate-code-detector.js +9 -3
- package/dist/analyzers/code-smells/detectors/duplicate-code-detector.js.map +1 -1
- package/dist/analyzers/git/churn-calculator.d.ts +1 -0
- package/dist/analyzers/git/churn-calculator.js +25 -6
- package/dist/analyzers/git/churn-calculator.js.map +1 -1
- package/dist/analyzers/git/hotspot-detector.js +2 -2
- package/dist/analyzers/git/hotspot-detector.js.map +1 -1
- package/dist/analyzers/metrics/metrics-analyzer.js +1 -1
- package/dist/analyzers/metrics/metrics-analyzer.js.map +1 -1
- package/dist/analyzers/security/security-analyzer.js +1 -1
- package/dist/analyzers/security/security-analyzer.js.map +1 -1
- package/dist/checkpoint/manager.d.ts +1 -0
- package/dist/checkpoint/manager.js +49 -9
- package/dist/checkpoint/manager.js.map +1 -1
- package/dist/checkpoint/storage.js +2 -2
- package/dist/checkpoint/storage.js.map +1 -1
- package/dist/commands/mcp-migrate.d.ts +9 -0
- package/dist/commands/mcp-migrate.js +172 -0
- package/dist/commands/mcp-migrate.js.map +1 -0
- package/dist/commands/status.d.ts +7 -0
- package/dist/commands/status.js +211 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/vscode.d.ts +7 -0
- package/dist/commands/vscode.js +363 -0
- package/dist/commands/vscode.js.map +1 -0
- package/dist/index.js +79 -30
- package/dist/index.js.map +1 -1
- package/dist/llm/client.js +22 -4
- package/dist/llm/client.js.map +1 -1
- package/dist/mcp/automatosx-loader.d.ts +84 -0
- package/dist/mcp/automatosx-loader.js +238 -0
- package/dist/mcp/automatosx-loader.js.map +1 -0
- package/dist/mcp/client-mutex-patch.d.ts +36 -0
- package/dist/mcp/client-mutex-patch.js +75 -0
- package/dist/mcp/client-mutex-patch.js.map +1 -0
- package/dist/mcp/client-v2.d.ts +229 -0
- package/dist/mcp/client-v2.js +740 -0
- package/dist/mcp/client-v2.js.map +1 -0
- package/dist/mcp/client.d.ts +111 -13
- package/dist/mcp/client.js +168 -253
- package/dist/mcp/client.js.map +1 -1
- package/dist/mcp/config-detector-v2.d.ts +83 -0
- package/dist/mcp/config-detector-v2.js +328 -0
- package/dist/mcp/config-detector-v2.js.map +1 -0
- package/dist/mcp/config-detector.d.ts +90 -0
- package/dist/mcp/config-detector.js +242 -0
- package/dist/mcp/config-detector.js.map +1 -0
- package/dist/mcp/config-migrator-v2.d.ts +89 -0
- package/dist/mcp/config-migrator-v2.js +288 -0
- package/dist/mcp/config-migrator-v2.js.map +1 -0
- package/dist/mcp/config-migrator.d.ts +63 -0
- package/dist/mcp/config-migrator.js +269 -0
- package/dist/mcp/config-migrator.js.map +1 -0
- package/dist/mcp/config-v2.d.ts +106 -0
- package/dist/mcp/config-v2.js +417 -0
- package/dist/mcp/config-v2.js.map +1 -0
- package/dist/mcp/config.d.ts +12 -1
- package/dist/mcp/config.js +95 -10
- package/dist/mcp/config.js.map +1 -1
- package/dist/mcp/error-formatter.d.ts +46 -0
- package/dist/mcp/error-formatter.js +244 -0
- package/dist/mcp/error-formatter.js.map +1 -0
- package/dist/mcp/health.d.ts +5 -0
- package/dist/mcp/health.js +22 -2
- package/dist/mcp/health.js.map +1 -1
- package/dist/mcp/invariants.d.ts +141 -0
- package/dist/mcp/invariants.js +243 -0
- package/dist/mcp/invariants.js.map +1 -0
- package/dist/mcp/mutex-safe.d.ts +153 -0
- package/dist/mcp/mutex-safe.js +260 -0
- package/dist/mcp/mutex-safe.js.map +1 -0
- package/dist/mcp/mutex.d.ts +73 -0
- package/dist/mcp/mutex.js +130 -0
- package/dist/mcp/mutex.js.map +1 -0
- package/dist/mcp/reconnection.d.ts +4 -0
- package/dist/mcp/reconnection.js +15 -0
- package/dist/mcp/reconnection.js.map +1 -1
- package/dist/mcp/transports-v2.d.ts +152 -0
- package/dist/mcp/transports-v2.js +481 -0
- package/dist/mcp/transports-v2.js.map +1 -0
- package/dist/mcp/type-safety.d.ts +231 -0
- package/dist/mcp/type-safety.js +273 -0
- package/dist/mcp/type-safety.js.map +1 -0
- package/dist/planner/task-planner.js +13 -0
- package/dist/planner/task-planner.js.map +1 -1
- package/dist/planner/types.d.ts +6 -6
- package/dist/schemas/confirmation-schemas.d.ts +2 -2
- package/dist/schemas/settings-schemas.d.ts +196 -0
- package/dist/schemas/settings-schemas.js +146 -5
- package/dist/schemas/settings-schemas.js.map +1 -1
- package/dist/sdk/index.d.ts +118 -2
- package/dist/sdk/index.js +146 -4
- package/dist/sdk/index.js.map +1 -1
- package/dist/sdk/testing.d.ts +182 -0
- package/dist/sdk/testing.js +231 -0
- package/dist/sdk/testing.js.map +1 -1
- package/dist/sdk/version.d.ts +114 -15
- package/dist/sdk/version.js +137 -15
- package/dist/sdk/version.js.map +1 -1
- package/dist/tools/bash.js +54 -9
- package/dist/tools/bash.js.map +1 -1
- package/dist/tools/registry.d.ts +146 -0
- package/dist/tools/registry.js +170 -0
- package/dist/tools/registry.js.map +1 -0
- package/dist/tools/search.js +12 -2
- package/dist/tools/search.js.map +1 -1
- package/dist/tools/text-editor.js +84 -26
- package/dist/tools/text-editor.js.map +1 -1
- package/dist/ui/components/chat-history.js +6 -1
- package/dist/ui/components/chat-history.js.map +1 -1
- package/dist/ui/components/chat-input.d.ts +2 -1
- package/dist/ui/components/chat-input.js +5 -2
- package/dist/ui/components/chat-input.js.map +1 -1
- package/dist/ui/components/chat-interface.js +187 -5
- package/dist/ui/components/chat-interface.js.map +1 -1
- package/dist/ui/components/context-breakdown.d.ts +23 -0
- package/dist/ui/components/context-breakdown.js +124 -0
- package/dist/ui/components/context-breakdown.js.map +1 -0
- package/dist/ui/components/keyboard-help.d.ts +17 -0
- package/dist/ui/components/keyboard-help.js +116 -0
- package/dist/ui/components/keyboard-help.js.map +1 -0
- package/dist/ui/components/keyboard-hints.js +2 -2
- package/dist/ui/components/keyboard-hints.js.map +1 -1
- package/dist/ui/components/quick-actions.js +43 -7
- package/dist/ui/components/quick-actions.js.map +1 -1
- package/dist/ui/components/status-bar.d.ts +3 -0
- package/dist/ui/components/status-bar.js +25 -16
- package/dist/ui/components/status-bar.js.map +1 -1
- package/dist/ui/components/toast-notification.d.ts +42 -0
- package/dist/ui/components/toast-notification.js +30 -2
- package/dist/ui/components/toast-notification.js.map +1 -1
- package/dist/ui/components/tool-group-display.js +34 -4
- package/dist/ui/components/tool-group-display.js.map +1 -1
- package/dist/ui/components/welcome-panel.js +2 -2
- package/dist/ui/components/welcome-panel.js.map +1 -1
- package/dist/ui/hooks/use-enhanced-input.d.ts +9 -1
- package/dist/ui/hooks/use-enhanced-input.js +486 -41
- package/dist/ui/hooks/use-enhanced-input.js.map +1 -1
- package/dist/ui/hooks/use-input-handler.d.ts +11 -1
- package/dist/ui/hooks/use-input-handler.js +67 -3
- package/dist/ui/hooks/use-input-handler.js.map +1 -1
- package/dist/ui/hooks/use-input-history.d.ts +1 -1
- package/dist/ui/hooks/use-input-history.js +50 -14
- package/dist/ui/hooks/use-input-history.js.map +1 -1
- package/dist/ui/utils/bracketed-paste-handler.d.ts +97 -0
- package/dist/ui/utils/bracketed-paste-handler.js +322 -0
- package/dist/ui/utils/bracketed-paste-handler.js.map +1 -0
- package/dist/ui/utils/change-summarizer.js +16 -6
- package/dist/ui/utils/change-summarizer.js.map +1 -1
- package/dist/ui/utils/tool-grouper.d.ts +10 -1
- package/dist/ui/utils/tool-grouper.js +143 -30
- package/dist/ui/utils/tool-grouper.js.map +1 -1
- package/dist/utils/auto-accept-logger.d.ts +173 -0
- package/dist/utils/auto-accept-logger.js +420 -0
- package/dist/utils/auto-accept-logger.js.map +1 -0
- package/dist/utils/background-task-manager.d.ts +11 -0
- package/dist/utils/background-task-manager.js +124 -38
- package/dist/utils/background-task-manager.js.map +1 -1
- package/dist/utils/confirmation-service.d.ts +1 -0
- package/dist/utils/confirmation-service.js +6 -1
- package/dist/utils/confirmation-service.js.map +1 -1
- package/dist/utils/encryption.d.ts +8 -0
- package/dist/utils/encryption.js +44 -27
- package/dist/utils/encryption.js.map +1 -1
- package/dist/utils/enhanced-error-messages.d.ts +33 -0
- package/dist/utils/enhanced-error-messages.js +420 -0
- package/dist/utils/enhanced-error-messages.js.map +1 -0
- package/dist/utils/error-handler.d.ts +13 -3
- package/dist/utils/error-handler.js +16 -4
- package/dist/utils/error-handler.js.map +1 -1
- package/dist/utils/external-editor.d.ts +47 -0
- package/dist/utils/external-editor.js +179 -0
- package/dist/utils/external-editor.js.map +1 -0
- package/dist/utils/history-migration.d.ts +9 -0
- package/dist/utils/history-migration.js +36 -0
- package/dist/utils/history-migration.js.map +1 -0
- package/dist/utils/paste-utils.js +12 -11
- package/dist/utils/paste-utils.js.map +1 -1
- package/dist/utils/rate-limiter.js +7 -0
- package/dist/utils/rate-limiter.js.map +1 -1
- package/dist/utils/safety-rules.d.ts +64 -0
- package/dist/utils/safety-rules.js +225 -0
- package/dist/utils/safety-rules.js.map +1 -0
- package/dist/utils/settings-manager.d.ts +89 -1
- package/dist/utils/settings-manager.js +359 -3
- package/dist/utils/settings-manager.js.map +1 -1
- package/dist/utils/token-counter.d.ts +2 -0
- package/dist/utils/token-counter.js +17 -4
- package/dist/utils/token-counter.js.map +1 -1
- package/dist/utils/version.d.ts +11 -2
- package/dist/utils/version.js +54 -21
- package/dist/utils/version.js.map +1 -1
- package/package.json +2 -1
|
@@ -2,18 +2,107 @@ import { useState, useCallback, useRef, useEffect } from "react";
|
|
|
2
2
|
import { deleteCharBefore, deleteCharAfter, deleteWordBefore, deleteWordAfter, insertText, moveToLineStart, moveToPreviousWord, moveToNextWord, } from "../../utils/text-utils.js";
|
|
3
3
|
import { useInputHistory } from "./use-input-history.js";
|
|
4
4
|
import { PasteDetector, shouldCollapsePaste, createPastedBlock, generatePlaceholder, findBlockAtCursor, expandAllPlaceholders, } from "../../utils/paste-utils.js";
|
|
5
|
-
|
|
5
|
+
import { getSettingsManager } from "../../utils/settings-manager.js";
|
|
6
|
+
import { BracketedPasteHandler } from "../utils/bracketed-paste-handler.js";
|
|
7
|
+
/**
|
|
8
|
+
* Check if input appears incomplete and should not be submitted
|
|
9
|
+
* Used for smart mode to auto-insert newlines for incomplete input
|
|
10
|
+
*/
|
|
11
|
+
function isIncompleteInput(text, smartDetection) {
|
|
12
|
+
if (!smartDetection?.enabled)
|
|
13
|
+
return false;
|
|
14
|
+
const trimmed = text.trimEnd();
|
|
15
|
+
if (!trimmed)
|
|
16
|
+
return false;
|
|
17
|
+
// Check for unclosed brackets
|
|
18
|
+
if (smartDetection.checkBrackets) {
|
|
19
|
+
const brackets = {
|
|
20
|
+
'(': 0,
|
|
21
|
+
'[': 0,
|
|
22
|
+
'{': 0,
|
|
23
|
+
};
|
|
24
|
+
for (const char of trimmed) {
|
|
25
|
+
if (char === '(')
|
|
26
|
+
brackets['(']++;
|
|
27
|
+
else if (char === ')')
|
|
28
|
+
brackets['(']--;
|
|
29
|
+
else if (char === '[')
|
|
30
|
+
brackets['[']++;
|
|
31
|
+
else if (char === ']')
|
|
32
|
+
brackets['[']--;
|
|
33
|
+
else if (char === '{')
|
|
34
|
+
brackets['{']++;
|
|
35
|
+
else if (char === '}')
|
|
36
|
+
brackets['{']--;
|
|
37
|
+
}
|
|
38
|
+
// If any bracket is unclosed, input is incomplete
|
|
39
|
+
if (brackets['('] > 0 || brackets['['] > 0 || brackets['{'] > 0) {
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
// Check for trailing operators
|
|
44
|
+
if (smartDetection.checkOperators) {
|
|
45
|
+
const trailingOperators = [
|
|
46
|
+
'+', '-', '*', '/', '%', '=', '==', '===', '!=', '!==',
|
|
47
|
+
'<', '>', '<=', '>=', '&&', '||', '&', '|', '^',
|
|
48
|
+
'?', ':', ',', '.', '..', '...', '=>',
|
|
49
|
+
];
|
|
50
|
+
for (const op of trailingOperators) {
|
|
51
|
+
if (trimmed.endsWith(op)) {
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// Check for incomplete statements
|
|
57
|
+
if (smartDetection.checkStatements) {
|
|
58
|
+
const incompleteKeywords = [
|
|
59
|
+
'if', 'else', 'for', 'while', 'do', 'switch', 'case',
|
|
60
|
+
'function', 'const', 'let', 'var', 'class', 'interface',
|
|
61
|
+
'type', 'enum', 'import', 'export', 'return', 'throw',
|
|
62
|
+
'try', 'catch', 'finally', 'async', 'await',
|
|
63
|
+
];
|
|
64
|
+
// Check if line ends with a statement keyword (potentially incomplete)
|
|
65
|
+
const lastLine = trimmed.split('\n').pop() || '';
|
|
66
|
+
const words = lastLine.trim().split(/\s+/);
|
|
67
|
+
const lastWord = words[words.length - 1];
|
|
68
|
+
if (incompleteKeywords.includes(lastWord)) {
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
// Check for statement keywords at start of last line without closing
|
|
72
|
+
const firstWord = words[0];
|
|
73
|
+
if (incompleteKeywords.includes(firstWord) && !lastLine.includes('{') && !lastLine.includes(';')) {
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
export function useEnhancedInput({ onSubmit, onEscape, onSpecialKey, onVerboseToggle, onQuickActions, onBackgroundModeToggle, onCopyLastResponse, onAutoAcceptToggle, onThinkingModeToggle, onExternalEditor, onLargePaste, onPasteTruncated, onKeyboardHelp, projectDir, disabled = false, multiline = false, } = {}) {
|
|
6
80
|
const [input, setInputState] = useState("");
|
|
7
81
|
const [cursorPosition, setCursorPositionState] = useState(0);
|
|
8
82
|
const [pastedBlocks, setPastedBlocks] = useState([]);
|
|
9
|
-
|
|
83
|
+
// BUG FIX: Use ref instead of state to prevent race conditions with concurrent pastes
|
|
84
|
+
const pasteCounterRef = useRef(0);
|
|
10
85
|
const [currentBlockAtCursor, setCurrentBlockAtCursor] = useState(null);
|
|
86
|
+
const [isPasting, setIsPasting] = useState(false); // v3.8.0: Track paste accumulation state
|
|
87
|
+
// Load input configuration from settings (Phase 1) // FIX: Use RequiredInputSettings type to match getInputConfig() return type
|
|
88
|
+
const [inputConfig] = useState(() => {
|
|
89
|
+
return getSettingsManager().getInputConfig();
|
|
90
|
+
});
|
|
91
|
+
// Load paste configuration from settings (v3.8.0)
|
|
92
|
+
const [pasteConfig] = useState(() => {
|
|
93
|
+
return getSettingsManager().getPasteSettings();
|
|
94
|
+
});
|
|
11
95
|
const isMultilineRef = useRef(multiline);
|
|
12
96
|
const pasteDetectorRef = useRef(new PasteDetector());
|
|
13
97
|
const pasteTimeoutRef = useRef(null);
|
|
98
|
+
const bracketedPasteHandlerRef = useRef(new BracketedPasteHandler()); // v3.8.0: Bracketed paste mode handler
|
|
99
|
+
// v3.8.0: Fallback paste accumulation buffer (for terminals that send paste in chunks)
|
|
100
|
+
const fallbackPasteBufferRef = useRef('');
|
|
101
|
+
const fallbackPasteTimeoutRef = useRef(null);
|
|
102
|
+
const fallbackPasteLastChunkTimeRef = useRef(0); // Track when last chunk arrived
|
|
14
103
|
// Keep ref in sync with prop to avoid stale closure
|
|
15
104
|
isMultilineRef.current = multiline;
|
|
16
|
-
const { addToHistory, navigateHistory, resetHistory, setOriginalInput, isNavigatingHistory, } = useInputHistory();
|
|
105
|
+
const { addToHistory, navigateHistory, resetHistory, setOriginalInput, isNavigatingHistory, } = useInputHistory(projectDir);
|
|
17
106
|
const setInput = useCallback((text) => {
|
|
18
107
|
setInputState(text);
|
|
19
108
|
// Use functional update to get the current cursor position, avoiding stale closure
|
|
@@ -39,8 +128,17 @@ export function useEnhancedInput({ onSubmit, onEscape, onSpecialKey, onVerboseTo
|
|
|
39
128
|
setCursorPositionState(0);
|
|
40
129
|
setOriginalInput("");
|
|
41
130
|
setPastedBlocks([]);
|
|
42
|
-
|
|
131
|
+
pasteCounterRef.current = 0; // BUG FIX: Reset counter ref
|
|
132
|
+
setIsPasting(false); // v3.8.0: Reset paste state
|
|
43
133
|
pasteDetectorRef.current.reset();
|
|
134
|
+
bracketedPasteHandlerRef.current.reset(); // v3.8.0: Reset bracketed paste handler
|
|
135
|
+
// v3.8.0: Clear fallback paste buffer and timeout
|
|
136
|
+
fallbackPasteBufferRef.current = '';
|
|
137
|
+
fallbackPasteLastChunkTimeRef.current = 0;
|
|
138
|
+
if (fallbackPasteTimeoutRef.current) {
|
|
139
|
+
clearTimeout(fallbackPasteTimeoutRef.current);
|
|
140
|
+
fallbackPasteTimeoutRef.current = null;
|
|
141
|
+
}
|
|
44
142
|
}, [setOriginalInput]);
|
|
45
143
|
const insertAtCursor = useCallback((text) => {
|
|
46
144
|
const result = insertText(input, cursorPosition, text);
|
|
@@ -48,29 +146,44 @@ export function useEnhancedInput({ onSubmit, onEscape, onSpecialKey, onVerboseTo
|
|
|
48
146
|
setCursorPositionState(result.position);
|
|
49
147
|
setOriginalInput(result.text);
|
|
50
148
|
}, [input, cursorPosition, setOriginalInput]);
|
|
51
|
-
// Handle paste completion
|
|
149
|
+
// Handle paste completion
|
|
150
|
+
// Note: No timeout or accumulation needed - Ink batches the entire paste for us
|
|
151
|
+
// BUG FIX: Create refs to track latest values without causing re-renders
|
|
152
|
+
const inputRef = useRef(input);
|
|
153
|
+
const cursorPositionRef = useRef(cursorPosition);
|
|
154
|
+
// Keep refs in sync with state
|
|
155
|
+
useEffect(() => {
|
|
156
|
+
inputRef.current = input;
|
|
157
|
+
cursorPositionRef.current = cursorPosition;
|
|
158
|
+
}, [input, cursorPosition]);
|
|
52
159
|
const handlePasteComplete = useCallback((pastedContent) => {
|
|
53
|
-
//
|
|
160
|
+
// BUG FIX: Use refs to get CURRENT values, avoiding stale closures
|
|
161
|
+
const currentInput = inputRef.current;
|
|
162
|
+
const currentCursor = cursorPositionRef.current;
|
|
163
|
+
// Preserve all formatting - no trimming or normalization
|
|
164
|
+
// This ensures JSON indentation, newlines, and whitespace are intact
|
|
165
|
+
// Check if should collapse based on line count or character count
|
|
54
166
|
if (shouldCollapsePaste(pastedContent)) {
|
|
55
|
-
//
|
|
56
|
-
const
|
|
57
|
-
|
|
167
|
+
// BUG FIX: Use ref and increment immediately to prevent race conditions
|
|
168
|
+
const blockId = pasteCounterRef.current++;
|
|
169
|
+
// Create pasted block with CURRENT cursor position
|
|
170
|
+
const block = createPastedBlock(blockId, pastedContent, currentCursor);
|
|
58
171
|
setPastedBlocks(prev => [...prev, block]);
|
|
59
172
|
// Insert placeholder instead of full content
|
|
60
173
|
const placeholder = generatePlaceholder(block);
|
|
61
|
-
const result = insertText(
|
|
174
|
+
const result = insertText(currentInput, currentCursor, placeholder);
|
|
62
175
|
setInputState(result.text);
|
|
63
176
|
setCursorPositionState(result.position);
|
|
64
177
|
setOriginalInput(result.text);
|
|
65
178
|
}
|
|
66
179
|
else {
|
|
67
|
-
// Insert normally (below threshold)
|
|
68
|
-
const result = insertText(
|
|
180
|
+
// Insert normally (below threshold) with all formatting preserved
|
|
181
|
+
const result = insertText(currentInput, currentCursor, pastedContent);
|
|
69
182
|
setInputState(result.text);
|
|
70
183
|
setCursorPositionState(result.position);
|
|
71
184
|
setOriginalInput(result.text);
|
|
72
185
|
}
|
|
73
|
-
}, [
|
|
186
|
+
}, [setOriginalInput]);
|
|
74
187
|
// Toggle collapse/expand for block at cursor
|
|
75
188
|
const toggleBlockAtCursor = useCallback(() => {
|
|
76
189
|
const block = findBlockAtCursor(input, cursorPosition, pastedBlocks);
|
|
@@ -142,6 +255,27 @@ export function useEnhancedInput({ onSubmit, onEscape, onSpecialKey, onVerboseTo
|
|
|
142
255
|
return expandAllPlaceholders(text, pastedBlocks);
|
|
143
256
|
}, [pastedBlocks]);
|
|
144
257
|
const handleSubmit = useCallback(() => {
|
|
258
|
+
// BUG FIX: Check for pending paste accumulation before submitting
|
|
259
|
+
if (fallbackPasteBufferRef.current.length > 0) {
|
|
260
|
+
// There's accumulated paste content - flush it first
|
|
261
|
+
if (fallbackPasteTimeoutRef.current) {
|
|
262
|
+
clearTimeout(fallbackPasteTimeoutRef.current);
|
|
263
|
+
fallbackPasteTimeoutRef.current = null;
|
|
264
|
+
}
|
|
265
|
+
const accumulatedPaste = fallbackPasteBufferRef.current;
|
|
266
|
+
fallbackPasteBufferRef.current = '';
|
|
267
|
+
fallbackPasteLastChunkTimeRef.current = 0;
|
|
268
|
+
// Add accumulated paste to input before submitting
|
|
269
|
+
try {
|
|
270
|
+
handlePasteComplete(accumulatedPaste);
|
|
271
|
+
}
|
|
272
|
+
catch (error) {
|
|
273
|
+
console.error('Error flushing paste on submit:', error);
|
|
274
|
+
}
|
|
275
|
+
// Don't submit yet - let the paste complete first
|
|
276
|
+
// User can hit Enter again to submit
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
145
279
|
if (input.trim()) {
|
|
146
280
|
// Expand all placeholders before submission
|
|
147
281
|
const expandedInput = expandPlaceholdersForSubmit(input);
|
|
@@ -149,7 +283,7 @@ export function useEnhancedInput({ onSubmit, onEscape, onSpecialKey, onVerboseTo
|
|
|
149
283
|
onSubmit?.(expandedInput);
|
|
150
284
|
clearInput();
|
|
151
285
|
}
|
|
152
|
-
}, [input, addToHistory, onSubmit, clearInput, expandPlaceholdersForSubmit]);
|
|
286
|
+
}, [input, addToHistory, onSubmit, clearInput, expandPlaceholdersForSubmit, handlePasteComplete]);
|
|
153
287
|
const handleInput = useCallback((inputChar, key) => {
|
|
154
288
|
if (disabled)
|
|
155
289
|
return;
|
|
@@ -175,17 +309,53 @@ export function useEnhancedInput({ onSubmit, onEscape, onSpecialKey, onVerboseTo
|
|
|
175
309
|
onEscape?.();
|
|
176
310
|
return;
|
|
177
311
|
}
|
|
178
|
-
// Handle Enter/Return
|
|
312
|
+
// Handle Enter/Return - Phase 1: Configurable multi-line input
|
|
179
313
|
if (key.return) {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
314
|
+
const enterBehavior = inputConfig.enterBehavior || 'submit';
|
|
315
|
+
// Check if user pressed Shift+Enter (submit key in newline mode)
|
|
316
|
+
const isShiftEnter = key.shift && key.return;
|
|
317
|
+
if (enterBehavior === 'newline') {
|
|
318
|
+
// Newline mode: Enter inserts newline, Shift+Enter submits
|
|
319
|
+
if (isShiftEnter) {
|
|
320
|
+
handleSubmit();
|
|
321
|
+
}
|
|
322
|
+
else {
|
|
323
|
+
const result = insertText(input, cursorPosition, "\n");
|
|
324
|
+
setInputState(result.text);
|
|
325
|
+
setCursorPositionState(result.position);
|
|
326
|
+
setOriginalInput(result.text);
|
|
327
|
+
}
|
|
186
328
|
}
|
|
187
|
-
else {
|
|
188
|
-
|
|
329
|
+
else if (enterBehavior === 'submit') {
|
|
330
|
+
// Submit mode (legacy): Enter submits, Shift+Enter inserts newline
|
|
331
|
+
if (isShiftEnter) {
|
|
332
|
+
const result = insertText(input, cursorPosition, "\n");
|
|
333
|
+
setInputState(result.text);
|
|
334
|
+
setCursorPositionState(result.position);
|
|
335
|
+
setOriginalInput(result.text);
|
|
336
|
+
}
|
|
337
|
+
else {
|
|
338
|
+
handleSubmit();
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
else if (enterBehavior === 'smart') {
|
|
342
|
+
// Smart mode: Auto-detect incomplete input
|
|
343
|
+
// Shift+Enter always submits, otherwise check if input is incomplete
|
|
344
|
+
if (isShiftEnter) {
|
|
345
|
+
// Explicit submit with Shift+Enter
|
|
346
|
+
handleSubmit();
|
|
347
|
+
}
|
|
348
|
+
else if (isIncompleteInput(input, inputConfig.smartDetection)) {
|
|
349
|
+
// Input appears incomplete, insert newline
|
|
350
|
+
const result = insertText(input, cursorPosition, "\n");
|
|
351
|
+
setInputState(result.text);
|
|
352
|
+
setCursorPositionState(result.position);
|
|
353
|
+
setOriginalInput(result.text);
|
|
354
|
+
}
|
|
355
|
+
else {
|
|
356
|
+
// Input looks complete, submit
|
|
357
|
+
handleSubmit();
|
|
358
|
+
}
|
|
189
359
|
}
|
|
190
360
|
return;
|
|
191
361
|
}
|
|
@@ -341,46 +511,320 @@ export function useEnhancedInput({ onSubmit, onEscape, onSpecialKey, onVerboseTo
|
|
|
341
511
|
setOriginalInput("");
|
|
342
512
|
return;
|
|
343
513
|
}
|
|
514
|
+
// Handle Shift+Tab: Toggle auto-accept mode (Phase 2)
|
|
515
|
+
// Check for Shift+Tab combination
|
|
516
|
+
if (key.shift && key.tab) {
|
|
517
|
+
onAutoAcceptToggle?.();
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
// Handle Tab (alone): Toggle thinking mode (P2.4)
|
|
521
|
+
// Only if input is empty (to avoid interfering with autocomplete)
|
|
522
|
+
if (key.tab && !key.shift && !key.ctrl && !key.meta && input.length === 0) {
|
|
523
|
+
onThinkingModeToggle?.();
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
// Handle Ctrl+G: Open external editor (Phase 2)
|
|
527
|
+
// Check both key.ctrl with 'g' and raw ASCII code \x07 (Ctrl+G = ASCII 7)
|
|
528
|
+
if ((key.ctrl && inputChar === "g") || inputChar === "\x07") {
|
|
529
|
+
// Call async external editor handler
|
|
530
|
+
onExternalEditor?.(input).then((editedContent) => {
|
|
531
|
+
if (editedContent !== null) {
|
|
532
|
+
setInputState(editedContent);
|
|
533
|
+
setCursorPositionState(editedContent.length);
|
|
534
|
+
}
|
|
535
|
+
}).catch(() => {
|
|
536
|
+
// Ignore errors - user will see error in UI
|
|
537
|
+
});
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
// Handle ? key: Show keyboard shortcuts help (P1.4)
|
|
541
|
+
// Only trigger if input is empty (avoid interfering with normal typing)
|
|
542
|
+
if (inputChar === "?" && input.length === 0) {
|
|
543
|
+
onKeyboardHelp?.();
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
344
546
|
// Handle regular character input
|
|
345
547
|
if (inputChar && !key.ctrl && !key.meta) {
|
|
346
|
-
//
|
|
347
|
-
|
|
348
|
-
if
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
548
|
+
// v3.8.0: Bracketed Paste Mode Detection
|
|
549
|
+
// Uses industry-standard escape sequences for reliable paste detection
|
|
550
|
+
// Falls back to simple batched detection if not supported
|
|
551
|
+
const { enableBracketedPaste, enableFallback } = pasteConfig;
|
|
552
|
+
// Use bracketed paste handler if enabled
|
|
553
|
+
if (enableBracketedPaste) {
|
|
554
|
+
// BUG FIX: Check for orphaned content from timeout first
|
|
555
|
+
const orphanedContent = bracketedPasteHandlerRef.current.retrieveOrphanedContent();
|
|
556
|
+
if (orphanedContent) {
|
|
557
|
+
// Timeout occurred and we have accumulated content without end marker
|
|
558
|
+
// Process it as a paste
|
|
559
|
+
try {
|
|
560
|
+
handlePasteComplete(orphanedContent);
|
|
561
|
+
}
|
|
562
|
+
catch (error) {
|
|
563
|
+
console.error('Error handling orphaned paste:', error);
|
|
564
|
+
// Continue despite error - don't block current input
|
|
565
|
+
}
|
|
566
|
+
// Continue processing current input normally
|
|
567
|
+
}
|
|
568
|
+
const result = bracketedPasteHandlerRef.current.handleInput(inputChar);
|
|
569
|
+
// Update pasting state for visual indicator
|
|
570
|
+
if (result.isAccumulating !== isPasting) {
|
|
571
|
+
setIsPasting(result.isAccumulating);
|
|
572
|
+
}
|
|
573
|
+
// If still accumulating, don't process yet
|
|
574
|
+
if (result.isAccumulating) {
|
|
575
|
+
return;
|
|
352
576
|
}
|
|
353
|
-
//
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
const
|
|
358
|
-
|
|
359
|
-
|
|
577
|
+
// If paste detected and complete via bracketed paste mode
|
|
578
|
+
if (result.isPaste && result.content) {
|
|
579
|
+
const pastedContent = result.content;
|
|
580
|
+
// Phase 3: Large paste handling with truncation
|
|
581
|
+
const settingsManager = getSettingsManager();
|
|
582
|
+
const pasteSettings = settingsManager.getPasteSettings();
|
|
583
|
+
const { allowLargePaste, maxPasteLength, warningThreshold } = pasteSettings;
|
|
584
|
+
// Check if paste exceeds warning threshold
|
|
585
|
+
if (pastedContent.length >= warningThreshold) {
|
|
586
|
+
onLargePaste?.(pastedContent.length);
|
|
360
587
|
}
|
|
361
|
-
|
|
362
|
-
|
|
588
|
+
// Handle truncation if needed
|
|
589
|
+
let finalContent = pastedContent;
|
|
590
|
+
if (!allowLargePaste && pastedContent.length > maxPasteLength) {
|
|
591
|
+
// Truncate the paste
|
|
592
|
+
finalContent = pastedContent.slice(0, maxPasteLength);
|
|
593
|
+
// Notify about truncation
|
|
594
|
+
onPasteTruncated?.(pastedContent.length, maxPasteLength);
|
|
595
|
+
}
|
|
596
|
+
// Handle entire paste at once
|
|
597
|
+
handlePasteComplete(finalContent);
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
// Not detected via bracketed paste mode
|
|
601
|
+
// If fallback enabled AND looks like batched paste, accumulate chunks
|
|
602
|
+
if (enableFallback && result.content && result.content.length > 1) {
|
|
603
|
+
// BUG FIX: Accumulate chunks with timeout instead of immediate processing
|
|
604
|
+
// This prevents multiple blocks when paste arrives in chunks (SSH, tmux, etc)
|
|
605
|
+
const now = Date.now();
|
|
606
|
+
const isActiveAccumulation = fallbackPasteBufferRef.current.length > 0;
|
|
607
|
+
const timeSinceLastChunk = now - fallbackPasteLastChunkTimeRef.current;
|
|
608
|
+
// BUG FIX: Check buffer size to prevent overflow (100MB limit)
|
|
609
|
+
const MAX_BUFFER_SIZE = 100 * 1024 * 1024;
|
|
610
|
+
if (fallbackPasteBufferRef.current.length + result.content.length > MAX_BUFFER_SIZE) {
|
|
611
|
+
// Buffer overflow - process what we have immediately
|
|
612
|
+
const accumulatedContent = fallbackPasteBufferRef.current;
|
|
613
|
+
fallbackPasteBufferRef.current = '';
|
|
614
|
+
fallbackPasteLastChunkTimeRef.current = 0;
|
|
615
|
+
if (fallbackPasteTimeoutRef.current) {
|
|
616
|
+
clearTimeout(fallbackPasteTimeoutRef.current);
|
|
617
|
+
fallbackPasteTimeoutRef.current = null;
|
|
618
|
+
}
|
|
619
|
+
// Process accumulated content (truncated)
|
|
620
|
+
try {
|
|
621
|
+
handlePasteComplete(accumulatedContent);
|
|
622
|
+
}
|
|
623
|
+
catch (error) {
|
|
624
|
+
console.error('Error handling oversized paste:', error);
|
|
625
|
+
}
|
|
626
|
+
return;
|
|
627
|
+
}
|
|
628
|
+
// Add to accumulation buffer
|
|
629
|
+
fallbackPasteBufferRef.current += result.content;
|
|
630
|
+
fallbackPasteLastChunkTimeRef.current = now;
|
|
631
|
+
// Clear existing timeout
|
|
632
|
+
if (fallbackPasteTimeoutRef.current) {
|
|
633
|
+
clearTimeout(fallbackPasteTimeoutRef.current);
|
|
634
|
+
}
|
|
635
|
+
// Dynamic timeout: Use longer window if we're actively accumulating a paste
|
|
636
|
+
// Initial chunk: 200ms (fast for single-burst pastes)
|
|
637
|
+
// Subsequent chunks: 500ms (handles slow terminals/networks)
|
|
638
|
+
const timeoutMs = isActiveAccumulation && timeSinceLastChunk < 1000 ? 500 : 200;
|
|
639
|
+
// Set new timeout - if no more chunks arrive, process the paste
|
|
640
|
+
fallbackPasteTimeoutRef.current = setTimeout(() => {
|
|
641
|
+
// BUG FIX: Clear refs FIRST to prevent race conditions
|
|
642
|
+
const accumulatedContent = fallbackPasteBufferRef.current;
|
|
643
|
+
fallbackPasteBufferRef.current = '';
|
|
644
|
+
fallbackPasteLastChunkTimeRef.current = 0;
|
|
645
|
+
fallbackPasteTimeoutRef.current = null;
|
|
646
|
+
if (!accumulatedContent)
|
|
647
|
+
return;
|
|
648
|
+
const pastedContent = accumulatedContent.replace(/\r/g, '\n');
|
|
649
|
+
// Phase 3: Large paste handling with truncation
|
|
650
|
+
const settingsManager = getSettingsManager();
|
|
651
|
+
const pasteSettings = settingsManager.getPasteSettings();
|
|
652
|
+
const { allowLargePaste, maxPasteLength, warningThreshold } = pasteSettings;
|
|
653
|
+
// Check if paste exceeds warning threshold
|
|
654
|
+
if (pastedContent.length >= warningThreshold) {
|
|
655
|
+
onLargePaste?.(pastedContent.length);
|
|
656
|
+
}
|
|
657
|
+
// Handle truncation if needed
|
|
658
|
+
let finalContent = pastedContent;
|
|
659
|
+
if (!allowLargePaste && pastedContent.length > maxPasteLength) {
|
|
660
|
+
// Truncate the paste
|
|
661
|
+
finalContent = pastedContent.slice(0, maxPasteLength);
|
|
662
|
+
// Notify about truncation
|
|
663
|
+
onPasteTruncated?.(pastedContent.length, maxPasteLength);
|
|
664
|
+
}
|
|
665
|
+
// Handle entire accumulated paste at once
|
|
666
|
+
// Note: handlePasteComplete will use CURRENT input/cursor values from React state
|
|
667
|
+
try {
|
|
668
|
+
handlePasteComplete(finalContent);
|
|
669
|
+
}
|
|
670
|
+
catch (error) {
|
|
671
|
+
// BUG FIX: Catch errors to prevent timeout callback crash
|
|
672
|
+
console.error('Error handling paste:', error);
|
|
673
|
+
// Don't attempt fallback - state might be inconsistent
|
|
674
|
+
// The paste content is lost but system remains stable
|
|
675
|
+
}
|
|
676
|
+
}, timeoutMs); // Dynamic timeout: 200ms initial, 500ms for active accumulation
|
|
677
|
+
return;
|
|
678
|
+
}
|
|
679
|
+
// Normal single character input
|
|
680
|
+
if (result.content) {
|
|
681
|
+
// BUG FIX: If there's a pending paste accumulation, add character to it
|
|
682
|
+
// This prevents splitting user input into paste + character
|
|
683
|
+
if (fallbackPasteBufferRef.current.length > 0) {
|
|
684
|
+
// Add single character to accumulation buffer
|
|
685
|
+
fallbackPasteBufferRef.current += result.content;
|
|
686
|
+
// Extend timeout slightly (paste likely complete, but give it 100ms more)
|
|
687
|
+
if (fallbackPasteTimeoutRef.current) {
|
|
688
|
+
clearTimeout(fallbackPasteTimeoutRef.current);
|
|
689
|
+
}
|
|
690
|
+
fallbackPasteTimeoutRef.current = setTimeout(() => {
|
|
691
|
+
const accumulatedContent = fallbackPasteBufferRef.current;
|
|
692
|
+
fallbackPasteBufferRef.current = '';
|
|
693
|
+
fallbackPasteTimeoutRef.current = null;
|
|
694
|
+
fallbackPasteLastChunkTimeRef.current = 0;
|
|
695
|
+
if (accumulatedContent) {
|
|
696
|
+
handlePasteComplete(accumulatedContent);
|
|
697
|
+
}
|
|
698
|
+
}, 100); // Short timeout for single char after paste
|
|
699
|
+
return;
|
|
700
|
+
}
|
|
701
|
+
// No pending paste - process normal input
|
|
702
|
+
const result2 = insertText(input, cursorPosition, result.content);
|
|
703
|
+
setInputState(result2.text);
|
|
704
|
+
setCursorPositionState(result2.position);
|
|
705
|
+
setOriginalInput(result2.text);
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
else if (enableFallback) {
|
|
709
|
+
// Fallback: Simple batched detection (legacy behavior when bracketed paste disabled)
|
|
710
|
+
// When inputChar.length > 1, it's likely pasted content
|
|
711
|
+
const isPaste = inputChar.length > 1;
|
|
712
|
+
if (isPaste) {
|
|
713
|
+
// BUG FIX: Use same dynamic accumulation as bracketed paste fallback
|
|
714
|
+
// This ensures consistent behavior regardless of bracketed paste support
|
|
715
|
+
const now = Date.now();
|
|
716
|
+
const isActiveAccumulation = fallbackPasteBufferRef.current.length > 0;
|
|
717
|
+
const timeSinceLastChunk = now - fallbackPasteLastChunkTimeRef.current;
|
|
718
|
+
// Normalize line endings: convert \r to \n
|
|
719
|
+
const normalizedInput = inputChar.replace(/\r/g, '\n');
|
|
720
|
+
// BUG FIX: Check buffer size to prevent overflow (100MB limit)
|
|
721
|
+
const MAX_BUFFER_SIZE = 100 * 1024 * 1024;
|
|
722
|
+
if (fallbackPasteBufferRef.current.length + normalizedInput.length > MAX_BUFFER_SIZE) {
|
|
723
|
+
// Buffer overflow - process what we have immediately
|
|
724
|
+
const accumulatedContent = fallbackPasteBufferRef.current;
|
|
725
|
+
fallbackPasteBufferRef.current = '';
|
|
726
|
+
fallbackPasteLastChunkTimeRef.current = 0;
|
|
727
|
+
if (fallbackPasteTimeoutRef.current) {
|
|
728
|
+
clearTimeout(fallbackPasteTimeoutRef.current);
|
|
729
|
+
fallbackPasteTimeoutRef.current = null;
|
|
730
|
+
}
|
|
731
|
+
// Process accumulated content (truncated)
|
|
732
|
+
try {
|
|
733
|
+
handlePasteComplete(accumulatedContent);
|
|
734
|
+
}
|
|
735
|
+
catch (error) {
|
|
736
|
+
console.error('Error handling oversized paste:', error);
|
|
737
|
+
}
|
|
738
|
+
return;
|
|
739
|
+
}
|
|
740
|
+
// Add to accumulation buffer
|
|
741
|
+
fallbackPasteBufferRef.current += normalizedInput;
|
|
742
|
+
fallbackPasteLastChunkTimeRef.current = now;
|
|
743
|
+
// Clear existing timeout
|
|
744
|
+
if (fallbackPasteTimeoutRef.current) {
|
|
745
|
+
clearTimeout(fallbackPasteTimeoutRef.current);
|
|
746
|
+
}
|
|
747
|
+
// Dynamic timeout: Use longer window if we're actively accumulating a paste
|
|
748
|
+
const timeoutMs = isActiveAccumulation && timeSinceLastChunk < 1000 ? 500 : 200;
|
|
749
|
+
// Set new timeout - if no more chunks arrive, process the paste
|
|
750
|
+
fallbackPasteTimeoutRef.current = setTimeout(() => {
|
|
751
|
+
// BUG FIX: Clear refs FIRST to prevent race conditions
|
|
752
|
+
const accumulatedContent = fallbackPasteBufferRef.current;
|
|
753
|
+
fallbackPasteBufferRef.current = '';
|
|
754
|
+
fallbackPasteLastChunkTimeRef.current = 0;
|
|
755
|
+
fallbackPasteTimeoutRef.current = null;
|
|
756
|
+
if (!accumulatedContent)
|
|
757
|
+
return;
|
|
758
|
+
// Phase 3: Large paste handling with truncation
|
|
759
|
+
const settingsManager = getSettingsManager();
|
|
760
|
+
const pasteSettings = settingsManager.getPasteSettings();
|
|
761
|
+
const { allowLargePaste, maxPasteLength, warningThreshold } = pasteSettings;
|
|
762
|
+
// Check if paste exceeds warning threshold
|
|
763
|
+
if (accumulatedContent.length >= warningThreshold) {
|
|
764
|
+
onLargePaste?.(accumulatedContent.length);
|
|
765
|
+
}
|
|
766
|
+
// Handle truncation if needed
|
|
767
|
+
let finalContent = accumulatedContent;
|
|
768
|
+
if (!allowLargePaste && accumulatedContent.length > maxPasteLength) {
|
|
769
|
+
// Truncate the paste
|
|
770
|
+
finalContent = accumulatedContent.slice(0, maxPasteLength);
|
|
771
|
+
// Notify about truncation
|
|
772
|
+
onPasteTruncated?.(accumulatedContent.length, maxPasteLength);
|
|
773
|
+
}
|
|
774
|
+
// Handle entire accumulated paste at once
|
|
775
|
+
// Note: handlePasteComplete will use CURRENT input/cursor values from React state
|
|
776
|
+
try {
|
|
777
|
+
handlePasteComplete(finalContent);
|
|
778
|
+
}
|
|
779
|
+
catch (error) {
|
|
780
|
+
// BUG FIX: Catch errors to prevent timeout callback crash
|
|
781
|
+
console.error('Error handling paste:', error);
|
|
782
|
+
// Don't attempt fallback - state might be inconsistent
|
|
783
|
+
// The paste content is lost but system remains stable
|
|
784
|
+
}
|
|
785
|
+
}, timeoutMs);
|
|
786
|
+
return;
|
|
787
|
+
}
|
|
788
|
+
else {
|
|
789
|
+
// Normal single character input
|
|
790
|
+
const result = insertText(input, cursorPosition, inputChar);
|
|
791
|
+
setInputState(result.text);
|
|
792
|
+
setCursorPositionState(result.position);
|
|
793
|
+
setOriginalInput(result.text);
|
|
794
|
+
}
|
|
363
795
|
}
|
|
364
796
|
else {
|
|
365
|
-
//
|
|
797
|
+
// Both bracketed paste and fallback disabled - normal input only
|
|
366
798
|
const result = insertText(input, cursorPosition, inputChar);
|
|
367
799
|
setInputState(result.text);
|
|
368
800
|
setCursorPositionState(result.position);
|
|
369
801
|
setOriginalInput(result.text);
|
|
370
802
|
}
|
|
371
803
|
}
|
|
372
|
-
}, [disabled, onSpecialKey, onVerboseToggle, onQuickActions, onBackgroundModeToggle, onCopyLastResponse, input, cursorPosition, multiline, handleSubmit, navigateHistory, setOriginalInput, toggleBlockAtCursor, handlePasteComplete]);
|
|
804
|
+
}, [disabled, onSpecialKey, onVerboseToggle, onQuickActions, onBackgroundModeToggle, onCopyLastResponse, onAutoAcceptToggle, onThinkingModeToggle, onKeyboardHelp, onLargePaste, onPasteTruncated, input, cursorPosition, multiline, handleSubmit, navigateHistory, setOriginalInput, toggleBlockAtCursor, handlePasteComplete, pasteConfig, isPasting]);
|
|
373
805
|
// Update current block at cursor when cursor position or input changes
|
|
374
806
|
useEffect(() => {
|
|
375
807
|
const block = findBlockAtCursor(input, cursorPosition, pastedBlocks);
|
|
376
808
|
setCurrentBlockAtCursor(block);
|
|
377
809
|
}, [input, cursorPosition, pastedBlocks]);
|
|
378
|
-
//
|
|
810
|
+
// BUG FIX: Comprehensive cleanup on unmount
|
|
379
811
|
useEffect(() => {
|
|
380
812
|
return () => {
|
|
813
|
+
// Clear all timeouts
|
|
381
814
|
if (pasteTimeoutRef.current) {
|
|
382
815
|
clearTimeout(pasteTimeoutRef.current);
|
|
816
|
+
pasteTimeoutRef.current = null;
|
|
817
|
+
}
|
|
818
|
+
if (fallbackPasteTimeoutRef.current) {
|
|
819
|
+
clearTimeout(fallbackPasteTimeoutRef.current);
|
|
820
|
+
fallbackPasteTimeoutRef.current = null;
|
|
383
821
|
}
|
|
822
|
+
// Reset all detectors and handlers
|
|
823
|
+
pasteDetectorRef.current.reset();
|
|
824
|
+
bracketedPasteHandlerRef.current.dispose();
|
|
825
|
+
// Clear buffers
|
|
826
|
+
fallbackPasteBufferRef.current = '';
|
|
827
|
+
fallbackPasteLastChunkTimeRef.current = 0;
|
|
384
828
|
};
|
|
385
829
|
}, []);
|
|
386
830
|
return {
|
|
@@ -389,6 +833,7 @@ export function useEnhancedInput({ onSubmit, onEscape, onSpecialKey, onVerboseTo
|
|
|
389
833
|
isMultiline: isMultilineRef.current,
|
|
390
834
|
pastedBlocks,
|
|
391
835
|
currentBlockAtCursor,
|
|
836
|
+
isPasting, // v3.8.0: Expose paste accumulation state for visual indicator
|
|
392
837
|
setInput,
|
|
393
838
|
setCursorPosition,
|
|
394
839
|
clearInput,
|