ccmanager 3.2.5 → 3.2.7
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 +20 -0
- package/dist/services/sessionManager.d.ts +9 -0
- package/dist/services/sessionManager.js +53 -59
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -20,6 +20,7 @@ https://github.com/user-attachments/assets/15914a88-e288-4ac9-94d5-8127f2e19dbf
|
|
|
20
20
|
- Configurable state detection strategies for different CLI tools
|
|
21
21
|
- Status change hooks for automation and notifications
|
|
22
22
|
- Devcontainer integration
|
|
23
|
+
- **Auto Approval (experimental)**: Automatically approve safe prompts using AI verification
|
|
23
24
|
|
|
24
25
|
## Why CCManager over Claude Squad?
|
|
25
26
|
|
|
@@ -249,6 +250,25 @@ CCManager can automatically generate worktree directory paths based on branch na
|
|
|
249
250
|
|
|
250
251
|
For detailed configuration and examples, see [docs/worktree-auto-directory.md](docs/worktree-auto-directory.md).
|
|
251
252
|
|
|
253
|
+
## Auto Approval (Experimental)
|
|
254
|
+
|
|
255
|
+
CCManager can automatically approve Claude Code prompts that don't require user permission, reducing manual intervention while maintaining safety for sensitive operations.
|
|
256
|
+
|
|
257
|
+
### Features
|
|
258
|
+
|
|
259
|
+
- **Automatic decision**: Uses Claude (Haiku) to analyze prompts and determine if they need manual approval
|
|
260
|
+
- **Custom commands**: Replace the default verifier with your own script or different AI model
|
|
261
|
+
- **Safe fallback**: Always defaults to manual approval on errors or timeouts
|
|
262
|
+
- **Interruptible**: Press any key to cancel auto-approval and review manually
|
|
263
|
+
|
|
264
|
+
### Quick Start
|
|
265
|
+
|
|
266
|
+
1. Navigate to **Configuration** → **Other & Experimental**
|
|
267
|
+
2. Enable **Auto Approval (experimental)**
|
|
268
|
+
3. (Optional) Configure a custom command for verification
|
|
269
|
+
|
|
270
|
+
For detailed configuration and usage, see [docs/auto-approval.md](docs/auto-approval.md).
|
|
271
|
+
|
|
252
272
|
## Devcontainer Integration
|
|
253
273
|
|
|
254
274
|
CCManager supports running AI assistant sessions inside devcontainers while keeping the manager itself on the host machine. This enables sandboxed development environments with restricted network access while maintaining host-level notifications and automation.
|
|
@@ -18,6 +18,15 @@ export declare class SessionManager extends EventEmitter implements ISessionMana
|
|
|
18
18
|
private getTerminalContent;
|
|
19
19
|
private handleAutoApproval;
|
|
20
20
|
private cancelAutoApprovalVerification;
|
|
21
|
+
/**
|
|
22
|
+
* Update session state with automatic status hook execution.
|
|
23
|
+
* This method ensures that executeStatusHook is always called when state changes.
|
|
24
|
+
*
|
|
25
|
+
* @param session - The session to update
|
|
26
|
+
* @param newState - The new state to set
|
|
27
|
+
* @param additionalUpdates - Optional additional state data updates
|
|
28
|
+
*/
|
|
29
|
+
private updateSessionState;
|
|
21
30
|
constructor();
|
|
22
31
|
private createSessionId;
|
|
23
32
|
private createTerminal;
|
|
@@ -85,15 +85,10 @@ export class SessionManager extends EventEmitter {
|
|
|
85
85
|
if (autoApprovalResult.needsPermission) {
|
|
86
86
|
// Change state to waiting_input to ask for user permission
|
|
87
87
|
logger.info(`[${session.id}] Auto-approval verification determined user permission needed`);
|
|
88
|
-
await
|
|
89
|
-
...data,
|
|
90
|
-
state: 'waiting_input',
|
|
88
|
+
await this.updateSessionState(session, 'waiting_input', {
|
|
91
89
|
autoApprovalFailed: true,
|
|
92
90
|
autoApprovalReason: autoApprovalResult.reason,
|
|
93
|
-
|
|
94
|
-
pendingStateStart: undefined,
|
|
95
|
-
}));
|
|
96
|
-
this.emit('sessionStateChanged', session);
|
|
91
|
+
});
|
|
97
92
|
}
|
|
98
93
|
else {
|
|
99
94
|
// Auto-approve by simulating Enter key press
|
|
@@ -101,14 +96,9 @@ export class SessionManager extends EventEmitter {
|
|
|
101
96
|
session.process.write('\r');
|
|
102
97
|
// Force state to busy to prevent endless auto-approval
|
|
103
98
|
// when the state detection still sees pending_auto_approval
|
|
104
|
-
await
|
|
105
|
-
...data,
|
|
106
|
-
state: 'busy',
|
|
99
|
+
await this.updateSessionState(session, 'busy', {
|
|
107
100
|
autoApprovalReason: undefined,
|
|
108
|
-
|
|
109
|
-
pendingStateStart: undefined,
|
|
110
|
-
}));
|
|
111
|
-
this.emit('sessionStateChanged', session);
|
|
101
|
+
});
|
|
112
102
|
}
|
|
113
103
|
})
|
|
114
104
|
.catch(async (error) => {
|
|
@@ -120,16 +110,11 @@ export class SessionManager extends EventEmitter {
|
|
|
120
110
|
logger.error(`[${session.id}] Auto-approval verification failed, requiring user permission`, error);
|
|
121
111
|
const currentState = session.stateMutex.getSnapshot().state;
|
|
122
112
|
if (currentState === 'pending_auto_approval') {
|
|
123
|
-
await
|
|
124
|
-
...data,
|
|
125
|
-
state: 'waiting_input',
|
|
113
|
+
await this.updateSessionState(session, 'waiting_input', {
|
|
126
114
|
autoApprovalFailed: true,
|
|
127
115
|
autoApprovalReason: error?.message ??
|
|
128
116
|
'Auto-approval verification failed',
|
|
129
|
-
|
|
130
|
-
pendingStateStart: undefined,
|
|
131
|
-
}));
|
|
132
|
-
this.emit('sessionStateChanged', session);
|
|
117
|
+
});
|
|
133
118
|
}
|
|
134
119
|
})
|
|
135
120
|
.finally(async () => {
|
|
@@ -157,6 +142,28 @@ export class SessionManager extends EventEmitter {
|
|
|
157
142
|
}));
|
|
158
143
|
logger.info(`[${session.id}] Cancelled auto-approval verification: ${reason}`);
|
|
159
144
|
}
|
|
145
|
+
/**
|
|
146
|
+
* Update session state with automatic status hook execution.
|
|
147
|
+
* This method ensures that executeStatusHook is always called when state changes.
|
|
148
|
+
*
|
|
149
|
+
* @param session - The session to update
|
|
150
|
+
* @param newState - The new state to set
|
|
151
|
+
* @param additionalUpdates - Optional additional state data updates
|
|
152
|
+
*/
|
|
153
|
+
async updateSessionState(session, newState, additionalUpdates = {}) {
|
|
154
|
+
const oldState = session.stateMutex.getSnapshot().state;
|
|
155
|
+
await session.stateMutex.update(data => ({
|
|
156
|
+
...data,
|
|
157
|
+
state: newState,
|
|
158
|
+
pendingState: undefined,
|
|
159
|
+
pendingStateStart: undefined,
|
|
160
|
+
...additionalUpdates,
|
|
161
|
+
}));
|
|
162
|
+
if (oldState !== newState) {
|
|
163
|
+
void Effect.runPromise(executeStatusHook(oldState, newState, session));
|
|
164
|
+
this.emit('sessionStateChanged', session);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
160
167
|
constructor() {
|
|
161
168
|
super();
|
|
162
169
|
Object.defineProperty(this, "sessions", {
|
|
@@ -393,31 +400,23 @@ export class SessionManager extends EventEmitter {
|
|
|
393
400
|
// Check if the pending state has persisted long enough
|
|
394
401
|
const duration = now - stateData.pendingStateStart;
|
|
395
402
|
if (duration >= STATE_PERSISTENCE_DURATION_MS) {
|
|
396
|
-
//
|
|
397
|
-
void session.stateMutex.update(data => {
|
|
398
|
-
const newData = {
|
|
399
|
-
...data,
|
|
400
|
-
state: detectedState,
|
|
401
|
-
pendingState: undefined,
|
|
402
|
-
pendingStateStart: undefined,
|
|
403
|
-
};
|
|
404
|
-
// If we previously blocked auto-approval and have moved out of a user prompt,
|
|
405
|
-
// allow future auto-approval attempts.
|
|
406
|
-
if (data.autoApprovalFailed &&
|
|
407
|
-
detectedState !== 'waiting_input' &&
|
|
408
|
-
detectedState !== 'pending_auto_approval') {
|
|
409
|
-
newData.autoApprovalFailed = false;
|
|
410
|
-
newData.autoApprovalReason = undefined;
|
|
411
|
-
}
|
|
412
|
-
return newData;
|
|
413
|
-
});
|
|
403
|
+
// Cancel auto-approval verification if state is changing away from pending_auto_approval
|
|
414
404
|
if (stateData.autoApprovalAbortController &&
|
|
415
405
|
detectedState !== 'pending_auto_approval') {
|
|
416
406
|
this.cancelAutoApprovalVerification(session, `state changed to ${detectedState}`);
|
|
417
407
|
}
|
|
418
|
-
//
|
|
419
|
-
|
|
420
|
-
|
|
408
|
+
// Build additional updates for auto-approval reset
|
|
409
|
+
const additionalUpdates = {};
|
|
410
|
+
// If we previously blocked auto-approval and have moved out of a user prompt,
|
|
411
|
+
// allow future auto-approval attempts.
|
|
412
|
+
if (stateData.autoApprovalFailed &&
|
|
413
|
+
detectedState !== 'waiting_input' &&
|
|
414
|
+
detectedState !== 'pending_auto_approval') {
|
|
415
|
+
additionalUpdates.autoApprovalFailed = false;
|
|
416
|
+
additionalUpdates.autoApprovalReason = undefined;
|
|
417
|
+
}
|
|
418
|
+
// Confirm the state change with hook execution
|
|
419
|
+
void this.updateSessionState(session, detectedState, additionalUpdates);
|
|
421
420
|
}
|
|
422
421
|
}
|
|
423
422
|
}
|
|
@@ -452,13 +451,7 @@ export class SessionManager extends EventEmitter {
|
|
|
452
451
|
session.stateCheckInterval = undefined;
|
|
453
452
|
}
|
|
454
453
|
// Clear any pending state and update state to idle before destroying
|
|
455
|
-
void
|
|
456
|
-
...data,
|
|
457
|
-
state: 'idle',
|
|
458
|
-
pendingState: undefined,
|
|
459
|
-
pendingStateStart: undefined,
|
|
460
|
-
}));
|
|
461
|
-
this.emit('sessionStateChanged', session);
|
|
454
|
+
void this.updateSessionState(session, 'idle');
|
|
462
455
|
this.destroySession(session.worktreePath);
|
|
463
456
|
this.emit('sessionExit', session);
|
|
464
457
|
}
|
|
@@ -490,21 +483,22 @@ export class SessionManager extends EventEmitter {
|
|
|
490
483
|
return;
|
|
491
484
|
}
|
|
492
485
|
this.cancelAutoApprovalVerification(session, reason);
|
|
493
|
-
|
|
494
|
-
|
|
486
|
+
if (stateData.state === 'pending_auto_approval') {
|
|
487
|
+
// State change: pending_auto_approval -> waiting_input
|
|
488
|
+
void this.updateSessionState(session, 'waiting_input', {
|
|
489
|
+
autoApprovalFailed: true,
|
|
490
|
+
autoApprovalReason: reason,
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
else {
|
|
494
|
+
// No state change, just update other fields
|
|
495
|
+
void session.stateMutex.update(data => ({
|
|
495
496
|
...data,
|
|
496
497
|
autoApprovalFailed: true,
|
|
497
498
|
autoApprovalReason: reason,
|
|
498
499
|
pendingState: undefined,
|
|
499
500
|
pendingStateStart: undefined,
|
|
500
|
-
};
|
|
501
|
-
if (data.state === 'pending_auto_approval') {
|
|
502
|
-
newData.state = 'waiting_input';
|
|
503
|
-
}
|
|
504
|
-
return newData;
|
|
505
|
-
});
|
|
506
|
-
if (stateData.state === 'pending_auto_approval') {
|
|
507
|
-
this.emit('sessionStateChanged', session);
|
|
501
|
+
}));
|
|
508
502
|
}
|
|
509
503
|
}
|
|
510
504
|
destroySession(worktreePath) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ccmanager",
|
|
3
|
-
"version": "3.2.
|
|
3
|
+
"version": "3.2.7",
|
|
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.2.
|
|
45
|
-
"@kodaikabasawa/ccmanager-darwin-x64": "3.2.
|
|
46
|
-
"@kodaikabasawa/ccmanager-linux-arm64": "3.2.
|
|
47
|
-
"@kodaikabasawa/ccmanager-linux-x64": "3.2.
|
|
48
|
-
"@kodaikabasawa/ccmanager-win32-x64": "3.2.
|
|
44
|
+
"@kodaikabasawa/ccmanager-darwin-arm64": "3.2.7",
|
|
45
|
+
"@kodaikabasawa/ccmanager-darwin-x64": "3.2.7",
|
|
46
|
+
"@kodaikabasawa/ccmanager-linux-arm64": "3.2.7",
|
|
47
|
+
"@kodaikabasawa/ccmanager-linux-x64": "3.2.7",
|
|
48
|
+
"@kodaikabasawa/ccmanager-win32-x64": "3.2.7"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"@eslint/js": "^9.28.0",
|