Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/hooks/useSSEStream.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ interface ToolUseInfo {
interface ToolResultInfo {
tool_use_id: string;
content: string;
is_error?: boolean;
}

export interface SSECallbacks {
Expand Down Expand Up @@ -73,6 +74,7 @@ function handleSSEEvent(
callbacks.onToolResult({
tool_use_id: resultData.tool_use_id,
content: resultData.content,
is_error: resultData.is_error,
});
} catch {
// skip malformed tool_result data
Expand Down
10 changes: 9 additions & 1 deletion src/lib/bridge/permission-broker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import type { PermissionUpdate } from '@anthropic-ai/claude-agent-sdk';
import type { ChannelAddress, OutboundMessage } from './types';
import type { BaseChannelAdapter } from './channel-adapter';
import { deliver } from './delivery-layer';
import { insertPermissionLink, getPermissionLink, markPermissionLinkResolved, getSession, getDb } from '../db';
import { insertPermissionLink, getPermissionLink, markPermissionLinkResolved, getSession, getDb, getSetting } from '../db';
import { resolvePendingPermission } from '../permission-registry';
import { escapeHtml } from './adapters/telegram-utils';

Expand All @@ -36,6 +36,14 @@ export async function forwardPermissionRequest(
suggestions?: unknown[],
replyToMessageId?: string,
): Promise<void> {
// Check if auto-approval is enabled globally — auto-approve without IM notification
const globalAutoApprove = getSetting('dangerously_skip_permissions') === 'true';
if (globalAutoApprove) {
console.log(`[bridge] Auto-approved permission ${permissionRequestId} (tool=${toolName}) due to global auto-approval setting`);
resolvePendingPermission(permissionRequestId, { behavior: 'allow' });
return;
}

// Check if this session uses full_access permission profile — auto-approve without IM notification
if (sessionId) {
const session = getSession(sessionId);
Expand Down
47 changes: 33 additions & 14 deletions src/lib/claude-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { registerPendingPermission } from './permission-registry';
import { registerConversation, unregisterConversation } from './conversation-registry';
import { captureCapabilities, isCacheFresh, setCachedPlugins } from './agent-sdk-capabilities';
import { getSetting, updateSdkSessionId, createPermissionRequest } from './db';
// Auto-approval is now handled at the CodePilot level, not via SDK bypassPermissions
import { resolveForClaudeCode, toClaudeCodeEnv } from './provider-resolver';
import { findClaudeBinary, findGitBash, getExpandedPath, invalidateClaudePathCache } from './platform';
import { notifyPermissionRequest, notifyGeneric } from './telegram-bot';
Expand Down Expand Up @@ -317,14 +318,26 @@ export async function generateTextViaSdk(params: {
const queryOptions: Options = {
cwd: os.homedir(),
abortController,
permissionMode: 'bypassPermissions',
allowDangerouslySkipPermissions: true,
permissionMode: 'acceptEdits',
env: sanitizeEnv(sdkEnv),
settingSources: resolved.settingSources as Options['settingSources'],
systemPrompt: params.system,
maxTurns: 1,
};

// Add auto-approval handler for this simple query
const globalAutoApprove = getSetting('dangerously_skip_permissions') === 'true';
if (globalAutoApprove) {
queryOptions.canUseTool = async (toolName, _input, opts) => {
console.log(`[claude-client] Auto-approved ${toolName} (auto-approval enabled)`);
return {
behavior: 'allow',
updatedInput: _input,
...(opts.suggestions ? { updatedPermissions: opts.suggestions } : {}),
};
};
}

if (params.model) {
queryOptions.model = params.model;
}
Expand Down Expand Up @@ -456,18 +469,15 @@ export function streamClaude(options: ClaudeStreamOptions): ReadableStream<strin
console.warn('[claude-client] No API key found: no active provider, no legacy settings, and no ANTHROPIC_API_KEY/ANTHROPIC_AUTH_TOKEN in environment');
}


// Check if dangerously_skip_permissions is enabled globally or per-session
const globalSkip = getSetting('dangerously_skip_permissions') === 'true';
const skipPermissions = globalSkip || !!sessionBypassPermissions;
// Check if auto-approval is enabled globally or per-session
const globalAutoApprove = getSetting('dangerously_skip_permissions') === 'true';
const shouldAutoApprove = globalAutoApprove || !!sessionBypassPermissions;

const queryOptions: Options = {
cwd: resolvedWorkingDirectory.path,
abortController,
includePartialMessages: true,
permissionMode: skipPermissions
? 'bypassPermissions'
: ((permissionMode as Options['permissionMode']) || 'acceptEdits'),
permissionMode: (permissionMode as Options['permissionMode']) || 'acceptEdits',
env: sanitizeEnv(sdkEnv),
// Load settings so the SDK behaves like the CLI (tool permissions,
// CLAUDE.md, etc.). When an active provider is configured in
Expand All @@ -477,10 +487,6 @@ export function streamClaude(options: ClaudeStreamOptions): ReadableStream<strin
settingSources: resolved.settingSources as Options['settingSources'],
};

if (skipPermissions) {
queryOptions.allowDangerouslySkipPermissions = true;
}

// Find claude binary for packaged app where PATH is limited.
// On Windows, npm installs Claude CLI as a .cmd wrapper which cannot
// be spawned directly without shell:true. Parse the wrapper to
Expand Down Expand Up @@ -617,10 +623,20 @@ export function streamClaude(options: ClaudeStreamOptions): ReadableStream<strin
queryOptions.resume = sdkSessionId;
}

// Permission handler: sends SSE event and waits for user response
// Permission handler: auto-approve if enabled, or sends SSE event and waits for user response
queryOptions.canUseTool = async (toolName, input, opts) => {
const permissionRequestId = `perm-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;

// If auto-approval is enabled, approve immediately without user interaction
if (shouldAutoApprove) {
console.log(`[claude-client] Auto-approved ${toolName} (auto-approval enabled)`);
return {
behavior: 'allow',
updatedInput: input,
...(opts.suggestions ? { updatedPermissions: opts.suggestions } : {}),
};
}

const permEvent: PermissionRequestEvent = {
permissionRequestId,
toolName,
Expand Down Expand Up @@ -916,6 +932,9 @@ export function streamClaude(options: ClaudeStreamOptions): ReadableStream<strin
is_error: block.is_error || false,
}),
}));
if (block.is_error) {
console.warn(`[claude-client] Tool ${block.tool_use_id} failed: ${resultContent}`);
}

// Deferred TodoWrite sync: only emit task_update after successful execution
if (!block.is_error && pendingTodoWrites.has(block.tool_use_id)) {
Expand Down
1 change: 1 addition & 0 deletions src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -916,6 +916,7 @@ export interface ToolUseInfo {
export interface ToolResultInfo {
tool_use_id: string;
content: string;
is_error?: boolean;
}

export type StreamPhase = 'active' | 'completed' | 'error' | 'stopped';
Expand Down
Loading