-
Notifications
You must be signed in to change notification settings - Fork 489
feat(performance): boost concurrency and vram limits #452
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
GhostDragonAlpha
wants to merge
23
commits into
AutoMaker-Org:main
from
GhostDragonAlpha:performance/concurrency-vram-fix
Closed
Changes from all commits
Commits
Show all changes
23 commits
Select commit
Hold shift + click to select a range
372e6ce
feat: Implement World Model UI enhancements and Smart Expand
claude 61d709b
backup: AutoMaker state before World Model customization
claude 406258c
fix: Add missing SmartExpandDialog export to dialogs index
claude 7e0f501
fix: Remove duplicate Preview Info block causing JSX syntax error
claude c0c6132
fix: Move handleRunSmartExpand after useBoardActions to fix temporal …
claude 8b8cfd6
feat: Enhance Smart Expand with World Model ancestry
claude fc5eff8
feat: Implement Subspec Creation System with recursive context propag…
claude 6cb3837
feat: Modularize AI providers, integrate Z.AI, and genericize model s…
claude 6bddc98
fix: Resolve remaining type conflicts and syntax errors in libs
claude 623aee8
fix: Resolve server-side conflicts and install dependencies
claude aafd886
feat: Z.AI provider integration, World Model view, enhanced model sup…
claude 1d44800
docs: add JSDoc headers to UI components for docstring coverage
claude e4a36a2
fix(server): robust github cli detection for windows shims
claude 7779117
chore: enable auto-open in browser for web dev mode
claude d54b844
chore: restore _dev:web script alias for compatibility
claude 1492757
chore: temporary nuclear auth bypass for local web debugging
claude 9cc8ff8
chore: revert auth bypass (secure for production)
claude 39f785d
docs: update README with Z.AI integration details
claude 00f26a2
fix(types): add zai api key to credentials interface for persistence
claude b1e0156
Performance Workstation Unlimited Memory
claude 646fa66
Performance Workstation: Efficient Idle Mode
claude 31b5d7c
Fix memory leak: Cap logs at 10k lines
claude e2fd343
Performance: Increase maxConcurrency to 24 (matching CPU cores)
claude File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,153 @@ | ||
| /** | ||
| * Cluster Manager for AutoMaker Server | ||
| * | ||
| * Enables multi-core CPU utilization by spawning worker processes. | ||
| * The master process manages workers and restarts them on crash. | ||
| * Workers share the port via OS-level load balancing. | ||
| * | ||
| * Usage: | ||
| * import { initCluster } from './cluster-manager.js'; | ||
| * initCluster(() => { startServer(); }); | ||
| */ | ||
|
|
||
| import cluster from 'cluster'; | ||
| import os from 'os'; | ||
| import { createLogger } from './utils/logger.js'; | ||
|
|
||
| const logger = createLogger('Cluster'); | ||
|
|
||
| // Configuration | ||
| const CLUSTER_ENABLED = process.env.CLUSTER_MODE === 'true'; | ||
| const WORKER_COUNT = parseInt(process.env.WORKER_COUNT || '0', 10) || os.cpus().length; | ||
| const RESTART_DELAY_MS = 1000; | ||
|
|
||
| // Track worker restarts to prevent rapid restart loops | ||
| const workerRestarts = new Map<number, number>(); | ||
| const MAX_RESTARTS_PER_MINUTE = 5; | ||
|
|
||
| /** | ||
| * Initialize cluster mode if enabled. | ||
| * In cluster mode, the master spawns workers that each run the server. | ||
| * | ||
| * @param startWorker - Function to start the server (called in each worker) | ||
| * @returns true if this process should continue (worker or non-cluster), false if master | ||
| */ | ||
| export function initCluster(startWorker: () => void): boolean { | ||
| // Skip cluster mode if disabled or in development | ||
| if (!CLUSTER_ENABLED) { | ||
| logger.info('Cluster mode disabled, running single-process'); | ||
| startWorker(); | ||
| return true; | ||
| } | ||
|
|
||
| if (cluster.isPrimary) { | ||
| logger.info(`Master process ${process.pid} starting ${WORKER_COUNT} workers`); | ||
|
|
||
| // Fork workers | ||
| for (let i = 0; i < WORKER_COUNT; i++) { | ||
| forkWorker(); | ||
| } | ||
|
|
||
| // Handle worker exit - restart with exponential backoff | ||
| cluster.on('exit', (worker, code, signal) => { | ||
| const workerId = worker.id; | ||
| const exitReason = signal ? `signal ${signal}` : `code ${code}`; | ||
|
|
||
| if (code !== 0) { | ||
| logger.warn(`Worker ${workerId} (PID ${worker.process.pid}) died (${exitReason})`); | ||
|
|
||
| // Check restart rate limiting | ||
| const now = Date.now(); | ||
| const lastRestarts = workerRestarts.get(workerId) || 0; | ||
|
|
||
| if (lastRestarts >= MAX_RESTARTS_PER_MINUTE) { | ||
| logger.error(`Worker ${workerId} restarted too many times, not restarting`); | ||
| return; | ||
| } | ||
|
|
||
| workerRestarts.set(workerId, lastRestarts + 1); | ||
|
|
||
| // Clear restart count after 1 minute | ||
| setTimeout(() => { | ||
| workerRestarts.set(workerId, Math.max(0, (workerRestarts.get(workerId) || 0) - 1)); | ||
| }, 60000); | ||
|
|
||
| // Restart with delay | ||
| setTimeout(() => { | ||
| logger.info(`Restarting worker ${workerId}...`); | ||
| forkWorker(); | ||
| }, RESTART_DELAY_MS); | ||
| } else { | ||
| logger.info(`Worker ${workerId} exited gracefully`); | ||
| } | ||
| }); | ||
|
|
||
| // Handle graceful shutdown | ||
| const shutdown = () => { | ||
| logger.info('Master shutting down, terminating workers...'); | ||
| for (const id in cluster.workers) { | ||
| cluster.workers[id]?.kill('SIGTERM'); | ||
| } | ||
| process.exit(0); | ||
| }; | ||
|
|
||
| process.on('SIGINT', shutdown); | ||
| process.on('SIGTERM', shutdown); | ||
|
|
||
| return false; // Master doesn't run server code | ||
| } else { | ||
| // Worker process - run the server | ||
| logger.info(`Worker ${cluster.worker?.id} (PID ${process.pid}) started`); | ||
| startWorker(); | ||
| return true; | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Fork a new worker process | ||
| */ | ||
| function forkWorker(): void { | ||
| const worker = cluster.fork(); | ||
|
|
||
| // Handle worker messages (for inter-process communication if needed) | ||
| worker.on('message', (msg: { type: string; data?: unknown }) => { | ||
| if (msg.type === 'broadcast') { | ||
| // Broadcast message to all workers | ||
| for (const id in cluster.workers) { | ||
| if (cluster.workers[id] !== worker) { | ||
| cluster.workers[id]?.send(msg); | ||
| } | ||
| } | ||
| } | ||
| }); | ||
| } | ||
|
|
||
| /** | ||
| * Check if this process is the master/primary | ||
| */ | ||
| export function isMaster(): boolean { | ||
| return cluster.isPrimary; | ||
| } | ||
|
|
||
| /** | ||
| * Get the current worker ID (0 if not in cluster mode or if master) | ||
| */ | ||
| export function getWorkerId(): number { | ||
| return cluster.worker?.id || 0; | ||
| } | ||
|
|
||
| /** | ||
| * Get total worker count | ||
| */ | ||
| export function getWorkerCount(): number { | ||
| return CLUSTER_ENABLED ? WORKER_COUNT : 1; | ||
| } | ||
|
|
||
| /** | ||
| * Broadcast a message to all workers (call from any worker) | ||
| */ | ||
| export function broadcastToWorkers(data: unknown): void { | ||
| if (cluster.isWorker && process.send) { | ||
| process.send({ type: 'broadcast', data }); | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,71 @@ | ||
| /** | ||
| * Diagnostic Test: ZaiTools Write Persistence | ||
| * | ||
| * Tests whether ZaiTools.executeWriteFile correctly writes to disk. | ||
| * Run with: npx tsx apps/server/src/diagnostics/test-write-persistence.ts | ||
| */ | ||
|
|
||
| import path from 'path'; | ||
| import fs from 'fs/promises'; | ||
| import { ZaiTools } from '../providers/zai-tools.js'; | ||
|
|
||
| async function testWritePersistence() { | ||
| const testDir = process.cwd(); | ||
| const testFilePath = path.join(testDir, 'test-write-persistence-output.txt'); | ||
| const testContent = `Test file created at ${new Date().toISOString()}\nIf you see this, ZaiTools.Write is working correctly.`; | ||
|
|
||
| console.log('=== ZaiTools Write Persistence Test ===\n'); | ||
| console.log(`Working directory: ${testDir}`); | ||
| console.log(`Test file path: ${testFilePath}\n`); | ||
|
|
||
| const zaiTools = new ZaiTools(testDir); | ||
|
|
||
| // Test 1: Write via executeTool | ||
| console.log('1. Testing executeTool("Write", {...})...'); | ||
| const result = await zaiTools.executeTool('Write', { | ||
| path: testFilePath, | ||
| content: testContent, | ||
| }); | ||
| console.log(` Result: ${result}`); | ||
|
|
||
| // Test 2: Verify file exists | ||
| console.log('\n2. Verifying file exists on disk...'); | ||
| try { | ||
| const stat = await fs.stat(testFilePath); | ||
| console.log(` ✅ File exists! Size: ${stat.size} bytes`); | ||
| } catch (error) { | ||
| console.log(` ❌ File does NOT exist: ${(error as Error).message}`); | ||
| console.log(' This confirms the Write tool is BROKEN.'); | ||
| process.exit(1); | ||
| } | ||
|
|
||
| // Test 3: Read content back | ||
| console.log('\n3. Reading content back...'); | ||
| try { | ||
| const readContent = await fs.readFile(testFilePath, 'utf-8'); | ||
| if (readContent === testContent) { | ||
| console.log(' ✅ Content matches exactly!'); | ||
| } else { | ||
| console.log(' ⚠️ Content differs:'); | ||
| console.log(` Expected: ${testContent.substring(0, 50)}...`); | ||
| console.log(` Got: ${readContent.substring(0, 50)}...`); | ||
| } | ||
| } catch (error) { | ||
| console.log(` ❌ Failed to read: ${(error as Error).message}`); | ||
| } | ||
|
|
||
| // Cleanup | ||
| console.log('\n4. Cleaning up test file...'); | ||
| try { | ||
| await fs.unlink(testFilePath); | ||
| console.log(' ✅ Test file removed.'); | ||
| } catch { | ||
| console.log(' ⚠️ Could not remove test file (non-critical).'); | ||
| } | ||
|
|
||
| console.log('\n=== Test Complete ==='); | ||
| console.log('If you see ✅ for steps 1-3, ZaiTools.Write is working correctly.'); | ||
| console.log('The issue must be in how auto-mode-service invokes it during feature execution.\n'); | ||
| } | ||
|
|
||
| testWritePersistence().catch(console.error); |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
nowvariable is declared but never used. This unused variable should be removed to improve code clarity.