feat: Add branch-based beta toggle for switching between stable and beta versions

Adds Version Channel section to Settings sidebar allowing users to:
- See current branch (main or beta/7.0) and stability status
- Switch to beta branch to access Endless Mode features
- Switch back to stable for production use
- Pull updates for current branch

Implementation:
- BranchManager.ts: Git operations for branch detection/switching
- worker-service.ts: /api/branch/* endpoints (status, switch, update)
- Sidebar.tsx: Version Channel UI with branch state and handlers

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Alex Newman
2025-11-25 14:12:49 -05:00
parent e3a63c0294
commit 8caf159d99
5 changed files with 608 additions and 102 deletions
+89
View File
@@ -29,6 +29,7 @@ import { SSEBroadcaster } from './worker/SSEBroadcaster.js';
import { SDKAgent } from './worker/SDKAgent.js';
import { PaginationHelper } from './worker/PaginationHelper.js';
import { SettingsManager } from './worker/SettingsManager.js';
import { getBranchInfo, switchBranch, pullUpdates, type BranchInfo, type SwitchResult } from './worker/BranchManager.js';
export class WorkerService {
private app: express.Application;
@@ -173,6 +174,11 @@ export class WorkerService {
this.app.get('/api/mcp/status', this.handleGetMcpStatus.bind(this));
this.app.post('/api/mcp/toggle', this.handleToggleMcp.bind(this));
// Branch switching (beta toggle)
this.app.get('/api/branch/status', this.handleGetBranchStatus.bind(this));
this.app.post('/api/branch/switch', this.handleSwitchBranch.bind(this));
this.app.post('/api/branch/update', this.handleUpdateBranch.bind(this));
// Search API endpoints (for skill-based search)
// Unified endpoints (new consolidated API)
this.app.get('/api/search', this.handleUnifiedSearch.bind(this));
@@ -1035,6 +1041,89 @@ export class WorkerService {
}
}
// ============================================================================
// Branch Switching Handlers (Beta Toggle)
// ============================================================================
/**
* GET /api/branch/status - Get current branch information
*/
private handleGetBranchStatus(req: Request, res: Response): void {
try {
const info = getBranchInfo();
res.json(info);
} catch (error) {
logger.failure('WORKER', 'Failed to get branch status', {}, error as Error);
res.status(500).json({ error: (error as Error).message });
}
}
/**
* POST /api/branch/switch - Switch to a different branch
* Body: { branch: "main" | "beta/7.0" }
*/
private async handleSwitchBranch(req: Request, res: Response): Promise<void> {
try {
const { branch } = req.body;
if (!branch) {
res.status(400).json({ success: false, error: 'Missing branch parameter' });
return;
}
// Validate branch name
const allowedBranches = ['main', 'beta/7.0'];
if (!allowedBranches.includes(branch)) {
res.status(400).json({
success: false,
error: `Invalid branch. Allowed: ${allowedBranches.join(', ')}`
});
return;
}
logger.info('WORKER', 'Branch switch requested', { branch });
const result = await switchBranch(branch);
if (result.success) {
// Schedule worker restart after response is sent
setTimeout(() => {
logger.info('WORKER', 'Restarting worker after branch switch');
process.exit(0); // PM2 will restart the worker
}, 1000);
}
res.json(result);
} catch (error) {
logger.failure('WORKER', 'Branch switch failed', {}, error as Error);
res.status(500).json({ success: false, error: (error as Error).message });
}
}
/**
* POST /api/branch/update - Pull latest updates for current branch
*/
private async handleUpdateBranch(req: Request, res: Response): Promise<void> {
try {
logger.info('WORKER', 'Branch update requested');
const result = await pullUpdates();
if (result.success) {
// Schedule worker restart after response is sent
setTimeout(() => {
logger.info('WORKER', 'Restarting worker after branch update');
process.exit(0); // PM2 will restart the worker
}, 1000);
}
res.json(result);
} catch (error) {
logger.failure('WORKER', 'Branch update failed', {}, error as Error);
res.status(500).json({ success: false, error: (error as Error).message });
}
}
// ============================================================================
// Search API Handlers (for skill-based search)
// ============================================================================