feat: add GitHub issue fix command and release command

- Introduced a new command for fetching and validating GitHub issues, allowing users to address issues directly from the command line.
- Added a release command to bump the version of the application and build the Electron app, ensuring version consistency across UI and server packages.
- Updated package.json files for both UI and server to version 0.7.1, reflecting the latest changes.
- Implemented version utility in the server to read the version from package.json, enhancing version management across the application.
This commit is contained in:
WebDevCody
2025-12-31 23:24:01 -05:00
parent eae60ab6b9
commit 98381441b9
38 changed files with 1406 additions and 295 deletions

View File

@@ -7,6 +7,7 @@ export type ErrorType =
| 'abort'
| 'execution'
| 'rate_limit'
| 'quota_exhausted'
| 'unknown';
/**
@@ -19,6 +20,7 @@ export interface ErrorInfo {
isAuth: boolean;
isCancellation: boolean;
isRateLimit: boolean;
isQuotaExhausted: boolean; // Session/weekly usage limit reached
retryAfter?: number; // Seconds to wait before retrying (for rate limit errors)
originalError: unknown;
}

View File

@@ -351,7 +351,7 @@ export interface GlobalSettings {
// Claude Agent SDK Settings
/** Auto-load CLAUDE.md files using SDK's settingSources option */
autoLoadClaudeMd?: boolean;
/** Enable sandbox mode for bash commands (default: true, disable if issues occur) */
/** Enable sandbox mode for bash commands (default: false, enable for additional security) */
enableSandboxMode?: boolean;
// MCP Server Configuration
@@ -523,7 +523,7 @@ export const DEFAULT_GLOBAL_SETTINGS: GlobalSettings = {
worktreePanelCollapsed: false,
lastSelectedSessionByProject: {},
autoLoadClaudeMd: false,
enableSandboxMode: true,
enableSandboxMode: false,
mcpServers: [],
// Default to true for autonomous workflow. Security is enforced when adding servers
// via the security warning dialog that explains the risks.

View File

@@ -4,6 +4,7 @@
* Provides utilities for:
* - Detecting abort/cancellation errors
* - Detecting authentication errors
* - Detecting rate limit and quota exhaustion errors
* - Classifying errors by type
* - Generating user-friendly error messages
*/
@@ -52,7 +53,7 @@ export function isAuthenticationError(errorMessage: string): boolean {
}
/**
* Check if an error is a rate limit error
* Check if an error is a rate limit error (429 Too Many Requests)
*
* @param error - The error to check
* @returns True if the error is a rate limit error
@@ -62,6 +63,60 @@ export function isRateLimitError(error: unknown): boolean {
return message.includes('429') || message.includes('rate_limit');
}
/**
* Check if an error indicates quota/usage exhaustion
* This includes session limits, weekly limits, credit/billing issues, and overloaded errors
*
* @param error - The error to check
* @returns True if the error indicates quota exhaustion
*/
export function isQuotaExhaustedError(error: unknown): boolean {
const message = error instanceof Error ? error.message : String(error || '');
const lowerMessage = message.toLowerCase();
// Check for overloaded/capacity errors
if (
lowerMessage.includes('overloaded') ||
lowerMessage.includes('overloaded_error') ||
lowerMessage.includes('capacity')
) {
return true;
}
// Check for usage/quota limit patterns
if (
lowerMessage.includes('limit reached') ||
lowerMessage.includes('usage limit') ||
lowerMessage.includes('quota exceeded') ||
lowerMessage.includes('quota_exceeded') ||
lowerMessage.includes('session limit') ||
lowerMessage.includes('weekly limit') ||
lowerMessage.includes('monthly limit')
) {
return true;
}
// Check for billing/credit issues
if (
lowerMessage.includes('credit balance') ||
lowerMessage.includes('insufficient credits') ||
lowerMessage.includes('insufficient balance') ||
lowerMessage.includes('no credits') ||
lowerMessage.includes('out of credits') ||
lowerMessage.includes('billing') ||
lowerMessage.includes('payment required')
) {
return true;
}
// Check for upgrade prompts (often indicates limit reached)
if (lowerMessage.includes('/upgrade') || lowerMessage.includes('extra-usage')) {
return true;
}
return false;
}
/**
* Extract retry-after duration from rate limit error
*
@@ -98,11 +153,15 @@ export function classifyError(error: unknown): ErrorInfo {
const isAuth = isAuthenticationError(message);
const isCancellation = isCancellationError(message);
const isRateLimit = isRateLimitError(error);
const isQuotaExhausted = isQuotaExhaustedError(error);
const retryAfter = isRateLimit ? (extractRetryAfter(error) ?? 60) : undefined;
let type: ErrorType;
if (isAuth) {
type = 'authentication';
} else if (isQuotaExhausted) {
// Quota exhaustion takes priority over rate limit since it's more specific
type = 'quota_exhausted';
} else if (isRateLimit) {
type = 'rate_limit';
} else if (isAbort) {
@@ -122,6 +181,7 @@ export function classifyError(error: unknown): ErrorInfo {
isAuth,
isCancellation,
isRateLimit,
isQuotaExhausted,
retryAfter,
originalError: error,
};
@@ -144,6 +204,10 @@ export function getUserFriendlyErrorMessage(error: unknown): string {
return 'Authentication failed. Please check your API key.';
}
if (info.isQuotaExhausted) {
return 'Usage limit reached. Auto Mode has been paused. Please wait for your quota to reset or upgrade your plan.';
}
if (info.isRateLimit) {
const retryMsg = info.retryAfter
? ` Please wait ${info.retryAfter} seconds before retrying.`

View File

@@ -9,6 +9,7 @@ export {
isCancellationError,
isAuthenticationError,
isRateLimitError,
isQuotaExhaustedError,
extractRetryAfter,
classifyError,
getUserFriendlyErrorMessage,

View File

@@ -4,6 +4,7 @@ import {
isCancellationError,
isAuthenticationError,
isRateLimitError,
isQuotaExhaustedError,
extractRetryAfter,
classifyError,
getUserFriendlyErrorMessage,
@@ -129,6 +130,55 @@ describe('error-handler.ts', () => {
});
});
describe('isQuotaExhaustedError', () => {
it('should return true for overloaded errors', () => {
expect(isQuotaExhaustedError(new Error('overloaded_error: service is busy'))).toBe(true);
expect(isQuotaExhaustedError(new Error('Server is overloaded'))).toBe(true);
expect(isQuotaExhaustedError(new Error('At capacity'))).toBe(true);
});
it('should return true for usage limit errors', () => {
expect(isQuotaExhaustedError(new Error('limit reached'))).toBe(true);
expect(isQuotaExhaustedError(new Error('Usage limit exceeded'))).toBe(true);
expect(isQuotaExhaustedError(new Error('quota exceeded'))).toBe(true);
expect(isQuotaExhaustedError(new Error('quota_exceeded'))).toBe(true);
expect(isQuotaExhaustedError(new Error('session limit reached'))).toBe(true);
expect(isQuotaExhaustedError(new Error('weekly limit hit'))).toBe(true);
expect(isQuotaExhaustedError(new Error('monthly limit reached'))).toBe(true);
});
it('should return true for billing/credit errors', () => {
expect(isQuotaExhaustedError(new Error('credit balance is too low'))).toBe(true);
expect(isQuotaExhaustedError(new Error('insufficient credits'))).toBe(true);
expect(isQuotaExhaustedError(new Error('insufficient balance'))).toBe(true);
expect(isQuotaExhaustedError(new Error('no credits remaining'))).toBe(true);
expect(isQuotaExhaustedError(new Error('out of credits'))).toBe(true);
expect(isQuotaExhaustedError(new Error('billing issue detected'))).toBe(true);
expect(isQuotaExhaustedError(new Error('payment required'))).toBe(true);
});
it('should return true for upgrade prompts', () => {
expect(isQuotaExhaustedError(new Error('Please /upgrade your plan'))).toBe(true);
expect(isQuotaExhaustedError(new Error('extra-usage not enabled'))).toBe(true);
});
it('should return false for regular errors', () => {
expect(isQuotaExhaustedError(new Error('Something went wrong'))).toBe(false);
expect(isQuotaExhaustedError(new Error('Network error'))).toBe(false);
expect(isQuotaExhaustedError(new Error(''))).toBe(false);
});
it('should return false for null/undefined', () => {
expect(isQuotaExhaustedError(null)).toBe(false);
expect(isQuotaExhaustedError(undefined)).toBe(false);
});
it('should handle string errors', () => {
expect(isQuotaExhaustedError('overloaded')).toBe(true);
expect(isQuotaExhaustedError('regular error')).toBe(false);
});
});
describe('extractRetryAfter', () => {
it('should extract retry-after from error message', () => {
const error = new Error('Rate limit exceeded. retry-after: 60');
@@ -170,10 +220,37 @@ describe('error-handler.ts', () => {
expect(result.isAbort).toBe(false);
expect(result.isCancellation).toBe(false);
expect(result.isRateLimit).toBe(false);
expect(result.isQuotaExhausted).toBe(false);
expect(result.message).toBe('Authentication failed');
expect(result.originalError).toBe(error);
});
it('should classify quota exhausted errors', () => {
const error = new Error('overloaded_error: service is busy');
const result = classifyError(error);
expect(result.type).toBe('quota_exhausted');
expect(result.isQuotaExhausted).toBe(true);
expect(result.isRateLimit).toBe(false);
expect(result.isAuth).toBe(false);
});
it('should classify credit balance errors as quota exhausted', () => {
const error = new Error('credit balance is too low');
const result = classifyError(error);
expect(result.type).toBe('quota_exhausted');
expect(result.isQuotaExhausted).toBe(true);
});
it('should classify usage limit errors as quota exhausted', () => {
const error = new Error('usage limit reached');
const result = classifyError(error);
expect(result.type).toBe('quota_exhausted');
expect(result.isQuotaExhausted).toBe(true);
});
it('should classify rate limit errors', () => {
const error = new Error('Error: 429 rate_limit_error');
const result = classifyError(error);
@@ -320,6 +397,14 @@ describe('error-handler.ts', () => {
expect(message).toBe('Authentication failed. Please check your API key.');
});
it('should return friendly message for quota exhausted errors', () => {
const error = new Error('overloaded_error');
const message = getUserFriendlyErrorMessage(error);
expect(message).toContain('Usage limit reached');
expect(message).toContain('Auto Mode has been paused');
});
it('should return friendly message for rate limit errors', () => {
const error = new Error('429 rate_limit_error');
const message = getUserFriendlyErrorMessage(error);