Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
372e6ce
feat: Implement World Model UI enhancements and Smart Expand
claude Jan 10, 2026
61d709b
backup: AutoMaker state before World Model customization
claude Jan 10, 2026
406258c
fix: Add missing SmartExpandDialog export to dialogs index
claude Jan 10, 2026
7e0f501
fix: Remove duplicate Preview Info block causing JSX syntax error
claude Jan 10, 2026
c0c6132
fix: Move handleRunSmartExpand after useBoardActions to fix temporal …
claude Jan 10, 2026
8b8cfd6
feat: Enhance Smart Expand with World Model ancestry
claude Jan 10, 2026
fc5eff8
feat: Implement Subspec Creation System with recursive context propag…
claude Jan 11, 2026
6cb3837
feat: Modularize AI providers, integrate Z.AI, and genericize model s…
claude Jan 11, 2026
6bddc98
fix: Resolve remaining type conflicts and syntax errors in libs
claude Jan 11, 2026
623aee8
fix: Resolve server-side conflicts and install dependencies
claude Jan 11, 2026
aafd886
feat: Z.AI provider integration, World Model view, enhanced model sup…
claude Jan 12, 2026
1d44800
docs: add JSDoc headers to UI components for docstring coverage
claude Jan 12, 2026
e4a36a2
fix(server): robust github cli detection for windows shims
claude Jan 12, 2026
7779117
chore: enable auto-open in browser for web dev mode
claude Jan 12, 2026
d54b844
chore: restore _dev:web script alias for compatibility
claude Jan 12, 2026
1492757
chore: temporary nuclear auth bypass for local web debugging
claude Jan 12, 2026
9cc8ff8
chore: revert auth bypass (secure for production)
claude Jan 12, 2026
39f785d
docs: update README with Z.AI integration details
claude Jan 12, 2026
00f26a2
fix(types): add zai api key to credentials interface for persistence
claude Jan 12, 2026
03d8dae
Performance Workstation Unlimited Memory
claude Jan 12, 2026
0900c42
Performance Workstation: Efficient Idle Mode
claude Jan 12, 2026
0f3ce68
Fix memory leak: Cap logs at 10k lines
claude Jan 12, 2026
0b73b37
Performance: Increase maxConcurrency to 24 (matching CPU cores)
claude Jan 12, 2026
281a054
Chore: Add react-virtuoso dependency
claude Jan 12, 2026
d881114
Perf: Use Virtual Scrolling for LogViewer (react-virtuoso)
claude Jan 12, 2026
cfa84da
Perf: Fix virtualization layout in AgentOutputModal
claude Jan 12, 2026
ed3be2b
Perf: Optimize LogViewer with useDeferredValue
claude Jan 12, 2026
63587cd
Fix: Add missing useDeferredValue import
claude Jan 12, 2026
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
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,9 @@ Traditional development tools help you write code. Automaker helps you **orchest
4. **Review & Verify** - Review the changes, run tests, and approve when ready
5. **Ship Faster** - Build entire applications in days, not weeks

### Powered by Claude Agent SDK
### Powered by Claude Agent SDK & Z.AI

Automaker leverages the [Claude Agent SDK](https://www.npmjs.com/package/@anthropic-ai/claude-agent-sdk) to give AI agents full access to your codebase. Agents can read files, write code, execute commands, run tests, and make git commitsβ€”all while working in isolated git worktrees to keep your main branch safe. The SDK provides autonomous AI agents that can use tools, make decisions, and complete complex multi-step tasks without constant human intervention.
Automaker leverages the [Claude Agent SDK](https://www.npmjs.com/package/@anthropic-ai/claude-agent-sdk) and proprietary **Z.AI Provider** to give AI agents full access to your codebase. Configure agents to use Anthropic's Claude 3.5 Sonnet or **Z.AI's GLM-4** models for powerful, reasoning-first development. Agents can read files, write code, execute commands, run tests, and make git commitsβ€”all while working in isolated git worktrees to keep your main branch safe. The SDK provides autonomous AI agents that can use tools, make decisions, and complete complex multi-step tasks without constant human intervention.

### Why This Matters

Expand Down Expand Up @@ -130,7 +130,8 @@ npm run dev
**Authentication Setup:** On first run, Automaker will automatically show a setup wizard where you can configure authentication. You can choose to:

- Use **Claude Code CLI** (recommended) - Automaker will detect your CLI credentials automatically
- Enter an **API key** directly in the wizard
- Enter an **Anthropic API key** directly in the wizard
- Enter a **Z.AI API key** directly in the wizard (for Z.AI models)

If you prefer to set up authentication before running (e.g., for headless deployments or CI/CD), you can set it manually:

Expand Down Expand Up @@ -412,7 +413,7 @@ The application can store your API key securely in the settings UI. The key is p

### AI & Planning

- 🧠 **Multi-Model Support** - Choose from Claude Opus, Sonnet, and Haiku per feature
- 🧠 **Multi-Model Support** - Choose from Claude Opus, Sonnet, Haiku, or **Z.AI GLM-4** per feature
- πŸ’­ **Extended Thinking** - Enable thinking modes (none, medium, deep, ultra) for complex problem-solving
- πŸ“ **Planning Modes** - Four planning levels: skip (direct implementation), lite (quick plan), spec (task breakdown), full (phased execution)
- βœ… **Plan Approval** - Review and approve AI-generated plans before implementation begins
Expand Down Expand Up @@ -477,6 +478,7 @@ The application can store your API key securely in the settings UI. The key is p
- **Express 5** - HTTP server framework
- **TypeScript 5.9** - Type safety
- **Claude Agent SDK** - AI agent integration (@anthropic-ai/claude-agent-sdk)
- **Z.AI Provider** - Custom integration for Z.AI models
- **WebSocket (ws)** - Real-time event streaming
- **node-pty** - PTY terminal sessions

Expand Down
5 changes: 5 additions & 0 deletions apps/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@
"@automaker/model-resolver": "1.0.0",
"@automaker/platform": "1.0.0",
"@automaker/prompts": "1.0.0",
"@automaker/provider-claude": "1.0.0",
"@automaker/provider-zai": "1.0.0",
"@automaker/providers-core": "1.0.0",
"@automaker/types": "1.0.0",
"@automaker/utils": "1.0.0",
"@modelcontextprotocol/sdk": "1.25.1",
Expand All @@ -38,6 +41,7 @@
"cors": "2.8.5",
"dotenv": "17.2.3",
"express": "5.2.1",
"jsonwebtoken": "^9.0.3",
"morgan": "1.10.1",
"node-pty": "1.1.0-beta41",
"ws": "8.18.3"
Expand All @@ -47,6 +51,7 @@
"@types/cookie-parser": "1.4.10",
"@types/cors": "2.8.19",
"@types/express": "5.0.6",
"@types/jsonwebtoken": "^9.0.10",
"@types/morgan": "1.9.10",
"@types/node": "22.19.3",
"@types/ws": "8.18.1",
Expand Down
153 changes: 153 additions & 0 deletions apps/server/src/cluster-manager.ts
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 });
}
}
71 changes: 71 additions & 0 deletions apps/server/src/diagnostics/test-write-persistence.ts
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);
Loading