mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-08 16:39:42 +02:00
feat(claude): add 'auto' startup permission mode
Adds Anthropic's classifier-guarded low-prompt mode (--permission-mode auto) as a fourth ClaudeMode alongside skip-permissions/normal/allowedTools. Wired through both spawn paths (buildPermissionArgs for direct PTY, buildClaudePermissionFlags for tmux), the getClaudeModeConfig validator, and the App Settings Startup Mode picker. Exports buildSpawnCommand for test coverage. This is the prerequisite for multi-user mode section 6.3, which downgrades non-granted users' sessions to 'auto'. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -21,6 +21,8 @@ function buildPermissionArgs(claudeMode: ClaudeMode, allowedTools?: string): str
|
|||||||
switch (claudeMode) {
|
switch (claudeMode) {
|
||||||
case 'dangerously-skip-permissions':
|
case 'dangerously-skip-permissions':
|
||||||
return ['--dangerously-skip-permissions'];
|
return ['--dangerously-skip-permissions'];
|
||||||
|
case 'auto':
|
||||||
|
return ['--permission-mode', 'auto'];
|
||||||
case 'allowedTools':
|
case 'allowedTools':
|
||||||
if (allowedTools) {
|
if (allowedTools) {
|
||||||
return ['--allowedTools', allowedTools];
|
return ['--allowedTools', allowedTools];
|
||||||
|
|||||||
+1
-1
@@ -1523,7 +1523,7 @@ export class Session extends EventEmitter {
|
|||||||
|
|
||||||
// === Auto-accept workspace trust dialog ===
|
// === Auto-accept workspace trust dialog ===
|
||||||
// Claude CLI 2.x shows "Yes, I trust this folder" prompt on first launch per directory.
|
// Claude CLI 2.x shows "Yes, I trust this folder" prompt on first launch per directory.
|
||||||
// Codeman sessions always use --dangerously-skip-permissions, so auto-accept.
|
// Codeman sessions run permission-skipping or classifier-guarded (auto) modes, so auto-accept.
|
||||||
if (!this._trustDialogAccepted && data.includes('trust this folder')) {
|
if (!this._trustDialogAccepted && data.includes('trust this folder')) {
|
||||||
this._trustDialogAccepted = true;
|
this._trustDialogAccepted = true;
|
||||||
console.log(`[Session] Auto-accepting workspace trust dialog for: ${this.id}`);
|
console.log(`[Session] Auto-accepting workspace trust dialog for: ${this.id}`);
|
||||||
|
|||||||
+3
-1
@@ -563,6 +563,8 @@ function buildClaudePermissionFlags(claudeMode?: ClaudeMode, allowedTools?: stri
|
|||||||
switch (mode) {
|
switch (mode) {
|
||||||
case 'dangerously-skip-permissions':
|
case 'dangerously-skip-permissions':
|
||||||
return ' --dangerously-skip-permissions';
|
return ' --dangerously-skip-permissions';
|
||||||
|
case 'auto':
|
||||||
|
return ' --permission-mode auto';
|
||||||
case 'allowedTools':
|
case 'allowedTools':
|
||||||
if (allowedTools) {
|
if (allowedTools) {
|
||||||
// Sanitize: allow tool names with patterns like Bash(git:*), space/comma-separated
|
// Sanitize: allow tool names with patterns like Bash(git:*), space/comma-separated
|
||||||
@@ -674,7 +676,7 @@ function buildEffortSettingsFlag(effort?: EffortLevel): string {
|
|||||||
return flag && value ? ` ${flag} '${value}'` : '';
|
return flag && value ? ` ${flag} '${value}'` : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildSpawnCommand(options: {
|
export function buildSpawnCommand(options: {
|
||||||
mode: SessionMode;
|
mode: SessionMode;
|
||||||
sessionId: string;
|
sessionId: string;
|
||||||
model?: string;
|
model?: string;
|
||||||
|
|||||||
@@ -35,10 +35,11 @@ export type SessionStatus = 'idle' | 'busy' | 'stopped' | 'error';
|
|||||||
/**
|
/**
|
||||||
* Claude CLI startup permission mode.
|
* Claude CLI startup permission mode.
|
||||||
* - `'dangerously-skip-permissions'`: Bypass all permission prompts (default)
|
* - `'dangerously-skip-permissions'`: Bypass all permission prompts (default)
|
||||||
|
* - `'auto'`: Anthropic's classifier-guarded low-prompt mode (`--permission-mode auto`)
|
||||||
* - `'normal'`: Standard mode with permission prompts
|
* - `'normal'`: Standard mode with permission prompts
|
||||||
* - `'allowedTools'`: Only allow specific tools (requires allowedTools list)
|
* - `'allowedTools'`: Only allow specific tools (requires allowedTools list)
|
||||||
*/
|
*/
|
||||||
export type ClaudeMode = 'dangerously-skip-permissions' | 'normal' | 'allowedTools';
|
export type ClaudeMode = 'dangerously-skip-permissions' | 'auto' | 'normal' | 'allowedTools';
|
||||||
|
|
||||||
/** Session mode: which CLI backend a session runs */
|
/** Session mode: which CLI backend a session runs */
|
||||||
export type SessionMode = 'claude' | 'shell' | 'opencode' | 'codex' | 'gemini';
|
export type SessionMode = 'claude' | 'shell' | 'opencode' | 'codex' | 'gemini';
|
||||||
|
|||||||
@@ -1449,6 +1449,7 @@
|
|||||||
<label>Startup Mode</label>
|
<label>Startup Mode</label>
|
||||||
<select id="appSettingsClaudeMode" class="form-select">
|
<select id="appSettingsClaudeMode" class="form-select">
|
||||||
<option value="dangerously-skip-permissions">Skip Permissions (default)</option>
|
<option value="dangerously-skip-permissions">Skip Permissions (default)</option>
|
||||||
|
<option value="auto">Auto (classifier-guarded, low prompts)</option>
|
||||||
<option value="normal">Normal (with prompts)</option>
|
<option value="normal">Normal (with prompts)</option>
|
||||||
<option value="allowedTools">Allowed Tools Only</option>
|
<option value="allowedTools">Allowed Tools Only</option>
|
||||||
</select>
|
</select>
|
||||||
|
|||||||
+6
-1
@@ -1514,7 +1514,12 @@ export class WebServer extends EventEmitter {
|
|||||||
const claudeMode = settings.claudeMode as string | undefined;
|
const claudeMode = settings.claudeMode as string | undefined;
|
||||||
const allowedTools = settings.allowedTools as string | undefined;
|
const allowedTools = settings.allowedTools as string | undefined;
|
||||||
// Only return valid modes
|
// Only return valid modes
|
||||||
if (claudeMode === 'dangerously-skip-permissions' || claudeMode === 'normal' || claudeMode === 'allowedTools') {
|
if (
|
||||||
|
claudeMode === 'dangerously-skip-permissions' ||
|
||||||
|
claudeMode === 'auto' ||
|
||||||
|
claudeMode === 'normal' ||
|
||||||
|
claudeMode === 'allowedTools'
|
||||||
|
) {
|
||||||
return { claudeMode, allowedTools };
|
return { claudeMode, allowedTools };
|
||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
/**
|
||||||
|
* @fileoverview Tests for Claude CLI startup permission modes, focused on the
|
||||||
|
* 'auto' mode (`--permission-mode auto`, Anthropic's recommended low-prompt mode)
|
||||||
|
* added alongside the default `--dangerously-skip-permissions`.
|
||||||
|
*
|
||||||
|
* Covers BOTH spawn paths, which build the permission flags independently:
|
||||||
|
* - session-cli-builder.buildInteractiveArgs (direct PTY, non-mux fallback)
|
||||||
|
* - tmux-manager.buildSpawnCommand (tmux pane command string)
|
||||||
|
* The default must stay 'dangerously-skip-permissions' when the setting is unset.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
import { buildInteractiveArgs } from '../src/session-cli-builder.js';
|
||||||
|
import { buildSpawnCommand } from '../src/tmux-manager.js';
|
||||||
|
|
||||||
|
describe('buildInteractiveArgs permission modes (direct PTY path)', () => {
|
||||||
|
it('keeps --dangerously-skip-permissions as the skip-mode flag', () => {
|
||||||
|
const args = buildInteractiveArgs('sid-1', 'dangerously-skip-permissions');
|
||||||
|
expect(args).toContain('--dangerously-skip-permissions');
|
||||||
|
expect(args).not.toContain('--permission-mode');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('auto mode emits --permission-mode auto and never the skip flag', () => {
|
||||||
|
const args = buildInteractiveArgs('sid-1', 'auto');
|
||||||
|
const idx = args.indexOf('--permission-mode');
|
||||||
|
expect(idx).toBeGreaterThanOrEqual(0);
|
||||||
|
expect(args[idx + 1]).toBe('auto');
|
||||||
|
expect(args).not.toContain('--dangerously-skip-permissions');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('normal mode emits no permission flag at all', () => {
|
||||||
|
const args = buildInteractiveArgs('sid-1', 'normal');
|
||||||
|
expect(args).not.toContain('--dangerously-skip-permissions');
|
||||||
|
expect(args).not.toContain('--permission-mode');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('allowedTools mode is unchanged by the auto addition', () => {
|
||||||
|
const args = buildInteractiveArgs('sid-1', 'allowedTools', undefined, 'Read,Grep');
|
||||||
|
expect(args).toEqual(expect.arrayContaining(['--allowedTools', 'Read,Grep']));
|
||||||
|
expect(args).not.toContain('--permission-mode');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('auto mode composes with model and effort flags', () => {
|
||||||
|
const args = buildInteractiveArgs('sid-1', 'auto', 'opus', undefined, 'high');
|
||||||
|
expect(args).toEqual(expect.arrayContaining(['--permission-mode', 'auto', '--model', 'opus', '--effort', 'high']));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('buildSpawnCommand permission modes (tmux path)', () => {
|
||||||
|
it('unset claudeMode defaults to --dangerously-skip-permissions', () => {
|
||||||
|
const cmd = buildSpawnCommand({ mode: 'claude', sessionId: 'sid-1' });
|
||||||
|
expect(cmd).toContain('claude --dangerously-skip-permissions --session-id "sid-1"');
|
||||||
|
expect(cmd).not.toContain('--permission-mode');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('auto mode emits --permission-mode auto and never the skip flag', () => {
|
||||||
|
const cmd = buildSpawnCommand({ mode: 'claude', sessionId: 'sid-1', claudeMode: 'auto' });
|
||||||
|
expect(cmd).toContain('claude --permission-mode auto --session-id "sid-1"');
|
||||||
|
expect(cmd).not.toContain('--dangerously-skip-permissions');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('auto mode carries into BOTH legs of the resume fallback command', () => {
|
||||||
|
const cmd = buildSpawnCommand({
|
||||||
|
mode: 'claude',
|
||||||
|
sessionId: 'sid-1',
|
||||||
|
claudeMode: 'auto',
|
||||||
|
resumeSessionId: 'abc-123',
|
||||||
|
});
|
||||||
|
const [resumeLeg, fallbackLeg] = cmd.split('||');
|
||||||
|
expect(resumeLeg).toContain('--permission-mode auto');
|
||||||
|
expect(resumeLeg).toContain('--resume "abc-123"');
|
||||||
|
expect(fallbackLeg).toContain('--permission-mode auto');
|
||||||
|
expect(fallbackLeg).toContain('--session-id "sid-1"');
|
||||||
|
expect(cmd).not.toContain('--dangerously-skip-permissions');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('normal mode emits no permission flag', () => {
|
||||||
|
const cmd = buildSpawnCommand({ mode: 'claude', sessionId: 'sid-1', claudeMode: 'normal' });
|
||||||
|
expect(cmd).toContain('claude --session-id "sid-1"');
|
||||||
|
expect(cmd).not.toContain('--permission-mode');
|
||||||
|
expect(cmd).not.toContain('--dangerously-skip-permissions');
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user