ccmanager 3.6.0 → 3.6.3
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/Menu.recent-projects.test.js +3 -1
- package/dist/services/sessionManager.d.ts +1 -0
- package/dist/services/sessionManager.js +6 -1
- package/dist/services/stateDetector/claude.test.js +52 -0
- package/dist/services/stateDetector/testUtils.d.ts +2 -1
- package/dist/services/stateDetector/testUtils.js +4 -1
- package/dist/utils/screenCapture.js +6 -3
- package/package.json +6 -6
|
@@ -126,7 +126,9 @@ describe('Menu - Recent Projects', () => {
|
|
|
126
126
|
backgroundTasks: 0,
|
|
127
127
|
});
|
|
128
128
|
vi.spyOn(SessionManager, 'formatSessionCounts').mockReturnValue('');
|
|
129
|
-
const { lastFrame } = render(_jsx(Menu, { sessionManager: mockSessionManager, worktreeService: mockWorktreeService, onSelectWorktree: vi.fn(), onSelectRecentProject: vi.fn(), multiProject: true, version: "test" }));
|
|
129
|
+
const { lastFrame, rerender } = render(_jsx(Menu, { sessionManager: mockSessionManager, worktreeService: mockWorktreeService, onSelectWorktree: vi.fn(), onSelectRecentProject: vi.fn(), multiProject: true, version: "test" }));
|
|
130
|
+
// Force a rerender to ensure all effects have run
|
|
131
|
+
rerender(_jsx(Menu, { sessionManager: mockSessionManager, worktreeService: mockWorktreeService, onSelectWorktree: vi.fn(), onSelectRecentProject: vi.fn(), multiProject: true, version: "test" }));
|
|
130
132
|
// Wait for Effect to execute
|
|
131
133
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|
132
134
|
const output = lastFrame();
|
|
@@ -17,6 +17,7 @@ export declare class SessionManager extends EventEmitter implements ISessionMana
|
|
|
17
17
|
private spawn;
|
|
18
18
|
detectTerminalState(session: Session): SessionState;
|
|
19
19
|
detectBackgroundTask(session: Session): number;
|
|
20
|
+
private getTerminalContent;
|
|
20
21
|
private handleAutoApproval;
|
|
21
22
|
private cancelAutoApprovalVerification;
|
|
22
23
|
/**
|
|
@@ -46,6 +46,11 @@ export class SessionManager extends EventEmitter {
|
|
|
46
46
|
detectBackgroundTask(session) {
|
|
47
47
|
return session.stateDetector.detectBackgroundTask(session.terminal);
|
|
48
48
|
}
|
|
49
|
+
getTerminalContent(session) {
|
|
50
|
+
// Use the new screen capture utility that correctly handles
|
|
51
|
+
// both normal and alternate screen buffers
|
|
52
|
+
return getTerminalScreenContent(session.terminal, TERMINAL_CONTENT_MAX_LINES);
|
|
53
|
+
}
|
|
49
54
|
handleAutoApproval(session) {
|
|
50
55
|
// Cancel any existing verification before starting a new one
|
|
51
56
|
this.cancelAutoApprovalVerification(session, 'Restarting verification for pending auto-approval state');
|
|
@@ -56,7 +61,7 @@ export class SessionManager extends EventEmitter {
|
|
|
56
61
|
autoApprovalReason: undefined,
|
|
57
62
|
}));
|
|
58
63
|
// Get terminal content for verification
|
|
59
|
-
const terminalContent =
|
|
64
|
+
const terminalContent = this.getTerminalContent(session);
|
|
60
65
|
// Verify if permission is needed
|
|
61
66
|
void Effect.runPromise(autoApprovalVerifier.verifyNeedsPermission(terminalContent, {
|
|
62
67
|
signal: abortController.signal,
|
|
@@ -221,6 +221,58 @@ describe('ClaudeStateDetector', () => {
|
|
|
221
221
|
// Assert
|
|
222
222
|
expect(state).toBe('waiting_input');
|
|
223
223
|
});
|
|
224
|
+
it('should detect idle when scrolled past old busy state (baseY > 0)', () => {
|
|
225
|
+
// Arrange - Simulate scrollback where "esc to interrupt" is in history
|
|
226
|
+
// but the current viewport (baseY=5, rows=3) shows idle content
|
|
227
|
+
// Buffer: [0]: "Old content", [1]: "esc to interrupt", [2]: "More busy output",
|
|
228
|
+
// [3]: "...", [4]: "...", [5]: "Ready", [6]: "Prompt >", [7]: "idle"
|
|
229
|
+
// Viewport shows lines 5-7 (baseY=5, rows=3)
|
|
230
|
+
terminal = createMockTerminal([
|
|
231
|
+
'Old content',
|
|
232
|
+
'Previous output with esc to interrupt marker',
|
|
233
|
+
'More old busy output',
|
|
234
|
+
'Transition content',
|
|
235
|
+
'Processing done',
|
|
236
|
+
'Ready for input',
|
|
237
|
+
'Prompt >',
|
|
238
|
+
'idle state here',
|
|
239
|
+
], { baseY: 5, rows: 3 });
|
|
240
|
+
// Act
|
|
241
|
+
const state = detector.detectState(terminal, 'busy');
|
|
242
|
+
// Assert - Should detect idle because viewport shows lines 5-7
|
|
243
|
+
expect(state).toBe('idle');
|
|
244
|
+
});
|
|
245
|
+
it('should detect busy when scrolled to busy state (baseY shows busy content)', () => {
|
|
246
|
+
// Arrange - Viewport shows busy content
|
|
247
|
+
// Buffer has old idle content at top, busy content in viewport
|
|
248
|
+
terminal = createMockTerminal([
|
|
249
|
+
'Old idle content',
|
|
250
|
+
'Previous prompt',
|
|
251
|
+
'User input',
|
|
252
|
+
'Processing request...',
|
|
253
|
+
'Press esc to interrupt',
|
|
254
|
+
'Working...',
|
|
255
|
+
], { baseY: 3, rows: 3 });
|
|
256
|
+
// Act
|
|
257
|
+
const state = detector.detectState(terminal, 'idle');
|
|
258
|
+
// Assert - Should detect busy because viewport shows lines 3-5
|
|
259
|
+
expect(state).toBe('busy');
|
|
260
|
+
});
|
|
261
|
+
it('should detect waiting_input when viewport shows prompt after scroll', () => {
|
|
262
|
+
// Arrange - Scrollback has busy markers, but viewport shows waiting prompt
|
|
263
|
+
terminal = createMockTerminal([
|
|
264
|
+
'Previous busy content',
|
|
265
|
+
'esc to interrupt was here',
|
|
266
|
+
'Old output',
|
|
267
|
+
'Do you want to continue?',
|
|
268
|
+
'❯ 1. Yes',
|
|
269
|
+
' 2. No',
|
|
270
|
+
], { baseY: 3, rows: 3 });
|
|
271
|
+
// Act
|
|
272
|
+
const state = detector.detectState(terminal, 'busy');
|
|
273
|
+
// Assert - Should detect waiting_input from viewport
|
|
274
|
+
expect(state).toBe('waiting_input');
|
|
275
|
+
});
|
|
224
276
|
});
|
|
225
277
|
describe('detectBackgroundTask', () => {
|
|
226
278
|
it('should return count 1 when "1 background task" is in status bar', () => {
|
|
@@ -2,10 +2,11 @@ import type { Terminal } from '../../types/index.js';
|
|
|
2
2
|
/**
|
|
3
3
|
* Creates a mock Terminal object for testing state detectors.
|
|
4
4
|
* @param lines - Array of strings representing terminal output lines
|
|
5
|
-
* @param options - Optional configuration for rows and
|
|
5
|
+
* @param options - Optional configuration for rows, cols, and baseY
|
|
6
6
|
* @returns Mock Terminal object with buffer interface
|
|
7
7
|
*/
|
|
8
8
|
export declare const createMockTerminal: (lines: string[], options?: {
|
|
9
9
|
rows?: number;
|
|
10
10
|
cols?: number;
|
|
11
|
+
baseY?: number;
|
|
11
12
|
}) => Terminal;
|
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Creates a mock Terminal object for testing state detectors.
|
|
3
3
|
* @param lines - Array of strings representing terminal output lines
|
|
4
|
-
* @param options - Optional configuration for rows and
|
|
4
|
+
* @param options - Optional configuration for rows, cols, and baseY
|
|
5
5
|
* @returns Mock Terminal object with buffer interface
|
|
6
6
|
*/
|
|
7
7
|
export const createMockTerminal = (lines, options) => {
|
|
8
8
|
const rows = options?.rows ?? lines.length;
|
|
9
9
|
const cols = options?.cols ?? 80;
|
|
10
|
+
const baseY = options?.baseY ?? 0;
|
|
10
11
|
const buffer = {
|
|
11
12
|
length: lines.length,
|
|
12
13
|
active: {
|
|
14
|
+
type: 'normal',
|
|
13
15
|
length: lines.length,
|
|
16
|
+
baseY,
|
|
14
17
|
getLine: (index) => {
|
|
15
18
|
if (index >= 0 && index < lines.length) {
|
|
16
19
|
return {
|
|
@@ -15,10 +15,13 @@ function lineToString(line, cols) {
|
|
|
15
15
|
export function captureScreen(terminal) {
|
|
16
16
|
const buffer = terminal.buffer.active;
|
|
17
17
|
const lines = [];
|
|
18
|
-
//
|
|
19
|
-
//
|
|
18
|
+
// baseY is the offset of the viewport within the buffer
|
|
19
|
+
// For alternate buffer: baseY is always 0
|
|
20
|
+
// For normal buffer: baseY indicates how much has been scrolled
|
|
21
|
+
const baseY = buffer.baseY;
|
|
22
|
+
// Capture the visible viewport (not the beginning of scrollback)
|
|
20
23
|
for (let y = 0; y < terminal.rows; y++) {
|
|
21
|
-
const line = buffer.getLine(y);
|
|
24
|
+
const line = buffer.getLine(baseY + y);
|
|
22
25
|
lines.push(lineToString(line, terminal.cols));
|
|
23
26
|
}
|
|
24
27
|
return {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ccmanager",
|
|
3
|
-
"version": "3.6.
|
|
3
|
+
"version": "3.6.3",
|
|
4
4
|
"description": "TUI application for managing multiple Claude Code sessions across Git worktrees",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Kodai Kabasawa",
|
|
@@ -41,11 +41,11 @@
|
|
|
41
41
|
"bin"
|
|
42
42
|
],
|
|
43
43
|
"optionalDependencies": {
|
|
44
|
-
"@kodaikabasawa/ccmanager-darwin-arm64": "3.6.
|
|
45
|
-
"@kodaikabasawa/ccmanager-darwin-x64": "3.6.
|
|
46
|
-
"@kodaikabasawa/ccmanager-linux-arm64": "3.6.
|
|
47
|
-
"@kodaikabasawa/ccmanager-linux-x64": "3.6.
|
|
48
|
-
"@kodaikabasawa/ccmanager-win32-x64": "3.6.
|
|
44
|
+
"@kodaikabasawa/ccmanager-darwin-arm64": "3.6.3",
|
|
45
|
+
"@kodaikabasawa/ccmanager-darwin-x64": "3.6.3",
|
|
46
|
+
"@kodaikabasawa/ccmanager-linux-arm64": "3.6.3",
|
|
47
|
+
"@kodaikabasawa/ccmanager-linux-x64": "3.6.3",
|
|
48
|
+
"@kodaikabasawa/ccmanager-win32-x64": "3.6.3"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"@eslint/js": "^9.28.0",
|