diff --git a/.gitignore b/.gitignore
index 1c19aa51..736a7235 100644
--- a/.gitignore
+++ b/.gitignore
@@ -98,3 +98,5 @@ data/
# GSD planning docs (local-only)
.planning/
+.mcp.json
+.planning
diff --git a/CLAUDE.md b/CLAUDE.md
index 128cd8d7..84dd1fbb 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -161,7 +161,7 @@ Use `resolveModelString()` from `@automaker/model-resolver` to convert model ali
- `haiku` → `claude-haiku-4-5`
- `sonnet` → `claude-sonnet-4-20250514`
-- `opus` → `claude-opus-4-5-20251101`
+- `opus` → `claude-opus-4-6`
## Environment Variables
diff --git a/Dockerfile b/Dockerfile
index 03911b45..a68901e4 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -118,6 +118,7 @@ RUN curl -fsSL https://opencode.ai/install | bash && \
echo "=== Checking OpenCode CLI installation ===" && \
ls -la /home/automaker/.local/bin/ && \
(which opencode && opencode --version) || echo "opencode installed (may need auth setup)"
+
USER root
# Add PATH to profile so it's available in all interactive shells (for login shells)
@@ -147,6 +148,15 @@ COPY --from=server-builder /app/apps/server/package*.json ./apps/server/
# Copy node_modules (includes symlinks to libs)
COPY --from=server-builder /app/node_modules ./node_modules
+# Install Playwright Chromium browser for AI agent verification tests
+# This adds ~300MB to the image but enables automated testing mode out of the box
+# Using the locally installed playwright ensures we use the pinned version from package-lock.json
+USER automaker
+RUN ./node_modules/.bin/playwright install chromium && \
+ echo "=== Playwright Chromium installed ===" && \
+ ls -la /home/automaker/.cache/ms-playwright/
+USER root
+
# Create data and projects directories
RUN mkdir -p /data /projects && chown automaker:automaker /data /projects
diff --git a/README.md b/README.md
index 49b14343..98f8683e 100644
--- a/README.md
+++ b/README.md
@@ -367,6 +367,42 @@ services:
The Docker image supports both AMD64 and ARM64 architectures. The GitHub CLI and Claude CLI are automatically downloaded for the correct architecture during build.
+##### Playwright for Automated Testing
+
+The Docker image includes **Playwright Chromium pre-installed** for AI agent verification tests. When agents implement features in automated testing mode, they use Playwright to verify the implementation works correctly.
+
+**No additional setup required** - Playwright verification works out of the box.
+
+#### Optional: Persist browsers for manual updates
+
+By default, Playwright Chromium is pre-installed in the Docker image. If you need to manually update browsers or want to persist browser installations across container restarts (not image rebuilds), you can mount a volume.
+
+**Important:** When you first add this volume mount to an existing setup, the empty volume will override the pre-installed browsers. You must re-install them:
+
+```bash
+# After adding the volume mount for the first time
+docker exec --user automaker -w /app automaker-server npx playwright install chromium
+```
+
+Add this to your `docker-compose.override.yml`:
+
+```yaml
+services:
+ server:
+ volumes:
+ - playwright-cache:/home/automaker/.cache/ms-playwright
+
+volumes:
+ playwright-cache:
+ name: automaker-playwright-cache
+```
+
+**Updating browsers manually:**
+
+```bash
+docker exec --user automaker -w /app automaker-server npx playwright install chromium
+```
+
### Testing
#### End-to-End Tests (Playwright)
diff --git a/apps/server/package.json b/apps/server/package.json
index c9015aea..4a7a75a8 100644
--- a/apps/server/package.json
+++ b/apps/server/package.json
@@ -24,7 +24,7 @@
"test:unit": "vitest run tests/unit"
},
"dependencies": {
- "@anthropic-ai/claude-agent-sdk": "0.1.76",
+ "@anthropic-ai/claude-agent-sdk": "0.2.32",
"@automaker/dependency-resolver": "1.0.0",
"@automaker/git-utils": "1.0.0",
"@automaker/model-resolver": "1.0.0",
@@ -34,7 +34,7 @@
"@automaker/utils": "1.0.0",
"@github/copilot-sdk": "^0.1.16",
"@modelcontextprotocol/sdk": "1.25.2",
- "@openai/codex-sdk": "^0.77.0",
+ "@openai/codex-sdk": "^0.98.0",
"cookie-parser": "1.4.7",
"cors": "2.8.5",
"dotenv": "17.2.3",
@@ -45,6 +45,7 @@
"yaml": "2.7.0"
},
"devDependencies": {
+ "@playwright/test": "1.57.0",
"@types/cookie": "0.6.0",
"@types/cookie-parser": "1.4.10",
"@types/cors": "2.8.19",
diff --git a/apps/server/src/index.ts b/apps/server/src/index.ts
index 6c63a865..a78e9e83 100644
--- a/apps/server/src/index.ts
+++ b/apps/server/src/index.ts
@@ -121,21 +121,57 @@ const BOX_CONTENT_WIDTH = 67;
// The Claude Agent SDK can use either ANTHROPIC_API_KEY or Claude Code CLI authentication
(async () => {
const hasAnthropicKey = !!process.env.ANTHROPIC_API_KEY;
+ const hasEnvOAuthToken = !!process.env.CLAUDE_CODE_OAUTH_TOKEN;
+
+ logger.debug('[CREDENTIAL_CHECK] Starting credential detection...');
+ logger.debug('[CREDENTIAL_CHECK] Environment variables:', {
+ hasAnthropicKey,
+ hasEnvOAuthToken,
+ });
if (hasAnthropicKey) {
logger.info('✓ ANTHROPIC_API_KEY detected');
return;
}
+ if (hasEnvOAuthToken) {
+ logger.info('✓ CLAUDE_CODE_OAUTH_TOKEN detected');
+ return;
+ }
+
// Check for Claude Code CLI authentication
+ // Store indicators outside the try block so we can use them in the warning message
+ let cliAuthIndicators: Awaited> | null = null;
+
try {
- const indicators = await getClaudeAuthIndicators();
+ cliAuthIndicators = await getClaudeAuthIndicators();
+ const indicators = cliAuthIndicators;
+
+ // Log detailed credential detection results
+ const { checks, ...indicatorSummary } = indicators;
+ logger.debug('[CREDENTIAL_CHECK] Claude CLI auth indicators:', indicatorSummary);
+
+ logger.debug('[CREDENTIAL_CHECK] File check details:', checks);
+
const hasCliAuth =
indicators.hasStatsCacheWithActivity ||
(indicators.hasSettingsFile && indicators.hasProjectsSessions) ||
(indicators.hasCredentialsFile &&
(indicators.credentials?.hasOAuthToken || indicators.credentials?.hasApiKey));
+ logger.debug('[CREDENTIAL_CHECK] Auth determination:', {
+ hasCliAuth,
+ reason: hasCliAuth
+ ? indicators.hasStatsCacheWithActivity
+ ? 'stats cache with activity'
+ : indicators.hasSettingsFile && indicators.hasProjectsSessions
+ ? 'settings file + project sessions'
+ : indicators.credentials?.hasOAuthToken
+ ? 'credentials file with OAuth token'
+ : 'credentials file with API key'
+ : 'no valid credentials found',
+ });
+
if (hasCliAuth) {
logger.info('✓ Claude Code CLI authentication detected');
return;
@@ -145,7 +181,7 @@ const BOX_CONTENT_WIDTH = 67;
logger.warn('Error checking for Claude Code CLI authentication:', error);
}
- // No authentication found - show warning
+ // No authentication found - show warning with paths that were checked
const wHeader = '⚠️ WARNING: No Claude authentication configured'.padEnd(BOX_CONTENT_WIDTH);
const w1 = 'The Claude Agent SDK requires authentication to function.'.padEnd(BOX_CONTENT_WIDTH);
const w2 = 'Options:'.padEnd(BOX_CONTENT_WIDTH);
@@ -158,6 +194,33 @@ const BOX_CONTENT_WIDTH = 67;
BOX_CONTENT_WIDTH
);
+ // Build paths checked summary from the indicators (if available)
+ let pathsCheckedInfo = '';
+ if (cliAuthIndicators) {
+ const pathsChecked: string[] = [];
+
+ // Collect paths that were checked (paths are always populated strings)
+ pathsChecked.push(`Settings: ${cliAuthIndicators.checks.settingsFile.path}`);
+ pathsChecked.push(`Stats cache: ${cliAuthIndicators.checks.statsCache.path}`);
+ pathsChecked.push(`Projects dir: ${cliAuthIndicators.checks.projectsDir.path}`);
+ for (const credFile of cliAuthIndicators.checks.credentialFiles) {
+ pathsChecked.push(`Credentials: ${credFile.path}`);
+ }
+
+ if (pathsChecked.length > 0) {
+ pathsCheckedInfo = `
+║ ║
+║ ${'Paths checked:'.padEnd(BOX_CONTENT_WIDTH)}║
+${pathsChecked
+ .map((p) => {
+ const maxLen = BOX_CONTENT_WIDTH - 4;
+ const display = p.length > maxLen ? '...' + p.slice(-(maxLen - 3)) : p;
+ return `║ ${display.padEnd(maxLen)} ║`;
+ })
+ .join('\n')}`;
+ }
+ }
+
logger.warn(`
╔═════════════════════════════════════════════════════════════════════╗
║ ${wHeader}║
@@ -169,7 +232,7 @@ const BOX_CONTENT_WIDTH = 67;
║ ${w3}║
║ ${w4}║
║ ${w5}║
-║ ${w6}║
+║ ${w6}║${pathsCheckedInfo}
║ ║
╚═════════════════════════════════════════════════════════════════════╝
`);
diff --git a/apps/server/src/lib/sdk-options.ts b/apps/server/src/lib/sdk-options.ts
index cc1df2f5..674350a5 100644
--- a/apps/server/src/lib/sdk-options.ts
+++ b/apps/server/src/lib/sdk-options.ts
@@ -253,11 +253,27 @@ function buildMcpOptions(config: CreateSdkOptionsConfig): McpOptions {
/**
* Build thinking options for SDK configuration.
* Converts ThinkingLevel to maxThinkingTokens for the Claude SDK.
+ * For adaptive thinking (Opus 4.6), omits maxThinkingTokens to let the model
+ * decide its own reasoning depth.
*
* @param thinkingLevel - The thinking level to convert
- * @returns Object with maxThinkingTokens if thinking is enabled
+ * @returns Object with maxThinkingTokens if thinking is enabled with a budget
*/
function buildThinkingOptions(thinkingLevel?: ThinkingLevel): Partial {
+ if (!thinkingLevel || thinkingLevel === 'none') {
+ return {};
+ }
+
+ // Adaptive thinking (Opus 4.6): don't set maxThinkingTokens
+ // The model will use adaptive thinking by default
+ if (thinkingLevel === 'adaptive') {
+ logger.debug(
+ `buildThinkingOptions: thinkingLevel="adaptive" -> no maxThinkingTokens (model decides)`
+ );
+ return {};
+ }
+
+ // Manual budget-based thinking for Haiku/Sonnet
const maxThinkingTokens = getThinkingTokenBudget(thinkingLevel);
logger.debug(
`buildThinkingOptions: thinkingLevel="${thinkingLevel}" -> maxThinkingTokens=${maxThinkingTokens}`
diff --git a/apps/server/src/providers/claude-provider.ts b/apps/server/src/providers/claude-provider.ts
index cfb59093..78a0a0c7 100644
--- a/apps/server/src/providers/claude-provider.ts
+++ b/apps/server/src/providers/claude-provider.ts
@@ -219,8 +219,11 @@ export class ClaudeProvider extends BaseProvider {
// claudeCompatibleProvider takes precedence over claudeApiProfile
const providerConfig = claudeCompatibleProvider || claudeApiProfile;
- // Convert thinking level to token budget
- const maxThinkingTokens = getThinkingTokenBudget(thinkingLevel);
+ // Build thinking configuration
+ // Adaptive thinking (Opus 4.6): don't set maxThinkingTokens, model uses adaptive by default
+ // Manual thinking (Haiku/Sonnet): use budget_tokens
+ const maxThinkingTokens =
+ thinkingLevel === 'adaptive' ? undefined : getThinkingTokenBudget(thinkingLevel);
// Build Claude SDK options
const sdkOptions: Options = {
@@ -349,13 +352,13 @@ export class ClaudeProvider extends BaseProvider {
getAvailableModels(): ModelDefinition[] {
const models = [
{
- id: 'claude-opus-4-5-20251101',
- name: 'Claude Opus 4.5',
- modelString: 'claude-opus-4-5-20251101',
+ id: 'claude-opus-4-6',
+ name: 'Claude Opus 4.6',
+ modelString: 'claude-opus-4-6',
provider: 'anthropic',
- description: 'Most capable Claude model',
+ description: 'Most capable Claude model with adaptive thinking',
contextWindow: 200000,
- maxOutputTokens: 16000,
+ maxOutputTokens: 128000,
supportsVision: true,
supportsTools: true,
tier: 'premium' as const,
diff --git a/apps/server/src/providers/codex-models.ts b/apps/server/src/providers/codex-models.ts
index 141d5355..7840888b 100644
--- a/apps/server/src/providers/codex-models.ts
+++ b/apps/server/src/providers/codex-models.ts
@@ -19,12 +19,11 @@ const MAX_OUTPUT_16K = 16000;
export const CODEX_MODELS: ModelDefinition[] = [
// ========== Recommended Codex Models ==========
{
- id: CODEX_MODEL_MAP.gpt52Codex,
- name: 'GPT-5.2-Codex',
- modelString: CODEX_MODEL_MAP.gpt52Codex,
+ id: CODEX_MODEL_MAP.gpt53Codex,
+ name: 'GPT-5.3-Codex',
+ modelString: CODEX_MODEL_MAP.gpt53Codex,
provider: 'openai',
- description:
- 'Most advanced agentic coding model for complex software engineering (default for ChatGPT users).',
+ description: 'Latest frontier agentic coding model.',
contextWindow: CONTEXT_WINDOW_256K,
maxOutputTokens: MAX_OUTPUT_32K,
supportsVision: true,
@@ -33,12 +32,25 @@ export const CODEX_MODELS: ModelDefinition[] = [
default: true,
hasReasoning: true,
},
+ {
+ id: CODEX_MODEL_MAP.gpt52Codex,
+ name: 'GPT-5.2-Codex',
+ modelString: CODEX_MODEL_MAP.gpt52Codex,
+ provider: 'openai',
+ description: 'Frontier agentic coding model.',
+ contextWindow: CONTEXT_WINDOW_256K,
+ maxOutputTokens: MAX_OUTPUT_32K,
+ supportsVision: true,
+ supportsTools: true,
+ tier: 'premium' as const,
+ hasReasoning: true,
+ },
{
id: CODEX_MODEL_MAP.gpt51CodexMax,
name: 'GPT-5.1-Codex-Max',
modelString: CODEX_MODEL_MAP.gpt51CodexMax,
provider: 'openai',
- description: 'Optimized for long-horizon, agentic coding tasks in Codex.',
+ description: 'Codex-optimized flagship for deep and fast reasoning.',
contextWindow: CONTEXT_WINDOW_256K,
maxOutputTokens: MAX_OUTPUT_32K,
supportsVision: true,
@@ -51,7 +63,7 @@ export const CODEX_MODELS: ModelDefinition[] = [
name: 'GPT-5.1-Codex-Mini',
modelString: CODEX_MODEL_MAP.gpt51CodexMini,
provider: 'openai',
- description: 'Smaller, more cost-effective version for faster workflows.',
+ description: 'Optimized for codex. Cheaper, faster, but less capable.',
contextWindow: CONTEXT_WINDOW_128K,
maxOutputTokens: MAX_OUTPUT_16K,
supportsVision: true,
@@ -66,7 +78,7 @@ export const CODEX_MODELS: ModelDefinition[] = [
name: 'GPT-5.2',
modelString: CODEX_MODEL_MAP.gpt52,
provider: 'openai',
- description: 'Best general agentic model for tasks across industries and domains.',
+ description: 'Latest frontier model with improvements across knowledge, reasoning and coding.',
contextWindow: CONTEXT_WINDOW_256K,
maxOutputTokens: MAX_OUTPUT_32K,
supportsVision: true,
diff --git a/apps/server/src/providers/provider-factory.ts b/apps/server/src/providers/provider-factory.ts
index 1e91760f..a6dff69e 100644
--- a/apps/server/src/providers/provider-factory.ts
+++ b/apps/server/src/providers/provider-factory.ts
@@ -103,7 +103,7 @@ export class ProviderFactory {
/**
* Get the appropriate provider for a given model ID
*
- * @param modelId Model identifier (e.g., "claude-opus-4-5-20251101", "cursor-gpt-4o", "cursor-auto")
+ * @param modelId Model identifier (e.g., "claude-opus-4-6", "cursor-gpt-4o", "cursor-auto")
* @param options Optional settings
* @param options.throwOnDisconnected Throw error if provider is disconnected (default: true)
* @returns Provider instance for the model
diff --git a/apps/server/src/routes/setup/routes/verify-claude-auth.ts b/apps/server/src/routes/setup/routes/verify-claude-auth.ts
index df04d462..7df27c3d 100644
--- a/apps/server/src/routes/setup/routes/verify-claude-auth.ts
+++ b/apps/server/src/routes/setup/routes/verify-claude-auth.ts
@@ -6,6 +6,7 @@
import type { Request, Response } from 'express';
import { query } from '@anthropic-ai/claude-agent-sdk';
import { createLogger } from '@automaker/utils';
+import { getClaudeAuthIndicators } from '@automaker/platform';
import { getApiKey } from '../common.js';
import {
createSecureAuthEnv,
@@ -320,9 +321,28 @@ export function createVerifyClaudeAuthHandler() {
authMethod,
});
+ // Determine specific auth type for success messages
+ const effectiveAuthMethod = authMethod ?? 'api_key';
+ let authType: 'oauth' | 'api_key' | 'cli' | undefined;
+ if (authenticated) {
+ if (effectiveAuthMethod === 'api_key') {
+ authType = 'api_key';
+ } else if (effectiveAuthMethod === 'cli') {
+ // Check if CLI auth is via OAuth (Claude Code subscription) or generic CLI
+ try {
+ const indicators = await getClaudeAuthIndicators();
+ authType = indicators.credentials?.hasOAuthToken ? 'oauth' : 'cli';
+ } catch {
+ // Fall back to generic CLI if credential check fails
+ authType = 'cli';
+ }
+ }
+ }
+
res.json({
success: true,
authenticated,
+ authType,
error: errorMessage || undefined,
});
} catch (error) {
diff --git a/apps/server/tests/unit/lib/model-resolver.test.ts b/apps/server/tests/unit/lib/model-resolver.test.ts
index c1bff78d..65e3115d 100644
--- a/apps/server/tests/unit/lib/model-resolver.test.ts
+++ b/apps/server/tests/unit/lib/model-resolver.test.ts
@@ -35,7 +35,7 @@ describe('model-resolver.ts', () => {
it("should resolve 'opus' alias to full model string", () => {
const result = resolveModelString('opus');
- expect(result).toBe('claude-opus-4-5-20251101');
+ expect(result).toBe('claude-opus-4-6');
expect(consoleSpy.log).toHaveBeenCalledWith(
expect.stringContaining('Migrated legacy ID: "opus" -> "claude-opus"')
);
@@ -117,7 +117,7 @@ describe('model-resolver.ts', () => {
describe('getEffectiveModel', () => {
it('should prioritize explicit model over session and default', () => {
const result = getEffectiveModel('opus', 'haiku', 'gpt-5.2');
- expect(result).toBe('claude-opus-4-5-20251101');
+ expect(result).toBe('claude-opus-4-6');
});
it('should use session model when explicit is not provided', () => {
diff --git a/apps/server/tests/unit/lib/sdk-options.test.ts b/apps/server/tests/unit/lib/sdk-options.test.ts
index 029cd8fa..69d69794 100644
--- a/apps/server/tests/unit/lib/sdk-options.test.ts
+++ b/apps/server/tests/unit/lib/sdk-options.test.ts
@@ -491,5 +491,29 @@ describe('sdk-options.ts', () => {
expect(options.maxThinkingTokens).toBeUndefined();
});
});
+
+ describe('adaptive thinking for Opus 4.6', () => {
+ it('should not set maxThinkingTokens for adaptive thinking (model decides)', async () => {
+ const { createAutoModeOptions } = await import('@/lib/sdk-options.js');
+
+ const options = createAutoModeOptions({
+ cwd: '/test/path',
+ thinkingLevel: 'adaptive',
+ });
+
+ expect(options.maxThinkingTokens).toBeUndefined();
+ });
+
+ it('should not include maxThinkingTokens when thinkingLevel is "none"', async () => {
+ const { createAutoModeOptions } = await import('@/lib/sdk-options.js');
+
+ const options = createAutoModeOptions({
+ cwd: '/test/path',
+ thinkingLevel: 'none',
+ });
+
+ expect(options.maxThinkingTokens).toBeUndefined();
+ });
+ });
});
});
diff --git a/apps/server/tests/unit/providers/claude-provider.test.ts b/apps/server/tests/unit/providers/claude-provider.test.ts
index c3f83f8f..7df211ef 100644
--- a/apps/server/tests/unit/providers/claude-provider.test.ts
+++ b/apps/server/tests/unit/providers/claude-provider.test.ts
@@ -39,7 +39,7 @@ describe('claude-provider.ts', () => {
const generator = provider.executeQuery({
prompt: 'Hello',
- model: 'claude-opus-4-5-20251101',
+ model: 'claude-opus-4-6',
cwd: '/test',
});
@@ -59,7 +59,7 @@ describe('claude-provider.ts', () => {
const generator = provider.executeQuery({
prompt: 'Test prompt',
- model: 'claude-opus-4-5-20251101',
+ model: 'claude-opus-4-6',
cwd: '/test/dir',
systemPrompt: 'You are helpful',
maxTurns: 10,
@@ -71,7 +71,7 @@ describe('claude-provider.ts', () => {
expect(sdk.query).toHaveBeenCalledWith({
prompt: 'Test prompt',
options: expect.objectContaining({
- model: 'claude-opus-4-5-20251101',
+ model: 'claude-opus-4-6',
systemPrompt: 'You are helpful',
maxTurns: 10,
cwd: '/test/dir',
@@ -91,7 +91,7 @@ describe('claude-provider.ts', () => {
const generator = provider.executeQuery({
prompt: 'Test',
- model: 'claude-opus-4-5-20251101',
+ model: 'claude-opus-4-6',
cwd: '/test',
});
@@ -116,7 +116,7 @@ describe('claude-provider.ts', () => {
const generator = provider.executeQuery({
prompt: 'Test',
- model: 'claude-opus-4-5-20251101',
+ model: 'claude-opus-4-6',
cwd: '/test',
abortController,
});
@@ -145,7 +145,7 @@ describe('claude-provider.ts', () => {
const generator = provider.executeQuery({
prompt: 'Current message',
- model: 'claude-opus-4-5-20251101',
+ model: 'claude-opus-4-6',
cwd: '/test',
conversationHistory,
sdkSessionId: 'test-session-id',
@@ -176,7 +176,7 @@ describe('claude-provider.ts', () => {
const generator = provider.executeQuery({
prompt: arrayPrompt as any,
- model: 'claude-opus-4-5-20251101',
+ model: 'claude-opus-4-6',
cwd: '/test',
});
@@ -196,7 +196,7 @@ describe('claude-provider.ts', () => {
const generator = provider.executeQuery({
prompt: 'Test',
- model: 'claude-opus-4-5-20251101',
+ model: 'claude-opus-4-6',
cwd: '/test',
});
@@ -222,7 +222,7 @@ describe('claude-provider.ts', () => {
const generator = provider.executeQuery({
prompt: 'Test',
- model: 'claude-opus-4-5-20251101',
+ model: 'claude-opus-4-6',
cwd: '/test',
});
@@ -286,7 +286,7 @@ describe('claude-provider.ts', () => {
const generator = provider.executeQuery({
prompt: 'Test',
- model: 'claude-opus-4-5-20251101',
+ model: 'claude-opus-4-6',
cwd: '/test',
});
@@ -313,7 +313,7 @@ describe('claude-provider.ts', () => {
const generator = provider.executeQuery({
prompt: 'Test',
- model: 'claude-opus-4-5-20251101',
+ model: 'claude-opus-4-6',
cwd: '/test',
});
@@ -341,7 +341,7 @@ describe('claude-provider.ts', () => {
const generator = provider.executeQuery({
prompt: 'Test',
- model: 'claude-opus-4-5-20251101',
+ model: 'claude-opus-4-6',
cwd: '/test',
});
@@ -366,12 +366,12 @@ describe('claude-provider.ts', () => {
expect(models).toHaveLength(4);
});
- it('should include Claude Opus 4.5', () => {
+ it('should include Claude Opus 4.6', () => {
const models = provider.getAvailableModels();
- const opus = models.find((m) => m.id === 'claude-opus-4-5-20251101');
+ const opus = models.find((m) => m.id === 'claude-opus-4-6');
expect(opus).toBeDefined();
- expect(opus?.name).toBe('Claude Opus 4.5');
+ expect(opus?.name).toBe('Claude Opus 4.6');
expect(opus?.provider).toBe('anthropic');
});
@@ -400,7 +400,7 @@ describe('claude-provider.ts', () => {
it('should mark Opus as default', () => {
const models = provider.getAvailableModels();
- const opus = models.find((m) => m.id === 'claude-opus-4-5-20251101');
+ const opus = models.find((m) => m.id === 'claude-opus-4-6');
expect(opus?.default).toBe(true);
});
diff --git a/apps/server/tests/unit/providers/provider-factory.test.ts b/apps/server/tests/unit/providers/provider-factory.test.ts
index fbf01e90..b9aef928 100644
--- a/apps/server/tests/unit/providers/provider-factory.test.ts
+++ b/apps/server/tests/unit/providers/provider-factory.test.ts
@@ -54,8 +54,8 @@ describe('provider-factory.ts', () => {
describe('getProviderForModel', () => {
describe('Claude models (claude-* prefix)', () => {
- it('should return ClaudeProvider for claude-opus-4-5-20251101', () => {
- const provider = ProviderFactory.getProviderForModel('claude-opus-4-5-20251101');
+ it('should return ClaudeProvider for claude-opus-4-6', () => {
+ const provider = ProviderFactory.getProviderForModel('claude-opus-4-6');
expect(provider).toBeInstanceOf(ClaudeProvider);
});
@@ -70,7 +70,7 @@ describe('provider-factory.ts', () => {
});
it('should be case-insensitive for claude models', () => {
- const provider = ProviderFactory.getProviderForModel('CLAUDE-OPUS-4-5-20251101');
+ const provider = ProviderFactory.getProviderForModel('CLAUDE-OPUS-4-6');
expect(provider).toBeInstanceOf(ClaudeProvider);
});
});
diff --git a/apps/ui/docs/AGENT_ARCHITECTURE.md b/apps/ui/docs/AGENT_ARCHITECTURE.md
index 4c9f0d11..f5c374c4 100644
--- a/apps/ui/docs/AGENT_ARCHITECTURE.md
+++ b/apps/ui/docs/AGENT_ARCHITECTURE.md
@@ -199,7 +199,7 @@ The agent is configured with:
```javascript
{
- model: "claude-opus-4-5-20251101",
+ model: "claude-opus-4-6",
maxTurns: 20,
cwd: workingDirectory,
allowedTools: [
diff --git a/apps/ui/src/components/dialogs/sandbox-risk-dialog.tsx b/apps/ui/src/components/dialogs/sandbox-risk-dialog.tsx
index 3a5f6d35..7b597c8c 100644
--- a/apps/ui/src/components/dialogs/sandbox-risk-dialog.tsx
+++ b/apps/ui/src/components/dialogs/sandbox-risk-dialog.tsx
@@ -69,6 +69,29 @@ export function SandboxRiskDialog({ open, onConfirm, onDeny }: SandboxRiskDialog
For safer operation, consider running Automaker in Docker. See the README for
instructions.
+
+
+
+ Already running in Docker? Try these troubleshooting steps:
+
+
+ -
+ Ensure
IS_CONTAINERIZED=true is
+ set in your docker-compose environment
+
+ -
+ Verify the server container has the environment variable:{' '}
+
+ docker exec automaker-server printenv IS_CONTAINERIZED
+
+
+ - Rebuild and restart containers if you recently changed the configuration
+ -
+ Check the server logs for startup messages:{' '}
+
docker-compose logs server
+
+
+
diff --git a/apps/ui/src/components/views/board-view/dialogs/add-feature-dialog.tsx b/apps/ui/src/components/views/board-view/dialogs/add-feature-dialog.tsx
index b8dd8776..a816204f 100644
--- a/apps/ui/src/components/views/board-view/dialogs/add-feature-dialog.tsx
+++ b/apps/ui/src/components/views/board-view/dialogs/add-feature-dialog.tsx
@@ -28,7 +28,7 @@ import { cn } from '@/lib/utils';
import { modelSupportsThinking } from '@/lib/utils';
import { useAppStore, ThinkingLevel, FeatureImage, PlanningMode, Feature } from '@/store/app-store';
import type { ReasoningEffort, PhaseModelEntry, AgentModel } from '@automaker/types';
-import { supportsReasoningEffort } from '@automaker/types';
+import { supportsReasoningEffort, isAdaptiveThinkingModel } from '@automaker/types';
import {
PrioritySelector,
WorkModeSelector,
@@ -264,7 +264,20 @@ export function AddFeatureDialog({
}, [planningMode]);
const handleModelChange = (entry: PhaseModelEntry) => {
- setModelEntry(entry);
+ // Normalize thinking level when switching between adaptive and non-adaptive models
+ const isNewModelAdaptive =
+ typeof entry.model === 'string' && isAdaptiveThinkingModel(entry.model);
+ const currentLevel = entry.thinkingLevel || 'none';
+
+ if (isNewModelAdaptive && currentLevel !== 'none' && currentLevel !== 'adaptive') {
+ // Switching TO Opus 4.6 with a manual level -> auto-switch to 'adaptive'
+ setModelEntry({ ...entry, thinkingLevel: 'adaptive' });
+ } else if (!isNewModelAdaptive && currentLevel === 'adaptive') {
+ // Switching FROM Opus 4.6 with adaptive -> auto-switch to 'high'
+ setModelEntry({ ...entry, thinkingLevel: 'high' });
+ } else {
+ setModelEntry(entry);
+ }
};
const buildFeatureData = (): FeatureData | null => {
diff --git a/apps/ui/src/components/views/board-view/shared/model-constants.ts b/apps/ui/src/components/views/board-view/shared/model-constants.ts
index c56ad46a..2816e556 100644
--- a/apps/ui/src/components/views/board-view/shared/model-constants.ts
+++ b/apps/ui/src/components/views/board-view/shared/model-constants.ts
@@ -167,7 +167,14 @@ export const ALL_MODELS: ModelOption[] = [
...COPILOT_MODELS,
];
-export const THINKING_LEVELS: ThinkingLevel[] = ['none', 'low', 'medium', 'high', 'ultrathink'];
+export const THINKING_LEVELS: ThinkingLevel[] = [
+ 'none',
+ 'low',
+ 'medium',
+ 'high',
+ 'ultrathink',
+ 'adaptive',
+];
export const THINKING_LEVEL_LABELS: Record = {
none: 'None',
@@ -175,6 +182,7 @@ export const THINKING_LEVEL_LABELS: Record = {
medium: 'Med',
high: 'High',
ultrathink: 'Ultra',
+ adaptive: 'Adaptive',
};
/**
diff --git a/apps/ui/src/components/views/board-view/shared/thinking-level-selector.tsx b/apps/ui/src/components/views/board-view/shared/thinking-level-selector.tsx
index 74b791a3..3a69d587 100644
--- a/apps/ui/src/components/views/board-view/shared/thinking-level-selector.tsx
+++ b/apps/ui/src/components/views/board-view/shared/thinking-level-selector.tsx
@@ -2,19 +2,26 @@ import { Label } from '@/components/ui/label';
import { Brain } from 'lucide-react';
import { cn } from '@/lib/utils';
import { ThinkingLevel } from '@/store/app-store';
-import { THINKING_LEVELS, THINKING_LEVEL_LABELS } from './model-constants';
+import { THINKING_LEVEL_LABELS } from './model-constants';
+import { getThinkingLevelsForModel } from '@automaker/types';
interface ThinkingLevelSelectorProps {
selectedLevel: ThinkingLevel;
onLevelSelect: (level: ThinkingLevel) => void;
testIdPrefix?: string;
+ /** Model ID is required for correct thinking level filtering.
+ * Without it, adaptive thinking won't be available for Opus 4.6. */
+ model?: string;
}
export function ThinkingLevelSelector({
selectedLevel,
onLevelSelect,
testIdPrefix = 'thinking-level',
+ model,
}: ThinkingLevelSelectorProps) {
+ const levels = getThinkingLevelsForModel(model || '');
+
return (
- {THINKING_LEVELS.map((level) => (
+ {levels.map((level) => (
);
diff --git a/apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx b/apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx
index 20420388..0f3c7889 100644
--- a/apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx
+++ b/apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx
@@ -21,6 +21,7 @@ import {
isGroupSelected,
getSelectedVariant,
codexModelHasThinking,
+ getThinkingLevelsForModel,
} from '@automaker/types';
import {
CLAUDE_MODELS,
@@ -28,7 +29,6 @@ import {
OPENCODE_MODELS,
GEMINI_MODELS,
COPILOT_MODELS,
- THINKING_LEVELS,
THINKING_LEVEL_LABELS,
REASONING_EFFORT_LEVELS,
REASONING_EFFORT_LABELS,
@@ -1296,7 +1296,9 @@ export function PhaseModelSelector({
Thinking Level
- {THINKING_LEVELS.map((level) => (
+ {getThinkingLevelsForModel(
+ model.mapsToClaudeModel === 'opus' ? 'claude-opus' : model.id || ''
+ ).map((level) => (
{isSelected && currentThinking === level && (
@@ -1402,7 +1405,9 @@ export function PhaseModelSelector({
Thinking Level
- {THINKING_LEVELS.map((level) => (
+ {getThinkingLevelsForModel(
+ model.mapsToClaudeModel === 'opus' ? 'claude-opus' : model.id || ''
+ ).map((level) => (