mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-02 20:43:36 +00:00
Merge remote-tracking branch 'origin/v0.10.0rc' into stefandevo/main
This commit is contained in:
@@ -13,6 +13,7 @@ import { getApiKey, getSessionToken, getServerUrlSync } from './http-api-client'
|
||||
|
||||
// Server URL - uses shared cached URL from http-api-client
|
||||
const getServerUrl = (): string => getServerUrlSync();
|
||||
const DEFAULT_CACHE_MODE: RequestCache = 'no-store';
|
||||
|
||||
export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
|
||||
|
||||
@@ -80,7 +81,7 @@ export async function apiFetch(
|
||||
method: HttpMethod = 'GET',
|
||||
options: ApiFetchOptions = {}
|
||||
): Promise<Response> {
|
||||
const { headers: additionalHeaders, body, skipAuth, ...restOptions } = options;
|
||||
const { headers: additionalHeaders, body, skipAuth, cache, ...restOptions } = options;
|
||||
|
||||
const headers = skipAuth
|
||||
? { 'Content-Type': 'application/json', ...additionalHeaders }
|
||||
@@ -90,6 +91,7 @@ export async function apiFetch(
|
||||
method,
|
||||
headers,
|
||||
credentials: 'include',
|
||||
cache: cache ?? DEFAULT_CACHE_MODE,
|
||||
...restOptions,
|
||||
};
|
||||
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
import { type CodexCreditsSnapshot, type CodexPlanType } from '@/store/app-store';
|
||||
import { type CodexPlanType } from '@/store/app-store';
|
||||
|
||||
const WINDOW_DEFAULT_LABEL = 'Usage window';
|
||||
const RESET_LABEL = 'Resets';
|
||||
const UNKNOWN_LABEL = 'Unknown';
|
||||
const UNAVAILABLE_LABEL = 'Unavailable';
|
||||
const UNLIMITED_LABEL = 'Unlimited';
|
||||
const AVAILABLE_LABEL = 'Available';
|
||||
const NONE_LABEL = 'None';
|
||||
const DAY_UNIT = 'day';
|
||||
const HOUR_UNIT = 'hour';
|
||||
const MINUTE_UNIT = 'min';
|
||||
@@ -77,10 +73,3 @@ export function formatCodexPlanType(plan: CodexPlanType | null): string {
|
||||
if (!plan) return UNKNOWN_LABEL;
|
||||
return PLAN_TYPE_LABELS[plan] ?? plan;
|
||||
}
|
||||
|
||||
export function formatCodexCredits(snapshot: CodexCreditsSnapshot | null): string {
|
||||
if (!snapshot) return UNAVAILABLE_LABEL;
|
||||
if (snapshot.unlimited) return UNLIMITED_LABEL;
|
||||
if (snapshot.balance) return snapshot.balance;
|
||||
return snapshot.hasCredits ? AVAILABLE_LABEL : NONE_LABEL;
|
||||
}
|
||||
|
||||
@@ -433,11 +433,12 @@ export interface SpecRegenerationAPI {
|
||||
success: boolean;
|
||||
error?: string;
|
||||
}>;
|
||||
stop: () => Promise<{ success: boolean; error?: string }>;
|
||||
status: () => Promise<{
|
||||
stop: (projectPath?: string) => Promise<{ success: boolean; error?: string }>;
|
||||
status: (projectPath?: string) => Promise<{
|
||||
success: boolean;
|
||||
isRunning?: boolean;
|
||||
currentPhase?: string;
|
||||
projectPath?: string;
|
||||
error?: string;
|
||||
}>;
|
||||
onEvent: (callback: (event: SpecRegenerationEvent) => void) => () => void;
|
||||
@@ -461,7 +462,8 @@ export interface FeaturesAPI {
|
||||
featureId: string,
|
||||
updates: Partial<Feature>,
|
||||
descriptionHistorySource?: 'enhance' | 'edit',
|
||||
enhancementMode?: 'improve' | 'technical' | 'simplify' | 'acceptance'
|
||||
enhancementMode?: 'improve' | 'technical' | 'simplify' | 'acceptance' | 'ux-reviewer',
|
||||
preEnhancementDescription?: string
|
||||
) => Promise<{ success: boolean; feature?: Feature; error?: string }>;
|
||||
delete: (projectPath: string, featureId: string) => Promise<{ success: boolean; error?: string }>;
|
||||
getAgentOutput: (
|
||||
@@ -532,6 +534,9 @@ export interface AutoModeAPI {
|
||||
editedPlan?: string,
|
||||
feedback?: string
|
||||
) => Promise<{ success: boolean; error?: string }>;
|
||||
resumeInterrupted: (
|
||||
projectPath: string
|
||||
) => Promise<{ success: boolean; message?: string; error?: string }>;
|
||||
onEvent: (callback: (event: AutoModeEvent) => void) => () => void;
|
||||
}
|
||||
|
||||
@@ -608,7 +613,8 @@ export interface ElectronAPI {
|
||||
enhance: (
|
||||
originalText: string,
|
||||
enhancementMode: string,
|
||||
model?: string
|
||||
model?: string,
|
||||
thinkingLevel?: string
|
||||
) => Promise<{
|
||||
success: boolean;
|
||||
enhancedText?: string;
|
||||
@@ -727,6 +733,20 @@ export interface ElectronAPI {
|
||||
ideation?: IdeationAPI;
|
||||
codex?: {
|
||||
getUsage: () => Promise<CodexUsageResponse>;
|
||||
getModels: (refresh?: boolean) => Promise<{
|
||||
success: boolean;
|
||||
models?: Array<{
|
||||
id: string;
|
||||
label: string;
|
||||
description: string;
|
||||
hasThinking: boolean;
|
||||
supportsVision: boolean;
|
||||
tier: 'premium' | 'standard' | 'basic';
|
||||
isDefault: boolean;
|
||||
}>;
|
||||
cachedAt?: number;
|
||||
error?: string;
|
||||
}>;
|
||||
};
|
||||
settings?: {
|
||||
getStatus: () => Promise<{
|
||||
@@ -2086,6 +2106,11 @@ function createMockAutoModeAPI(): AutoModeAPI {
|
||||
return { success: true };
|
||||
},
|
||||
|
||||
resumeInterrupted: async (projectPath: string) => {
|
||||
console.log('[Mock] Resume interrupted features for:', projectPath);
|
||||
return { success: true, message: 'Mock: no interrupted features' };
|
||||
},
|
||||
|
||||
onEvent: (callback: (event: AutoModeEvent) => void) => {
|
||||
mockAutoModeCallbacks.push(callback);
|
||||
return () => {
|
||||
@@ -2515,7 +2540,7 @@ function createMockSpecRegenerationAPI(): SpecRegenerationAPI {
|
||||
return { success: true };
|
||||
},
|
||||
|
||||
stop: async () => {
|
||||
stop: async (_projectPath?: string) => {
|
||||
mockSpecRegenerationRunning = false;
|
||||
mockSpecRegenerationPhase = '';
|
||||
if (mockSpecRegenerationTimeout) {
|
||||
@@ -2525,7 +2550,7 @@ function createMockSpecRegenerationAPI(): SpecRegenerationAPI {
|
||||
return { success: true };
|
||||
},
|
||||
|
||||
status: async () => {
|
||||
status: async (_projectPath?: string) => {
|
||||
return {
|
||||
success: true,
|
||||
isRunning: mockSpecRegenerationRunning,
|
||||
@@ -3020,6 +3045,7 @@ export interface Project {
|
||||
path: string;
|
||||
lastOpened?: string;
|
||||
theme?: string; // Per-project theme override (uses ThemeMode from app-store)
|
||||
isFavorite?: boolean; // Pin project to top of dashboard
|
||||
}
|
||||
|
||||
export interface TrashedProject extends Project {
|
||||
|
||||
@@ -39,6 +39,7 @@ import type { WorktreeAPI, GitAPI, ModelDefinition, ProviderStatus } from '@/typ
|
||||
import { getGlobalFileBrowser } from '@/contexts/file-browser-context';
|
||||
|
||||
const logger = createLogger('HttpClient');
|
||||
const NO_STORE_CACHE_MODE: RequestCache = 'no-store';
|
||||
|
||||
// Cached server URL (set during initialization in Electron mode)
|
||||
let cachedServerUrl: string | null = null;
|
||||
@@ -69,6 +70,7 @@ const handleUnauthorized = (): void => {
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
credentials: 'include',
|
||||
body: '{}',
|
||||
cache: NO_STORE_CACHE_MODE,
|
||||
}).catch(() => {});
|
||||
notifyLoggedOut();
|
||||
};
|
||||
@@ -296,6 +298,7 @@ export const checkAuthStatus = async (): Promise<{
|
||||
const response = await fetch(`${getServerUrl()}/api/auth/status`, {
|
||||
credentials: 'include',
|
||||
headers: getApiKey() ? { 'X-API-Key': getApiKey()! } : undefined,
|
||||
cache: NO_STORE_CACHE_MODE,
|
||||
});
|
||||
const data = await response.json();
|
||||
return {
|
||||
@@ -322,6 +325,7 @@ export const login = async (
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
credentials: 'include',
|
||||
body: JSON.stringify({ apiKey }),
|
||||
cache: NO_STORE_CACHE_MODE,
|
||||
});
|
||||
const data = await response.json();
|
||||
|
||||
@@ -361,6 +365,7 @@ export const fetchSessionToken = async (): Promise<boolean> => {
|
||||
try {
|
||||
const response = await fetch(`${getServerUrl()}/api/auth/status`, {
|
||||
credentials: 'include', // Send the session cookie
|
||||
cache: NO_STORE_CACHE_MODE,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
@@ -391,6 +396,7 @@ export const logout = async (): Promise<{ success: boolean }> => {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
credentials: 'include',
|
||||
cache: NO_STORE_CACHE_MODE,
|
||||
});
|
||||
|
||||
// Clear the cached session token
|
||||
@@ -439,6 +445,7 @@ export const verifySession = async (): Promise<boolean> => {
|
||||
const response = await fetch(`${getServerUrl()}/api/settings/status`, {
|
||||
headers,
|
||||
credentials: 'include',
|
||||
cache: NO_STORE_CACHE_MODE,
|
||||
// Avoid hanging indefinitely during backend reloads or network issues
|
||||
signal: AbortSignal.timeout(2500),
|
||||
});
|
||||
@@ -475,6 +482,7 @@ export const checkSandboxEnvironment = async (): Promise<{
|
||||
try {
|
||||
const response = await fetch(`${getServerUrl()}/api/health/environment`, {
|
||||
method: 'GET',
|
||||
cache: NO_STORE_CACHE_MODE,
|
||||
signal: AbortSignal.timeout(5000),
|
||||
});
|
||||
|
||||
@@ -556,6 +564,7 @@ export class HttpApiClient implements ElectronAPI {
|
||||
const response = await fetch(`${this.serverUrl}/api/auth/token`, {
|
||||
headers,
|
||||
credentials: 'include',
|
||||
cache: NO_STORE_CACHE_MODE,
|
||||
});
|
||||
|
||||
if (response.status === 401 || response.status === 403) {
|
||||
@@ -587,6 +596,17 @@ export class HttpApiClient implements ElectronAPI {
|
||||
|
||||
this.isConnecting = true;
|
||||
|
||||
// Wait for API key initialization to complete before attempting connection
|
||||
// This prevents race conditions during app startup
|
||||
waitForApiKeyInit()
|
||||
.then(() => this.doConnectWebSocketInternal())
|
||||
.catch((error) => {
|
||||
logger.error('Failed to initialize for WebSocket connection:', error);
|
||||
this.isConnecting = false;
|
||||
});
|
||||
}
|
||||
|
||||
private doConnectWebSocketInternal(): void {
|
||||
// Electron mode typically authenticates with the injected API key.
|
||||
// However, in external-server/cookie-auth flows, the API key may be unavailable.
|
||||
// In that case, fall back to the same wsToken/cookie authentication used in web mode
|
||||
@@ -771,6 +791,7 @@ export class HttpApiClient implements ElectronAPI {
|
||||
const response = await fetch(`${this.serverUrl}${endpoint}`, {
|
||||
headers: this.getHeaders(),
|
||||
credentials: 'include', // Include cookies for session auth
|
||||
cache: NO_STORE_CACHE_MODE,
|
||||
});
|
||||
|
||||
if (response.status === 401 || response.status === 403) {
|
||||
@@ -1438,6 +1459,16 @@ export class HttpApiClient implements ElectronAPI {
|
||||
features?: Feature[];
|
||||
error?: string;
|
||||
}>;
|
||||
bulkDelete: (
|
||||
projectPath: string,
|
||||
featureIds: string[]
|
||||
) => Promise<{
|
||||
success: boolean;
|
||||
deletedCount?: number;
|
||||
failedCount?: number;
|
||||
results?: Array<{ featureId: string; success: boolean; error?: string }>;
|
||||
error?: string;
|
||||
}>;
|
||||
} = {
|
||||
getAll: (projectPath: string) => this.post('/api/features/list', { projectPath }),
|
||||
get: (projectPath: string, featureId: string) =>
|
||||
@@ -1449,7 +1480,8 @@ export class HttpApiClient implements ElectronAPI {
|
||||
featureId: string,
|
||||
updates: Partial<Feature>,
|
||||
descriptionHistorySource?: 'enhance' | 'edit',
|
||||
enhancementMode?: 'improve' | 'technical' | 'simplify' | 'acceptance'
|
||||
enhancementMode?: 'improve' | 'technical' | 'simplify' | 'acceptance' | 'ux-reviewer',
|
||||
preEnhancementDescription?: string
|
||||
) =>
|
||||
this.post('/api/features/update', {
|
||||
projectPath,
|
||||
@@ -1457,6 +1489,7 @@ export class HttpApiClient implements ElectronAPI {
|
||||
updates,
|
||||
descriptionHistorySource,
|
||||
enhancementMode,
|
||||
preEnhancementDescription,
|
||||
}),
|
||||
delete: (projectPath: string, featureId: string) =>
|
||||
this.post('/api/features/delete', { projectPath, featureId }),
|
||||
@@ -1466,6 +1499,8 @@ export class HttpApiClient implements ElectronAPI {
|
||||
this.post('/api/features/generate-title', { description }),
|
||||
bulkUpdate: (projectPath: string, featureIds: string[], updates: Partial<Feature>) =>
|
||||
this.post('/api/features/bulk-update', { projectPath, featureIds, updates }),
|
||||
bulkDelete: (projectPath: string, featureIds: string[]) =>
|
||||
this.post('/api/features/bulk-delete', { projectPath, featureIds }),
|
||||
};
|
||||
|
||||
// Auto Mode API
|
||||
@@ -1533,6 +1568,8 @@ export class HttpApiClient implements ElectronAPI {
|
||||
editedPlan,
|
||||
feedback,
|
||||
}),
|
||||
resumeInterrupted: (projectPath: string) =>
|
||||
this.post('/api/auto-mode/resume-interrupted', { projectPath }),
|
||||
onEvent: (callback: (event: AutoModeEvent) => void) => {
|
||||
return this.subscribeToEvent('auto-mode:event', callback as EventCallback);
|
||||
},
|
||||
@@ -1669,8 +1706,13 @@ export class HttpApiClient implements ElectronAPI {
|
||||
projectPath,
|
||||
maxFeatures,
|
||||
}),
|
||||
stop: () => this.post('/api/spec-regeneration/stop'),
|
||||
status: () => this.get('/api/spec-regeneration/status'),
|
||||
stop: (projectPath?: string) => this.post('/api/spec-regeneration/stop', { projectPath }),
|
||||
status: (projectPath?: string) =>
|
||||
this.get(
|
||||
projectPath
|
||||
? `/api/spec-regeneration/status?projectPath=${encodeURIComponent(projectPath)}`
|
||||
: '/api/spec-regeneration/status'
|
||||
),
|
||||
onEvent: (callback: (event: SpecRegenerationEvent) => void) => {
|
||||
return this.subscribeToEvent('spec-regeneration:event', callback as EventCallback);
|
||||
},
|
||||
@@ -1859,19 +1901,15 @@ export class HttpApiClient implements ElectronAPI {
|
||||
theme: string;
|
||||
sidebarOpen: boolean;
|
||||
chatHistoryOpen: boolean;
|
||||
kanbanCardDetailLevel: string;
|
||||
maxConcurrency: number;
|
||||
defaultSkipTests: boolean;
|
||||
enableDependencyBlocking: boolean;
|
||||
useWorktrees: boolean;
|
||||
showProfilesOnly: boolean;
|
||||
defaultPlanningMode: string;
|
||||
defaultRequirePlanApproval: boolean;
|
||||
defaultAIProfileId: string | null;
|
||||
muteDoneSound: boolean;
|
||||
enhancementModel: string;
|
||||
keyboardShortcuts: Record<string, string>;
|
||||
aiProfiles: unknown[];
|
||||
projects: unknown[];
|
||||
trashedProjects: unknown[];
|
||||
projectHistory: string[];
|
||||
@@ -1955,6 +1993,7 @@ export class HttpApiClient implements ElectronAPI {
|
||||
cardBorderOpacity: number;
|
||||
hideScrollbar: boolean;
|
||||
};
|
||||
worktreePanelVisible?: boolean;
|
||||
lastSelectedSessionId?: string;
|
||||
};
|
||||
error?: string;
|
||||
@@ -2057,6 +2096,25 @@ export class HttpApiClient implements ElectronAPI {
|
||||
// Codex API
|
||||
codex = {
|
||||
getUsage: (): Promise<CodexUsageResponse> => this.get('/api/codex/usage'),
|
||||
getModels: (
|
||||
refresh = false
|
||||
): Promise<{
|
||||
success: boolean;
|
||||
models?: Array<{
|
||||
id: string;
|
||||
label: string;
|
||||
description: string;
|
||||
hasThinking: boolean;
|
||||
supportsVision: boolean;
|
||||
tier: 'premium' | 'standard' | 'basic';
|
||||
isDefault: boolean;
|
||||
}>;
|
||||
cachedAt?: number;
|
||||
error?: string;
|
||||
}> => {
|
||||
const url = `/api/codex/models${refresh ? '?refresh=true' : ''}`;
|
||||
return this.get(url);
|
||||
},
|
||||
};
|
||||
|
||||
// Context API
|
||||
|
||||
Reference in New Issue
Block a user