ccmanager 3.1.0 → 3.1.2
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.
|
@@ -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
|
});
|
|
@@ -381,10 +381,6 @@ export class SessionManager extends EventEmitter {
|
|
|
381
381
|
session.autoApprovalFailed = false;
|
|
382
382
|
session.autoApprovalReason = undefined;
|
|
383
383
|
}
|
|
384
|
-
// Handle auto-approval if state is pending_auto_approval
|
|
385
|
-
if (detectedState === 'pending_auto_approval') {
|
|
386
|
-
this.handleAutoApproval(session);
|
|
387
|
-
}
|
|
388
384
|
// Execute status hook asynchronously (non-blocking) using Effect
|
|
389
385
|
void Effect.runPromise(executeStatusHook(oldState, detectedState, session));
|
|
390
386
|
this.emit('sessionStateChanged', session);
|
|
@@ -396,6 +392,13 @@ export class SessionManager extends EventEmitter {
|
|
|
396
392
|
session.pendingState = undefined;
|
|
397
393
|
session.pendingStateStart = undefined;
|
|
398
394
|
}
|
|
395
|
+
// Handle auto-approval if state is pending_auto_approval and no verification is in progress.
|
|
396
|
+
// This ensures auto-approval is retried when the state remains pending_auto_approval
|
|
397
|
+
// but the previous verification completed (success, failure, timeout, or abort).
|
|
398
|
+
if (session.state === 'pending_auto_approval' &&
|
|
399
|
+
!session.autoApprovalAbortController) {
|
|
400
|
+
this.handleAutoApproval(session);
|
|
401
|
+
}
|
|
399
402
|
}, STATE_CHECK_INTERVAL_MS);
|
|
400
403
|
// Setup exit handler
|
|
401
404
|
this.setupExitHandler(session);
|
|
@@ -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)) {
|