ccmanager 3.0.0 → 3.1.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/dist/components/ConfigureOther.js +26 -0
- package/dist/components/ConfigureTimeout.d.ts +9 -0
- package/dist/components/ConfigureTimeout.js +42 -0
- package/dist/services/__tests__/stateDetector.claude.test.js +0 -141
- package/dist/services/autoApprovalVerifier.js +12 -5
- package/dist/services/configurationManager.d.ts +1 -0
- package/dist/services/configurationManager.js +27 -6
- package/dist/services/sessionManager.statePersistence.test.js +3 -3
- package/dist/services/stateDetector.js +0 -14
- package/dist/types/index.d.ts +1 -0
- package/package.json +1 -1
|
@@ -4,6 +4,7 @@ import SelectInput from 'ink-select-input';
|
|
|
4
4
|
import { configurationManager } from '../services/configurationManager.js';
|
|
5
5
|
import { shortcutManager } from '../services/shortcutManager.js';
|
|
6
6
|
import ConfigureCustomCommand from './ConfigureCustomCommand.js';
|
|
7
|
+
import ConfigureTimeout from './ConfigureTimeout.js';
|
|
7
8
|
import CustomCommandSummary from './CustomCommandSummary.js';
|
|
8
9
|
const ConfigureOther = ({ onComplete }) => {
|
|
9
10
|
const autoApprovalConfig = configurationManager.getAutoApprovalConfig();
|
|
@@ -11,6 +12,8 @@ const ConfigureOther = ({ onComplete }) => {
|
|
|
11
12
|
const [autoApprovalEnabled, setAutoApprovalEnabled] = useState(autoApprovalConfig.enabled);
|
|
12
13
|
const [customCommand, setCustomCommand] = useState(autoApprovalConfig.customCommand ?? '');
|
|
13
14
|
const [customCommandDraft, setCustomCommandDraft] = useState(customCommand);
|
|
15
|
+
const [timeout, setTimeout] = useState(autoApprovalConfig.timeout ?? 30);
|
|
16
|
+
const [timeoutDraft, setTimeoutDraft] = useState(timeout);
|
|
14
17
|
useInput((input, key) => {
|
|
15
18
|
if (shortcutManager.matchesShortcut('cancel', input, key)) {
|
|
16
19
|
if (view === 'customCommand') {
|
|
@@ -18,6 +21,11 @@ const ConfigureOther = ({ onComplete }) => {
|
|
|
18
21
|
setView('main');
|
|
19
22
|
return;
|
|
20
23
|
}
|
|
24
|
+
if (view === 'timeout') {
|
|
25
|
+
setTimeoutDraft(timeout);
|
|
26
|
+
setView('main');
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
21
29
|
onComplete();
|
|
22
30
|
}
|
|
23
31
|
});
|
|
@@ -30,6 +38,10 @@ const ConfigureOther = ({ onComplete }) => {
|
|
|
30
38
|
label: '✏️ Edit Custom Command',
|
|
31
39
|
value: 'customCommand',
|
|
32
40
|
},
|
|
41
|
+
{
|
|
42
|
+
label: `⏱️ Set Timeout (${timeout}s)`,
|
|
43
|
+
value: 'timeout',
|
|
44
|
+
},
|
|
33
45
|
{
|
|
34
46
|
label: '💾 Save Changes',
|
|
35
47
|
value: 'save',
|
|
@@ -48,10 +60,15 @@ const ConfigureOther = ({ onComplete }) => {
|
|
|
48
60
|
setCustomCommandDraft(customCommand);
|
|
49
61
|
setView('customCommand');
|
|
50
62
|
break;
|
|
63
|
+
case 'timeout':
|
|
64
|
+
setTimeoutDraft(timeout);
|
|
65
|
+
setView('timeout');
|
|
66
|
+
break;
|
|
51
67
|
case 'save':
|
|
52
68
|
configurationManager.setAutoApprovalConfig({
|
|
53
69
|
enabled: autoApprovalEnabled,
|
|
54
70
|
customCommand: customCommand.trim() || undefined,
|
|
71
|
+
timeout,
|
|
55
72
|
});
|
|
56
73
|
onComplete();
|
|
57
74
|
break;
|
|
@@ -71,6 +88,15 @@ const ConfigureOther = ({ onComplete }) => {
|
|
|
71
88
|
setView('main');
|
|
72
89
|
} }));
|
|
73
90
|
}
|
|
91
|
+
if (view === 'timeout') {
|
|
92
|
+
return (React.createElement(ConfigureTimeout, { value: timeoutDraft, onChange: setTimeoutDraft, onCancel: () => {
|
|
93
|
+
setTimeoutDraft(timeout);
|
|
94
|
+
setView('main');
|
|
95
|
+
}, onSubmit: value => {
|
|
96
|
+
setTimeout(value);
|
|
97
|
+
setView('main');
|
|
98
|
+
} }));
|
|
99
|
+
}
|
|
74
100
|
return (React.createElement(Box, { flexDirection: "column" },
|
|
75
101
|
React.createElement(Box, { marginBottom: 1 },
|
|
76
102
|
React.createElement(Text, { bold: true, color: "green" }, "Other & Experimental Settings")),
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
interface ConfigureTimeoutProps {
|
|
3
|
+
value: number;
|
|
4
|
+
onChange: (value: number) => void;
|
|
5
|
+
onSubmit: (value: number) => void;
|
|
6
|
+
onCancel: () => void;
|
|
7
|
+
}
|
|
8
|
+
declare const ConfigureTimeout: React.FC<ConfigureTimeoutProps>;
|
|
9
|
+
export default ConfigureTimeout;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Box, Text, useInput } from 'ink';
|
|
3
|
+
import TextInputWrapper from './TextInputWrapper.js';
|
|
4
|
+
import { shortcutManager } from '../services/shortcutManager.js';
|
|
5
|
+
const ConfigureTimeout = ({ value, onChange, onSubmit, onCancel, }) => {
|
|
6
|
+
const [inputValue, setInputValue] = React.useState(String(value));
|
|
7
|
+
const handleChange = (newValue) => {
|
|
8
|
+
// Only allow numeric input
|
|
9
|
+
const filtered = newValue.replace(/[^0-9]/g, '');
|
|
10
|
+
setInputValue(filtered);
|
|
11
|
+
const parsed = parseInt(filtered, 10);
|
|
12
|
+
if (!isNaN(parsed) && parsed > 0) {
|
|
13
|
+
onChange(parsed);
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
const handleSubmit = () => {
|
|
17
|
+
const parsed = parseInt(inputValue, 10);
|
|
18
|
+
if (!isNaN(parsed) && parsed > 0) {
|
|
19
|
+
onSubmit(parsed);
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
useInput((input, key) => {
|
|
23
|
+
if (shortcutManager.matchesShortcut('cancel', input, key)) {
|
|
24
|
+
onCancel();
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
return (React.createElement(Box, { flexDirection: "column" },
|
|
28
|
+
React.createElement(Box, { marginBottom: 1 },
|
|
29
|
+
React.createElement(Text, { bold: true, color: "green" }, "Auto-Approval Timeout")),
|
|
30
|
+
React.createElement(Box, { marginBottom: 1 },
|
|
31
|
+
React.createElement(Text, null, "Enter timeout in seconds for auto-approval verification:")),
|
|
32
|
+
React.createElement(Box, { marginBottom: 1 },
|
|
33
|
+
React.createElement(TextInputWrapper, { value: inputValue, onChange: handleChange, onSubmit: handleSubmit, placeholder: "e.g. 30", focus: true })),
|
|
34
|
+
React.createElement(Box, { marginBottom: 1 },
|
|
35
|
+
React.createElement(Text, { dimColor: true }, "Must be a positive integer (minimum: 1 second, default: 30 seconds)")),
|
|
36
|
+
React.createElement(Box, null,
|
|
37
|
+
React.createElement(Text, { dimColor: true },
|
|
38
|
+
"Press Enter to save, ",
|
|
39
|
+
shortcutManager.getShortcutDisplay('cancel'),
|
|
40
|
+
" to go back"))));
|
|
41
|
+
};
|
|
42
|
+
export default ConfigureTimeout;
|
|
@@ -8,30 +8,6 @@ describe('ClaudeStateDetector', () => {
|
|
|
8
8
|
detector = new ClaudeStateDetector();
|
|
9
9
|
});
|
|
10
10
|
describe('detectState', () => {
|
|
11
|
-
it('should detect waiting_input when "Do you want" prompt is present', () => {
|
|
12
|
-
// Arrange
|
|
13
|
-
terminal = createMockTerminal([
|
|
14
|
-
'Some previous output',
|
|
15
|
-
'│ Do you want to continue? (y/n)',
|
|
16
|
-
'│ > ',
|
|
17
|
-
]);
|
|
18
|
-
// Act
|
|
19
|
-
const state = detector.detectState(terminal, 'idle');
|
|
20
|
-
// Assert
|
|
21
|
-
expect(state).toBe('waiting_input');
|
|
22
|
-
});
|
|
23
|
-
it('should detect waiting_input when "Would you like" prompt is present', () => {
|
|
24
|
-
// Arrange
|
|
25
|
-
terminal = createMockTerminal([
|
|
26
|
-
'Some output',
|
|
27
|
-
'│ Would you like to save changes?',
|
|
28
|
-
'│ > ',
|
|
29
|
-
]);
|
|
30
|
-
// Act
|
|
31
|
-
const state = detector.detectState(terminal, 'idle');
|
|
32
|
-
// Assert
|
|
33
|
-
expect(state).toBe('waiting_input');
|
|
34
|
-
});
|
|
35
11
|
it('should detect busy when "ESC to interrupt" is present', () => {
|
|
36
12
|
// Arrange
|
|
37
13
|
terminal = createMockTerminal([
|
|
@@ -74,37 +50,6 @@ describe('ClaudeStateDetector', () => {
|
|
|
74
50
|
// Assert
|
|
75
51
|
expect(state).toBe('idle');
|
|
76
52
|
});
|
|
77
|
-
it('should only consider last 30 lines', () => {
|
|
78
|
-
// Arrange
|
|
79
|
-
const lines = [];
|
|
80
|
-
// Add more than 30 lines
|
|
81
|
-
for (let i = 0; i < 40; i++) {
|
|
82
|
-
lines.push(`Line ${i}`);
|
|
83
|
-
}
|
|
84
|
-
// The "Do you want" should be outside the 30 line window
|
|
85
|
-
lines.push('│ Do you want to continue?');
|
|
86
|
-
// Add 30 more lines to push it out
|
|
87
|
-
for (let i = 0; i < 30; i++) {
|
|
88
|
-
lines.push(`Recent line ${i}`);
|
|
89
|
-
}
|
|
90
|
-
terminal = createMockTerminal(lines);
|
|
91
|
-
// Act
|
|
92
|
-
const state = detector.detectState(terminal, 'idle');
|
|
93
|
-
// Assert
|
|
94
|
-
expect(state).toBe('idle'); // Should not detect the old prompt
|
|
95
|
-
});
|
|
96
|
-
it('should prioritize waiting_input over busy state', () => {
|
|
97
|
-
// Arrange
|
|
98
|
-
terminal = createMockTerminal([
|
|
99
|
-
'Press ESC to interrupt',
|
|
100
|
-
'│ Do you want to continue?',
|
|
101
|
-
'│ > ',
|
|
102
|
-
]);
|
|
103
|
-
// Act
|
|
104
|
-
const state = detector.detectState(terminal, 'idle');
|
|
105
|
-
// Assert
|
|
106
|
-
expect(state).toBe('waiting_input'); // waiting_input should take precedence
|
|
107
|
-
});
|
|
108
53
|
it('should maintain current state when "ctrl+r to toggle" is present', () => {
|
|
109
54
|
// Arrange
|
|
110
55
|
terminal = createMockTerminal([
|
|
@@ -222,91 +167,5 @@ describe('ClaudeStateDetector', () => {
|
|
|
222
167
|
// Assert
|
|
223
168
|
expect(state).toBe('waiting_input');
|
|
224
169
|
});
|
|
225
|
-
it('should detect waiting_input when "enter to select" is present', () => {
|
|
226
|
-
// Arrange
|
|
227
|
-
terminal = createMockTerminal([
|
|
228
|
-
'Select an option:',
|
|
229
|
-
'',
|
|
230
|
-
'❯ Option 1',
|
|
231
|
-
' Option 2',
|
|
232
|
-
'',
|
|
233
|
-
'Enter to select',
|
|
234
|
-
]);
|
|
235
|
-
// Act
|
|
236
|
-
const state = detector.detectState(terminal, 'idle');
|
|
237
|
-
// Assert
|
|
238
|
-
expect(state).toBe('waiting_input');
|
|
239
|
-
});
|
|
240
|
-
it('should detect waiting_input when "tab/arrow keys to navigate" is present', () => {
|
|
241
|
-
// Arrange
|
|
242
|
-
terminal = createMockTerminal([
|
|
243
|
-
'Choose your action:',
|
|
244
|
-
'',
|
|
245
|
-
'❯ Continue',
|
|
246
|
-
' Skip',
|
|
247
|
-
'',
|
|
248
|
-
'Tab/arrow keys to navigate',
|
|
249
|
-
]);
|
|
250
|
-
// Act
|
|
251
|
-
const state = detector.detectState(terminal, 'idle');
|
|
252
|
-
// Assert
|
|
253
|
-
expect(state).toBe('waiting_input');
|
|
254
|
-
});
|
|
255
|
-
it('should detect waiting_input when "esc to cancel" is present', () => {
|
|
256
|
-
// Arrange
|
|
257
|
-
terminal = createMockTerminal([
|
|
258
|
-
'Interactive selection:',
|
|
259
|
-
'',
|
|
260
|
-
'❯ Yes',
|
|
261
|
-
' No',
|
|
262
|
-
'',
|
|
263
|
-
'Esc to cancel',
|
|
264
|
-
]);
|
|
265
|
-
// Act
|
|
266
|
-
const state = detector.detectState(terminal, 'idle');
|
|
267
|
-
// Assert
|
|
268
|
-
expect(state).toBe('waiting_input');
|
|
269
|
-
});
|
|
270
|
-
it('should detect waiting_input when "ready to submit your answers?" is present', () => {
|
|
271
|
-
// Arrange
|
|
272
|
-
terminal = createMockTerminal([
|
|
273
|
-
'Review your selections:',
|
|
274
|
-
'',
|
|
275
|
-
'Choice 1: Yes',
|
|
276
|
-
'Choice 2: No',
|
|
277
|
-
'',
|
|
278
|
-
'Ready to submit your answers?',
|
|
279
|
-
]);
|
|
280
|
-
// Act
|
|
281
|
-
const state = detector.detectState(terminal, 'idle');
|
|
282
|
-
// Assert
|
|
283
|
-
expect(state).toBe('waiting_input');
|
|
284
|
-
});
|
|
285
|
-
it('should detect waiting_input with mixed case interactive patterns', () => {
|
|
286
|
-
// Arrange
|
|
287
|
-
terminal = createMockTerminal([
|
|
288
|
-
'Select options:',
|
|
289
|
-
'',
|
|
290
|
-
'ENTER TO SELECT',
|
|
291
|
-
'TAB/ARROW KEYS TO NAVIGATE',
|
|
292
|
-
]);
|
|
293
|
-
// Act
|
|
294
|
-
const state = detector.detectState(terminal, 'idle');
|
|
295
|
-
// Assert
|
|
296
|
-
expect(state).toBe('waiting_input');
|
|
297
|
-
});
|
|
298
|
-
it('should prioritize interactive patterns over busy state', () => {
|
|
299
|
-
// Arrange
|
|
300
|
-
terminal = createMockTerminal([
|
|
301
|
-
'Press ESC to interrupt',
|
|
302
|
-
'',
|
|
303
|
-
'Select an option:',
|
|
304
|
-
'Enter to select',
|
|
305
|
-
]);
|
|
306
|
-
// Act
|
|
307
|
-
const state = detector.detectState(terminal, 'idle');
|
|
308
|
-
// Assert
|
|
309
|
-
expect(state).toBe('waiting_input'); // Interactive pattern should take precedence
|
|
310
|
-
});
|
|
311
170
|
});
|
|
312
171
|
});
|
|
@@ -3,7 +3,12 @@ import { ProcessError } from '../types/errors.js';
|
|
|
3
3
|
import { configurationManager } from './configurationManager.js';
|
|
4
4
|
import { logger } from '../utils/logger.js';
|
|
5
5
|
import { execFile, spawn, } from 'child_process';
|
|
6
|
-
const
|
|
6
|
+
const DEFAULT_TIMEOUT_SECONDS = 30;
|
|
7
|
+
const getTimeoutMs = () => {
|
|
8
|
+
const config = configurationManager.getAutoApprovalConfig();
|
|
9
|
+
const timeoutSeconds = config.timeout ?? DEFAULT_TIMEOUT_SECONDS;
|
|
10
|
+
return timeoutSeconds * 1000;
|
|
11
|
+
};
|
|
7
12
|
const createAbortError = () => {
|
|
8
13
|
const error = new Error('Auto-approval verification aborted');
|
|
9
14
|
error.name = 'AbortError';
|
|
@@ -80,15 +85,16 @@ export class AutoApprovalVerifier {
|
|
|
80
85
|
return;
|
|
81
86
|
signal.removeEventListener('abort', abortListener);
|
|
82
87
|
};
|
|
88
|
+
const timeoutMs = getTimeoutMs();
|
|
83
89
|
const timeoutId = setTimeout(() => {
|
|
84
90
|
settle(() => {
|
|
85
91
|
logger.warn('Auto-approval verification timed out, terminating helper Claude process');
|
|
86
92
|
if (child?.pid) {
|
|
87
93
|
child.kill('SIGKILL');
|
|
88
94
|
}
|
|
89
|
-
reject(new Error(
|
|
95
|
+
reject(new Error(`Auto-approval verification timed out after ${timeoutMs / 1000}s`));
|
|
90
96
|
});
|
|
91
|
-
},
|
|
97
|
+
}, timeoutMs);
|
|
92
98
|
if (signal) {
|
|
93
99
|
if (signal.aborted) {
|
|
94
100
|
abortListener();
|
|
@@ -166,13 +172,14 @@ export class AutoApprovalVerifier {
|
|
|
166
172
|
const child = spawn(command, [], spawnOptions);
|
|
167
173
|
let stdout = '';
|
|
168
174
|
let stderr = '';
|
|
175
|
+
const timeoutMs = getTimeoutMs();
|
|
169
176
|
timeoutId = setTimeout(() => {
|
|
170
177
|
logger.warn('Auto-approval custom command timed out, terminating process');
|
|
171
178
|
settle(() => {
|
|
172
179
|
child.kill('SIGKILL');
|
|
173
|
-
reject(new Error(
|
|
180
|
+
reject(new Error(`Auto-approval verification custom command timed out after ${timeoutMs / 1000}s`));
|
|
174
181
|
});
|
|
175
|
-
},
|
|
182
|
+
}, timeoutMs);
|
|
176
183
|
if (signal) {
|
|
177
184
|
if (signal.aborted) {
|
|
178
185
|
abortListener();
|
|
@@ -24,6 +24,7 @@ export declare class ConfigurationManager {
|
|
|
24
24
|
getAutoApprovalConfig(): NonNullable<ConfigurationData['autoApproval']>;
|
|
25
25
|
setAutoApprovalConfig(autoApproval: NonNullable<ConfigurationData['autoApproval']>): void;
|
|
26
26
|
setAutoApprovalEnabled(enabled: boolean): void;
|
|
27
|
+
setAutoApprovalTimeout(timeout: number): void;
|
|
27
28
|
getCommandConfig(): CommandConfig;
|
|
28
29
|
setCommandConfig(commandConfig: CommandConfig): void;
|
|
29
30
|
private migrateLegacyCommandToPresets;
|
|
@@ -102,10 +102,16 @@ export class ConfigurationManager {
|
|
|
102
102
|
if (!this.config.autoApproval) {
|
|
103
103
|
this.config.autoApproval = {
|
|
104
104
|
enabled: false,
|
|
105
|
+
timeout: 30,
|
|
105
106
|
};
|
|
106
107
|
}
|
|
107
|
-
else
|
|
108
|
-
this.config.autoApproval
|
|
108
|
+
else {
|
|
109
|
+
if (!Object.prototype.hasOwnProperty.call(this.config.autoApproval, 'enabled')) {
|
|
110
|
+
this.config.autoApproval.enabled = false;
|
|
111
|
+
}
|
|
112
|
+
if (!Object.prototype.hasOwnProperty.call(this.config.autoApproval, 'timeout')) {
|
|
113
|
+
this.config.autoApproval.timeout = 30;
|
|
114
|
+
}
|
|
109
115
|
}
|
|
110
116
|
// Migrate legacy command config to presets if needed
|
|
111
117
|
this.migrateLegacyCommandToPresets();
|
|
@@ -174,9 +180,14 @@ export class ConfigurationManager {
|
|
|
174
180
|
this.saveConfig();
|
|
175
181
|
}
|
|
176
182
|
getAutoApprovalConfig() {
|
|
177
|
-
|
|
183
|
+
const config = this.config.autoApproval || {
|
|
178
184
|
enabled: false,
|
|
179
|
-
}
|
|
185
|
+
};
|
|
186
|
+
// Default timeout to 30 seconds if not set
|
|
187
|
+
return {
|
|
188
|
+
...config,
|
|
189
|
+
timeout: config.timeout ?? 30,
|
|
190
|
+
};
|
|
180
191
|
}
|
|
181
192
|
setAutoApprovalConfig(autoApproval) {
|
|
182
193
|
this.config.autoApproval = autoApproval;
|
|
@@ -186,6 +197,10 @@ export class ConfigurationManager {
|
|
|
186
197
|
const currentConfig = this.getAutoApprovalConfig();
|
|
187
198
|
this.setAutoApprovalConfig({ ...currentConfig, enabled });
|
|
188
199
|
}
|
|
200
|
+
setAutoApprovalTimeout(timeout) {
|
|
201
|
+
const currentConfig = this.getAutoApprovalConfig();
|
|
202
|
+
this.setAutoApprovalConfig({ ...currentConfig, timeout });
|
|
203
|
+
}
|
|
189
204
|
getCommandConfig() {
|
|
190
205
|
// For backward compatibility, return the default preset as CommandConfig
|
|
191
206
|
const defaultPreset = this.getDefaultPreset();
|
|
@@ -535,10 +550,16 @@ export class ConfigurationManager {
|
|
|
535
550
|
if (!config.autoApproval) {
|
|
536
551
|
config.autoApproval = {
|
|
537
552
|
enabled: false,
|
|
553
|
+
timeout: 30,
|
|
538
554
|
};
|
|
539
555
|
}
|
|
540
|
-
else
|
|
541
|
-
config.autoApproval
|
|
556
|
+
else {
|
|
557
|
+
if (!Object.prototype.hasOwnProperty.call(config.autoApproval, 'enabled')) {
|
|
558
|
+
config.autoApproval.enabled = false;
|
|
559
|
+
}
|
|
560
|
+
if (!Object.prototype.hasOwnProperty.call(config.autoApproval, 'timeout')) {
|
|
561
|
+
config.autoApproval.timeout = 30;
|
|
562
|
+
}
|
|
542
563
|
}
|
|
543
564
|
return config;
|
|
544
565
|
}
|
|
@@ -127,7 +127,7 @@ describe('SessionManager - State Persistence', () => {
|
|
|
127
127
|
vi.advanceTimersByTime(STATE_CHECK_INTERVAL_MS * 2);
|
|
128
128
|
expect(session.pendingState).toBe('idle');
|
|
129
129
|
// Simulate output that would trigger waiting_input state
|
|
130
|
-
eventEmitter.emit('data', '
|
|
130
|
+
eventEmitter.emit('data', 'Do you want to continue?\n❯ 1. Yes');
|
|
131
131
|
// Advance time to trigger another check
|
|
132
132
|
vi.advanceTimersByTime(STATE_CHECK_INTERVAL_MS);
|
|
133
133
|
// Pending state should now be waiting_input, not idle
|
|
@@ -172,7 +172,7 @@ describe('SessionManager - State Persistence', () => {
|
|
|
172
172
|
expect(session.pendingState).toBe('idle');
|
|
173
173
|
// Now change to a different state before idle persists
|
|
174
174
|
// Clear terminal first and add waiting prompt
|
|
175
|
-
eventEmitter.emit('data', '\x1b[2J\x1b[
|
|
175
|
+
eventEmitter.emit('data', '\x1b[2J\x1b[HDo you want to continue?\n❯ 1. Yes');
|
|
176
176
|
// Advance time to detect new state but still less than persistence duration from first change
|
|
177
177
|
vi.advanceTimersByTime(STATE_CHECK_INTERVAL_MS); // Another 100ms, total 200ms exactly at threshold
|
|
178
178
|
// Pending state should have changed to waiting_input
|
|
@@ -210,7 +210,7 @@ describe('SessionManager - State Persistence', () => {
|
|
|
210
210
|
// Session 1 goes to idle
|
|
211
211
|
eventEmitter1.emit('data', 'Idle output for session 1');
|
|
212
212
|
// Session 2 goes to waiting_input
|
|
213
|
-
eventEmitter2.emit('data', '
|
|
213
|
+
eventEmitter2.emit('data', 'Do you want to continue?\n❯ 1. Yes');
|
|
214
214
|
// Advance time to check but not confirm
|
|
215
215
|
vi.advanceTimersByTime(STATE_CHECK_INTERVAL_MS * 2);
|
|
216
216
|
// Both should have pending states but not changed yet
|
|
@@ -45,20 +45,6 @@ export class ClaudeStateDetector extends BaseStateDetector {
|
|
|
45
45
|
if (lowerContent.includes('ctrl+r to toggle')) {
|
|
46
46
|
return currentState;
|
|
47
47
|
}
|
|
48
|
-
// Check for interactive selection interface patterns
|
|
49
|
-
// These patterns indicate Claude is waiting for user interaction with navigation/selection UI
|
|
50
|
-
const hasInteractivePattern = lowerContent.includes('enter to select') ||
|
|
51
|
-
lowerContent.includes('tab/arrow keys to navigate') ||
|
|
52
|
-
lowerContent.includes('esc to cancel') ||
|
|
53
|
-
lowerContent.includes('ready to submit your answers?');
|
|
54
|
-
if (hasInteractivePattern) {
|
|
55
|
-
return 'waiting_input';
|
|
56
|
-
}
|
|
57
|
-
// Check for waiting prompts with box character
|
|
58
|
-
if (content.includes('│ Do you want') ||
|
|
59
|
-
content.includes('│ Would you like')) {
|
|
60
|
-
return 'waiting_input';
|
|
61
|
-
}
|
|
62
48
|
// Check for "Do you want" or "Would you like" pattern with options
|
|
63
49
|
// Handles both simple ("Do you want...\nYes") and complex (numbered options) formats
|
|
64
50
|
if (/(?:do you want|would you like).+\n+[\s\S]*?(?:yes|❯)/.test(lowerContent)) {
|
package/dist/types/index.d.ts
CHANGED