mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-05 15:09:42 +02:00
fix: restrict auto-accept to plan mode only, block AskUserQuestion prompts
The auto-accept feature was too aggressive - it would press Enter for any silence without a completion message, including AskUserQuestion prompts. Now uses the elicitation_dialog notification hook to detect when Claude is asking a question, and blocks auto-accept in that case. Only plan mode approvals (silence with no completion message AND no elicitation signal) trigger auto-accept. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -20,7 +20,7 @@ describe('generateHooksConfig', () => {
|
||||
it('should have Notification hooks array', () => {
|
||||
const config = generateHooksConfig();
|
||||
expect(config.hooks.Notification).toBeInstanceOf(Array);
|
||||
expect(config.hooks.Notification).toHaveLength(2);
|
||||
expect(config.hooks.Notification).toHaveLength(3);
|
||||
});
|
||||
|
||||
it('should have Stop hooks array', () => {
|
||||
@@ -43,6 +43,13 @@ describe('generateHooksConfig', () => {
|
||||
expect(permHook).toBeDefined();
|
||||
});
|
||||
|
||||
it('should configure elicitation_dialog matcher', () => {
|
||||
const config = generateHooksConfig();
|
||||
const notifHooks = config.hooks.Notification as Array<{ matcher?: string }>;
|
||||
const elicitHook = notifHooks.find(h => h.matcher === 'elicitation_dialog');
|
||||
expect(elicitHook).toBeDefined();
|
||||
});
|
||||
|
||||
it('should use env vars in curl commands (not hardcoded URLs)', () => {
|
||||
const config = generateHooksConfig();
|
||||
const notifHooks = config.hooks.Notification as Array<{ hooks: Array<{ command: string }> }>;
|
||||
@@ -69,6 +76,7 @@ describe('generateHooksConfig', () => {
|
||||
const notifHooks = config.hooks.Notification as Array<{ hooks: Array<{ command: string }> }>;
|
||||
expect(notifHooks[0].hooks[0].command).toContain('"idle_prompt"');
|
||||
expect(notifHooks[1].hooks[0].command).toContain('"permission_prompt"');
|
||||
expect(notifHooks[2].hooks[0].command).toContain('"elicitation_dialog"');
|
||||
const stopHooks = config.hooks.Stop as Array<{ hooks: Array<{ command: string }> }>;
|
||||
expect(stopHooks[0].hooks[0].command).toContain('"stop"');
|
||||
});
|
||||
@@ -118,7 +126,7 @@ describe('writeHooksConfig', () => {
|
||||
const settingsPath = join(testDir, '.claude', 'settings.local.json');
|
||||
const parsed = JSON.parse(readFileSync(settingsPath, 'utf-8'));
|
||||
expect(parsed.hooks).toBeDefined();
|
||||
expect(parsed.hooks.Notification).toHaveLength(2);
|
||||
expect(parsed.hooks.Notification).toHaveLength(3);
|
||||
expect(parsed.hooks.Stop).toHaveLength(1);
|
||||
});
|
||||
|
||||
|
||||
@@ -1055,5 +1055,64 @@ describe('RespawnController Edge Cases', () => {
|
||||
expect(autoAcceptFired).toBe(false);
|
||||
autoAcceptController.stop();
|
||||
});
|
||||
|
||||
it('should NOT auto-accept when elicitation dialog is signaled', async () => {
|
||||
const autoAcceptController = new RespawnController(session as unknown as Session, {
|
||||
autoAcceptPrompts: true,
|
||||
autoAcceptDelayMs: 100,
|
||||
completionConfirmMs: 50,
|
||||
noOutputTimeoutMs: 5000,
|
||||
});
|
||||
|
||||
let autoAcceptFired = false;
|
||||
autoAcceptController.on('autoAcceptSent', () => {
|
||||
autoAcceptFired = true;
|
||||
});
|
||||
|
||||
autoAcceptController.start();
|
||||
session.simulateTerminalOutput('Which option do you prefer?');
|
||||
|
||||
// Signal that an elicitation dialog (AskUserQuestion) was detected
|
||||
autoAcceptController.signalElicitation();
|
||||
|
||||
// Wait for autoAcceptDelayMs to expire
|
||||
await new Promise(resolve => setTimeout(resolve, 200));
|
||||
|
||||
// Auto-accept should NOT fire because elicitation was signaled
|
||||
expect(autoAcceptFired).toBe(false);
|
||||
autoAcceptController.stop();
|
||||
});
|
||||
|
||||
it('should clear elicitation flag when working patterns detected', async () => {
|
||||
const autoAcceptController = new RespawnController(session as unknown as Session, {
|
||||
autoAcceptPrompts: true,
|
||||
autoAcceptDelayMs: 100,
|
||||
completionConfirmMs: 50,
|
||||
noOutputTimeoutMs: 5000,
|
||||
});
|
||||
|
||||
let autoAcceptFired = false;
|
||||
autoAcceptController.on('autoAcceptSent', () => {
|
||||
autoAcceptFired = true;
|
||||
});
|
||||
|
||||
autoAcceptController.start();
|
||||
session.simulateTerminalOutput('Question output');
|
||||
|
||||
// Signal elicitation
|
||||
autoAcceptController.signalElicitation();
|
||||
|
||||
// Working pattern clears the elicitation flag (new turn started)
|
||||
session.simulateTerminalOutput('Thinking');
|
||||
|
||||
// New silence after work - plan mode approval
|
||||
session.simulateTerminalOutput('Plan: Here is the plan...');
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 200));
|
||||
|
||||
// Auto-accept should fire now (elicitation cleared by working pattern)
|
||||
expect(autoAcceptFired).toBe(true);
|
||||
autoAcceptController.stop();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user