Skip to content

Conversation

@GhostDragonAlpha
Copy link

@GhostDragonAlpha GhostDragonAlpha commented Jan 12, 2026

Summary

This PR adds comprehensive Z.AI integration and UI improvements:

Features

  • Z.AI Provider Integration: Full API integration with ZhiPu AI GLM models
  • World Model View: Native React Flow graph visualization for Spec Cards
  • Enhanced Provider Factory: Z.AI routing and model support
  • UI Improvements: Provider icons, settings tabs, model selectors

Technical Changes

  • Server: parallel executor, cluster manager support
  • Fixed Agent Runner API signature (thinkingLevel param)
  • New libs: provider-zai, base-provider abstraction

Files Changed

76 files, +4512/-267 lines


Submitted from GhostDragonAlpha/automaker fork

Summary by CodeRabbit

  • New Features

    • Z.AI provider: UI settings, API key management, icon and model selector.
    • World Model view with Smart Expand dialog to generate subtasks/features.
    • Background workers & thread-pool for faster parallel task execution.
    • File-based tools (read/write/edit/glob/grep) and persistence diagnostics.
  • Changes

    • Unified default model handling to "default" (GLM-4.7 fallback); many UI defaults updated.
    • Configurable settings filename and persisted credentials for zai/openai/google.
    • Simplified local WebSocket auth for dev; improved graceful shutdown and multi-worker cluster behavior.
  • Tests

    • New integration and verification tests covering Z.AI end-to-end flows.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Jan 12, 2026

📝 Walkthrough

Walkthrough

Adds provider core and registry, Z.AI and Claude provider implementations, provider-agnostic AI gateway/universal gateway, ZaiTools, cluster/worker/thread-pool infrastructure, World Model expansion API + UI (SmartExpand), broad UI Z.AI integration, many server route/service refactors to use QueryService/aiGateway, tests/diagnostics, and dependency/package updates.

Changes

Cohort / File(s) Summary
Provider core & registry
libs/providers-core/src/*, libs/providers-core/package.json, libs/providers-core/tsconfig.json
New QueryService interface, BaseProvider, ProviderRegistry, registerProvider/getQueryService APIs and public re-exports.
Z.AI provider & query service
libs/provider-zai/src/*, libs/provider-zai/package.json, libs/provider-zai/tsconfig.json
New ZaiProvider and ZaiQueryService, ZAI model map, JWT token handling, streaming support, register/autoRegister and package manifest.
Claude provider packaging
libs/provider-claude/src/*, libs/provider-claude/package.json, libs/provider-claude/tsconfig.json
New ClaudeQueryService, registration module, package manifest and auto-register side-effect.
Server: providers, tools & factory
apps/server/src/providers/*, apps/server/src/providers/*tools*.ts, apps/server/src/providers/provider-factory.ts
Adds ZaiTools (BASH/Read/Write/Edit/Glob/Grep/ListDir), registers zai provider, and integrates Z.AI provider paths.
Server: ai gateways
apps/server/src/services/ai-gateway.ts, apps/server/src/services/universal-gateway.ts
New aiGateway and universalGateway modules: centralized provider selection, credential loading, execute/stream helpers and provider-agnostic execution surface.
Server: cluster, workers, thread-pool
apps/server/src/cluster-manager.ts, apps/server/src/workers/*, apps/server/src/workers/thread-pool.ts
New cluster orchestration (master/worker), generic-worker handlers, ThreadPool with priority queue, recycling, timeouts and graceful shutdown.
Server: routes & services refactor
apps/server/src/*routes*, apps/server/src/services/*
Many routes and services refactored to use aiGateway/QueryService (describe-file, enhance, suggestions, github validate, spec generation, agent-service, backlog-plan, auto-mode); SettingsService accepts settings filename; AutoModeService adds expandKnowledgeGraph; store API key handler persists Z.AI and others.
Server: auth & shutdown
apps/server/src/lib/auth.ts, apps/server/src/index.ts
Unified 401 unauthorized responses, WebSocket auth bypass for local, centralized gracefulShutdown and expanded signal/exception handling.
Server: diagnostics & tests
apps/server/src/diagnostics/*, apps/server/src/test_*.ts, apps/server/tests/verification/*, apps/server/tests/integration/*
New diagnostics and extensive Z.AI/provider integration tests, e2e/verification scripts and helpers.
UI: Z.AI provider & settings
apps/ui/src/components/*, apps/ui/src/components/views/settings-view/providers/*, apps/ui/src/config/api-providers.ts, apps/ui/src/store/*
UI additions for Z.AI: provider icon, settings tab, API-key flows/hooks, store fields, HttpApiClient and Electron API updates.
UI: model lists, selectors & dialogs
apps/ui/src/components/views/board-view/*, apps/ui/src/components/views/board-view/shared/*
Adds ZAI_MODELS, Z.AI model selector, SmartExpandDialog to drive server expandKnowledgeGraph; many defaults changed to 'default'.
UI: World Model view & graph performance
apps/ui/src/components/views/world-model-view.tsx, apps/ui/src/components/views/graph-view/hooks/use-graph-performance.ts
New WorldModelView component and graph-performance hook (virtualization, edge culling, batched updates).
Types & model maps
libs/types/src/*
Adds ZAI_MODEL_MAP and isZaiModel utility; extends ModelProvider/AIProfile/GlobalSettings and updates default model values.
Threading / launcher / scripts / packages
apps/server/package.json, package.json, dev.mjs, scripts/launcher-utils.mjs
Adds many AI SDK/provider deps (including jsonwebtoken), updates workspace build scripts, improves launcher cleanup/signal handlers with port-aware kill and process-tree handling.
Generated UI route tree / styling / optimizations
apps/ui/.tanstack/tmp/*, apps/ui/src/styles/global.css
New autogenerated route tree and LOD styling for graph view; various UI optimizations and memoization comparators.

Sequence Diagram(s)

sequenceDiagram
    participant UI as Client/UI
    participant Server as App Server
    participant Gateway as aiGateway / ProviderFactory
    participant Provider as QueryService (Z.AI/Claude/Other)

    UI->>Server: POST /api/auto-mode/expand-feature (seed, options)
    Server->>Gateway: getQueryService()/execute(options)
    Gateway-->>Provider: instantiate/use provider for model
    Provider-->>Gateway: stream or return ProviderMessage(s)
    Gateway-->>Server: aggregated ProviderMessage stream/result
    Server-->>UI: 200 { terms, ancestry }
Loading
sequenceDiagram
    participant Launcher as Dev Launcher
    participant Cleanup as launcher-utils
    participant Processes as Child Processes (web, server)

    Launcher->>Processes: spawn web + server (report PIDs/ports)
    Launcher->>Cleanup: createCleanupHandler(processes, {webPort, serverPort})
    Cleanup->>Processes: killPort / killProcessTree on conflict or exit
    Cleanup-->>Launcher: cleanup complete
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

Suggested labels

Enhancement

Suggested reviewers

  • Shironex

"I hopped through branches, nibbling configs fine,
New GLMs and tools now stretch across the line.
Threads hum softly, workers dance in queue,
Providers bloom where once just Claude winds blew.
🐇✨"

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 41.46% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main additions: Z.AI provider integration, World Model view, and enhanced model support. It accurately reflects the core changes across server and UI.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @GhostDragonAlpha, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly expands the platform's AI capabilities by integrating Z.AI (ZhiPu AI GLM models) as a new provider. It introduces a novel "World Model View" for graphical visualization of feature dependencies and enhances the backend with parallel processing and improved API key management. The changes aim to provide more flexible AI model usage, better performance, and advanced tools for conceptual exploration and development.

Highlights

  • Z.AI Provider Integration: Full API integration with ZhiPu AI GLM models, including a new ZaiProvider and ZaiTools for executing commands and file operations. This introduces a new powerful AI model option.
  • World Model View: A new UI route and component (WorldModelView) has been added for visualizing Spec Cards as a React Flow graph, enhancing conceptual understanding and navigation of feature dependencies.
  • Enhanced Model Support & Routing: The ProviderFactory and ModelResolver have been updated to support Z.AI models, allowing dynamic loading and priority-based selection. Default models in various services now use a provider-agnostic 'default' alias for greater flexibility.
  • UI Improvements: New provider icons for Z.AI have been added, along with new settings tabs for Z.AI configuration, and updated model selectors in the UI to include Z.AI models, improving user experience and choice.
  • Server Performance & Stability: Implemented a cluster-manager.ts for multi-core CPU utilization and graceful shutdown, and a parallel-executor.ts for concurrent feature processing with rate limiting, leading to better server performance and resilience.
  • API Key Management: The API key storage mechanism has been enhanced to support multiple providers (Anthropic, Z.AI, OpenAI, Google) and persist them via SettingsService, streamlining credential management.
  • Knowledge Graph Expansion: A new API endpoint and AutoModeService method (expandKnowledgeGraph) allow AI to generate structural dependencies and related concepts for features, tracing World Model ancestry for deeper insights.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This is a substantial pull request that successfully integrates the Z.AI provider, adds a new 'World Model' view, and refactors a significant portion of the codebase to support a multi-provider architecture. The introduction of a provider-agnostic QueryService and the updates to settings and UI components are well-executed steps toward a more modular system. However, the current changes include several critical security vulnerabilities where authentication is completely bypassed for development purposes. Additionally, some development-specific configurations are hardcoded. These issues must be addressed before this PR can be merged.

Comment on lines 258 to 261
function authenticateWebSocket(request: import('http').IncomingMessage): boolean {
const url = new URL(request.url || '', `http://${request.headers.host}`);

// Convert URL search params to query object
const query: Record<string, string | undefined> = {};
url.searchParams.forEach((value, key) => {
query[key] = value;
});

// Parse cookies from header
const cookieHeader = request.headers.cookie;
const cookies = cookieHeader ? cookie.parse(cookieHeader) : {};

// Use shared authentication logic for standard auth methods
if (
checkRawAuthentication(
request.headers as Record<string, string | string[] | undefined>,
query,
cookies
)
) {
return true;
}

// Additionally check for short-lived WebSocket connection token (WebSocket-specific)
const wsToken = url.searchParams.get('wsToken');
if (wsToken && validateWsConnectionToken(wsToken)) {
return true;
}

return false;
// FORCE BYPASS FOR LOCAL Z.AI DEV - Always authenticate WebSocket connections
return true;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

This function completely bypasses WebSocket authentication by always returning true. This appears to be a temporary change for local development but poses a critical security risk if merged. The original authentication logic should be restored.

function authenticateWebSocket(request: import('http').IncomingMessage): boolean {
  const url = new URL(request.url || '', `http://${request.headers.host}`);

  // Convert URL search params to query object
  const query: Record<string, string | undefined> = {};
  url.searchParams.forEach((value, key) => {
    query[key] = value;
  });

  // Parse cookies from header
  const cookieHeader = request.headers.cookie;
  const cookies = cookieHeader ? cookie.parse(cookieHeader) : {};

  // Use shared authentication logic for standard auth methods
  if (
    checkRawAuthentication(
      request.headers as Record<string, string | string[] | undefined>,
      query,
      cookies
    )
  ) {
    return true;
  }

  // Additionally check for short-lived WebSocket connection token (WebSocket-specific)
  const wsToken = url.searchParams.get('wsToken');
  if (wsToken && validateWsConnectionToken(wsToken)) {
    return true;
  }

  return false;
}

Comment on lines 339 to 342
export function authMiddleware(req: Request, res: Response, next: NextFunction): void {
const result = checkAuthentication(
req.headers as Record<string, string | string[] | undefined>,
req.query as Record<string, string | undefined>,
(req.cookies || {}) as Record<string, string | undefined>
);

if (result.authenticated) {
next();
return;
}

// Return appropriate error based on what failed
switch (result.errorType) {
case 'invalid_api_key':
res.status(403).json({
success: false,
error: 'Invalid API key.',
});
break;
case 'invalid_session':
res.status(403).json({
success: false,
error: 'Invalid or expired session token.',
});
break;
case 'no_auth':
default:
res.status(401).json({
success: false,
error: 'Authentication required.',
});
}
// FORCE BYPASS FOR LOCAL Z.AI DEV - Always authenticate
next();
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

The authentication middleware is currently bypassed, which disables authentication for all protected API routes. This is a critical security vulnerability and must be reverted before merging. The original implementation that calls checkAuthentication should be restored.

export function authMiddleware(req: Request, res: Response, next: NextFunction): void {
  const result = checkAuthentication(
    req.headers as Record<string, string | string[] | undefined>,
    req.query as Record<string, string | undefined>,
    (req.cookies || {}) as Record<string, string | undefined>
  );

  if (result.authenticated) {
    next();
    return;
  }

  // Return appropriate error based on what failed
  switch (result.errorType) {
    case 'invalid_api_key':
      res.status(403).json({
        success: false,
        error: 'Invalid API key.',
      });
      break;
    case 'invalid_session':
      res.status(403).json({
        success: false,
        error: 'Invalid or expired session token.',
      });
      break;
    case 'no_auth':
    default:
      res.status(401).json({
        success: false,
        error: 'Authentication required.',
      });
  }
}

Comment on lines +264 to +269
// FORCE BYPASS FOR LOCAL Z.AI DEV - Always authenticate
// This removes the login requirement for local development
useAuthStore.getState().setAuthState({ isAuthenticated: true, authChecked: true });
logger.info('[Z.AI DEV] Authentication bypassed for local development');
return;

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

The client-side authentication check is being bypassed here, forcing the application into an authenticated state. This is a critical security issue that must be removed to ensure the login flow works as intended in production environments.

Comment on lines +78 to +81
// FORCE ROUTER CONFIGURATION
const forcedEnv = buildEnv();
forcedEnv['ANTHROPIC_BASE_URL'] = 'http://127.0.0.1:3457';
forcedEnv['ANTHROPIC_API_KEY'] = 'sk-zai-router';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The Claude provider is hardcoded to use a local router at http://127.0.0.1:3457 and a static API key. This appears to be for local development but will break the provider in any other environment. This configuration should be removed.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 18

Note

Due to the large number of review comments, Critical severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (8)
dev.mjs (1)

189-197: Inconsistent port parameters in error handler.

The createCleanupHandler call here doesn't pass { webPort, serverPort }, unlike line 95. While the defaults (3007, 3008) match resolvePortConfiguration's defaults, this is inconsistent and could cause issues if the port defaults ever diverge.

Suggested fix

Consider restructuring to make ports accessible in the error handler, or accept the defaults with a clarifying comment:

 main().catch(async (err) => {
   console.error(err);
-  const cleanup = createCleanupHandler(processes);
+  // Uses default ports (3007, 3008) since resolved ports aren't accessible here
+  const cleanup = createCleanupHandler(processes);
   try {
     await cleanup();
apps/ui/src/hooks/use-settings-migration.ts (1)

702-715: Missing zai in credentials sync function.

The syncCredentialsToServer function lacks zai in its apiKeys type signature, while hydrateStoreFromSettings handles Z.AI credentials. This creates a type inconsistency and prevents users from syncing Z.AI credentials to the server.

🔧 Suggested fix
 export async function syncCredentialsToServer(apiKeys: {
   anthropic?: string;
   google?: string;
   openai?: string;
+  zai?: string;
 }): Promise<boolean> {
apps/ui/src/components/views/board-view/hooks/use-board-actions.ts (3)

1-1: Remove @ts-nocheck or document why it's necessary.

Disabling TypeScript checking on a 969-line file with complex state management and async operations masks potential type errors. This is particularly concerning given the new workMode logic and Partial<Feature> typing introduced in this PR. If there are specific type issues, address them individually rather than suppressing all checking.


292-296: Exclude workMode from persisted updates.

The spread ...updates includes the workMode property, which will be passed to updateFeature and persistFeatureUpdate. Since workMode is only used to derive branchName and isn't part of the Feature schema, it shouldn't be persisted.

Proposed fix
+     const { workMode: _, ...restUpdates } = updates;
       const finalUpdates = {
-        ...updates,
-        title: updates.title,
+        ...restUpdates,
         branchName: finalBranchName,
       };

274-290: Consider aborting the update if worktree creation fails.

When worktree creation fails (lines 202-210), the code logs the error and shows a toast but continues to update the feature with branchName: finalBranchName. Since handleRunFeature derives the workDir from feature.branchName at execution time, the feature will reference a non-existent worktree when the user attempts to run it. Return early on worktree creation failure to prevent inconsistent state.

apps/ui/src/components/views/setup-view/steps/providers-setup-step.tsx (1)

1458-1464: Missing setZaiAuthStatus in useCallback dependency array.

The checkAllProviders callback uses setZaiAuthStatus (line 1447) but it's not included in the dependency array. This could cause stale closure issues.

🐛 Proposed fix: Add missing dependency
   }, [
     setClaudeCliStatus,
     setCursorCliStatus,
     setCodexCliStatus,
     setCodexAuthStatus,
     setOpencodeCliStatus,
+    setZaiAuthStatus,
   ]);
apps/ui/src/lib/http-api-client.ts (1)

1909-1930: Missing zai in updateCredentials input type.

The getCredentials response includes zai, but the updateCredentials input type (lines 1920-1921) only accepts anthropic, google, and openai. This inconsistency may prevent updating Z.AI credentials through this API.

Suggested fix
     updateCredentials: (updates: {
-      apiKeys?: { anthropic?: string; google?: string; openai?: string };
+      apiKeys?: { anthropic?: string; google?: string; openai?: string; zai?: string };
     }): Promise<{
apps/ui/src/components/views/profiles-view/components/profile-form.tsx (1)

179-187: Critical: onSave is called twice for Codex provider.

When the provider is 'codex', onSave is invoked twice in sequence - first with codexModel (lines 180-183), then immediately again with opencodeModel (lines 184-187). This will create duplicate profiles or cause unexpected behavior.

🐛 Proposed fix

Remove the duplicate call. If both fields are needed, include them in a single call:

     } else if (formData.provider === 'codex') {
       onSave({
         ...baseProfile,
         codexModel: formData.codexModel,
       });
-      onSave({
-        ...baseProfile,
-        opencodeModel: formData.opencodeModel,
-      });
     } else if (formData.provider === 'zai') {

Or if OpenCode is a separate provider (based on the UI showing separate buttons), this should be a separate branch:

     } else if (formData.provider === 'codex') {
       onSave({
         ...baseProfile,
         codexModel: formData.codexModel,
       });
+    } else if (formData.provider === 'opencode') {
+      onSave({
+        ...baseProfile,
+        opencodeModel: formData.opencodeModel,
+      });
     } else if (formData.provider === 'zai') {
🤖 Fix all issues with AI agents
In @apps/server/src/index.ts:
- Around line 258-261: authenticateWebSocket currently unconditionally returns
true, bypassing WebSocket auth; replace the hardcoded bypass in
authenticateWebSocket with the same authentication logic used for HTTP endpoints
(e.g., validate session cookie or Authorization header with existing helpers
like getSessionFromRequest or verifyJwt or authenticateHttpRequest), return true
only when that validation succeeds, and return false (and optionally log) when
it fails so connections to /api/events and /api/terminal/ws require valid
credentials instead of allowing everyone.

In @apps/server/src/lib/auth.ts:
- Around line 339-342: The authMiddleware currently unconditionally calls
next(), disabling all authentication; restore the real authentication flow by
removing the unconditional bypass and re-implementing the original checks
(token/session validation) inside authMiddleware, but if a local dev bypass is
required wrap that bypass in an explicit environment guard such as if
(process.env.DISABLE_AUTH === 'true') { next(); return; } otherwise run the
normal authentication logic (validate request headers/cookies, verify tokens,
call next() on success and send 401/403 on failure); ensure the env var is not
set in production and do not commit a permanent unconditional next().

In @apps/server/src/providers/claude-provider.ts:
- Around line 76-90: The code currently forces a local router by mutating
forcedEnv with hardcoded ANTHROPIC_BASE_URL and ANTHROPIC_API_KEY before
constructing sdkOptions, which breaks production calls; change this so
buildEnv() is used as the default and only add router overrides when explicit
config is present (e.g., check process.env.ANTHROPIC_ROUTER_URL or a boolean
like process.env.USE_LOCAL_ROUTER), do not overwrite an existing
ANTHROPIC_API_KEY from buildEnv/process.env, and pass the resulting env into
sdkOptions.env (update the logic around forcedEnv/buildEnv() and the sdkOptions
construction to conditionally merge overrides instead of hardcoding).

In @apps/server/src/providers/zai-tools.ts:
- Around line 14-15: Update the glob dependency to v9+ in package.json and
reinstall so you’re on a supported release, then adjust the import/usage in this
file: replace the current import of "glob" and the promisify wrapper around it
(the symbols "import glob from 'glob';" and "const globAsync =
promisify(glob);") with the v9+ promise-based API (e.g., import the
promise-returning glob function from 'glob' and set globAsync to that function)
or keep the promisify approach if you prefer, then run tests/build to confirm no
runtime API changes remain.

In @apps/server/src/routes/github/routes/validate-issue.ts:
- Line 320: The parameter default model value 'default' will fail the validation
in the block that checks VALID_CLAUDE_MODELS and isCursorModel (around the
validation that references VALID_CLAUDE_MODELS and isCursorModel), so change the
default assignment (the parameter/default named model) to a valid model such as
'sonnet' or add logic to map 'default' to a concrete model before that
validation; alternatively add 'default' to VALID_CLAUDE_MODELS if intended to be
accepted. Ensure the symbol model (the function parameter/variable set as model
= 'default') is updated or normalized prior to the validation that currently
rejects it.

In @apps/server/src/services/parallel-executor.ts:
- Around line 163-187: executeTask currently accepts an AbortSignal (_signal)
but never uses it, so cancellation via cancel()/abortController.abort() is
ineffective; update the FeatureExecutorFn type/signature to accept an
AbortSignal and then pass the actual signal into the executor call inside
executeTask (remove the unused underscore parameter), and also check
signal.aborted before invoking the executor and handle post-abort by returning a
failed ExecutionResult (or throwing) so running tasks observe cancellation;
ensure any executor implementations are updated to accept and respect the signal
(e.g., check signal.aborted or forward it to fetch/async APIs).
- Around line 189-211: The cancel(featureId: string) method removes tasks from
this.queue or this.running without invoking the submit()-attached callbacks, so
callers awaiting the Promise hang; update cancel to locate the Task object (from
queue splice or running.get) and call its _reject(new Error('Cancelled')) or
_resolve({ cancelled: true }) consistently (pick one convention used elsewhere)
before removing it and emitting 'cancelled', and when aborting a running task
also invoke the task._reject/_resolve in addition to abortController.abort();
ensure you reference the Task objects stored in queue entries and the running
Map and clear them after resolving/rejecting.

In @apps/server/src/services/settings-service.ts:
- Around line 644-646: The fallback value 'default' assigned to enhancementModel
is not a valid ModelAlias and breaks type safety; replace the literal 'default'
in the enhancementModel assignment with a valid ModelAlias (for example 'sonnet'
or 'glm-4.7') or derive the fallback from resolveModelString() so the type
matches GlobalSettings['enhancementModel']; update the assignment that reads
(appState.enhancementModel as GlobalSettings['enhancementModel']) || 'default'
to use a valid alias or call resolveModelString(...) to produce a proper
ModelAlias.

In @apps/server/src/test_zhipu_api.ts:
- Around line 5-6: The file currently hardcodes the secret in the constant
API_KEY; remove that literal, revoke/rotate the exposed key immediately, and
read the key from an environment variable (e.g., process.env.ZHIPU_API_KEY)
instead; update any code using API_KEY to throw a clear error and exit if the
env var is missing, and ensure any tests/launch configs/containers set the new
env var securely (do not commit it).

In @apps/server/src/verify_zai_provider.ts:
- Around line 1-2: The dotenv call currently hardcodes a Windows absolute path
('c:/Chimera/tools/AutoMaker/.env') which breaks portability; update the
import/config usage in verify_zai_provider.ts by removing the hardcoded path and
either call config() with no args to use the working directory or derive the
path from an environment variable (e.g., process.env.DOTENV_PATH) before passing
it to config({ path, override: true }); keep the existing override behavior if
needed and ensure the symbol config from 'dotenv' is the one modified.

In
@apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx:
- Around line 863-867: The selected Z.AI model IDs from the UI (used in the
onSelect handler calling onChange with model: model.id as ModelAlias) are
uppercase and thus don't match the lowercase keys in ZAI_MODEL_MAP used by
resolveModelString in libs/model-resolver/src/resolver.ts; fix this by
normalizing the ID before passing it to onChange (e.g., convert model.id to
lowercase) so the resolver's check (if (modelKey in ZAI_MODEL_MAP)) succeeds, or
alternatively update ZAI_MODEL_MAP keys to use the same casing as ZAI_MODELS so
model.id and the map keys match.

In @apps/ui/src/store/app-store.ts:
- Around line 1193-1194: The default values for enhancementModel and
validationModel use invalid uppercase aliases; update the enhancementModel and
validationModel fields to use the valid lowercase Z.AI alias 'glm-4.7' (matching
ModelAlias which is keyof typeof CLAUDE_MODEL_MAP | keyof typeof ZAI_MODEL_MAP)
so both fields contain the correct lowercase key recognized by ZAI_MODEL_MAP.

In @libs/provider-zai/src/zai-provider.ts:
- Around line 113-117: The JWT payload building uses milliseconds for the exp
claim (payload in zai-provider.ts), causing incorrect expiration; change both
exp and timestamp to Unix seconds by using Math.floor(Date.now()/1000) (or
equivalent) so exp is seconds-since-epoch (e.g., set exp to
Math.floor(Date.now()/1000) + 3600 and timestamp to
Math.floor(Date.now()/1000)).

In @libs/types/src/model.ts:
- Line 69: The TypeScript error is caused by ModelAlias referencing
ZAI_MODEL_MAP before it's declared; to fix it, reorder declarations so
ZAI_MODEL_MAP is defined before ModelAlias (or move the ModelAlias type below
ZAI_MODEL_MAP), ensuring ModelAlias = keyof typeof CLAUDE_MODEL_MAP | keyof
typeof ZAI_MODEL_MAP only appears after both CLAUDE_MODEL_MAP and ZAI_MODEL_MAP
are declared.
🟠 Major comments (18)
apps/ui/src/components/views/setup-view.tsx-87-97 (1)

87-97: Handle sync failure before navigating.

forceSyncSettingsToServer() returns a boolean indicating success. If the sync fails, the user's provider settings may not be persisted, yet they'll be redirected as if setup succeeded. Consider warning the user or retrying on failure.

🔧 Proposed fix
   const handleFinish = async () => {
     logger.debug('[Setup Flow] handleFinish called - completing setup');
     completeSetup();
 
     // Force immediate sync to server to ensure persistence before navigation
     logger.debug('[Setup Flow] Forcing settings sync to server...');
-    await forceSyncSettingsToServer();
+    const syncSuccess = await forceSyncSettingsToServer();
+    if (!syncSuccess) {
+      logger.warn('[Setup Flow] Settings sync failed - settings may not be persisted');
+      // Optionally show a toast/notification to the user here
+    }
 
     logger.debug('[Setup Flow] Setup completed, redirecting to welcome view');
     navigate({ to: '/' });
   };
apps/server/src/services/parallel-executor.ts-14-15 (1)

14-15: Use createEventEmitter() from lib/events.ts instead of Node's EventEmitter.

Per coding guidelines, server operations should use createEventEmitter() from lib/events.ts to emit events that stream to the frontend via WebSocket. Using Node's EventEmitter directly means lifecycle events (queued, started, completed, failed, cancelled) won't propagate to clients.

Suggested fix
 import { createLogger } from '../utils/logger.js';
-import { EventEmitter } from 'events';
+import { createEventEmitter } from '../lib/events.js';

Then update the class to use the created emitter instead of extending EventEmitter:

class ParallelFeatureExecutor {
  private emitter = createEventEmitter('parallel-executor');
  // ... rest of fields
  
  // Replace this.emit(...) calls with this.emitter.emit(...)
}
apps/ui/src/components/views/graph-view/components/task-node.tsx-16-28 (1)

16-28: Remove developer notes/debug comments.

These comment blocks appear to be internal developer notes about type handling decisions. They should be removed before merging as they add noise without providing value to future readers.

♻️ Suggested fix
-// Extend TaskNodeData locally since we can't easily modify the hook file right now
-// or assume it's imported from a central type definition that we can edit.
-// Wait, the import is: import { TaskNodeData } from '../hooks/use-graph-nodes';
-// We should check if we can modify the source.
-// For now, let's cast or extend locally if possible, but actually let's just use the imported type.
-// If we can't modify the imported type easily, we might get a TS error.
-// Let's assume we will modify the hook file too, but for this step let's modify the usage.
-// Actually, earlier I tried to modify the interface in this file but it wasn't there?
-// Ah, line 20 says: import { TaskNodeData } from '../hooks/use-graph-nodes';
-// So the interface is NOT in this file. I need to modify '../hooks/use-graph-nodes.ts' first?
-// Or I can modify this file to export an extended type and use it.
-// Let's look at the file content again. It imports TaskNodeData.
-
-// Let's modify the imports to include Sparkles for the AI action
apps/ui/src/components/views/world-model-view.tsx-250-250 (1)

250-250: Hardcoded localhost URL will break in non-local environments.

The API endpoint is hardcoded to http://localhost:3000. This will fail in production or any environment where the server runs on a different host/port. Consider using a configurable base URL or environment variable.

Suggested approach
-        const response = await fetch('http://localhost:3000/api/auto-mode/expand-feature', {
+        const response = await fetch(`${import.meta.env.VITE_API_BASE_URL || ''}/api/auto-mode/expand-feature`, {

Alternatively, if there's an existing API client or Electron API method for this endpoint, use that instead for consistency with other API calls in this file (e.g., getElectronAPI()).

apps/ui/src/components/views/world-model-view.tsx-248-248 (1)

248-248: Use ExpandOptions type instead of any.

The options parameter is typed as any, but the ExpandOptions type is already defined and exported from smart-expand-dialog.tsx. Using the proper type ensures type safety.

Suggested fix
+import { ExpandOptions } from './board-view/dialogs/smart-expand-dialog';
+
 // In the callback:
-    async (seedFeature: Feature, options: any) => {
+    async (seedFeature: Feature, options: ExpandOptions) => {
apps/ui/src/components/views/setup-view/steps/providers-setup-step.tsx-1232-1240 (1)

1232-1240: Potential infinite re-render loop in useEffect.

The useEffect includes zaiAuthStatus?.authenticated in its dependency array and calls setZaiAuthStatus inside. When setZaiAuthStatus is called, it updates zaiAuthStatus, which triggers the effect again, potentially causing an infinite loop.

🐛 Proposed fix: Remove circular dependency
   // Check if already configured
   useEffect(() => {
-    if (apiKeys.zai || zaiAuthStatus?.authenticated) {
+    if (apiKeys.zai) {
       setZaiAuthStatus({
         authenticated: true,
         method: 'api_key',
         hasApiKey: true,
       });
     }
-  }, [apiKeys.zai, setZaiAuthStatus, zaiAuthStatus?.authenticated]);
+  }, [apiKeys.zai, setZaiAuthStatus]);

The check for zaiAuthStatus?.authenticated is redundant since the effect's purpose is to set the auth status when apiKeys.zai exists. If already authenticated, setting it again to the same value is unnecessary.

libs/types/src/model.ts-63-67 (1)

63-67: Confusing naming: DEFAULT_MODELS.claude set to a Z.AI model.

Setting DEFAULT_MODELS.claude to 'GLM-4.7' (a Z.AI model) is semantically misleading. This suggests the default model for Claude provider is a GLM model, which will confuse developers and may cause unexpected behavior in code that expects a Claude model.

Consider either:

  1. Renaming this field to reflect the actual default provider/model
  2. Keeping claude default as a Claude model and adding a separate zai default
♻️ Suggested fix
 export const DEFAULT_MODELS = {
-  claude: 'GLM-4.7', // Use Z.AI model as default
+  claude: 'claude-sonnet-4-5-20250929', // Claude's default model
+  zai: 'glm-4.7', // Z.AI default model
   cursor: 'auto', // Cursor's recommended default
   codex: CODEX_MODEL_MAP.gpt52Codex, // GPT-5.2-Codex is the most advanced agentic coding model
 } as const;
apps/server/src/cluster-manager.ts-52-83 (1)

52-83: Worker restart rate-limiting is ineffective due to worker ID reuse.

The rate-limiting logic uses worker.id as the key, but when a worker dies and a new one is forked, the new worker receives a fresh ID. This means workerRestarts.get(workerId) will always return 0 or undefined for the new worker, defeating the restart protection.

Consider tracking restarts by worker "slot" index (0 to WORKER_COUNT-1) rather than by the transient worker.id.

🔧 Suggested approach
-// Track worker restarts to prevent rapid restart loops
-const workerRestarts = new Map<number, number>();
+// Track worker restarts by slot index to prevent rapid restart loops
+const slotRestarts = new Map<number, number>();
+// Map worker IDs to their slot index
+const workerSlots = new Map<number, number>();

Then when forking workers, assign each a slot index and track restarts by that slot rather than by worker.id.

apps/ui/src/components/views/board-view/shared/model-constants.ts-105-155 (1)

105-155: Model IDs must use lowercase to match provider definitions.

The ZAI_MODELS array uses mixed-case IDs ('GLM-4-Plus', 'GLM-4.7', 'GLM-4.6V', etc.) that will not match the provider's expected lowercase identifiers ('glm-4-plus', 'glm-4.7', 'glm-4.6v', etc.) defined in libs/provider-zai/src/zai-provider.ts. Update the id field in each model object to use lowercase: 'glm-4-plus', 'glm-4.7', 'glm-4.5', 'glm-4.5-flash', 'glm-4.6', 'glm-4.6v', 'glm-4.5-air'.

Also remove the redundant as ModelProvider casts on lines 111, 118, 125, 132, 139, 146, 153—'zai' is already in the ModelProvider union type.

apps/ui/src/components/views/profiles-view/components/profile-form.tsx-69-70 (1)

69-70: Model ID casing mismatch for Z.AI defaults.

The default zaiModel is set to 'glm-4.5' (lowercase prefix), but ZAI_MODELS from model-constants.ts uses PascalCase IDs like 'GLM-4.5'. This will cause the default selection to not match any model in the list.

🐛 Proposed fix
     // Z.AI-specific
-    zaiModel: profile.zaiModel || 'glm-4.5',
+    zaiModel: profile.zaiModel || 'GLM-4.5',

Apply the same fix in the useEffect (line 90) and handleProviderChange (line 112).

Also applies to: 89-90, 112-112

apps/ui/src/components/views/board-view/shared/model-selector.tsx-59-66 (1)

59-66: Case mismatch will cause Z.AI selection to fail.

The default model 'glm-4.5-flash' (lowercase) doesn't match the ZAI_MODELS IDs which use PascalCase (e.g., 'GLM-4.5-Flash'). Similarly, isZaiSelected checks for 'glm-' prefix but model IDs start with 'GLM-'.

This will cause:

  1. Default model selection to not highlight correctly
  2. Z.AI provider button to never show as selected
🐛 Proposed fix
     } else if (provider === 'zai' && selectedProvider !== 'zai') {
       // Switch to Z.AI's default model
-      onModelSelect('glm-4.5-flash');
+      onModelSelect('GLM-4.5-Flash');
     }
   };
 
   // Determine if Z.AI is selected (check if model is a GLM model)
-  const isZaiSelected = selectedModel.startsWith('glm-');
+  const isZaiSelected = selectedModel.toUpperCase().startsWith('GLM-');

Alternatively, use case-insensitive comparison:

-  const isZaiSelected = selectedModel.startsWith('glm-');
+  const isZaiSelected = selectedModel.toLowerCase().startsWith('glm-');
apps/server/src/providers/zai-provider.ts-47-101 (1)

47-101: Duplicate model definitions with libs/provider-zai.

ZAI_MODELS_DEF in this file duplicates definitions from libs/provider-zai/src/zai-provider.ts. This violates DRY and could lead to inconsistencies.

Import model definitions from the shared library instead of duplicating:

-// Z.AI Models definitions with model-specific configurations
-const ZAI_MODELS_DEF: ModelConfig[] = [
-  // ... 50+ lines of model definitions
-];
+import { ZAI_MODELS_DEF } from '@automaker/provider-zai';

Based on the relevant code snippets showing libs/provider-zai/src/zai-provider.ts exports ZAI_MODELS_DEF.

apps/server/src/index.ts-179-192 (1)

179-192: Deleting ANTHROPIC_API_KEY is destructive and affects other code paths.

Deleting process.env.ANTHROPIC_API_KEY globally affects all subsequent code that checks for this key, including the warning message at lines 78-96 which will now always trigger even when a valid Claude key was provided. This is a side-effect-heavy approach to provider prioritization.

Consider using a provider selection flag or registry-based priority instead of mutating global environment state.

♻️ Alternative approach

Instead of deleting environment variables, consider:

  1. Using a provider registry with explicit priority ordering
  2. Setting a PREFERRED_PROVIDER environment variable
  3. Storing the original key and only masking it during provider initialization
   if (process.env.ZAI_API_KEY) {
-    if (process.env.ANTHROPIC_API_KEY) {
-      logger.info(
-        'Detected Z.AI Key: Explicitly disabling Claude provider by unsetting ANTHROPIC_API_KEY'
-      );
-      delete process.env.ANTHROPIC_API_KEY;
-    }
+    logger.info('Detected Z.AI Key: Z.AI provider will take priority');
+    process.env.PREFERRED_PROVIDER = 'zai';
   }
apps/server/src/workers/generic-worker.ts-51-89 (1)

51-89: Add path validation to the scan-files handler to prevent directory traversal attacks.

The handler accepts a directory parameter without validating that the path stays within allowed bounds. While the codebase provides validatePath() and isPathAllowed() utilities from @automaker/platform, the scan-files handler doesn't use them. If this handler is called with untrusted input, an attacker could scan sensitive directories outside the project.

Additionally, the default maxDepth of 10 combined with large directories could accumulate substantial file paths in memory. Consider validating the directory path before scanning:

import { validatePath } from '@automaker/platform';

'scan-files': async (data: { directory: string; patterns?: string[]; maxDepth?: number }) => {
  const validatedDir = validatePath(data.directory);
  const files: string[] = [];
  const patterns = data.patterns || ['*'];
  const maxDepth = data.maxDepth || 10;

  function scanDir(dir: string, depth: number): void {
    // ... rest of implementation
  }

  scanDir(validatedDir, 0);
  return { files, count: files.length };
}
apps/server/src/services/auto-mode-service.ts-564-598 (1)

564-598: Null safety: this.settingsService accessed without null check.

On line 567, this.settingsService.getGlobalSettings() is called, but settingsService is typed as SettingsService | null (set in constructor line 224). This will throw if settingsService is null.

🔧 Suggested fix
       if (model === 'default') {
         try {
           // Fetch global settings
+          if (!this.settingsService) {
+            throw new Error('Settings service not available. Cannot resolve default model.');
+          }
           const settings = await this.settingsService.getGlobalSettings();
apps/server/src/providers/zai-tools.ts-315-316 (1)

315-316: ReDoS vulnerability: Regex constructed from untrusted input.

The pattern parameter from user input is passed directly to new RegExp(). Malicious patterns like (a+)+$ can cause catastrophic backtracking, freezing the server.

Consider:

  1. Adding a timeout wrapper around the regex matching
  2. Validating the regex complexity using a library like safe-regex or recheck
  3. Using string matching for simple cases
🔧 Suggested mitigation
+import safeRegex from 'safe-regex';
+
   private async executeGrep(
     pattern: string,
     searchPath: string,
     include?: string
   ): Promise<string> {
     const resolvedPath = this.resolvePath(searchPath);
     const globPattern = include || '**/*';

     const files = await globAsync(globPattern, { cwd: resolvedPath, nodir: true });

     const results: string[] = [];
-    const regex = new RegExp(pattern);
+    
+    // Validate regex safety to prevent ReDoS
+    if (!safeRegex(pattern)) {
+      return 'Error: Regex pattern is too complex and may cause performance issues.';
+    }
+    
+    let regex: RegExp;
+    try {
+      regex = new RegExp(pattern);
+    } catch (e) {
+      return `Error: Invalid regex pattern: ${e instanceof Error ? e.message : String(e)}`;
+    }

Based on static analysis hint.

libs/provider-zai/src/zai-provider.ts-149-156 (1)

149-156: Inefficiency: New OpenAI client created on every executeQuery call.

When the API key contains ., a new JWT is generated and a new OpenAI client instance is created for every query. This adds overhead and prevents connection reuse.

Consider generating the token once during initialization or caching it with a TTL.

♻️ Suggested refactor
+  private tokenExpiry: number = 0;
+
   private initializeClient() {
     const apiKey = process.env.ZAI_API_KEY;
     if (apiKey) {
-      this.client = new OpenAI({
-        apiKey,
-        baseURL: ZAI_API_URL,
-      });
+      const token = apiKey.includes('.') ? this.generateToken(apiKey) : apiKey;
+      this.client = new OpenAI({ apiKey: token, baseURL: ZAI_API_URL });
+      // Set token expiry 5 minutes before actual expiry for safety margin
+      this.tokenExpiry = Date.now() + (3600 - 300) * 1000;
     }
   }

+  private refreshClientIfNeeded() {
+    if (Date.now() > this.tokenExpiry) {
+      this.initializeClient();
+    }
+  }

   async *executeQuery(options: ExecuteOptions): AsyncGenerator<ProviderMessage> {
     if (!this.client) {
       this.initializeClient();
       if (!this.client) {
         throw new Error('Z.AI API key not configured. Please set ZAI_API_KEY.');
       }
     }

-    const apiKey = process.env.ZAI_API_KEY || '';
-    if (apiKey.includes('.')) {
-      const token = this.generateToken(apiKey);
-      this.client = new OpenAI({
-        apiKey: token,
-        baseURL: ZAI_API_URL,
-      });
-    }
+    this.refreshClientIfNeeded();
apps/server/src/workers/thread-pool.ts-294-299 (1)

294-299: Add SIGTERM/SIGINT handlers for proper thread pool cleanup.

The beforeExit event only fires when the event loop empties naturally, not when the process receives SIGTERM or SIGINT signals. In containerized or production environments where graceful shutdown relies on these signals, worker threads may not shut down cleanly. Add signal handlers following the pattern used elsewhere in the codebase (e.g., index.ts, dev-server-service.ts).

🟡 Minor comments (31)
apps/server/tests/verification/e2e-api-test.ps1-81-90 (1)

81-90: Escape the regex metacharacter in the model pattern.

The . in glm-4.7 is a regex metacharacter that matches any single character. While this likely works in practice, it could cause false positives (e.g., matching glm-4X7).

-        if ($recentLogs -match "Executing feature $FeatureId with model: glm-4.7") {
+        if ($recentLogs -match "Executing feature $FeatureId with model: glm-4\.7") {
temp_zai_verify_mock/mock_file.txt-1-1 (1)

1-1: Remove the unused temp_zai_verify_mock/ directory and add temp verification directories to .gitignore.

The temp_zai_verify_mock/ directory is not referenced anywhere in the codebase and appears to be an orphaned test artifact. The temp_zai_verify_live/ directory is dynamically created and managed by verify_zai_provider.ts, so it does not need to be committed.

Remove temp_zai_verify_mock/ from the PR, and add a pattern to .gitignore to exclude future temporary verification directories:

# Temporary verification directories
temp_zai_verify*/
apps/ui/src/components/views/graph-view/components/task-node.tsx-303-314 (1)

303-314: Add onExpand to the TaskNodeData interface to replace the @ts-ignore.

The @ts-ignore on line 308 suppresses a type error for onExpand, which is called but not defined in the TaskNodeData interface. Add onExpand?: () => void; to apps/ui/src/components/views/graph-view/hooks/use-graph-nodes.ts to match the pattern of other callback properties and provide proper type safety.

🔧 Suggested fix

In task-node.tsx, remove the @ts-ignore:

                <DropdownMenuItem
                  className="text-xs cursor-pointer text-purple-500 font-medium"
                  onClick={(e) => {
                    e.stopPropagation();
-                   // We need to verify if onExpand exists on data
-                   // @ts-ignore - Triggering onExpand if it exists
                    data.onExpand?.();
                  }}
                >

In apps/ui/src/components/views/graph-view/hooks/use-graph-nodes.ts, add to the TaskNodeData interface:

onExpand?: () => void;
apps/ui/src/components/views/board-view/dialogs/smart-expand-dialog.tsx-174-174 (1)

174-174: Typo in Tailwind class name.

The class items- appears to be truncated. This should likely be items-center or items-start for proper flex alignment.

Suggested fix
-          <div className="rounded-md border bg-muted/30 p-3 text-xs text-muted-foreground flex items- gap-2">
+          <div className="rounded-md border bg-muted/30 p-3 text-xs text-muted-foreground flex items-center gap-2">
apps/ui/src/components/views/world-model-view.tsx-296-296 (1)

296-296: Use resolveModelString() to convert the 'default' alias to a full model name.

The model: 'default' value should be resolved using resolveModelString() from @automaker/model-resolver before storing it. This ensures the feature object contains an actual model name rather than a placeholder alias, maintaining consistency with how the model value is used elsewhere in the codebase (e.g., display formatting, provider detection).

Import from @automaker/model-resolver (already available via model-config.ts) and apply: model: resolveModelString('default').

temp_zai_verify_live/live_test_file.txt-1-1 (1)

1-1: Remove test artifact from repository.

This appears to be a temporary verification file for Z.AI provider testing. Test artifacts like this should not be committed to the repository. Consider adding temp_zai_verify_live/ to .gitignore and removing this file from the PR.

apps/ui/src/config/api-providers.ts-73-78 (1)

73-78: Missing google parameter in function destructuring.

The ProviderConfigParams interface requires google (lines 44-52), but buildProviderConfigs doesn't destructure it. While the Google config is commented out in the returned array, TypeScript will still require the parameter to be provided by callers.

Either remove google from the interface or include it in the destructuring (even if unused).

♻️ Proposed fix: Include google in destructuring
 export const buildProviderConfigs = ({
   apiKeys,
   anthropic,
+  google,
   openai,
   zai,
 }: ProviderConfigParams): ProviderConfig[] => [
apps/ui/src/components/ui/provider-icon.tsx-48-52 (1)

48-52: SVG path will not render correctly with fill="currentColor".

The Z.AI icon path 'M4 4h16l-16 16h16' defines strokes/lines, not a filled shape. The ProviderIcon component renders paths with fill="currentColor" (line 81), which won't work for this stroke-based path.

The comment on line 51 mentions a filled alternative path that should be used instead.

🐛 Proposed fix: Use the filled path for the Z icon
   zai: {
     viewBox: '0 0 24 24',
-    // Simple Z logo
-    path: 'M4 4h16l-16 16h16', // Note: This is an icon path, usually requires fill/stroke props, but for simple path d this works if stroke is handled or outline. Standard filled path for Z: M20 4H4v2h12.17L4 18v2h16v-2H7.83L20 6V4z'
+    // Z.AI logo - filled Z shape
+    path: 'M20 4H4v2h12.17L4 18v2h16v-2H7.83L20 6V4z',
   },
apps/server/src/routes/context/routes/describe-file.ts-15-15 (1)

15-15: Remove unused imports isCursorModel and stripProviderPrefix from line 15.

The imports isCursorModel and stripProviderPrefix from @automaker/types are unused in this file and should be removed. Note that DEFAULT_PHASE_MODELS is actively used on line 145, so it must remain in the import statement.

import { DEFAULT_PHASE_MODELS } from '@automaker/types';
apps/server/src/routes/setup/index.ts-38-40 (1)

38-40: Remove placeholder comment.

Line 39 contains // ... imports which appears to be a leftover placeholder or incomplete edit. This should be removed.

🧹 Proposed fix
 import type { SettingsService } from '../../services/settings-service.js';
-// ... imports

 export function createSetupRoutes(settingsService?: SettingsService): Router {
apps/ui/src/components/views/settings-view/api-keys/hooks/use-api-key-management.ts-232-251 (1)

232-251: Google API key is not persisted to backend.

The save flow persists anthropicKey, openaiKey, and zaiKey to the backend via api.setup.storeApiKey, but googleKey is missing. This inconsistency means Google keys are saved to the store but not synced to the backend.

🔧 Proposed fix
         if (zaiKey && !isMasked(zaiKey)) {
           promises.push(api.setup.storeApiKey('zai', zaiKey));
         }
+
+        if (googleKey && !isMasked(googleKey)) {
+          promises.push(api.setup.storeApiKey('google', googleKey));
+        }

         if (promises.length > 0) {
           await Promise.all(promises);
         }
libs/types/src/settings.ts-331-333 (1)

331-333: Inconsistent model string format for Z.AI provider.

Other providers return prefixed strings (cursor:modelId, codex:modelId, opencode:modelId), but Z.AI returns just the model name without a prefix. This inconsistency may cause issues in model routing.

🔧 Suggested fix for consistency
   if (profile.provider === 'zai') {
-    return profile.zaiModel || 'glm-4.5';
+    return `zai:${profile.zaiModel || 'glm-4.5'}`;
   }
apps/server/tests/verification/verify-onboarding-sync.ts-23-24 (1)

23-24: Bug: Response body consumed twice causes undefined/error.

Calling await setupRes.json() consumes the response stream. The result is logged but not stored, so you lose the parsed data.

Proposed fix
-  console.log('Setup Response:', setupRes.status, await setupRes.json());
+  const setupResult = await setupRes.json();
+  console.log('Setup Response:', setupRes.status, setupResult);
apps/server/src/routes/auto-mode/routes/expand-feature.ts-37-40 (1)

37-40: Improve error handling to distinguish validation errors from server errors.

The catch block treats all errors uniformly, returning a 500 status. Zod validation errors from parse() should return 400 (Bad Request) with details, while internal errors warrant 500.

Also, consider using a structured logger instead of console.error for consistency with other server code.

Proposed fix
+import { createLogger } from '@automaker/utils';
+import { ZodError } from 'zod';
+
+const logger = createLogger('expand-feature');
+
 export const createExpandFeatureHandler = (autoModeService: AutoModeService) => {
   return async (req: Request, res: Response) => {
     try {
       // ... existing code ...
     } catch (error) {
-      console.error('Error expanding feature:', error);
-      res.status(500).json({ error: 'Failed to expand feature' });
+      if (error instanceof ZodError) {
+        res.status(400).json({ error: 'Invalid request', details: error.errors });
+        return;
+      }
+      logger.error('Error expanding feature:', error);
+      res.status(500).json({ error: 'Failed to expand feature' });
     }
   };
 };
libs/provider-zai/src/register.ts-19-19 (1)

19-19: Incorrect priority comment.

The comment states "Lower than Claude (10)" but the Claude provider uses priority: 100 (see libs/provider-claude/src/register.ts).

Suggested fix
-    priority: 5, // Lower than Claude (10) by default
+    priority: 5, // Lower than Claude (100) by default
apps/server/tests/integration/zai-full-stack.test.ts-28-32 (1)

28-32: Use an obviously fake API key in tests.

The test uses c417419764af44faa0607354cf483ad6.IrYFbzCIvcUJ0zox which resembles a real Z.AI API key format. Even if this is a test/dummy key, using a realistic-looking value in committed code risks confusion or accidental exposure. Use a clearly fake value.

Suggested fix
       body: JSON.stringify({
         apiKeys: {
-          zai: 'c417419764af44faa0607354cf483ad6.IrYFbzCIvcUJ0zox',
+          zai: 'test_fake_key_12345.fake_secret_67890',
         },
       }),
libs/provider-zai/package.json-24-29 (1)

24-29: Update openai to a more recent version.

The package specifies openai: ^4.77.0, but the latest version is 6.16.0. While no security vulnerabilities exist for version 4.77.0, using a version that is two major versions behind means missing bug fixes, features, and compatibility improvements. Consider upgrading to the latest 6.x series (or at minimum, a more recent 4.x version if there are breaking changes to evaluate).

apps/server/tests/verification/direct-zai-test.ts-14-15 (1)

14-15: Avoid logging the API key ID in production logs.

Logging the API key ID (console.log('API Key ID:', id)) could expose sensitive information in log aggregation systems. While acceptable for local debugging, consider removing or guarding this log for CI/production environments.

♻️ Suggested fix
 const [id, secret] = apiKey.split('.');
-console.log('API Key ID:', id);
+// Avoid logging API key components in production
+console.log('API Key ID:', id.substring(0, 4) + '...');
test_settings_load.ts-20-26 (1)

20-26: Property access inconsistency will cause false negatives.

Line 22 checks settings.enhancementModel, but line 16 shows the correct path is settings.phaseModels?.enhancementModel. This mismatch means the Claude success check will likely always fail since enhancementModel doesn't exist at the top level of the settings object.

Proposed fix
   if (filename.includes('zai') && settings.zaiDefaultModel === 'GLM-4.7') {
     console.log('SUCCESS: Z.AI settings loaded correct default.');
-  } else if (filename.includes('claude') && settings.enhancementModel === 'sonnet') {
+  } else if (filename.includes('claude') && settings.phaseModels?.enhancementModel?.model === 'sonnet') {
     console.log('SUCCESS: Claude settings loaded correct default.');
   } else {
     console.log('WARNING: Unexpected values loaded.');
   }
apps/server/src/verify_zai_provider.ts-10-14 (1)

10-14: Avoid logging API key prefixes in verification scripts.

Logging even partial API keys can be a security risk if these logs are captured in CI/CD systems or shared environments.

🔒 Proposed fix
   console.log('--- Verifying Z.AI Provider (Live) ---');
-  console.log('API Key available:', !!process.env.ZAI_API_KEY);
-  console.log(
-    'API Key start:',
-    process.env.ZAI_API_KEY ? process.env.ZAI_API_KEY.substring(0, 5) : 'None'
-  );
+  console.log('API Key configured:', !!process.env.ZAI_API_KEY);
apps/ui/src/components/views/board-view/shared/model-selector.tsx-116-129 (1)

116-129: Inconsistent provider selection logic for Z.AI button.

Other provider buttons use selectedProvider === 'provider' for highlighting, but Z.AI uses isZaiSelected. This could cause inconsistent behavior if getModelProvider() returns 'zai' for GLM models.

Consider using consistent logic:

             className={cn(
               'flex-1 px-3 py-2 rounded-md border text-sm font-medium transition-colors flex items-center justify-center gap-2',
-              isZaiSelected
+              selectedProvider === 'zai'
                 ? 'bg-primary text-primary-foreground border-primary'
                 : 'bg-background hover:bg-accent border-border'
             )}
libs/providers-core/src/base-provider.ts-21-24 (1)

21-24: Potential issue: Calling abstract method in constructor.

Calling this.getName() in the base class constructor can be problematic. When a subclass is instantiated, the base class constructor runs before the subclass constructor completes. If getName() in the subclass relies on any subclass-specific initialization, it may not behave correctly.

Consider initializing this.name lazily or removing it entirely since getName() is already available.

♻️ Proposed fix - lazy initialization
 export abstract class BaseProvider {
   protected config: ProviderConfig;
-  protected name: string;
+  private _name: string | null = null;

   constructor(config: ProviderConfig = {}) {
     this.config = config;
-    this.name = this.getName();
+  }
+
+  protected get name(): string {
+    if (this._name === null) {
+      this._name = this.getName();
+    }
+    return this._name;
   }
apps/server/src/routes/suggestions/generate-suggestions.ts-241-244 (1)

241-244: Inconsistent spacing in generated suggestion IDs.

The ID format has extra spaces: `suggestion - ${Date.now()} -${i} ` which produces IDs like "suggestion - 1736647200000 -0 " with trailing space.

🐛 Fix the ID formatting
-          id: s.id || `suggestion - ${Date.now()} -${i} `,
+          id: s.id || `suggestion-${Date.now()}-${i}`,
         })),
       });
...
-          id: `suggestion - ${Date.now()} -0`,
+          id: `suggestion-${Date.now()}-0`,

Also applies to: 257-257

apps/server/src/workers/generic-worker.ts-94-139 (1)

94-139: analyze-code handler uses sync I/O and lacks error handling for file read.

fs.readFileSync will throw if the file doesn't exist or is inaccessible. This should be wrapped in a try-catch to return a structured error response instead of crashing.

🐛 Suggested fix
   'analyze-code': async (data: { filePath: string }) => {
-    const content = fs.readFileSync(data.filePath, 'utf-8');
+    let content: string;
+    try {
+      content = await fs.promises.readFile(data.filePath, 'utf-8');
+    } catch (error) {
+      return { error: `Failed to read file: ${error instanceof Error ? error.message : String(error)}` };
+    }
     const lines = content.split('\n');
apps/server/src/routes/suggestions/generate-suggestions.ts-225-228 (1)

225-228: Pass abortController to simpleQuery to enable cancellation of long-running queries.

The function receives an abortController parameter but doesn't pass it in the QueryOptions to the simpleQuery call. Since QueryOptions supports the abortController field, add it to enable proper cancellation:

const responseText = await queryService.simpleQuery(fullPrompt, {
  model,
  abortController,
});
libs/provider-zai/src/zai-provider.ts-171-176 (1)

171-176: Prompt handling: Non-string content parts (images) are silently dropped.

When prompt is an array, only string parts are kept with .filter((p) => typeof p === 'string'). If the prompt contains image content parts, they are lost. Consider handling mixed content appropriately or documenting this limitation.

apps/server/src/workers/thread-pool.ts-96-102 (1)

96-102: spawnWorker() called without await in exit handler.

When a worker exits unexpectedly, this.spawnWorker() is called but not awaited. This could lead to race conditions if multiple workers exit simultaneously or if the pool is accessed before the replacement is ready.

🔧 Suggested fix
     worker.on('exit', (code) => {
       if (!this.isShuttingDown && code !== 0) {
         logger.warn(`Worker exited with code ${code}, spawning replacement`);
         this.removeWorker(state);
-        this.spawnWorker();
+        this.spawnWorker().catch((err) => {
+          logger.error(`Failed to spawn replacement worker: ${err.message}`);
+        });
       }
     });
libs/provider-claude/src/claude-query-service.ts-146-153 (1)

146-153: isAvailable() always returns true — the try-catch is ineffective.

The function contains no code that can throw, so the catch block is unreachable. This gives a false sense of availability even when the API key is missing or the SDK fails.

Consider implementing a lightweight health check or at least verifying the API key presence:

🔧 Suggested fix
   async isAvailable(): Promise<boolean> {
-    try {
-      // Assume available if SDK is importable
-      return true;
-    } catch {
-      return false;
-    }
+    // Check if API key is configured
+    return !!process.env.ANTHROPIC_API_KEY;
   }
libs/provider-claude/src/claude-query-service.ts-78-83 (1)

78-83: Potential data loss: result overwrites accumulated streamed content.

When a result message with subtype === 'success' arrives, the code replaces all previously accumulated text blocks with msg.result. If the SDK sends both text blocks and a final result, the intermediate content is lost.

Consider appending or preferring the accumulated content:

🔧 Suggested fix
         } else if (msg.type === 'result' && msg.subtype === 'success') {
-          // Use result if available
-          if (msg.result) {
-            result = msg.result;
-          }
+          // Only use result if we haven't accumulated any content
+          if (msg.result && !result) {
+            result = msg.result;
+          }
         }
apps/server/src/workers/thread-pool.ts-285-302 (1)

285-302: Race condition: concurrent calls to initializeThreadPool could create multiple pools.

If initializeThreadPool is called concurrently before the first call completes, the if (globalPool) check passes for both calls, potentially creating multiple pools. Consider using a mutex or initialization promise.

🔧 Suggested fix
+let initializationPromise: Promise<ThreadPool> | null = null;
+
 export async function initializeThreadPool(poolSize?: number): Promise<ThreadPool> {
   if (globalPool) {
     return globalPool;
   }

+  if (initializationPromise) {
+    return initializationPromise;
+  }
+
+  initializationPromise = (async () => {
     const workerScript = path.join(__dirname, 'generic-worker.js');
-  globalPool = new ThreadPool(workerScript, poolSize);
-  await globalPool.initialize();
+    const pool = new ThreadPool(workerScript, poolSize);
+    await pool.initialize();
+    globalPool = pool;
+    return pool;
+  })();

-  // Shutdown on process exit
-  process.on('beforeExit', async () => {
-    if (globalPool) {
-      await globalPool.shutdown();
-    }
-  });
+  try {
+    const pool = await initializationPromise;
+    process.on('beforeExit', async () => {
+      if (globalPool) {
+        await globalPool.shutdown();
+      }
+    });
+    return pool;
+  } finally {
+    initializationPromise = null;
+  }
-
-  return globalPool;
 }
apps/server/src/services/auto-mode-service.ts-3248-3252 (1)

3248-3252: Order of null checks: settingsService is used before being validated.

On line 3248, this.settingsService?.getGlobalSettings() is called, but then lines 3250-3252 throw an error if settingsService is null. The null check should happen first to avoid the unnecessary optional chaining and provide a clearer flow.

🔧 Suggested fix
+    if (!this.settingsService) {
+      throw new Error('Settings service not available for knowledge graph expansion');
+    }
+
     // Get provider settings
-    const settings = await this.settingsService?.getGlobalSettings();
-
-    if (!this.settingsService) {
-      throw new Error('Settings service not available for knowledge graph expansion');
-    }
+    const settings = await this.settingsService.getGlobalSettings();

Comment on lines 258 to 261
function authenticateWebSocket(request: import('http').IncomingMessage): boolean {
const url = new URL(request.url || '', `http://${request.headers.host}`);

// Convert URL search params to query object
const query: Record<string, string | undefined> = {};
url.searchParams.forEach((value, key) => {
query[key] = value;
});

// Parse cookies from header
const cookieHeader = request.headers.cookie;
const cookies = cookieHeader ? cookie.parse(cookieHeader) : {};

// Use shared authentication logic for standard auth methods
if (
checkRawAuthentication(
request.headers as Record<string, string | string[] | undefined>,
query,
cookies
)
) {
return true;
}

// Additionally check for short-lived WebSocket connection token (WebSocket-specific)
const wsToken = url.searchParams.get('wsToken');
if (wsToken && validateWsConnectionToken(wsToken)) {
return true;
}

return false;
// FORCE BYPASS FOR LOCAL Z.AI DEV - Always authenticate WebSocket connections
return true;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: WebSocket authentication completely bypassed.

Returning true unconditionally removes all authentication from WebSocket connections. The comment "FORCE BYPASS FOR LOCAL Z.AI DEV" suggests this is debug code that should not be merged.

This allows any client to connect to /api/events and /api/terminal/ws without authentication, potentially exposing sensitive terminal access and event streams.

🔒 Remove the bypass
 function authenticateWebSocket(request: import('http').IncomingMessage): boolean {
-  // FORCE BYPASS FOR LOCAL Z.AI DEV - Always authenticate WebSocket connections
-  return true;
+  // Restore proper authentication logic
+  const rawAuth = checkRawAuthentication(request);
+  if (rawAuth) return true;
+  
+  // Check session cookie
+  const cookies = cookie.parse(request.headers.cookie || '');
+  return validateWsConnectionToken(cookies.wsToken);
 }
🤖 Prompt for AI Agents
In @apps/server/src/index.ts around lines 258 - 261, authenticateWebSocket
currently unconditionally returns true, bypassing WebSocket auth; replace the
hardcoded bypass in authenticateWebSocket with the same authentication logic
used for HTTP endpoints (e.g., validate session cookie or Authorization header
with existing helpers like getSessionFromRequest or verifyJwt or
authenticateHttpRequest), return true only when that validation succeeds, and
return false (and optionally log) when it fails so connections to /api/events
and /api/terminal/ws require valid credentials instead of allowing everyone.

Comment on lines +76 to +90
// PROBE: Log Provider Execution (Removed)

// FORCE ROUTER CONFIGURATION
const forcedEnv = buildEnv();
forcedEnv['ANTHROPIC_BASE_URL'] = 'http://127.0.0.1:3457';
forcedEnv['ANTHROPIC_API_KEY'] = 'sk-zai-router';

// Build Claude SDK options
const sdkOptions: Options = {
model,
systemPrompt,
maxTurns,
cwd,
// Pass only explicitly allowed environment variables to SDK
env: buildEnv(),
env: forcedEnv,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Hardcoded localhost router configuration will break production Claude API calls.

This forces all Claude SDK requests to http://127.0.0.1:3457 with a placeholder API key sk-zai-router. This appears to be debug/development code that:

  1. Breaks Claude API calls when the local router isn't running
  2. Ignores the user's actual ANTHROPIC_API_KEY
  3. Cannot be disabled without code changes

If this routing is intentional for the Z.AI integration, it should be configurable via environment variables or settings, not hardcoded.

🐛 Suggested fix: Make router configuration optional
-    // PROBE: Log Provider Execution (Removed)
-
-    // FORCE ROUTER CONFIGURATION
-    const forcedEnv = buildEnv();
-    forcedEnv['ANTHROPIC_BASE_URL'] = 'http://127.0.0.1:3457';
-    forcedEnv['ANTHROPIC_API_KEY'] = 'sk-zai-router';
+    const sdkEnv = buildEnv();
+    
+    // Optional router configuration (for Z.AI routing when enabled)
+    if (process.env.USE_ZAI_ROUTER === 'true') {
+      sdkEnv['ANTHROPIC_BASE_URL'] = process.env.ZAI_ROUTER_URL || 'http://127.0.0.1:3457';
+      sdkEnv['ANTHROPIC_API_KEY'] = process.env.ZAI_ROUTER_KEY || 'sk-zai-router';
+    }

     // Build Claude SDK options
     const sdkOptions: Options = {
       model,
       systemPrompt,
       maxTurns,
       cwd,
       // Pass only explicitly allowed environment variables to SDK
-      env: forcedEnv,
+      env: sdkEnv,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// PROBE: Log Provider Execution (Removed)
// FORCE ROUTER CONFIGURATION
const forcedEnv = buildEnv();
forcedEnv['ANTHROPIC_BASE_URL'] = 'http://127.0.0.1:3457';
forcedEnv['ANTHROPIC_API_KEY'] = 'sk-zai-router';
// Build Claude SDK options
const sdkOptions: Options = {
model,
systemPrompt,
maxTurns,
cwd,
// Pass only explicitly allowed environment variables to SDK
env: buildEnv(),
env: forcedEnv,
// FORCE ROUTER CONFIGURATION
const sdkEnv = buildEnv();
// Optional router configuration (for Z.AI routing when enabled)
if (process.env.USE_ZAI_ROUTER === 'true') {
sdkEnv['ANTHROPIC_BASE_URL'] = process.env.ZAI_ROUTER_URL || 'http://127.0.0.1:3457';
sdkEnv['ANTHROPIC_API_KEY'] = process.env.ZAI_ROUTER_KEY || 'sk-zai-router';
}
// Build Claude SDK options
const sdkOptions: Options = {
model,
systemPrompt,
maxTurns,
cwd,
// Pass only explicitly allowed environment variables to SDK
env: sdkEnv,
🤖 Prompt for AI Agents
In @apps/server/src/providers/claude-provider.ts around lines 76 - 90, The code
currently forces a local router by mutating forcedEnv with hardcoded
ANTHROPIC_BASE_URL and ANTHROPIC_API_KEY before constructing sdkOptions, which
breaks production calls; change this so buildEnv() is used as the default and
only add router overrides when explicit config is present (e.g., check
process.env.ANTHROPIC_ROUTER_URL or a boolean like
process.env.USE_LOCAL_ROUTER), do not overwrite an existing ANTHROPIC_API_KEY
from buildEnv/process.env, and pass the resulting env into sdkOptions.env
(update the logic around forcedEnv/buildEnv() and the sdkOptions construction to
conditionally merge overrides instead of hardcoding).

Comment on lines +238 to +410
while (turnCount < maxTurns) {
turnCount++;

try {
// Find model definition to ensure correct casing (GLM-4.7)
const modelDef = ZAI_MODELS_DEF.find((m) => m.id === model);
const apiModel = modelDef ? modelDef.modelString : model;

// Call Z.AI API
const stream = await this.client.chat.completions.create({
model: apiModel,
messages,
max_tokens: 8192, // Increased to support large file operations
stream: true,
// Only pass tools if we have them
tools: tools && tools.length > 0 ? tools : undefined,
tool_choice: tools && tools.length > 0 ? 'auto' : undefined,
});

let currentContent = '';
// Support multiple simultaneous tool calls from GLM-4
const currentToolCalls: { id: string; name: string; arguments: string }[] = [];

// We need to accumulate the full response for history,
// but also yield incremental updates to UI.

for await (const chunk of stream) {
const delta = chunk.choices[0]?.delta;
const finishReason = chunk.choices[0]?.finish_reason;

// 1. Handle Text Content
// Check for reasoning content (GLM-4 specific)
const reasoning = (delta as any)?.reasoning_content || (delta as any)?.thinking;
if (reasoning) {
currentContent += `\n*Thinking: ${reasoning}*\n`;
yield {
type: 'assistant',
message: {
role: 'assistant',
content: [{ type: 'text', text: `\n*Thinking: ${reasoning}*\n` }],
},
};
}

if (delta?.content) {
currentContent += delta.content;
yield {
type: 'assistant',
message: {
role: 'assistant',
content: [{ type: 'text', text: delta.content }],
},
};
}

// 2. Handle Tool Calls (Streaming)
// Z.AI GLM-4 can return multiple tool calls in a single response
// and streams partial data across multiple chunks
if (delta?.tool_calls) {
for (const toolCall of delta.tool_calls) {
const index = toolCall.index ?? 0;

// Initialize tool calls array if needed
if (!currentToolCalls[index]) {
currentToolCalls[index] = {
id: toolCall.id || `tool_${index}`,
name: toolCall.function?.name || '',
arguments: toolCall.function?.arguments || '',
};
} else {
// Append to existing tool call data
if (toolCall.id) {
currentToolCalls[index].id = toolCall.id;
}
if (toolCall.function?.name) {
currentToolCalls[index].name = toolCall.function.name;
}
if (toolCall.function?.arguments) {
currentToolCalls[index].arguments += toolCall.function.arguments;
}
}
}
}
}

// Append assistant response to history
const assistantMsg: OpenAI.ChatCompletionMessageParam = {
role: 'assistant',
content: currentContent || null,
};

// If we had tool calls, we need to handle them
if (currentToolCalls.length > 0) {
// Add all tool calls to assistant message
assistantMsg.tool_calls = currentToolCalls.map((tc) => ({
id: tc.id,
type: 'function' as const,
function: {
name: tc.name,
arguments: tc.arguments,
},
}));

messages.push(assistantMsg);

// Execute each tool call in sequence
for (const toolCall of currentToolCalls) {
// Normalize arguments
let args: Record<string, unknown> = {};
try {
args = JSON.parse(toolCall.arguments);
} catch (e) {
logger.error(`Failed to parse tool arguments for ${toolCall.name}`, e);
// Add error result to history
messages.push({
role: 'tool',
tool_call_id: toolCall.id,
content: `Error: Failed to parse tool arguments: ${e instanceof Error ? e.message : String(e)}`,
});
continue;
}

// Yield Tool Use to UI
yield {
type: 'assistant',
message: {
role: 'assistant',
content: [
{
type: 'tool_use',
name: toolCall.name,
input: args,
tool_use_id: toolCall.id,
},
],
},
};

// Execute Tool
logger.info(`Executing tool: ${toolCall.name}`, args);
const result = await zaiTools.executeTool(toolCall.name, args);

// Add tool result to history
messages.push({
role: 'tool',
tool_call_id: toolCall.id,
content: result,
});
}

// Loop continues to send all tool results back to model
continue;
} else {
// No tool calls -> Final response (text only)
messages.push(assistantMsg);

// Yield 'result' to signal completion of this query to AutoModeService
yield {
type: 'result',
subtype: 'success',
result: currentContent,
};
return;
}
} catch (error) {
logger.error('Z.AI execution loop failed', error);
yield {
type: 'error',
error: `Z.AI Error: ${error instanceof Error ? error.message : String(error)}`,
};
return;
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if ZaiTools has timeout handling
rg -n "timeout|Timeout|TIMEOUT" apps/server/src/providers/zai-tools.ts

Repository: AutoMaker-Org/automaker

Length of output: 49


🏁 Script executed:

# Also check the full zai-provider.ts file structure and imports
head -50 apps/server/src/providers/zai-provider.ts

Repository: AutoMaker-Org/automaker

Length of output: 1405


🏁 Script executed:

# Check for createEventEmitter usage in the file
rg -n "createEventEmitter\|EventEmitter" apps/server/src/providers/zai-provider.ts

Repository: AutoMaker-Org/automaker

Length of output: 49


🏁 Script executed:

# Check for resolveModelString usage
rg -n "resolveModelString" apps/server/src/providers/zai-provider.ts

Repository: AutoMaker-Org/automaker

Length of output: 49


🏁 Script executed:

# Find lib/events.ts
find . -name "events.ts" -type f | head -20

Repository: AutoMaker-Org/automaker

Length of output: 98


🏁 Script executed:

# Check if zai-tools exists with different extensions or locations
find . -name "*zai-tool*" -type f

Repository: AutoMaker-Org/automaker

Length of output: 107


🏁 Script executed:

# Look for the model resolution logic and lines around 326
sed -n '310,360p' apps/server/src/providers/zai-provider.ts

Repository: AutoMaker-Org/automaker

Length of output: 1857


🏁 Script executed:

# Check what happens with the stream response and events
sed -n '238,280p' apps/server/src/providers/zai-provider.ts

Repository: AutoMaker-Org/automaker

Length of output: 1742


🏁 Script executed:

# Search for any event emission in the file
rg -n "emit\|EventEmitter\|event" apps/server/src/providers/zai-provider.ts | head -20

Repository: AutoMaker-Org/automaker

Length of output: 49


🏁 Script executed:

# Check lib/events.ts to see createEventEmitter API
cat apps/server/src/lib/events.ts

Repository: AutoMaker-Org/automaker

Length of output: 1041


🏁 Script executed:

# Check zai-tools.ts for timeout handling
cat apps/server/src/providers/zai-tools.ts

Repository: AutoMaker-Org/automaker

Length of output: 9452


🏁 Script executed:

# Check the function signature to understand if it's a generator
sed -n '200,240p' apps/server/src/providers/zai-provider.ts

Repository: AutoMaker-Org/automaker

Length of output: 1384


🏁 Script executed:

# Check imports to see if resolveModelString is imported anywhere
rg -n "import.*resolveModelString\|from.*model-resolver" apps/server/src/providers/zai-provider.ts

Repository: AutoMaker-Org/automaker

Length of output: 49


Must implement createEventEmitter() and resolveModelString() per coding guidelines before this can be approved.

This agentic loop implementation is well-structured for streaming responses and tool execution, but it violates two mandatory coding guidelines:

  1. Missing createEventEmitter(): Line 238+ uses a generator pattern with yield, but the coding guideline requires using createEventEmitter() from apps/server/src/lib/events.ts for all server operations to emit events that stream to frontend via WebSocket.

  2. Missing resolveModelString(): Lines 242-243 perform local model lookup via ZAI_MODELS_DEF.find(), but the coding guideline requires using resolveModelString() from @automaker/model-resolver to convert model aliases (haiku, sonnet, opus) to full model names.

Additionally, the original concern about tool timeouts is valid—zai-tools.ts has no timeout protection on any tool execution methods (Bash, Read, Write, Edit, Glob, Grep). A slow or hanging tool will block the entire loop.

Comment on lines +14 to +15
import glob from 'glob';
const globAsync = promisify(glob);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# Check for glob package version in package.json files
find . -name "package.json" -type f -exec grep -l '"glob"' {} \; | head -10

Repository: AutoMaker-Org/automaker

Length of output: 49


🏁 Script executed:

# Find all package.json files and display glob-related dependencies
find . -name "package.json" -type f | head -20

Repository: AutoMaker-Org/automaker

Length of output: 531


🏁 Script executed:

# Check glob version in apps/server/package.json and root package.json
echo "=== apps/server/package.json ===" && grep -A2 '"glob"' ./apps/server/package.json && echo -e "\n=== root package.json ===" && grep -A2 '"glob"' ./package.json

Repository: AutoMaker-Org/automaker

Length of output: 99


🏁 Script executed:

# Read the full apps/server/package.json to see all dependencies
cat ./apps/server/package.json

Repository: AutoMaker-Org/automaker

Length of output: 1939


🏁 Script executed:

# Check which package has glob as a dependency
echo "=== Checking libs/provider-zai/package.json ===" && cat ./libs/provider-zai/package.json | grep -A2 '"glob"' && echo -e "\n=== Checking libs/providers-core/package.json ===" && cat ./libs/providers-core/package.json | grep -A2 '"glob"'

Repository: AutoMaker-Org/automaker

Length of output: 114


🏁 Script executed:

# Read full package.json files to find glob
echo "=== libs/provider-zai/package.json ===" && cat ./libs/provider-zai/package.json && echo -e "\n=== libs/providers-core/package.json ===" && cat ./libs/providers-core/package.json

Repository: AutoMaker-Org/automaker

Length of output: 1573


🏁 Script executed:

# Check the actual zai-tools.ts file
cat ./apps/server/src/providers/zai-tools.ts | head -30

Repository: AutoMaker-Org/automaker

Length of output: 766


🏁 Script executed:

# Check root package.json for glob dependency
cat ./package.json

Repository: AutoMaker-Org/automaker

Length of output: 4220


🏁 Script executed:

# Search for glob in lock files and check if it's a transitive dependency
echo "=== Searching for glob in lock files ===" && (grep -r '"glob"' --include="*lock*" . 2>/dev/null | head -10 || echo "No lock file matches found")

# Also check if glob is used elsewhere
echo -e "\n=== Searching for glob imports in codebase ===" && rg "import.*glob" --type ts --type js 2>/dev/null | head -20

Repository: AutoMaker-Org/automaker

Length of output: 965


🏁 Script executed:

# Find which packages depend on glob
echo "=== Packages with glob dependency ===" && grep -r '"glob"' --include="package.json" . 2>/dev/null

# Check the actual resolved version in package-lock.json for apps/server context
echo -e "\n=== Checking package-lock.json for glob version resolutions ===" && grep -A3 '"glob"' ./package-lock.json | grep -E '"version"|"glob"' | head -20

Repository: AutoMaker-Org/automaker

Length of output: 447


🏁 Script executed:

# Parse package-lock.json to find the dependency tree for glob
python3 << 'EOF'
import json

with open('./package-lock.json', 'r') as f:
    lock = json.load(f)

# Find glob in dependencies
def find_glob_dependents(deps, path=""):
    results = []
    if isinstance(deps, dict):
        for key, value in deps.items():
            if key == "glob":
                results.append((path or "root", value.get("version", "unknown")))
            elif isinstance(value, dict) and "dependencies" in value:
                results.extend(find_glob_dependents(value["dependencies"], key))
    return results

# Check dependencies and packages sections
all_results = []

if "dependencies" in lock:
    all_results.extend(find_glob_dependents(lock["dependencies"]))

if "packages" in lock:
    for pkg_path, pkg_data in lock["packages"].items():
        if "glob" in pkg_data.get("dependencies", {}):
            glob_spec = pkg_data["dependencies"]["glob"]
            all_results.append((pkg_path, glob_spec))

print("Packages that depend on glob:")
for pkg, version in sorted(set(all_results)):
    print(f"  {pkg}: {version}")
EOF

Repository: AutoMaker-Org/automaker

Length of output: 153


🏁 Script executed:

# Simpler approach: search for patterns in package-lock.json
echo "=== Finding glob in package-lock.json context ===" && grep -B5 '"glob"' ./package-lock.json | grep -E '"name"|"glob"|"version"' | head -40

Repository: AutoMaker-Org/automaker

Length of output: 394


🏁 Script executed:

# Search for the actual glob package entry in package-lock.json
echo "=== Direct glob package entry ===" && grep -A10 '"node_modules/glob"' ./package-lock.json | head -40

Repository: AutoMaker-Org/automaker

Length of output: 569


🏁 Script executed:

# Check how glob is exported in node_modules
echo "=== Checking glob v7.2.3 main entry ===" && grep -A5 '"main"' node_modules/glob/package.json 2>/dev/null || echo "node_modules not available in sandbox"

# Search the codebase for any issues with glob usage
echo -e "\n=== Full zai-tools.ts to see how globAsync is used ===" && cat ./apps/server/src/providers/zai-tools.ts | grep -A10 "globAsync"

Repository: AutoMaker-Org/automaker

Length of output: 1185


Update glob package to v9 or later — v7.2.3 is deprecated.

The installed version (glob v7.2.3) is marked as deprecated with the message "Glob versions prior to v9 are no longer supported". Upgrade to glob v9+ to maintain support. The promisify(glob) approach will continue to work with v9+, which also offers native promise alternatives if desired.

🤖 Prompt for AI Agents
In @apps/server/src/providers/zai-tools.ts around lines 14 - 15, Update the glob
dependency to v9+ in package.json and reinstall so you’re on a supported
release, then adjust the import/usage in this file: replace the current import
of "glob" and the promisify wrapper around it (the symbols "import glob from
'glob';" and "const globAsync = promisify(glob);") with the v9+ promise-based
API (e.g., import the promise-returning glob function from 'glob' and set
globAsync to that function) or keep the promisify approach if you prefer, then
run tests/build to confirm no runtime API changes remain.

// Model selection state
const [selectedProfileId, setSelectedProfileId] = useState<string | undefined>();
const [modelEntry, setModelEntry] = useState<PhaseModelEntry>({ model: 'opus' });
const [modelEntry, setModelEntry] = useState<PhaseModelEntry>({ model: 'default' });
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# Find the ModelAlias type definition and related model types
rg "type ModelAlias|ModelAlias\s*=" --type=ts -A 15

Repository: AutoMaker-Org/automaker

Length of output: 2315


🏁 Script executed:

# Also check for CursorModelId and CodexModelId definitions
rg "type CursorModelId|type CodexModelId|CodexModelId\s*=" --type=ts -A 10

Repository: AutoMaker-Org/automaker

Length of output: 4868


🏁 Script executed:

# Search for 'default' being used as a model value in the codebase
rg "model.*'default'|'default'.*model" --type=ts -B 2 -A 2

Repository: AutoMaker-Org/automaker

Length of output: 7896


🏁 Script executed:

# Check the PhaseModelEntry type definition
rg "PhaseModelEntry|interface PhaseModelEntry|type PhaseModelEntry" --type=ts -A 5

Repository: AutoMaker-Org/automaker

Length of output: 50381


🏁 Script executed:

# Examine the add-feature-dialog.tsx file around line 143 for context
sed -n '140,150p' apps/ui/src/components/views/board-view/dialogs/add-feature-dialog.tsx

Repository: AutoMaker-Org/automaker

Length of output: 583


🏁 Script executed:

# Check CLAUDE_MODEL_MAP keys to confirm 'default' is not a valid ModelAlias
rg "const CLAUDE_MODEL_MAP|CLAUDE_MODEL_MAP\s*=" --type=ts -A 20 | head -60

Repository: AutoMaker-Org/automaker

Length of output: 1424


🏁 Script executed:

# Check ZAI_MODEL_MAP keys as well
rg "const ZAI_MODEL_MAP|ZAI_MODEL_MAP\s*=" --type=ts -A 10 | head -40

Repository: AutoMaker-Org/automaker

Length of output: 521


'default' is not a valid model type per type definitions and should be declared as a supported model alias.

PhaseModelEntry.model is typed as ModelAlias | CursorModelId | CodexModelId, where ModelAlias = keyof typeof CLAUDE_MODEL_MAP | keyof typeof ZAI_MODEL_MAP. The only valid keys are 'haiku', 'sonnet', 'opus' (Claude), 'glm-4-plus', 'glm-4.5', etc. (Z.AI), and specific cursor/codex models—'default' is not among them.

Although resolveModelString() in the model-resolver explicitly handles 'default' as a runtime sentinel value that defers to backend defaults, and DEFAULT_PHASE_MODELS uses { model: 'default' } throughout, this design is not reflected in the type system. The // @ts-nocheck directive at line 1 suppresses the resulting type error.

To fix: either add 'default' to ModelAlias type definition in libs/types/src/model.ts, or use resolveModelString() per the coding guidelines to ensure proper type-safe model resolution.

Comment on lines +863 to +867
onSelect={() => {
onChange({
model: model.id as ModelAlias,
thinkingLevel: 'none',
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check what types ZAI_MODELS uses and find ModelAlias definition
rg -n "ZAI_MODELS|ZaiModel|ModelAlias" --type=ts libs/types/ apps/ui/src/ | head -50

Repository: AutoMaker-Org/automaker

Length of output: 5146


🏁 Script executed:

#!/bin/bash
# Find the actual definition of ModelAlias type
rg -n "type ModelAlias|export.*ModelAlias" --type=ts -A 3

Repository: AutoMaker-Org/automaker

Length of output: 2876


🏁 Script executed:

#!/bin/bash
# Check the context around the problematic cast in phase-model-selector.tsx
cat -n apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx | sed -n '850,880p'

Repository: AutoMaker-Org/automaker

Length of output: 1235


🏁 Script executed:

#!/bin/bash
# Find ZAI_MODEL_MAP definition
rg -n "ZAI_MODEL_MAP|CLAUDE_MODEL_MAP" --type=ts -A 10 | head -60

Repository: AutoMaker-Org/automaker

Length of output: 3182


🏁 Script executed:

#!/bin/bash
# Check the ZAI_MODELS constant definition
rg -n "export const ZAI_MODELS" --type=ts -A 15

Repository: AutoMaker-Org/automaker

Length of output: 3907


🏁 Script executed:

#!/bin/bash
# Get full ZAI_MODELS definition
cat -n apps/ui/src/components/views/board-view/shared/model-constants.ts | sed -n '105,150p'

Repository: AutoMaker-Org/automaker

Length of output: 1514


🏁 Script executed:

#!/bin/bash
# Check what onChange expects - find the onChange function signature
rg -n "onChange.*{" apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx -B 5 -A 2 | head -40

Repository: AutoMaker-Org/automaker

Length of output: 152


🏁 Script executed:

#!/bin/bash
# Find the component props/interface definition
rg -n "interface.*Props|type.*Props" apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx -A 20 | head -50

Repository: AutoMaker-Org/automaker

Length of output: 749


🏁 Script executed:

#!/bin/bash
# Look for onChange definition in the file
rg -n "onChange" apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx | head -20

Repository: AutoMaker-Org/automaker

Length of output: 473


🏁 Script executed:

#!/bin/bash
# Find PhaseModelEntry type definition
rg -n "type PhaseModelEntry|interface PhaseModelEntry" --type=ts -A 10

Repository: AutoMaker-Org/automaker

Length of output: 3673


🏁 Script executed:

#!/bin/bash
# Check the exact keys in ZAI_MODEL_MAP vs ZAI_MODELS
cat -n libs/types/src/model.ts | sed -n '81,89p'

Repository: AutoMaker-Org/automaker

Length of output: 368


🏁 Script executed:

#!/bin/bash
# Let's verify the exact mismatch - compare ZAI_MODELS IDs with ZAI_MODEL_MAP keys
echo "=== ZAI_MODELS IDs (from UI) ===" && \
cat -n apps/ui/src/components/views/board-view/shared/model-constants.ts | sed -n '105,160p' | grep "id:" && \
echo "" && \
echo "=== ZAI_MODEL_MAP keys (from types) ===" && \
cat -n libs/types/src/model.ts | sed -n '81,89p'

Repository: AutoMaker-Org/automaker

Length of output: 638


🏁 Script executed:

#!/bin/bash
# Check if there's a normalization/mapping function that converts the UI IDs to ModelAlias
rg -n "GLM-4|glm-4" apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx | head -20

Repository: AutoMaker-Org/automaker

Length of output: 49


🏁 Script executed:

#!/bin/bash
# Check how the PhaseModelEntry is used downstream and if case matters
rg -n "model: model.id as ModelAlias" apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx -B 3 -A 10

Repository: AutoMaker-Org/automaker

Length of output: 1246


🏁 Script executed:

#!/bin/bash
# Check if there's a model normalization/resolution happening before use
rg -n "resolveModelString|toLowerCase|normali" apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx

Repository: AutoMaker-Org/automaker

Length of output: 49


🏁 Script executed:

#!/bin/bash
# Check the model-resolver to see if it handles case normalization
cat -n libs/model-resolver/src/resolver.ts | head -100

Repository: AutoMaker-Org/automaker

Length of output: 4213


🏁 Script executed:

#!/bin/bash
# Check how the PhaseModelEntry is actually used when stored/retrieved
rg -n "PhaseModelEntry|resolveModelString" libs/model-resolver/src/resolver.ts -A 5 -B 2

Repository: AutoMaker-Org/automaker

Length of output: 2681


🏁 Script executed:

#!/bin/bash
# Confirm the case mismatch - check if uppercase IDs would work in ZAI_MODEL_MAP
python3 << 'EOF'
zai_model_map = {
    'glm-4-plus': 'glm-4-plus',
    'glm-4.7': 'glm-4.7',
    'glm-4.6': 'glm-4.6',
    'glm-4.5': 'glm-4.5',
    'glm-4.5-air': 'glm-4.5-air',
    'glm-4.5-flash': 'glm-4.5-flash',
    'glm-4.6v': 'glm-4.6v',
}

ui_ids = ['GLM-4-Plus', 'GLM-4.7', 'GLM-4.5', 'GLM-4.5-Flash', 'GLM-4.6', 'GLM-4.6V', 'GLM-4.5-Air']

print("Checking if UI IDs match ZAI_MODEL_MAP keys:")
for ui_id in ui_ids:
    in_map = ui_id in zai_model_map
    lowercase_in_map = ui_id.lower() in zai_model_map
    print(f"  '{ui_id}' in map: {in_map}, lowercase '{ui_id.lower()}' in map: {lowercase_in_map}")
EOF

Repository: AutoMaker-Org/automaker

Length of output: 153


🏁 Script executed:

#!/bin/bash
# Check if there's already a lowercase conversion happening somewhere for these models
rg -n "toLowerCase" apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx

Repository: AutoMaker-Org/automaker

Length of output: 49


🏁 Script executed:

#!/bin/bash
# See the full context - check what happens when the model is actually used/resolved
rg -n "resolvePhaseModel\|resolveModelString" apps/ui/src/ --type=ts | head -20

Repository: AutoMaker-Org/automaker

Length of output: 49


🏁 Script executed:

#!/bin/bash
# Let's verify the actual issue - check if resolveModelString would fail with uppercase IDs
cat -n libs/model-resolver/src/resolver.ts | sed -n '78,82p'

Repository: AutoMaker-Org/automaker

Length of output: 282


🏁 Script executed:

#!/bin/bash
# Check if there's any case conversion happening between UI and the model resolver
rg -n "GLM|glm" apps/ui/src/components/views/settings-view/model-defaults/ --type=ts | grep -i "lower\|upper\|normalize"

Repository: AutoMaker-Org/automaker

Length of output: 49


🏁 Script executed:

#!/bin/bash
# Verify what actually happens in resolvePhaseModel when passed an uppercase ID
cat -n libs/model-resolver/src/resolver.ts | sed -n '170,206p'

Repository: AutoMaker-Org/automaker

Length of output: 1503


🏁 Script executed:

#!/bin/bash
# Check if there's a case conversion happening in the store or app logic
rg -n "toLowerCase" apps/ui/src/store/app-store.ts

Repository: AutoMaker-Org/automaker

Length of output: 235


🏁 Script executed:

#!/bin/bash
# Check if PhaseModelEntry model field is ever normalized before being used
rg -n "phaseModel.*model.*toLowerCase\|model.*toLowerCase" --type=ts | head -10

Repository: AutoMaker-Org/automaker

Length of output: 49


🏁 Script executed:

#!/bin/bash
# Final verification - check if the uppercase IDs would actually cause TypeScript errors
rg -n "type ModelAlias" libs/types/src/model.ts -A 1

Repository: AutoMaker-Org/automaker

Length of output: 240


🏁 Script executed:

#!/bin/bash
# Check what the actual runtime behavior would be - is there a fallback in resolveModelString?
cat -n libs/model-resolver/src/resolver.ts | sed -n '78,115p'

Repository: AutoMaker-Org/automaker

Length of output: 1760


🏁 Script executed:

#!/bin/bash
# Check the fallback at the end of resolveModelString
cat -n libs/model-resolver/src/resolver.ts | sed -n '115,130p'

Repository: AutoMaker-Org/automaker

Length of output: 694


Z.AI model IDs have a case mismatch with ZAI_MODEL_MAP keys, causing resolution to fail.

The uppercase IDs from ZAI_MODELS (e.g., 'GLM-4.7') don't match the lowercase keys in ZAI_MODEL_MAP (e.g., 'glm-4.7'). When resolveModelString() checks if (modelKey in ZAI_MODEL_MAP) at line 79 of libs/model-resolver/src/resolver.ts, the check will fail for uppercase IDs, causing the resolver to fall back to the default model instead of using the selected Z.AI model. Either normalize the UI IDs to lowercase or update ZAI_MODEL_MAP keys to match the UI format.

🤖 Prompt for AI Agents
In
@apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx
around lines 863 - 867, The selected Z.AI model IDs from the UI (used in the
onSelect handler calling onChange with model: model.id as ModelAlias) are
uppercase and thus don't match the lowercase keys in ZAI_MODEL_MAP used by
resolveModelString in libs/model-resolver/src/resolver.ts; fix this by
normalizing the ID before passing it to onChange (e.g., convert model.id to
lowercase) so the resolver's check (if (modelKey in ZAI_MODEL_MAP)) succeeds, or
alternatively update ZAI_MODEL_MAP keys to use the same casing as ZAI_MODELS so
model.id and the map keys match.

Comment on lines +264 to +270
// FORCE BYPASS FOR LOCAL Z.AI DEV - Always authenticate
// This removes the login requirement for local development
useAuthStore.getState().setAuthState({ isAuthenticated: true, authChecked: true });
logger.info('[Z.AI DEV] Authentication bypassed for local development');
return;

// --- Original auth logic below (disabled) ---
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: Unconditional authentication bypass will ship to production.

This code bypasses authentication for all environments, not just local development. There is no environment check (NODE_ENV, feature flag, etc.) to gate this behavior. If merged, any user can access the application without authentication.

Additionally, the return statement makes lines 271-368 (the entire original auth flow) unreachable dead code.

🔒 Recommended fix: Remove or gate behind environment check

Option 1: Remove entirely (preferred for production-bound code)

-        // FORCE BYPASS FOR LOCAL Z.AI DEV - Always authenticate
-        // This removes the login requirement for local development
-        useAuthStore.getState().setAuthState({ isAuthenticated: true, authChecked: true });
-        logger.info('[Z.AI DEV] Authentication bypassed for local development');
-        return;
-
-        // --- Original auth logic below (disabled) ---

Option 2: Gate behind environment check (if dev bypass is truly needed)

+        // Development-only auth bypass for Z.AI testing
+        if (import.meta.env.DEV && import.meta.env.VITE_BYPASS_AUTH === 'true') {
+          useAuthStore.getState().setAuthState({ isAuthenticated: true, authChecked: true });
+          logger.warn('[Z.AI DEV] Authentication bypassed - DEV MODE ONLY');
+          return;
+        }
-        // FORCE BYPASS FOR LOCAL Z.AI DEV - Always authenticate
-        // This removes the login requirement for local development
-        useAuthStore.getState().setAuthState({ isAuthenticated: true, authChecked: true });
-        logger.info('[Z.AI DEV] Authentication bypassed for local development');
-        return;
-
-        // --- Original auth logic below (disabled) ---
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// FORCE BYPASS FOR LOCAL Z.AI DEV - Always authenticate
// This removes the login requirement for local development
useAuthStore.getState().setAuthState({ isAuthenticated: true, authChecked: true });
logger.info('[Z.AI DEV] Authentication bypassed for local development');
return;
// --- Original auth logic below (disabled) ---
// --- Original auth logic below ---

Comment on lines +113 to +117
const payload = {
api_key: id,
exp: Date.now() + 3600 * 1000,
timestamp: Date.now(),
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: JWT exp claim uses milliseconds instead of seconds.

The JWT standard expects exp (expiration) to be a Unix timestamp in seconds, but Date.now() returns milliseconds. This creates tokens that expire ~50 years in the future from the server's perspective, or may be rejected by strict validators.

🔧 Suggested fix
       const payload = {
         api_key: id,
-        exp: Date.now() + 3600 * 1000,
-        timestamp: Date.now(),
+        exp: Math.floor(Date.now() / 1000) + 3600, // 1 hour from now, in seconds
+        timestamp: Math.floor(Date.now() / 1000),
       };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const payload = {
api_key: id,
exp: Date.now() + 3600 * 1000,
timestamp: Date.now(),
};
const payload = {
api_key: id,
exp: Math.floor(Date.now() / 1000) + 3600, // 1 hour from now, in seconds
timestamp: Math.floor(Date.now() / 1000),
};
🤖 Prompt for AI Agents
In @libs/provider-zai/src/zai-provider.ts around lines 113 - 117, The JWT
payload building uses milliseconds for the exp claim (payload in
zai-provider.ts), causing incorrect expiration; change both exp and timestamp to
Unix seconds by using Math.floor(Date.now()/1000) (or equivalent) so exp is
seconds-since-epoch (e.g., set exp to Math.floor(Date.now()/1000) + 3600 and
timestamp to Math.floor(Date.now()/1000)).

} as const;

export type ModelAlias = keyof typeof CLAUDE_MODEL_MAP;
export type ModelAlias = keyof typeof CLAUDE_MODEL_MAP | keyof typeof ZAI_MODEL_MAP;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

TypeScript error: ZAI_MODEL_MAP used before declaration.

The ModelAlias type references ZAI_MODEL_MAP on line 69, but ZAI_MODEL_MAP is declared on line 81. This will cause a TypeScript compile error due to the temporal dead zone.

🐛 Proposed fix: Move ZAI_MODEL_MAP declaration before its usage

Move the ZAI_MODEL_MAP constant declaration to appear before line 69, or move the ModelAlias type definition to after the ZAI_MODEL_MAP declaration:

+/**
+ * Model alias mapping for Z.AI models
+ */
+export const ZAI_MODEL_MAP = {
+  'glm-4-plus': 'glm-4-plus',
+  'glm-4.7': 'glm-4.7',
+  'glm-4.6': 'glm-4.6',
+  'glm-4.5': 'glm-4.5',
+  'glm-4.5-air': 'glm-4.5-air',
+  'glm-4.5-flash': 'glm-4.5-flash',
+  'glm-4.6v': 'glm-4.6v',
+} as const;
+
 export type ModelAlias = keyof typeof CLAUDE_MODEL_MAP | keyof typeof ZAI_MODEL_MAP;
 export type CodexModelId = (typeof CODEX_MODEL_MAP)[keyof typeof CODEX_MODEL_MAP];

 // ... rest of the file ...

-/**
- * Model alias mapping for Z.AI models
- */
-export const ZAI_MODEL_MAP = {
-  'glm-4-plus': 'glm-4-plus',
-  'glm-4.7': 'glm-4.7',
-  'glm-4.6': 'glm-4.6',
-  'glm-4.5': 'glm-4.5',
-  'glm-4.5-air': 'glm-4.5-air',
-  'glm-4.5-flash': 'glm-4.5-flash',
-  'glm-4.6v': 'glm-4.6v',
-} as const;
🤖 Prompt for AI Agents
In @libs/types/src/model.ts at line 69, The TypeScript error is caused by
ModelAlias referencing ZAI_MODEL_MAP before it's declared; to fix it, reorder
declarations so ZAI_MODEL_MAP is defined before ModelAlias (or move the
ModelAlias type below ZAI_MODEL_MAP), ensuring ModelAlias = keyof typeof
CLAUDE_MODEL_MAP | keyof typeof ZAI_MODEL_MAP only appears after both
CLAUDE_MODEL_MAP and ZAI_MODEL_MAP are declared.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/ui/src/components/ui/provider-icon.tsx (1)

144-171: Missing model string detection for Z.AI models.

getUnderlyingModelIcon detects providers by model string patterns (e.g., sonnet → anthropic, gpt- → openai), but there's no detection for Z.AI/GLM models. Model strings like "glm-4" or "zhipu" will fall through to the default 'anthropic' icon.

Proposed fix to add zai model detection
  if (modelStr.includes('grok')) {
    return 'grok';
  }
+ if (modelStr.includes('glm') || modelStr.includes('zhipu') || modelStr.includes('zai')) {
+   return 'zai';
+ }
  if (modelStr.includes('cursor') || modelStr === 'auto' || modelStr === 'composer-1') {
    return 'cursor';
  }
🧹 Nitpick comments (5)
apps/ui/src/components/views/settings-view/providers/zai-settings-tab.tsx (3)

26-27: Remove unused imports.

ZAI_MODELS and Switch are imported but never used in this component. These appear to be leftover from planned model configuration UI mentioned in the TODO.

🧹 Proposed fix
-import { ZAI_MODELS } from '@/components/views/board-view/shared/model-constants';
-import { Switch } from '@/components/ui/switch';

35-38: Remove unused store destructures.

enabledZaiModels, zaiDefaultModel, setEnabledZaiModels, and setZaiDefaultModel are destructured from the store but never used. Remove these to avoid confusion about the component's actual dependencies.

🧹 Proposed fix
  const {
    apiKeys,
    setApiKeys,
-   enabledZaiModels,
-   zaiDefaultModel,
-   setEnabledZaiModels,
-   setZaiDefaultModel,
  } = useAppStore();

64-65: Log the caught error for debuggability.

The error is caught but not logged, making it difficult to diagnose failures. Use the logger instance that's already available.

🧹 Proposed fix
    } catch (e) {
+     logger.error('Failed to save Z.AI API key', e);
      toast.error('Failed to save API key');
    } finally {
apps/ui/src/components/views/world-model-view.tsx (2)

321-323: Use structured logger instead of console.error.

The codebase uses a logger utility (visible in the server code snippets). Consider using it here for consistent error tracking and observability.

+import { logger } from '@/lib/logger';

-console.error('Smart Expand error:', error);
+logger.error('Smart Expand error:', error);

This applies to other console.error calls at lines 142, 400, and 457 as well.


364-364: Type assertion workarounds indicate missing type definition.

Multiple as any casts and @ts-ignore (lines 364, 440-441, 447) suggest planSpec isn't properly typed in the Feature interface. Consider extending the type definition to avoid these workarounds.

// In app-store.ts Feature interface, ensure planSpec is typed:
interface PlanSpec {
  content?: string;
  status?: 'pending' | 'approved' | 'rejected';
  version?: number;
  reviewedByUser?: boolean;
}

export interface Feature extends ... {
  // ...
  planSpec?: PlanSpec;
}

This would eliminate the need for type assertions and improve maintainability.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between aafd886 and 1d44800.

📒 Files selected for processing (3)
  • apps/ui/src/components/ui/provider-icon.tsx
  • apps/ui/src/components/views/settings-view/providers/zai-settings-tab.tsx
  • apps/ui/src/components/views/world-model-view.tsx
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Always import from shared packages (@automaker/*), never from old relative paths

Files:

  • apps/ui/src/components/views/world-model-view.tsx
  • apps/ui/src/components/ui/provider-icon.tsx
  • apps/ui/src/components/views/settings-view/providers/zai-settings-tab.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use resolveModelString() from @automaker/model-resolver to convert model aliases (haiku, sonnet, opus) to full model names

Files:

  • apps/ui/src/components/views/world-model-view.tsx
  • apps/ui/src/components/ui/provider-icon.tsx
  • apps/ui/src/components/views/settings-view/providers/zai-settings-tab.tsx
🧠 Learnings (1)
📚 Learning: 2025-12-30T01:02:07.114Z
Learnt from: illia1f
Repo: AutoMaker-Org/automaker PR: 324
File: apps/ui/src/components/views/board-view/components/kanban-card/kanban-card.tsx:122-131
Timestamp: 2025-12-30T01:02:07.114Z
Learning: Tailwind CSS v4 uses postfix syntax for the important modifier: append ! at the end of the utility class (e.g., backdrop-blur-[0px]! or hover:bg-red-500!). The older v3 style used a prefix (!) at the start (e.g., !backdrop-blur-[0px]); prefer the postfix form for consistency across TSX files.

Applied to files:

  • apps/ui/src/components/views/world-model-view.tsx
  • apps/ui/src/components/ui/provider-icon.tsx
  • apps/ui/src/components/views/settings-view/providers/zai-settings-tab.tsx
🧬 Code graph analysis (2)
apps/ui/src/components/views/world-model-view.tsx (6)
apps/ui/src/store/app-store.ts (2)
  • useAppStore (1255-3145)
  • Feature (334-350)
apps/ui/src/components/views/board-view/hooks/use-board-actions.ts (1)
  • useBoardActions (56-969)
apps/ui/src/components/views/graph-view/graph-view.tsx (1)
  • GraphView (28-229)
apps/ui/src/components/views/board-view/dialogs/add-feature-dialog.tsx (1)
  • AddFeatureDialog (109-703)
apps/ui/src/components/views/board-view/dialogs/smart-expand-dialog.tsx (1)
  • SmartExpandDialog (35-213)
apps/server/src/services/auto-mode-service.ts (1)
  • followUpFeature (936-1182)
apps/ui/src/components/views/settings-view/providers/zai-settings-tab.tsx (3)
apps/server/src/routes/setup/common.ts (1)
  • setApiKey (25-27)
apps/ui/src/lib/electron.ts (1)
  • getElectronAPI (894-903)
apps/ui/src/components/ui/provider-icon.tsx (1)
  • ZaiIcon (121-123)
🔇 Additional comments (11)
apps/ui/src/components/views/settings-view/providers/zai-settings-tab.tsx (2)

48-69: LGTM on the save handler logic.

The handleSaveApiKey function correctly validates input, handles the async flow with proper loading state management, and uses a finally block to ensure isSaving is reset regardless of outcome.


71-103: LGTM on the UI implementation.

The card layout, input handling, and accessibility (label htmlFor + input id) are well-structured. The password input type appropriately masks the API key.

apps/ui/src/components/ui/provider-icon.tsx (5)

1-14: LGTM!

Header documentation is clear and correctly updated to include Z.AI in the provider list.


28-28: LGTM!

Provider key addition follows the established pattern.


121-123: LGTM!

Component export follows the same pattern as other provider icons.


137-137: LGTM!

Registry entry is consistent with other providers.


184-184: LGTM!

Icon map entry correctly wires zai to the ZaiIcon component.

apps/ui/src/components/views/world-model-view.tsx (4)

16-39: LGTM - Imports follow project conventions.

Imports correctly use the @/ alias mapping to shared packages within the UI app. Dialog imports are properly consolidated from the barrel export.


329-349: LGTM - Add-and-start flow implementation.

The approach of capturing feature IDs before/after creation to find the new feature is reasonable for this UI context. Error handling with toast notification is appropriate.


177-200: LGTM - Worktree selection logic.

Good use of a stable EMPTY_WORKTREES constant to prevent infinite re-renders in the selector. The worktree and branch resolution logic correctly handles both primary and specific worktree cases.


483-629: LGTM - Clean component composition.

The JSX structure is well-organized with proper dialog state management. All dialogs correctly wire up their open/close handlers with appropriate state cleanup (e.g., clearing spawnParentFeature and smartExpandFeature on dialog close).

Comment on lines +63 to +67
zai: {
viewBox: '0 0 24 24',
// Simple Z logo
path: 'M4 4h16l-16 16h16', // Note: This is an icon path, usually requires fill/stroke props, but for simple path d this works if stroke is handled or outline. Standard filled path for Z: M20 4H4v2h12.17L4 18v2h16v-2H7.83L20 6V4z'
},
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

SVG path is stroke-based but component uses fill rendering.

The path 'M4 4h16l-16 16h16' uses relative line commands designed for stroke rendering. However, ProviderIcon applies fill="currentColor" (line 96), which will result in the icon not rendering correctly—it will appear as a thin line or be invisible.

Use the filled path variant mentioned in the comment, or add stroke="currentColor" and fill="none" handling for this icon.

Proposed fix using filled path
  zai: {
    viewBox: '0 0 24 24',
-   // Simple Z logo
-   path: 'M4 4h16l-16 16h16', // Note: This is an icon path, usually requires fill/stroke props, but for simple path d this works if stroke is handled or outline. Standard filled path for Z: M20 4H4v2h12.17L4 18v2h16v-2H7.83L20 6V4z'
+   // Simple Z logo (filled variant)
+   path: 'M20 4H4v2h12.17L4 18v2h16v-2H7.83L20 6V4z',
  },

Comment on lines +262 to +266
const handleRunSmartExpand = useCallback(
async (seedFeature: Feature, options: any) => {
try {
const response = await fetch('http://localhost:3000/api/auto-mode/expand-feature', {
method: 'POST',
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Check for existing API base URL configurations
rg -n "API_BASE_URL|VITE_API|VITE_|localhost:3000" --type=ts --type=tsx -g '!node_modules' | head -40

Repository: AutoMaker-Org/automaker

Length of output: 94


🏁 Script executed:

# Find SmartExpandDialog to verify ExpandOptions type exists
fd -e ts -e tsx | xargs grep -l "SmartExpandDialog" | head -5

Repository: AutoMaker-Org/automaker

Length of output: 245


🏁 Script executed:

# Examine the world-model-view.tsx file around the flagged lines
cat -n apps/ui/src/components/views/world-model-view.tsx | sed -n '260,330p'

Repository: AutoMaker-Org/automaker

Length of output: 3286


🏁 Script executed:

# Check for other hardcoded localhost usages in the file
rg -n "localhost:3000" apps/ui/src/components/views/world-model-view.tsx

Repository: AutoMaker-Org/automaker

Length of output: 163


🏁 Script executed:

# Check SmartExpandDialog for ExpandOptions type definition
cat apps/ui/src/components/views/board-view/dialogs/smart-expand-dialog.tsx | head -100

Repository: AutoMaker-Org/automaker

Length of output: 3222


🏁 Script executed:

# Search for API configuration patterns (try without tsx flag)
rg -n "API_BASE_URL|VITE_API|import.meta.env" --type ts -g '!node_modules' | head -30

Repository: AutoMaker-Org/automaker

Length of output: 3175


🏁 Script executed:

# Check if resolveModelString is imported or used in world-model-view.tsx
rg -n "resolveModelString|@automaker/model-resolver" apps/ui/src/components/views/world-model-view.tsx

Repository: AutoMaker-Org/automaker

Length of output: 49


🏁 Script executed:

# Look for environment variable usage patterns in the app
rg -n "import.meta.env.VITE" apps/ui/src --type ts | head -20

Repository: AutoMaker-Org/automaker

Length of output: 1480


🏁 Script executed:

# Check how VITE_SERVER_URL is used and where the API_BASE_URL pattern should come from
cat apps/ui/src/lib/http-api-client.ts | sed -n '145,160p'

Repository: AutoMaker-Org/automaker

Length of output: 467


🏁 Script executed:

# Check if resolveModelString is available and where it's used
rg -n "resolveModelString" apps/ui/src --type ts | head -10

Repository: AutoMaker-Org/automaker

Length of output: 434


🏁 Script executed:

# Check if 'default' model string should be resolved per coding guidelines
rg -n "model.*default\|resolveModelString" apps/ui --type ts | head -20

Repository: AutoMaker-Org/automaker

Length of output: 49


🏁 Script executed:

# Look for getServerUrlSync to understand the URL pattern
rg -n "getServerUrlSync" apps/ui/src --type ts -B 2 -A 2 | head -30

Repository: AutoMaker-Org/automaker

Length of output: 1831


🏁 Script executed:

# Check if handleAddFeature expects a resolved model string or 'default'
rg -n "handleAddFeature" apps/ui/src/components/views/world-model-view.tsx -B 5 -A 15 | head -50

Repository: AutoMaker-Org/automaker

Length of output: 1706


🏁 Script executed:

# Find the handleAddFeature function definition
rg -n "const handleAddFeature|function handleAddFeature" apps/ui/src/components/views/world-model-view.tsx -A 20 | head -40

Repository: AutoMaker-Org/automaker

Length of output: 49


🏁 Script executed:

# Check what model parameter type is expected
ast-grep --pattern 'const handleAddFeature = useCallback(
  async ($_: $_) => {
    $$$
  },
  $_
)'

Repository: AutoMaker-Org/automaker

Length of output: 14879


🏁 Script executed:

# Check useBoardActions to see handleAddFeature signature
rg -n "useBoardActions\|handleAddFeature" apps/ui/src --type ts -B 2 -A 8 | grep -A 10 "handleAddFeature.*=" | head -30

Repository: AutoMaker-Org/automaker

Length of output: 49


🏁 Script executed:

# Check what ModelAlias type is and if 'default' is valid
rg -n "type ModelAlias|export.*ModelAlias" apps/ui/src --type ts | head -10

Repository: AutoMaker-Org/automaker

Length of output: 529


🏁 Script executed:

# Check if 'default' is a valid ModelAlias or needs resolution
rg -n "ModelAlias.*=" apps/ui/src --type ts -A 3 | head -20

Repository: AutoMaker-Org/automaker

Length of output: 1635


🏁 Script executed:

# Verify the getServerUrlSync import and usage pattern
cat apps/ui/src/lib/api-fetch.ts | head -40

Repository: AutoMaker-Org/automaker

Length of output: 1444


🏁 Script executed:

# Check what port the default server URL uses (not 3000)
rg -n "localhost:300" apps/ui/src --type ts | head -10

Repository: AutoMaker-Org/automaker

Length of output: 359


🏁 Script executed:

# Find the ModelAlias type definition
rg -n "type ModelAlias\|ModelAlias =" apps/ui/src --type ts | head -5

Repository: AutoMaker-Org/automaker

Length of output: 49


🏁 Script executed:

# Check if 'default' is accepted anywhere as a model value
rg -n "model.*default\|'default'" apps/ui/src/components/views/world-model-view.tsx -B 2 -A 2

Repository: AutoMaker-Org/automaker

Length of output: 49


🏁 Script executed:

# Verify the import structure and how model-resolver is used
cat apps/ui/src/config/model-config.ts

Repository: AutoMaker-Org/automaker

Length of output: 1412


🏁 Script executed:

# Find ModelAlias type definition more thoroughly
rg -n "ModelAlias" apps/ui/src --type ts | grep "type\|interface" | head -20

Repository: AutoMaker-Org/automaker

Length of output: 1878


🏁 Script executed:

# Check imports in world-model-view.tsx to see what's available
head -50 apps/ui/src/components/views/world-model-view.tsx | grep "import"

Repository: AutoMaker-Org/automaker

Length of output: 887


🏁 Script executed:

# Check auto-mode-service to understand if model resolution happens server-side
find apps -name "*auto-mode*" -type f | head -10

Repository: AutoMaker-Org/automaker

Length of output: 490


🏁 Script executed:

# Check auto-mode-service.ts to see how it handles the model parameter
cat apps/server/src/services/auto-mode-service.ts | head -100

Repository: AutoMaker-Org/automaker

Length of output: 3277


🏁 Script executed:

# Search for expand-feature endpoint in auto-mode-service
rg -n "expand-feature\|expandFeature" apps/server/src/services/auto-mode-service.ts -B 5 -A 15 | head -50

Repository: AutoMaker-Org/automaker

Length of output: 49


🏁 Script executed:

# Check if there's resolveModelString usage in the server auto-mode-service
rg -n "resolveModelString\|model.*default" apps/server/src/services/auto-mode-service.ts | head -10

Repository: AutoMaker-Org/automaker

Length of output: 49


🏁 Script executed:

# Search more thoroughly for expand-feature or similar endpoints
rg -n "expand\|Expand" apps/server/src/services/auto-mode-service.ts | head -20

Repository: AutoMaker-Org/automaker

Length of output: 49


🏁 Script executed:

# Check if there's a routes/endpoints file that handles expand-feature
find apps/server -name "*route*" -o -name "*controller*" | xargs grep -l "expand-feature" 2>/dev/null

Repository: AutoMaker-Org/automaker

Length of output: 51


🏁 Script executed:

# Search in the entire server codebase for expand-feature endpoint
rg -n "expand-feature" apps/server --type ts | head -10

Repository: AutoMaker-Org/automaker

Length of output: 252


🏁 Script executed:

# Check the expand-feature handler implementation
cat apps/server/src/routes/auto-mode/routes/expand-feature.ts

Repository: AutoMaker-Org/automaker

Length of output: 1292


🏁 Script executed:

# Check what resolveModelString does with 'default'
rg -n "export.*resolveModelString\|function resolveModelString" apps --type ts | head -5

Repository: AutoMaker-Org/automaker

Length of output: 49


🏁 Script executed:

# Find where resolveModelString is defined
find apps -name "*.ts" | xargs grep -l "export.*function resolveModelString\|export.*resolveModelString" | head -5

Repository: AutoMaker-Org/automaker

Length of output: 101


🏁 Script executed:

# Check @automaker/model-resolver package
find apps -path "*model-resolver*" -name "*.ts" | head -10

Repository: AutoMaker-Org/automaker

Length of output: 116


🏁 Script executed:

# Look at the actual expand-feature handler to see if model is used
cat apps/server/src/routes/auto-mode/routes/expand-feature.ts | grep -n "model"

Repository: AutoMaker-Org/automaker

Length of output: 49


🏁 Script executed:

# Check expandKnowledgeGraph method in auto-mode-service
rg -n "expandKnowledgeGraph" apps/server/src/services/auto-mode-service.ts -A 30 | head -50

Repository: AutoMaker-Org/automaker

Length of output: 1073


🏁 Script executed:

# Verify if 'default' is a valid ModelAlias value
rg -n "'haiku'|'sonnet'|'opus'\|'default'" apps/ui/src --type ts | grep -E "ModelAlias|CLAUDE_MODELS|validModels" | head -10

Repository: AutoMaker-Org/automaker

Length of output: 888


🏁 Script executed:

# Check app-store to see what ModelAlias values are accepted
rg -n "type ModelAlias\|ModelAlias.*=" apps/ui/src/store/app-store.ts -A 3 | head -20

Repository: AutoMaker-Org/automaker

Length of output: 49


🏁 Script executed:

# Verify the exact port used in other parts of the codebase
rg -n "localhost:300" apps --type ts | grep -v test | head -10

Repository: AutoMaker-Org/automaker

Length of output: 359


🏁 Script executed:

# Verify if 'default' is truly not in ModelAlias
rg -n "ModelAlias.*haiku.*sonnet.*opus" apps/ui/src/store/app-store.ts -B 2 -A 2

Repository: AutoMaker-Org/automaker

Length of output: 49


🏁 Script executed:

# Check if there's a model resolver that accepts 'default'
rg -n "default" apps/server/tests/unit/lib/model-resolver.test.ts | head -10

Repository: AutoMaker-Org/automaker

Length of output: 769


🏁 Script executed:

# Check what happens when model: 'default' is passed to handleAddFeature
rg -n "model.*Feature\|Feature.*model" apps/ui/src/store/app-store.ts | head -15

Repository: AutoMaker-Org/automaker

Length of output: 49


🏁 Script executed:

# Verify the exact ModelAlias type definition from app-store
cat apps/ui/src/store/app-store.ts | grep -A 5 "ModelAlias"

Repository: AutoMaker-Org/automaker

Length of output: 957


🏁 Script executed:

# Check if model-resolver handles 'default' conversion
cat apps/server/tests/unit/lib/model-resolver.test.ts | head -50

Repository: AutoMaker-Org/automaker

Length of output: 1800


🏁 Script executed:

# Verify the exact port mismatch - expand-feature should use what port?
rg -n "3008\|3000" apps/server --type ts | grep -E "port|localhost" | head -10

Repository: AutoMaker-Org/automaker

Length of output: 49


🏁 Script executed:

# Verify the actual Feature type to see what model parameter expects
rg -n "model\?: ModelAlias" apps/ui/src/store/app-store.ts -B 2 -A 2

Repository: AutoMaker-Org/automaker

Length of output: 49


🏁 Script executed:

# Confirm if 'default' model string would cause a type error
cat apps/ui/src/store/app-store.ts | sed -n '1,100p' | grep -A 100 "interface Feature"

Repository: AutoMaker-Org/automaker

Length of output: 49


🏁 Script executed:

# Find Feature interface definition
rg -n "export interface Feature\|type Feature =" apps/ui/src/store/app-store.ts -A 30 | head -60

Repository: AutoMaker-Org/automaker

Length of output: 49


🏁 Script executed:

# Check if Feature type has a model field with ModelAlias
rg -n "model.*:" apps/ui/src/store/app-store.ts | grep -i feature -A 2 -B 2 | head -20

Repository: AutoMaker-Org/automaker

Length of output: 49


Hardcoded localhost URL with wrong port and untyped options parameter.

Two issues in this handler:

  1. Line 263: options: any bypasses type safety. Import and use ExpandOptions from ./board-view/dialogs/smart-expand-dialog.
  2. Line 265: Hardcoded http://localhost:3000 uses the wrong port (should be 3008) and will fail when deployed. Use the existing getServerUrlSync() function from @/lib/http-api-client instead, which respects the VITE_SERVER_URL environment variable.

Additionally, Line 311: model: 'default' is not a valid ModelAlias. Use one of the valid aliases ('haiku', 'sonnet', 'opus') or consider using resolveModelString() per coding guidelines.

Suggested fix
+import type { ExpandOptions } from './board-view/dialogs/smart-expand-dialog';
+import { getServerUrlSync } from '@/lib/http-api-client';

 const handleRunSmartExpand = useCallback(
-  async (seedFeature: Feature, options: any) => {
+  async (seedFeature: Feature, options: ExpandOptions) => {
     try {
-      const response = await fetch('http://localhost:3000/api/auto-mode/expand-feature', {
+      const response = await fetch(`${getServerUrlSync()}/api/auto-mode/expand-feature`, {
         method: 'POST',

For the model parameter on line 311, replace model: 'default' with a valid alias like model: 'sonnet'.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In @apps/server/src/routes/setup/routes/gh-status.ts:
- Line 43: The execAsync call that detects the gh binary uses shell: true but
later execAsync invocations (the other gh command runs) omit that option, which
can cause failures when gh is only available via Windows shims; update all
execAsync calls that run gh (the findCommand call and the subsequent execAsync
usages in this module) to include { shell: true, env: execEnv } consistently, or
extract a small helper like runGh(command) that calls execAsync(command, {
shell: true, env: execEnv }) and use it everywhere (refer to execAsync and the
findCommand/gh invocation sites).
🧹 Nitpick comments (1)
apps/server/src/routes/setup/routes/gh-status.ts (1)

53-61: Redundant gh --version execution.

When the secondary check succeeds (line 56-60), status.version is already populated. However, lines 82-89 will run gh --version again unconditionally, overwriting the extracted version with potentially the same value.

Consider skipping the version extraction if it's already set:

♻️ Suggested improvement
   // Get version
+  if (!status.version) {
     try {
       const { stdout } = await execAsync('gh --version', { env: execEnv });
       // Extract version from output like "gh version 2.40.1 (2024-01-09)"
       const versionMatch = stdout.match(/gh version ([\d.]+)/);
       status.version = versionMatch ? versionMatch[1] : stdout.trim().split('\n')[0];
     } catch {
       // Version command failed
     }
+  }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1d44800 and e4a36a2.

📒 Files selected for processing (1)
  • apps/server/src/routes/setup/routes/gh-status.ts
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Always import from shared packages (@automaker/*), never from old relative paths

Files:

  • apps/server/src/routes/setup/routes/gh-status.ts
apps/server/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use createEventEmitter() from lib/events.ts for all server operations to emit events that stream to frontend via WebSocket

Files:

  • apps/server/src/routes/setup/routes/gh-status.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use resolveModelString() from @automaker/model-resolver to convert model aliases (haiku, sonnet, opus) to full model names

Files:

  • apps/server/src/routes/setup/routes/gh-status.ts
🔇 Additional comments (1)
apps/server/src/routes/setup/routes/gh-status.ts (1)

129-142: LGTM!

The handler function follows standard Express patterns with proper error handling and logging.

// gh not in PATH, try common locations from centralized system paths
const commonPaths = getGitHubCliPaths();
// Use shell: true to better support shims/aliases on Windows
const { stdout } = await execAsync(findCommand, { env: execEnv, shell: true });
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Inconsistent shell: true usage may break Windows shim scenarios.

Line 43 uses shell: true to support Windows shims/aliases, but subsequent execAsync calls (lines 56, 83, 95, 110) omit this option. If gh is only resolvable via a shim that requires shell mode, the initial detection might succeed while later commands fail.

Consider applying shell: true consistently to all gh invocations, or extracting a helper:

🔧 Suggested improvement
+const execOptions = { env: execEnv, shell: true };
+
 async function getGhStatus(): Promise<GhStatus> {
   // ...
-    const { stdout } = await execAsync(findCommand, { env: execEnv, shell: true });
+    const { stdout } = await execAsync(findCommand, execOptions);
   // ...
-      const { stdout } = await execAsync('gh --version', { env: execEnv });
+      const { stdout } = await execAsync('gh --version', execOptions);
   // ...
-    const { stdout } = await execAsync('gh --version', { env: execEnv });
+    const { stdout } = await execAsync('gh --version', execOptions);
   // ...
-    const { stdout } = await execAsync('gh api user --jq ".login"', { env: execEnv });
+    const { stdout } = await execAsync('gh api user --jq ".login"', execOptions);
   // ...
-      const { stdout } = await execAsync('gh auth status', { env: execEnv });
+      const { stdout } = await execAsync('gh auth status', execOptions);

Also applies to: 56-56, 83-83, 95-95, 110-110

🤖 Prompt for AI Agents
In @apps/server/src/routes/setup/routes/gh-status.ts at line 43, The execAsync
call that detects the gh binary uses shell: true but later execAsync invocations
(the other gh command runs) omit that option, which can cause failures when gh
is only available via Windows shims; update all execAsync calls that run gh (the
findCommand call and the subsequent execAsync usages in this module) to include
{ shell: true, env: execEnv } consistently, or extract a small helper like
runGh(command) that calls execAsync(command, { shell: true, env: execEnv }) and
use it everywhere (refer to execAsync and the findCommand/gh invocation sites).

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In @package.json:
- Around line 16-17: Remove the duplicate "dev:web" script entry (the one that
simply runs "npm run dev:web --workspace=apps/ui") so only the intended public
"dev:web" definition that builds packages first remains; ensure "_dev:web" stays
as-is and verify there are no other duplicate script keys in the scripts object.
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e4a36a2 and d54b844.

📒 Files selected for processing (2)
  • apps/ui/package.json
  • package.json
🧰 Additional context used
🪛 Biome (2.1.2)
package.json

[error] 16-16: The key dev:web was already declared.

This where a duplicated key was declared again.

If a key is defined multiple times, only the last definition takes effect. Previous definitions are ignored.

(lint/suspicious/noDuplicateObjectKeys)

🔇 Additional comments (2)
package.json (1)

33-33: LGTM!

The build order correctly builds providers-core before the individual provider implementations (provider-claude, provider-zai), ensuring dependencies are available.

apps/ui/package.json (1)

19-19: LGTM!

Adding --open is a minor developer experience improvement that auto-launches the browser when starting the dev server.

Comment on lines +16 to 17
"dev:web": "npm run dev:web --workspace=apps/ui",
"_dev:web": "npm run dev:web --workspace=apps/ui",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Duplicate dev:web key causes the first definition to be silently ignored.

JSON does not allow duplicate keys. The dev:web key at line 16 is overwritten by the definition at line 23. This line appears to be added in error since:

  1. Line 17 (_dev:web) already does the same thing
  2. The intended public dev:web at line 23 correctly builds packages first

Remove the duplicate line 16.

🐛 Proposed fix
     "dev": "node dev.mjs",
-    "dev:web": "npm run dev:web --workspace=apps/ui",
     "_dev:web": "npm run dev:web --workspace=apps/ui",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"dev:web": "npm run dev:web --workspace=apps/ui",
"_dev:web": "npm run dev:web --workspace=apps/ui",
"dev": "node dev.mjs",
"_dev:web": "npm run dev:web --workspace=apps/ui",
🧰 Tools
🪛 Biome (2.1.2)

[error] 16-16: The key dev:web was already declared.

This where a duplicated key was declared again.

If a key is defined multiple times, only the last definition takes effect. Previous definitions are ignored.

(lint/suspicious/noDuplicateObjectKeys)

🤖 Prompt for AI Agents
In @package.json around lines 16 - 17, Remove the duplicate "dev:web" script
entry (the one that simply runs "npm run dev:web --workspace=apps/ui") so only
the intended public "dev:web" definition that builds packages first remains;
ensure "_dev:web" stays as-is and verify there are no other duplicate script
keys in the scripts object.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
README.md (2)

76-78: Clarify Z.AI provider terminology for consistency.

Line 78 describes Z.AI as a "proprietary Z.AI Provider" while line 481 calls it a "Custom integration for Z.AI models." These terms have different implications—"proprietary" suggests closed-source/commercial software, while "custom integration" suggests an open-source integration built specifically for this project.

Use consistent terminology throughout the documentation to avoid confusion about the nature and licensing of the Z.AI provider.

📝 Suggested terminology alignment

If Z.AI is an open-source custom integration built for Automaker:

-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.
+Automaker leverages the [Claude Agent SDK](https://www.npmjs.com/package/@anthropic-ai/claude-agent-sdk) and a custom **Z.AI Provider integration** to give AI agents full access to your codebase.

133-134: Document Z.AI authentication setup with the same detail as Anthropic.

The Z.AI API key is mentioned only in the setup wizard section, but lacks the comprehensive setup documentation that Anthropic receives (environment variables, shell configuration, .env file examples). Based on the AI summary, ZAI_API_KEY is used throughout the codebase but isn't documented anywhere in the README.

Provide complete Z.AI authentication documentation to match the level of detail given to Anthropic setup, including:

  • Adding ZAI_API_KEY to the Environment Configuration section (around line 349)
  • Shell configuration example in the Authentication Setup section
  • .env file example showing ZAI_API_KEY=...
📝 Suggested documentation additions

In the Environment Configuration section (after line 349):

 #### Authentication (if not using Claude Code CLI)
 
 - `ANTHROPIC_API_KEY` - Your Anthropic API key for Claude Agent SDK (not needed if using Claude Code CLI)
+- `ZAI_API_KEY` - Your Z.AI API key for Z.AI GLM models (optional, only if using Z.AI models)

In the Authentication Setup section (after line 388):

Add a subsection or expand the existing examples:

#### Option 3: Z.AI API Key

If you want to use Z.AI GLM models, provide a Z.AI API key:

##### Shell Configuration
```bash
export ZAI_API_KEY="your-zai-key-here"
.env File
ZAI_API_KEY=your-zai-key-here

</details>

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used**: defaults

**Review profile**: CHILL

**Plan**: Pro

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 9cc8ff8226edad2d40998004a6253f813fe30884 and 39f785d842a7e0efd613967e89804f52ad09154e.

</details>

<details>
<summary>📒 Files selected for processing (1)</summary>

* `README.md`

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧠 Learnings (1)</summary>

<details>
<summary>📚 Learning: 2025-12-28T05:07:48.147Z</summary>

Learnt from: CR
Repo: AutoMaker-Org/automaker PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-28T05:07:48.147Z
Learning: Each feature executes in an isolated git worktree created via automaker/git-utils to protect the main branch during AI agent execution


**Applied to files:**
- `README.md`

</details>

</details>

</details>

<details>
<summary>🔇 Additional comments (1)</summary><blockquote>

<details>
<summary>README.md (1)</summary><blockquote>

`416-416`: **LGTM! Z.AI integration properly documented.**

The additions of Z.AI GLM-4 to the multi-model support list and Z.AI Provider to the backend tech stack are clear, accurate, and consistent with the PR's feature additions.




Also applies to: 481-481

</blockquote></details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In @libs/types/src/settings.ts:
- Around line 816-817: The fields enhancementModel and validationModel are typed
as ModelAlias but currently assigned a Z.AI model string ('GLM-4.7'), causing a
type mismatch; update their values to valid ModelAlias aliases (e.g., 'haiku',
'sonnet', 'opus') or change their type to accept Z.AI names if intended, and as
an alternative remove these deprecated fields entirely and rely on phaseModels
(update any references to enhancementModel/validationModel accordingly). Ensure
the change targets the enhancementModel and validationModel definitions and any
consumers that expect ModelAlias types so TypeScript compiles cleanly.
- Around line 331-333: The fallback hardcoded model 'glm-4.5' in the profile
provider check is inconsistent with the global default
DEFAULT_GLOBAL_SETTINGS.zaiDefaultModel ('GLM-4.7') and has a case mismatch;
update the logic for profile.provider === 'zai' to use
DEFAULT_GLOBAL_SETTINGS.zaiDefaultModel (or otherwise match its exact value
'GLM-4.7') instead of the hardcoded 'glm-4.5' so the fallback and casing align
with the global setting and avoid unexpected model selection.
- Around line 746-761: Phase model entries are typed to PhaseModelEntry.model
whose ModelAlias excludes 'default', causing a type error; update each phase
model value (e.g., enhancementModel, validationModel, specGenerationModel, etc.)
to a concrete alias included in CLAUDE_MODEL_MAP|ZAI_MODEL_MAP such as 'opus' or
adjust the ModelAlias union to include 'default'. Preferably import and call
resolveModelString from @automaker/model-resolver when constructing/validating
these PhaseModelEntry values so the runtime 'default' resolution is handled
properly—either change the literal strings to valid aliases or extend ModelAlias
to accept 'default' and ensure resolveModelString is used where models are
consumed.
🧹 Nitpick comments (1)
libs/types/src/settings.ts (1)

275-278: Consider using a typed model ID for consistency.

Other provider model fields use specific types (CursorModelId, CodexModelId, OpencodeModelId), but zaiModel is typed as string. Based on the AI summary, ZAI_MODEL_MAP exists in libs/types/src/model.ts. Consider defining a ZaiModelId type for consistency and type safety.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 39f785d and 00f26a2.

📒 Files selected for processing (1)
  • libs/types/src/settings.ts
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Always import from shared packages (@automaker/*), never from old relative paths

Files:

  • libs/types/src/settings.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use resolveModelString() from @automaker/model-resolver to convert model aliases (haiku, sonnet, opus) to full model names

Files:

  • libs/types/src/settings.ts
🧬 Code graph analysis (1)
libs/types/src/settings.ts (2)
libs/types/src/cursor-models.ts (1)
  • getAllCursorModelIds (188-190)
libs/types/src/opencode-models.ts (2)
  • getAllOpencodeModelIds (337-339)
  • DEFAULT_OPENCODE_MODEL (324-325)
🔇 Additional comments (5)
libs/types/src/settings.ts (5)

102-102: LGTM!

The ModelProvider type is correctly extended with the new 'zai' provider, maintaining consistency with existing providers.


307-310: Verify that all Z.AI models support reasoning/thinking.

The implementation returns true unconditionally for the zai provider. Please confirm this is accurate for all supported GLM models, as some model variants may not support extended thinking.


519-523: LGTM!

The Z.AI settings follow the same pattern as OpenCode settings, maintaining consistency in the interface design.


646-647: LGTM!

The Z.AI API key field is correctly added to the credentials interface, following the existing pattern.


858-858: LGTM!

The default empty credential for Z.AI is correctly added, consistent with the existing pattern for other providers.

Comment on lines +331 to +333
if (profile.provider === 'zai') {
return profile.zaiModel || 'glm-4.5';
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Inconsistent default model between fallback and global settings.

The fallback here is 'glm-4.5' but DEFAULT_GLOBAL_SETTINGS.zaiDefaultModel on line 823 is 'GLM-4.7'. This inconsistency could cause unexpected model selection when profile.zaiModel is undefined. Additionally, there's a case mismatch (lowercase vs uppercase).

🔧 Proposed fix to align with global default
   if (profile.provider === 'zai') {
-    return profile.zaiModel || 'glm-4.5';
+    return profile.zaiModel || 'GLM-4.7';
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (profile.provider === 'zai') {
return profile.zaiModel || 'glm-4.5';
}
if (profile.provider === 'zai') {
return profile.zaiModel || 'GLM-4.7';
}
🤖 Prompt for AI Agents
In @libs/types/src/settings.ts around lines 331 - 333, The fallback hardcoded
model 'glm-4.5' in the profile provider check is inconsistent with the global
default DEFAULT_GLOBAL_SETTINGS.zaiDefaultModel ('GLM-4.7') and has a case
mismatch; update the logic for profile.provider === 'zai' to use
DEFAULT_GLOBAL_SETTINGS.zaiDefaultModel (or otherwise match its exact value
'GLM-4.7') instead of the hardcoded 'glm-4.5' so the fallback and casing align
with the global setting and avoid unexpected model selection.

Comment on lines +746 to +761
enhancementModel: { model: 'default' },
fileDescriptionModel: { model: 'default' },
imageDescriptionModel: { model: 'default' },

// Validation - use smart models for accuracy
validationModel: { model: 'sonnet' },
validationModel: { model: 'default' },

// Generation - use powerful models for quality
specGenerationModel: { model: 'opus' },
featureGenerationModel: { model: 'sonnet' },
backlogPlanningModel: { model: 'sonnet' },
projectAnalysisModel: { model: 'sonnet' },
suggestionsModel: { model: 'sonnet' },
specGenerationModel: { model: 'default' },
featureGenerationModel: { model: 'default' },
backlogPlanningModel: { model: 'default' },
projectAnalysisModel: { model: 'default' },
suggestionsModel: { model: 'default' },

// Memory - use fast model for learning extraction (cost-effective)
memoryExtractionModel: { model: 'haiku' },
memoryExtractionModel: { model: 'default' },
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# Find ModelAlias type definition
rg "type\s+ModelAlias\s*=" --type=ts -A 10

Repository: AutoMaker-Org/automaker

Length of output: 715


🏁 Script executed:

# Also search for interface ModelAlias
rg "interface\s+ModelAlias" --type=ts -A 10

Repository: AutoMaker-Org/automaker

Length of output: 49


🏁 Script executed:

# Look for 'default' as a model alias in the codebase
rg "'default'" libs/types/src/ --type=ts -B 2 -A 2

Repository: AutoMaker-Org/automaker

Length of output: 1341


🏁 Script executed:

# Find the model-resolver package
fd -e ts -e js | grep -i "model-resolver"

Repository: AutoMaker-Org/automaker

Length of output: 265


🏁 Script executed:

# Check the imports in settings.ts
head -50 libs/types/src/settings.ts

Repository: AutoMaker-Org/automaker

Length of output: 1811


🏁 Script executed:

# Find CLAUDE_MODEL_MAP and ZAI_MODEL_MAP definitions
rg "CLAUDE_MODEL_MAP|ZAI_MODEL_MAP" --type=ts -B 2 -A 15 | head -100

Repository: AutoMaker-Org/automaker

Length of output: 6428


🏁 Script executed:

# Check the model-resolver implementation
cat libs/model-resolver/src/index.ts

Repository: AutoMaker-Org/automaker

Length of output: 471


🏁 Script executed:

# Check if 'default' is explicitly defined as a key
rg "default" libs/types/src/model.ts -B 3 -A 3

Repository: AutoMaker-Org/automaker

Length of output: 674


🏁 Script executed:

# Check imports in settings.ts to see if resolveModelString is imported
grep -n "import" libs/types/src/settings.ts | head -20

Repository: AutoMaker-Org/automaker

Length of output: 789


🏁 Script executed:

# Get complete CLAUDE_MODEL_MAP definition
rg "export const CLAUDE_MODEL_MAP" --type=ts -A 30

Repository: AutoMaker-Org/automaker

Length of output: 1987


🏁 Script executed:

# Get ZAI_MODEL_MAP definition
rg "export const ZAI_MODEL_MAP" --type=ts -A 20

Repository: AutoMaker-Org/automaker

Length of output: 521


🏁 Script executed:

# Check if 'default' is handled in resolver.ts
rg "'default'" libs/model-resolver/src/ --type=ts -B 3 -A 3

Repository: AutoMaker-Org/automaker

Length of output: 586


🏁 Script executed:

# Check PhaseModelEntry type definition
rg "type PhaseModelEntry" --type=ts -B 2 -A 10

Repository: AutoMaker-Org/automaker

Length of output: 3316


🏁 Script executed:

# Get full PhaseModelEntry definition
rg "type PhaseModelEntry\s*=" --type=ts -B 2 -A 15

Repository: AutoMaker-Org/automaker

Length of output: 49


🏁 Script executed:

# Check PhaseModelConfig type
rg "type PhaseModelConfig" --type=ts -B 2 -A 30

Repository: AutoMaker-Org/automaker

Length of output: 49


🏁 Script executed:

# Verify the full resolveModelString function signature
rg "export.*function resolveModelString" --type=ts -B 2 -A 20

Repository: AutoMaker-Org/automaker

Length of output: 1668


🏁 Script executed:

# Check if there are any TypeScript type errors in the build
fd -e ts | xargs grep -l "default" | xargs grep -l "PhaseModel" | head -5

Repository: AutoMaker-Org/automaker

Length of output: 342


🏁 Script executed:

# Search for PhaseModelEntry in types files
rg "PhaseModelEntry" libs/types/src/ --type=ts -B 2 -A 10

Repository: AutoMaker-Org/automaker

Length of output: 4574


🏁 Script executed:

# Check settings.ts more thoroughly for PhaseModelEntry usage
cat libs/types/src/settings.ts | grep -n "PhaseModel" -B 2 -A 2 | head -50

Repository: AutoMaker-Org/automaker

Length of output: 2151


🏁 Script executed:

# Final check: verify if 'default' would pass TypeScript
rg "model:\s*'default'" libs/types/src/settings.ts | wc -l

Repository: AutoMaker-Org/automaker

Length of output: 69


🏁 Script executed:

# Check if there are any type assertions or as const that might allow 'default'
rg "as const" libs/types/src/settings.ts -B 5 -A 2 | grep -A 5 -B 5 "default"

Repository: AutoMaker-Org/automaker

Length of output: 49


Fix type error: 'default' is not a valid ModelAlias.

Using 'default' for all phase models violates the PhaseModelEntry.model type. ModelAlias is defined as keyof typeof CLAUDE_MODEL_MAP | keyof typeof ZAI_MODEL_MAP, which only includes: haiku, sonnet, opus, glm-4-plus, glm-4.7, glm-4.6, glm-4.5, glm-4.5-air, glm-4.5-flash, glm-4.6v.

While resolveModelString() in the model-resolver handles 'default' at runtime, the type system rejects it. Replace with a concrete model alias (e.g., 'opus') or extend the type definition to include 'default' as a valid alias. Additionally, per coding guidelines, use resolveModelString() from @automaker/model-resolver to resolve model aliases.

🤖 Prompt for AI Agents
In @libs/types/src/settings.ts around lines 746 - 761, Phase model entries are
typed to PhaseModelEntry.model whose ModelAlias excludes 'default', causing a
type error; update each phase model value (e.g., enhancementModel,
validationModel, specGenerationModel, etc.) to a concrete alias included in
CLAUDE_MODEL_MAP|ZAI_MODEL_MAP such as 'opus' or adjust the ModelAlias union to
include 'default'. Preferably import and call resolveModelString from
@automaker/model-resolver when constructing/validating these PhaseModelEntry
values so the runtime 'default' resolution is handled properly—either change the
literal strings to valid aliases or extend ModelAlias to accept 'default' and
ensure resolveModelString is used where models are consumed.

Comment on lines +816 to +817
enhancementModel: 'GLM-4.7',
validationModel: 'GLM-4.7',
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Type mismatch: deprecated fields assigned Z.AI model names.

The deprecated enhancementModel and validationModel fields are typed as ModelAlias (Claude aliases like 'haiku', 'sonnet', 'opus'), but they're being assigned 'GLM-4.7' which is a Z.AI model name. This will likely cause TypeScript compilation errors.

🔧 Proposed fix to use valid ModelAlias values
   phaseModels: DEFAULT_PHASE_MODELS,
-  enhancementModel: 'GLM-4.7',
-  validationModel: 'GLM-4.7',
+  enhancementModel: 'haiku',
+  validationModel: 'sonnet',
   enabledCursorModels: getAllCursorModelIds(),

Alternatively, since these fields are deprecated in favor of phaseModels, consider removing them entirely if they're no longer used.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
enhancementModel: 'GLM-4.7',
validationModel: 'GLM-4.7',
enhancementModel: 'haiku',
validationModel: 'sonnet',
🤖 Prompt for AI Agents
In @libs/types/src/settings.ts around lines 816 - 817, The fields
enhancementModel and validationModel are typed as ModelAlias but currently
assigned a Z.AI model string ('GLM-4.7'), causing a type mismatch; update their
values to valid ModelAlias aliases (e.g., 'haiku', 'sonnet', 'opus') or change
their type to accept Z.AI names if intended, and as an alternative remove these
deprecated fields entirely and rely on phaseModels (update any references to
enhancementModel/validationModel accordingly). Ensure the change targets the
enhancementModel and validationModel definitions and any consumers that expect
ModelAlias types so TypeScript compiles cleanly.

claude and others added 8 commits January 12, 2026 15:06
… integrated

- AIGateway: Centralized credential loading with Ghost Key fix
- UniversalGateway: Vercel AI SDK wrapper (Apache 2.0)
- Providers: OpenAI, Anthropic, Google, Z.AI, Mistral, Cohere, xAI, Groq, OpenRouter, Ollama, Cursor, Codex
- Migrated 9 routes to use AIGateway
- Removed unused ProviderFactory imports from migrated routes
- Added audit logging with call ID, timing, and message count
- Parallel execution tested and working (1.77x speedup)
- All backend tests passing
OFFICIAL VERCEL SDK PROVIDERS (17):
- OpenAI, Anthropic, Google, Google Vertex, Mistral, Cohere, xAI
- Groq, Amazon Bedrock, Azure, Together.ai, Fireworks, DeepInfra
- DeepSeek, Cerebras, Perplexity, Baseten

OPENAI-COMPATIBLE PROVIDERS (10):
- Z.AI, OpenRouter, Ollama, Replicate, Anyscale, Novita
- SambaNova, Hyperbolic, Lepton, LM Studio

CLI-BASED PROVIDERS (3):
- Cursor, Codex, Claude CLI

All tests passing. Z.AI streaming verified.
Smart Expand:
- Inherit branchName from parent feature
- Call loadFeatures() after spawning for immediate refresh

World Model:
- Redirect /world-model route to / with boardViewMode='graph'
- Clicking 'World Model' in sidebar now opens same graph view as toggle
 Universal AI Gateway (30 Providers) + Smart Expand Fix + World Model Redirect
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 19

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (11)
apps/ui/src/components/views/graph-view/hooks/use-graph-nodes.ts (2)

101-110: Duplicate filter state assignments in node data.

Lines 102-104 already set isMatched, isHighlighted, and isDimmed. Lines 105-108 duplicate these exact assignments with a redundant comment. This appears to be a copy-paste error or merge conflict remnant.

Proposed fix
          blockingDependencies: blockingDeps,
          // Filter states
          isMatched,
          isHighlighted,
          isDimmed,
-          // Filter states
-          isMatched,
-          isHighlighted,
-          isDimmed,
          // Action callbacks - REMOVED: Now handled via Context in TaskNode
        },

21-28: Remove unused callback properties from TaskNodeData interface.

TaskNodeData declares callback properties (onViewLogs, onViewDetails, onStartTask, onStopTask, onResumeTask, onSpawnTask, onDeleteTask) at lines 22-28, but they're never assigned in the useMemo (which contains an explicit comment: "Action callbacks - REMOVED: Now handled via Context in TaskNode") and are never accessed from node data anywhere in the codebase. Callbacks are now exclusively provided via GraphActionsContext. Remove these properties to clarify the interface and avoid confusion.

apps/ui/src/components/views/graph-view/components/dependency-edge.tsx (1)

132-132: Delete button never renders due to stale condition check.

The condition edgeData?.onDeleteDependency will always be undefined since onDeleteDependency was removed from DependencyEdgeData (as noted in line 15). The delete button will never appear. This should check the context-based action instead.

Proposed fix
-      {(isHovered || selected) && edgeData?.onDeleteDependency && (
+      {(isHovered || selected) && actions.onDeleteDependency && (
apps/ui/src/hooks/use-settings-migration.ts (1)

716-720: Add zai to syncCredentialsToServer parameter type and update backend API definitions.

The function accepts anthropic, google, and openai but omits zai, creating a type mismatch with the ApiKeys interface in the app store (which includes zai). Currently, Z.AI credentials are added to the local apiKeys object (line 590) but cannot be synced to the server because the parameter type doesn't support them.

Update the function signature and the backend API definitions (http-api-client.ts and electron.ts):

🔧 Proposed fixes

apps/ui/src/hooks/use-settings-migration.ts:

 export async function syncCredentialsToServer(apiKeys: {
   anthropic?: string;
   google?: string;
   openai?: string;
+  zai?: string;
 }): Promise<boolean> {

apps/ui/src/lib/http-api-client.ts:

     updateCredentials: (updates: {
-      apiKeys?: { anthropic?: string; google?: string; openai?: string };
+      apiKeys?: { anthropic?: string; google?: string; openai?: string; zai?: string };
     })

apps/ui/src/lib/electron.ts:

     updateCredentials: (updates: {
-      apiKeys?: { anthropic?: string; google?: string; openai?: string };
+      apiKeys?: { anthropic?: string; google?: string; openai?: string; zai?: string };
     })
apps/server/src/routes/context/routes/describe-image.ts (1)

318-328: Prepared image content is never used—promptContent array is built but not passed, and the non-Cursor branch uses a non-existent executeQuery function.

The code reads the image and builds promptContent with base64 data (lines 318-328), but neither branch uses it:

  • Cursor branch (line 367): passes cursorPrompt (text with file path) to aiGateway.execute()
  • Non-Cursor branch (line 396): passes textPrompt (text with file path) to executeQuery() — which doesn't exist and would cause a runtime failure

Critical Issue: The executeQuery function is imported from a non-existent module (../../../lib/execute-query.js). This code cannot run.

Major Issue: This contradicts the file header (lines 8-11) which states the endpoint should use "multi-part content blocks (base64 image blocks)" to match agent runner behavior. The ExecuteOptions interface supports array prompts (string | Array<{ type: string; text?: string; source?: object }>), so the intended multi-part approach is fully supported.

Fix: Use aiGateway.execute() for both branches and pass promptContent directly:

🔧 Suggested fix
-// Build prompt text with image info (providers handle vision differently)
-const textPrompt = `${instructionText}\n\nImage file: ${actualPath}\nMIME type: ${imageData.mimeType}`;
-
 logger.info(`[${requestId}] Calling executeQuery()...`);
 const queryStart = Date.now();
 
 let responseText = '';
-for await (const msg of executeQuery({
-  prompt: textPrompt,
+for await (const msg of aiGateway.execute({
+  prompt: promptContent, // Pass multi-part prompt with image
   model,
   cwd,
   maxTurns: 1,
-  allowedTools: ['Read'], // Allow Read tool so provider can read the image if needed
+  allowedTools: [], // No tools needed when passing image directly
   readOnly: true,
 })) {

For Cursor branch, also pass promptContent if the CLI supports base64 image blocks. If not, add a comment explaining the limitation and why cursorPrompt (file path only) is necessary.

apps/server/src/routes/app-spec/generate-features-from-spec.ts (1)

44-47: Avoid logging full specs/prompts/responses (likely sensitive)
spec content, full prompt, and full model response are logged (Line 44-47, Line 90-94, Line 220-224). If this runs outside purely-local dev, this can leak proprietary code/specs/credentials into logs.

Suggested approach
- logger.info(`Spec preview (first 500 chars): ${spec.substring(0, 500)}`);
- logger.info(`Spec preview (last 500 chars): ${spec.substring(spec.length - 500)}`);
+ logger.debug(`Spec loaded (${spec.length} chars)`);
- logger.info('========== FULL RESPONSE TEXT ==========');
- logger.info(responseText);
- logger.info('========== END RESPONSE TEXT ==========');
+ logger.debug(`Feature response length: ${responseText.length} chars`);

Also applies to: 90-94, 220-224

apps/server/src/routes/github/routes/validate-issue.ts (2)

289-349: Bug: default model = 'default' is rejected by runtime validation
With Line 289 defaulting model to 'default', Line 339-348 will 400 every request that omits model (since 'default' isn’t in VALID_CLAUDE_MODELS and isn’t a Cursor model).

Minimal fix (restore previous default)
-        model = 'default',
+        model = 'opus',
Alternative (support 'default' explicitly)
-      const isValidClaudeModel = VALID_CLAUDE_MODELS.includes(model as ModelAlias);
+      const isValidClaudeModel =
+        model === 'default' || VALID_CLAUDE_MODELS.includes(model as ModelAlias);

117-217: Dead code: executeQuery function doesn't exist — refactor non-Cursor branch to use aiGateway.execute()

The non-Cursor branch at line 187–192 calls executeQuery() imported from '../../../lib/execute-query.js', but this file does not exist in the codebase. The Cursor branch correctly uses aiGateway.execute(), which also:

  • Resolves model aliases internally (resolveModelString at ai-gateway.ts:179)
  • Routes to the appropriate provider
  • Handles tool permissions through the provider

Consolidate both branches to use aiGateway.execute() instead of the non-existent executeQuery(), eliminating the need for manual model resolution. Alternatively, implement executeQuery as a wrapper around aiGateway.execute() if provider-agnostic convenience is needed elsewhere.

apps/server/src/routes/app-spec/generate-spec.ts (1)

40-47: Avoid logging user/project content at info level
Project overview previews and full response text can leak sensitive info (Line 40-47, Line 84-87, Line 281-285).

Also applies to: 84-87, 281-285

apps/server/src/index.ts (1)

271-287: Critical: WebSocket auth is hard-bypassed
authenticateWebSocket() always returns true (Line 271-274), which disables authentication for /api/events and /api/terminal/ws. This is a release-blocker unless it’s strictly gated to local/dev.

Proposed fix (dev-only bypass behind env flag)
 function authenticateWebSocket(request: import('http').IncomingMessage): boolean {
-  // FORCE BYPASS FOR LOCAL Z.AI DEV - Always authenticate WebSocket connections
-  return true;
+  // Optional local dev bypass (OFF by default)
+  if (process.env.AUTOMAKER_INSECURE_WS_AUTH === 'true') return true;
+  return checkRawAuthentication(request) || validateWsConnectionToken(request);
 }
apps/server/src/services/auto-mode-service.ts (1)

1368-1424: analyzeProject() should be read-only when using tool-capable providers
This path is analysis-only but the aiGateway.execute(...) call does not set readOnly: true (Line 1416-1424). If the underlying provider supports write tools, this increases blast radius.

Proposed fix
       const stream = aiGateway.execute({
         prompt,
         model: analysisModel,
         cwd: projectPath,
         maxTurns: sdkOptions.maxTurns,
         allowedTools: sdkOptions.allowedTools as string[],
         abortController,
         thinkingLevel: analysisThinkingLevel,
+        readOnly: true,
       });
🤖 Fix all issues with AI agents
In @apps/server/src/index.ts:
- Around line 178-206: The init IIFE runs asynchronously and is not awaited
before the server begins listening, so requests can hit
aiGateway.execute/universalGateway or agentService before
aiGateway.init/universalGateway.init/agentService.initialize complete; modify
startup so that startServer(PORT) is only called after awaiting the providers
and gateways initialization (await the async IIFE or refactor it into an
exported async function like initServices that sequentially awaits
aiGateway.init(), universalGateway.init(), and agentService.initialize()) and
then call startServer(PORT) once that promise resolves, ensuring aiGateway,
universalGateway, and agentService are fully initialized before accepting
requests.

In @apps/server/src/routes/app-spec/generate-features-from-spec.ts:
- Around line 8-15: resolvePhaseModel is returning a thinkingLevel that is
extracted but never forwarded; update both execution branches so thinkingLevel
is passed into the gateway and query calls. Specifically, when
isCursorModel(...) branch invokes aiGateway.execute, include thinkingLevel in
the GatewayOptions passed to aiGateway.execute; and when the non-cursor path
calls executeQuery, include thinkingLevel in the ExecuteOptions passed to
executeQuery. Locate resolvePhaseModel, isCursorModel, aiGateway.execute, and
executeQuery in this file and add the thinkingLevel field to the respective
options objects so extended thinking configurations are honored.

In @apps/server/src/routes/red-giant/routes/evolve-star.ts:
- Around line 34-36: The catch-all 500 response in the evolveStar route masks
client errors from the service; update the error handling in the evolveStar
route handler to distinguish client vs server errors by inspecting the thrown
error (e.g., check error instanceof NotFoundError / BadRequestError or
error.name) and map those to appropriate 4xx responses (404/400) while
preserving a 500 for unexpected errors; if the service currently throws plain
Errors, either add / use typed errors (e.g., NotFoundError) from the evolveStar
service method or check for specific error messages to decide the 4xx status,
and return res.status(appropriateCode).json({ success: false, error:
String(error) }) accordingly.

In @apps/server/src/services/ai-gateway.ts:
- Around line 64-69: The constructor's assignment to this.dataDir uses a ternary
with incorrect operator precedence so a truthy APPDATA can be chosen even when
dataDir is provided; update the constructor in the class (the constructor method
that sets this.dataDir) to first check dataDir explicitly and return it if
present, otherwise perform the APPDATA vs HOME ternary (or rewrite as a clear
if/else) so dataDir is honored; ensure you reference the same property
this.dataDir and use process.env.APPDATA and process.env.HOME in the fallback
logic.

In @apps/server/src/services/auto-mode-service.ts:
- Around line 2223-2235: The call to aiGateway.execute is incorrectly using
sdkOptions.systemPrompt instead of the assembled options.systemPrompt, so update
the systemPrompt argument in aiGateway.execute (where stream is created) to
prefer the user-assembled value, e.g. use options.systemPrompt if present and
fall back to sdkOptions.systemPrompt (options.systemPrompt ??
sdkOptions.systemPrompt), ensuring the earlier-assembled context is actually
passed through.
- Around line 561-599: The code assumes this.settingsService is non-null when
calling this.settingsService.getGlobalSettings(); add a null-check before that
call (e.g., if (!this.settingsService) { logger.error('SettingsService
unavailable'); throw new Error('Settings service not initialized'); }) to avoid
a runtime NPE, then proceed to call this.settingsService.getGlobalSettings();
update the error handling inside the model === 'default' branch to reflect this
precondition and reference this.settingsService and getGlobalSettings in the
guard.

In @apps/server/src/services/ideation-service.ts:
- Around line 808-811: The call to parseSuggestionsFromResponse is passing an
invalid IdeaCategory string 'features'; update the call in ideation-service.ts
to pass the correct singular category 'feature' (i.e., replace 'features' with
'feature') so it matches the IdeaCategory type used by
parseSuggestionsFromResponse and the libs/types definition.

In @apps/server/src/services/red-giant-service.ts:
- Around line 364-375: The calculateLuminosity function currently computes
radiusMeters, solarRadiusMeters, and massLuminosityRelation but never uses them;
fix by either removing those dead variables or applying them: compute luminosity
using (radiusMeters / solarRadiusMeters)^2 * (temperature / this.SOLAR_TEMP)^4
and then apply the mass-luminosity correction by multiplying by
massLuminosityRelation (Math.pow(mass, 3.5)); remove any remaining unused
variables (radiusMeters, solarRadiusMeters) if you choose the simpler solar-unit
form instead of converting to meters.

In @apps/server/src/services/universal-gateway.ts:
- Around line 286-294: The JWT exp currently uses Date.now() (milliseconds)
causing huge expirations; in generateZaiToken compute seconds instead (e.g.,
const nowSec = Math.floor(Date.now() / 1000)) and set exp to nowSec + 210
(instead of now + 210000), and use timestamp: nowSec (or keep a separate
millisecond timestamp if needed) so the exp claim is a valid Unix timestamp in
seconds.

In @apps/ui/src/components/views/board-view.tsx:
- Around line 1387-1417: Remove the duplicated, incomplete SmartExpandDialog
instance (the one using props open, onOpenChange, feature, onExpand with an
empty try/catch) and keep the single correct implementation that uses the
updated SmartExpandDialogProps (projectPath and onFeaturesCreated); delete the
entire duplicate block and ensure no other code refers to the removed onExpand
handler so the remaining SmartExpandDialog (with projectPath and
onFeaturesCreated) is the only dialog rendered.

In
@apps/ui/src/components/views/board-view/components/kanban-card/kanban-card.tsx:
- Around line 262-300: The comparator function areKanbanCardPropsEqual is
missing checks for rendering-related props so changes to glassmorphism,
cardBorderEnabled, cardBorderOpacity, and shortcutKey won't trigger rerenders;
update areKanbanCardPropsEqual to also compare prev.glassmorphism !==
next.glassmorphism, prev.cardBorderEnabled !== next.cardBorderEnabled,
prev.cardBorderOpacity !== next.cardBorderOpacity, and prev.shortcutKey !==
next.shortcutKey (alongside the existing checks for feature, contextContent,
summary, etc.) so visual style and CardActions props cause a re-render when
changed.

In @apps/ui/src/components/views/board-view/dialogs/smart-expand-dialog.tsx:
- Around line 108-117: task.priority is a string ('high'|'medium'|'low') but
api.features.create expects a numeric priority; update the creation loop that
calls api.features.create (inside the tasksToCreate iteration) to map
task.priority to a number (e.g. high=1, medium=2, low=3) and pass that numeric
value (or the numeric default 3) as the priority field instead of task.priority
|| 3, ensuring feature.category, feature.branchName and dependencies remain
unchanged.

In @apps/ui/src/main.ts:
- Around line 46-51: Remove the duplicate call to
app.commandLine.appendSwitch('disable-gpu-sandbox') and avoid unconditionally
disabling critical sandboxes: remove or guard the
app.commandLine.appendSwitch('no-sandbox') call so it is not applied in
normal/production runs (e.g., only enable via an explicit env/config flag or
developer mode) and add a short comment documenting the security trade-off and
that stakeholders must accept it before enabling; locate these changes around
the app.commandLine.appendSwitch calls in main.ts.

In @apps/ui/src/store/app-store.ts:
- Line 1181: The default maxConcurrency set to 24 is too aggressive for typical
users; change the default value of the maxConcurrency setting (the
maxConcurrency property in the app store) to a conservative number such as 3–6
(recommend 4) so it won't overwhelm machines, and optionally implement dynamic
adjustment by detecting available CPU cores/memory (e.g., using
navigator.hardwareConcurrency or a platform-specific check) to scale up only
when appropriate; update any related docs/comments to explain the conservative
default and the dynamic fallback logic.
- Around line 1193-1194: Restore the previous Claude-safe defaults by changing
the store defaults: set enhancementModel back to 'sonnet' and validationModel
back to 'opus' (replace the GLM-4.7 assignments for enhancementModel and
validationModel), or alternatively implement fallback logic in the code that
reads these values so if Z.AI credentials are not present it falls back to
'sonnet' and 'opus' when using the enhancementModel or validationModel config
keys.
- Around line 1145-1154: The built-in Z.AI profile object with id
'profile-zai-reasoning' uses an uppercase model string 'GLM-4.7'; change the
model field to the lowercase alias 'glm-4.7' so it matches the keys in
ZAI_MODEL_MAP, the ModelAlias type, and the behavior of resolveModelString
(which checks modelKey in ZAI_MODEL_MAP). Ensure the 'model' property value in
that profile is exactly 'glm-4.7'.

In @apps/ui/src/styles/global.css:
- Around line 1141-1144: The CSS rule targets the edge container
(.react-flow__edge.graph-edge-highlighted) but stroke-width only applies to SVG
path elements; update the selector to target the path node instead, e.g., change
or add a rule for .graph-zoomed-out .react-flow__edge.graph-edge-highlighted
path and set stroke-width: 2px !important there (keep opacity on the container
if desired) so the stroke width actually takes effect.

In @libs/provider-zai/src/zai-query-service.ts:
- Around line 76-81: The create call to this.client.chat.completions.create is
missing support for QueryOptions.abortController and QueryOptions.timeout, so
update the options passed in (in the methods
this.client.chat.completions.create, streamQuery, and query) to include the
abortController.signal (e.g., abortController?.signal) and the timeout value
from QueryOptions (e.g., timeout) alongside model (resolveModel), messages,
max_tokens and temperature; ensure you thread the same abort/timeout handling
into streamQuery and query so requests can be cancelled and will honor timeouts.
🧹 Nitpick comments (24)
apps/ui/package.json (1)

18-18: 32GB heap allocation may be excessive and exclusionary.

Setting --max-old-space-size=32768 reserves 32GB of heap memory for Node.js, which exceeds the total RAM on many developer machines. This could cause swap thrashing or OOM failures for contributors with 16GB or less RAM.

Consider a more conservative default (e.g., 4096–8192 MB) that still addresses the memory pressure from the new Z.AI tooling, or document the RAM requirements in the project's README/contributing guide.

Suggested change
-    "dev": "cross-env NODE_OPTIONS=--max-old-space-size=32768 vite",
+    "dev": "cross-env NODE_OPTIONS=--max-old-space-size=8192 vite",
...
-    "build": "cross-env NODE_OPTIONS=--max-old-space-size=32768 vite build",
+    "build": "cross-env NODE_OPTIONS=--max-old-space-size=8192 vite build",

If the full 32GB is genuinely required for certain workflows, consider making this configurable via an environment variable so developers can override it based on their system:

-    "dev": "cross-env NODE_OPTIONS=--max-old-space-size=32768 vite",
+    "dev": "cross-env NODE_OPTIONS=--max-old-space-size=${VITE_HEAP_SIZE:-8192} vite",

Also applies to: 22-22

apps/ui/src/main.ts (2)

10-12: Hardcoded thread pool size assumes 24-core machine.

The value 24 is hardcoded but not all machines have 24 cores. On machines with fewer cores this causes over-subscription; on machines with more cores it under-utilizes resources. Consider detecting the actual core count dynamically.

♻️ Suggested improvement
+import os from 'os';
+
 // CPU MAXIMIZATION: Set thread pool size equal to core count BEFORE any libs load
 // This unlocks massive parallel I/O for the Node.js backend
-process.env.UV_THREADPOOL_SIZE = '24';
+const coreCount = os.cpus().length;
+// UV_THREADPOOL_SIZE max is 1024, but values above core count have diminishing returns
+process.env.UV_THREADPOOL_SIZE = String(Math.min(coreCount, 128));

59-63: 64GB V8 heap is extremely aggressive.

--max-old-space-size=65536 allocates up to 64GB for the V8 heap. On machines with less physical RAM, this can cause severe memory pressure, swapping, and system instability. Many developer machines have 8-32GB total RAM.

Consider a more conservative default or dynamic calculation based on available system memory.

♻️ Suggested dynamic approach
+import os from 'os';
+
+// Use ~50% of system memory for V8 heap, capped at 16GB for safety
+const totalMemoryMB = Math.floor(os.totalmem() / (1024 * 1024));
+const maxHeapMB = Math.min(Math.floor(totalMemoryMB * 0.5), 16384);
+
 // CPU / RAM MAXIMIZATION: Unlock V8 Heap + Turbo GC (Multi-threaded)
 app.commandLine.appendSwitch(
   'js-flags',
-  '--max-old-space-size=65536 --parallel-marking --concurrent-sweeping --min-semi-space-size=64'
+  `--max-old-space-size=${maxHeapMB} --parallel-marking --concurrent-sweeping --min-semi-space-size=64`
 );
apps/ui/src/styles/global.css (1)

1094-1106: Consider using a dedicated CSS class instead of targeting Tailwind arbitrary value classes.

The selector .min-w-\[240px\] is tightly coupled to a specific Tailwind class in the component. If the component changes this class (e.g., to min-w-[260px]), these LOD styles will silently break.

💡 Suggested approach

Add a semantic class like .task-node-container to the component and target that instead:

-.graph-zoomed-out .react-flow__node-task .min-w-\[240px\] {
+.graph-zoomed-out .react-flow__node-task .task-node-container {
   min-width: 40px !important;
   /* ... */
 }

This decouples the CSS from Tailwind implementation details.

apps/server/src/services/red-giant-service.ts (2)

45-52: Consider stricter typing for event details.

Using [key: string]: any loses type safety. If event types have known detail shapes, consider discriminated unions:

// Example: typed details per event type
type SwellingDetails = { oldRadius: number; newRadius: number; changePercent: number; phase: string };
type CoolingDetails = { oldTemp: number; newTemp: number; changePercent: number; phase: string };
// etc.

397-399: Use substring instead of deprecated substr.

String.prototype.substr is deprecated. Use substring for future compatibility:

Suggested fix
  private generateId(): string {
-   return `red-giant-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
+   return `red-giant-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
  }
apps/server/src/routes/red-giant/routes/get-events.ts (1)

10-17: Consider validating limit parameter.

The limit parameter is passed directly to the service without validation. If a negative number or non-numeric value is provided, it could lead to unexpected behavior. The service's slice(0, limit) will return an empty array for negative limits, which may not be the intended behavior.

🔧 Optional: Add validation for limit
     try {
       const { limit, starId } = req.body as {
         limit?: number;
         starId?: string;
       };

+      if (limit !== undefined && (typeof limit !== 'number' || limit < 1)) {
+        res.status(400).json({
+          success: false,
+          error: 'limit must be a positive number',
+        });
+        return;
+      }
+
       const events = redGiantService.getEvents(limit, starId);
       res.json({ success: true, events, count: events.length });
apps/ui/src/components/views/board-view/dialogs/smart-expand-dialog.tsx (2)

15-15: Remove unused import GitGraph.

GitGraph is imported but never used in this component.

🧹 Proposed fix
-import { Sparkles, Loader2, GitGraph, Check, AlertCircle } from 'lucide-react';
+import { Sparkles, Loader2, Check, AlertCircle } from 'lucide-react';

46-46: Unused state: subspecTemplate has no corresponding UI input.

The subspecTemplate state is initialized and passed to the API (line 70), but there's no input field in the configure step to set its value. It will always be an empty string.

Consider either adding a UI input for this field or removing the unused state if it's not needed.

libs/provider-zai/src/zai-query-service.ts (1)

50-53: Remove leftover placeholder comment.

Line 53 contains // ... which appears to be leftover from development and should be removed.

🧹 Suggested fix
   getName(): string {
     return 'zai';
   }
-  // ...
apps/ui/src/components/views/graph-view/graph-canvas.tsx (1)

318-319: Move import to the top of the file.

The import statement is placed at the end of the file instead of with other imports at the top. This breaks the standard import organization convention.

Suggested change

Move line 318 to the import section at the top (around line 37):

 import { Button } from '@/components/ui/button';
+import { GraphActionsContext } from './context/graph-actions-context';
 
 // Define custom node and edge types...

And remove the import from line 318.

apps/ui/src/hooks/use-settings-migration.ts (2)

94-109: Consider clearing the timeout on successful migration.

The timeout fires regardless of whether migration completes successfully. While the migrationCompleted check prevents double-signaling, the timer continues running unnecessarily. This is a minor memory/resource consideration.

♻️ Optional improvement to clear timeout
+  let timeoutId: ReturnType<typeof setTimeout>;
   const timeoutPromise = new Promise<void>((resolve) => {
-    setTimeout(() => {
+    timeoutId = setTimeout(() => {
       if (!migrationCompleted) {
         logger.warn('Migration wait timed out (5s). Forcing completion signal.');
         migrationCompleted = true;
         if (migrationCompleteResolve) migrationCompleteResolve();
-        resolve();
       }
+      resolve();
     }, 5000);
   });
 
-  return Promise.race([migrationCompletePromise, timeoutPromise]);
+  return Promise.race([migrationCompletePromise, timeoutPromise]).finally(() => {
+    clearTimeout(timeoutId);
+  });

543-548: Remove duplicate JSDoc comment.

There's a duplicate /** Hydrate the Zustand store from settings object */ comment at lines 543-544 and 546-547.

🧹 Remove duplicate comment
-/**
- * Hydrate the Zustand store from settings object
- */
 /**
  * Hydrate the Zustand store from settings object
  */
 export function hydrateStoreFromSettings(
apps/server/src/routes/context/routes/describe-image.ts (3)

16-16: Unused import: stripProviderPrefix.

This import appears unused after the refactoring removed provider-specific option construction.

🧹 Proposed fix
-import { DEFAULT_PHASE_MODELS, isCursorModel, stripProviderPrefix } from '@automaker/types';
+import { DEFAULT_PHASE_MODELS, isCursorModel } from '@automaker/types';

183-225: Dead code: extractTextFromStream is defined but never used.

This function was replaced by inline stream processing in both the Cursor and non-Cursor branches but was not removed.

🧹 Remove the unused function
-/**
- * Extract text content from Claude SDK response messages and log high-signal stream events.
- */
-async function extractTextFromStream(
-  // eslint-disable-next-line @typescript-eslint/no-explicit-any
-  stream: AsyncIterable<any>,
-  requestId: string
-): Promise<string> {
-  let responseText = '';
-  let messageCount = 0;
-
-  logger.info(`[${requestId}] [Stream] Begin reading SDK stream...`);
-
-  for await (const msg of stream) {
-    messageCount++;
-    const msgType = msg?.type;
-    const msgSubtype = msg?.subtype;
-
-    // Keep this concise but informative. Full error object is logged in catch blocks.
-    logger.info(
-      `[${requestId}] [Stream] #${messageCount} type=${String(msgType)} subtype=${String(msgSubtype ?? '')}`
-    );
-
-    if (msgType === 'assistant' && msg.message?.content) {
-      const blocks = msg.message.content as Array<{ type: string; text?: string }>;
-      logger.info(`[${requestId}] [Stream] assistant blocks=${blocks.length}`);
-      for (const block of blocks) {
-        if (block.type === 'text' && block.text) {
-          responseText += block.text;
-        }
-      }
-    }
-
-    if (msgType === 'result' && msgSubtype === 'success') {
-      if (typeof msg.result === 'string' && msg.result.length > 0) {
-        responseText = msg.result;
-      }
-    }
-  }
-
-  logger.info(
-    `[${requestId}] [Stream] End of stream. messages=${messageCount} textLength=${responseText.length}`
-  );
-
-  return responseText;
-}

336-346: Unused variables: autoLoadClaudeMd and thinkingLevel.

  • autoLoadClaudeMd (line 336-340) is computed but never passed to the execution options.
  • thinkingLevel (line 346) is destructured but never used—both aiGateway.execute and executeQuery calls omit it.

Based on the provider snippets, thinkingLevel is a valid option for executeQuery. If thinking is intended for image description, pass it; otherwise, remove the destructuring.

🧹 Either use or remove these variables

If these settings should be used:

 for await (const msg of aiGateway.execute({
   prompt: cursorPrompt,
   model,
   cwd,
   maxTurns: 1,
   allowedTools: ['Read'],
   readOnly: true,
+  thinkingLevel,
 })) {
 for await (const msg of executeQuery({
   prompt: textPrompt,
   model,
   cwd,
   maxTurns: 1,
   allowedTools: ['Read'],
   readOnly: true,
+  thinkingLevel,
 })) {

Or remove if not needed:

-// Load autoLoadClaudeMd setting
-const autoLoadClaudeMd = await getAutoLoadClaudeMdSetting(
-  cwd,
-  settingsService,
-  '[DescribeImage]'
-);
 
 // Get model from phase settings
 const settings = await settingsService?.getGlobalSettings();
 const phaseModelEntry =
   settings?.phaseModels?.imageDescriptionModel || DEFAULT_PHASE_MODELS.imageDescriptionModel;
-const { model, thinkingLevel } = resolvePhaseModel(phaseModelEntry);
+const { model } = resolvePhaseModel(phaseModelEntry);
apps/server/src/routes/app-spec/generate-spec.ts (1)

94-106: Dead reads: autoLoadClaudeMd + thinkingLevel are computed but unused

  • autoLoadClaudeMd is read (Line 95-99) but never applied to either gateway path.
  • thinkingLevel is resolved (Line 105) but never passed to aiGateway.execute(...) / executeQuery(...).
Option A: remove dead reads
-  const autoLoadClaudeMd = await getAutoLoadClaudeMdSetting(
-    projectPath,
-    settingsService,
-    '[SpecRegeneration]'
-  );
+  // (removed) autoLoadClaudeMd was previously used for provider-specific wiring.
-  const { model, thinkingLevel } = resolvePhaseModel(phaseModelEntry);
+  const { model, thinkingLevel } = resolvePhaseModel(phaseModelEntry);
  ...
+  // TODO: pass thinkingLevel into aiGateway/executeQuery (if supported for the chosen provider)

Also applies to: 131-170, 191-264

apps/server/src/index.ts (1)

169-206: Avoid mutating process.env to “disable” a provider (surprising global side-effect)
Deleting process.env.ANTHROPIC_API_KEY (Line 180-187) can break other code paths that still rely on it (and makes debugging “why is Claude unavailable?” harder). Prefer provider selection/priority logic in the gateway/provider registry instead of global env mutation.

apps/server/src/services/auto-mode-service.ts (2)

3051-3079: Prefer extractJson() over bespoke JSON scraping for learnings
You already have extractJson in the repo; this block reimplements JSON extraction/parsing and will be brittle against edge cases.


3197-3366: expandKnowledgeGraph(): resolve model + align execution/auditing with aiGateway

  • modelConfig.model can be 'default' and is passed into queryService.simpleQuery without resolution.
  • This path bypasses aiGateway (credential precedence + audit logs), so it may behave inconsistently vs the rest of the PR.
apps/server/src/services/ideation-service.ts (1)

717-829: Well-structured generateSubtasks method for Smart Expand feature.

The implementation follows established patterns from generateSuggestions with appropriate additions:

  • Event emissions for progress tracking (ideation:subtasks)
  • Context loading with graceful fallback
  • Dedicated system prompt builder
  • Debug logging for troubleshooting

Minor observation: Two separate AbortController instances are created (lines 770 and 781). The one passed to createSuggestionsOptions is unused since aiGateway.execute() receives its own controller.

♻️ Optional: Consolidate AbortController usage
       // Create SDK options using Suggestions preset (designed for ideation/analysis)
+      const abortController = new AbortController();
       const sdkOptions = createSuggestionsOptions({
         cwd: projectPath,
         model: modelId,
         systemPrompt,
-        abortController: new AbortController(),
+        abortController,
       });

       // Use AIGateway for parallel-safe, provider-agnostic execution
       const stream = aiGateway.execute({
         prompt: `Decompose this task: "${parentTask}"`,
         model: modelId,
         cwd: projectPath,
         systemPrompt: sdkOptions.systemPrompt as string,
         maxTurns: 1,
         allowedTools: [],
-        abortController: new AbortController(),
+        abortController,
       });
apps/server/src/services/universal-gateway.ts (3)

33-75: Empty catch blocks silently swallow import errors.

The dynamic imports use empty catch blocks, which means failures are silently ignored. While this is intentional for optional providers, consider logging at debug level to aid troubleshooting when a provider is expected but unavailable.

♻️ Optional: Add debug logging for failed imports
 try {
   createGoogleVertex = require('@ai-sdk/google-vertex').createVertex;
-} catch {}
+} catch (e) {
+  logger.debug('[UniversalGateway] @ai-sdk/google-vertex not available');
+}

327-357: Credential loading is resilient but has an empty catch block.

The credential loading correctly handles missing files and merges environment variables. However, the empty catch block on line 348 silently ignores JSON parse errors or file read errors, which could mask configuration issues.

♻️ Recommended: Log credential loading errors
     try {
       if (fs.existsSync(credPath)) {
         const creds = JSON.parse(fs.readFileSync(credPath, 'utf-8'));
         if (creds.apiKeys) {
           for (const [key, value] of Object.entries(creds.apiKeys)) {
             if (value && typeof value === 'string') {
               this.credentials[key] = value.trim();
               process.env[this.getEnvVar(key)] = value.trim();
               logger.info(`[UniversalGateway] Loaded ${key} from credentials.json`);
             }
           }
         }
       }
-    } catch {}
+    } catch (err) {
+      logger.warn(`[UniversalGateway] Failed to load credentials.json: ${(err as Error).message}`);
+    }

364-390: Provider creation silently ignores factory errors.

The empty catch block on line 372 means provider initialization failures are silently ignored. This could make debugging difficult when a provider is expected to be available but isn't.

♻️ Recommended: Log provider creation failures
       if (key || !p.requiresAuth) {
         try {
           const instance = p.factory(key || '');
           if (instance) this.providers.set(p.name, instance);
-        } catch {}
+        } catch (err) {
+          logger.debug(`[UniversalGateway] Failed to create ${p.name} provider: ${(err as Error).message}`);
+        }
       }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 00f26a2 and eec05a0.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (36)
  • apps/server/package.json
  • apps/server/src/index.ts
  • apps/server/src/routes/app-spec/generate-features-from-spec.ts
  • apps/server/src/routes/app-spec/generate-spec.ts
  • apps/server/src/routes/backlog-plan/generate-plan.ts
  • apps/server/src/routes/context/routes/describe-image.ts
  • apps/server/src/routes/enhance-prompt/routes/enhance.ts
  • apps/server/src/routes/github/routes/validate-issue.ts
  • apps/server/src/routes/red-giant/routes/create-star.ts
  • apps/server/src/routes/red-giant/routes/delete-star.ts
  • apps/server/src/routes/red-giant/routes/evolve-star.ts
  • apps/server/src/routes/red-giant/routes/get-events.ts
  • apps/server/src/routes/red-giant/routes/get-metrics.ts
  • apps/server/src/routes/red-giant/routes/get-star.ts
  • apps/server/src/routes/red-giant/routes/list-stars.ts
  • apps/server/src/services/agent-service.ts
  • apps/server/src/services/ai-gateway.ts
  • apps/server/src/services/auto-mode-service.ts
  • apps/server/src/services/ideation-service.ts
  • apps/server/src/services/red-giant-service.ts
  • apps/server/src/services/universal-gateway.ts
  • apps/ui/package.json
  • apps/ui/src/components/views/board-view.tsx
  • apps/ui/src/components/views/board-view/components/kanban-card/kanban-card.tsx
  • apps/ui/src/components/views/board-view/dialogs/smart-expand-dialog.tsx
  • apps/ui/src/components/views/graph-view/components/dependency-edge.tsx
  • apps/ui/src/components/views/graph-view/components/task-node.tsx
  • apps/ui/src/components/views/graph-view/context/graph-actions-context.tsx
  • apps/ui/src/components/views/graph-view/graph-canvas.tsx
  • apps/ui/src/components/views/graph-view/hooks/use-graph-nodes.ts
  • apps/ui/src/hooks/use-settings-migration.ts
  • apps/ui/src/main.ts
  • apps/ui/src/routes/world-model.tsx
  • apps/ui/src/store/app-store.ts
  • apps/ui/src/styles/global.css
  • libs/provider-zai/src/zai-query-service.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/ui/src/components/views/graph-view/components/task-node.tsx
  • apps/server/package.json
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Always import from shared packages (@automaker/*), never from old relative paths

Files:

  • apps/server/src/routes/red-giant/routes/get-star.ts
  • apps/server/src/routes/red-giant/routes/evolve-star.ts
  • apps/server/src/routes/red-giant/routes/get-metrics.ts
  • apps/ui/src/routes/world-model.tsx
  • apps/server/src/routes/red-giant/routes/delete-star.ts
  • apps/server/src/routes/red-giant/routes/create-star.ts
  • apps/ui/src/components/views/graph-view/context/graph-actions-context.tsx
  • apps/ui/src/components/views/board-view/components/kanban-card/kanban-card.tsx
  • apps/server/src/services/agent-service.ts
  • apps/server/src/routes/red-giant/routes/get-events.ts
  • apps/server/src/routes/context/routes/describe-image.ts
  • apps/ui/src/components/views/graph-view/graph-canvas.tsx
  • apps/server/src/services/universal-gateway.ts
  • apps/server/src/index.ts
  • apps/server/src/routes/app-spec/generate-features-from-spec.ts
  • libs/provider-zai/src/zai-query-service.ts
  • apps/ui/src/hooks/use-settings-migration.ts
  • apps/ui/src/store/app-store.ts
  • apps/ui/src/main.ts
  • apps/server/src/services/auto-mode-service.ts
  • apps/server/src/services/ideation-service.ts
  • apps/ui/src/components/views/graph-view/components/dependency-edge.tsx
  • apps/server/src/routes/enhance-prompt/routes/enhance.ts
  • apps/ui/src/components/views/board-view/dialogs/smart-expand-dialog.tsx
  • apps/server/src/routes/github/routes/validate-issue.ts
  • apps/server/src/routes/backlog-plan/generate-plan.ts
  • apps/server/src/routes/red-giant/routes/list-stars.ts
  • apps/server/src/services/ai-gateway.ts
  • apps/server/src/services/red-giant-service.ts
  • apps/server/src/routes/app-spec/generate-spec.ts
  • apps/ui/src/components/views/graph-view/hooks/use-graph-nodes.ts
  • apps/ui/src/components/views/board-view.tsx
apps/server/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use createEventEmitter() from lib/events.ts for all server operations to emit events that stream to frontend via WebSocket

Files:

  • apps/server/src/routes/red-giant/routes/get-star.ts
  • apps/server/src/routes/red-giant/routes/evolve-star.ts
  • apps/server/src/routes/red-giant/routes/get-metrics.ts
  • apps/server/src/routes/red-giant/routes/delete-star.ts
  • apps/server/src/routes/red-giant/routes/create-star.ts
  • apps/server/src/services/agent-service.ts
  • apps/server/src/routes/red-giant/routes/get-events.ts
  • apps/server/src/routes/context/routes/describe-image.ts
  • apps/server/src/services/universal-gateway.ts
  • apps/server/src/index.ts
  • apps/server/src/routes/app-spec/generate-features-from-spec.ts
  • apps/server/src/services/auto-mode-service.ts
  • apps/server/src/services/ideation-service.ts
  • apps/server/src/routes/enhance-prompt/routes/enhance.ts
  • apps/server/src/routes/github/routes/validate-issue.ts
  • apps/server/src/routes/backlog-plan/generate-plan.ts
  • apps/server/src/routes/red-giant/routes/list-stars.ts
  • apps/server/src/services/ai-gateway.ts
  • apps/server/src/services/red-giant-service.ts
  • apps/server/src/routes/app-spec/generate-spec.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use resolveModelString() from @automaker/model-resolver to convert model aliases (haiku, sonnet, opus) to full model names

Files:

  • apps/server/src/routes/red-giant/routes/get-star.ts
  • apps/server/src/routes/red-giant/routes/evolve-star.ts
  • apps/server/src/routes/red-giant/routes/get-metrics.ts
  • apps/ui/src/routes/world-model.tsx
  • apps/server/src/routes/red-giant/routes/delete-star.ts
  • apps/server/src/routes/red-giant/routes/create-star.ts
  • apps/ui/src/components/views/graph-view/context/graph-actions-context.tsx
  • apps/ui/src/components/views/board-view/components/kanban-card/kanban-card.tsx
  • apps/server/src/services/agent-service.ts
  • apps/server/src/routes/red-giant/routes/get-events.ts
  • apps/server/src/routes/context/routes/describe-image.ts
  • apps/ui/src/components/views/graph-view/graph-canvas.tsx
  • apps/server/src/services/universal-gateway.ts
  • apps/server/src/index.ts
  • apps/server/src/routes/app-spec/generate-features-from-spec.ts
  • libs/provider-zai/src/zai-query-service.ts
  • apps/ui/src/hooks/use-settings-migration.ts
  • apps/ui/src/store/app-store.ts
  • apps/ui/src/main.ts
  • apps/server/src/services/auto-mode-service.ts
  • apps/server/src/services/ideation-service.ts
  • apps/ui/src/components/views/graph-view/components/dependency-edge.tsx
  • apps/server/src/routes/enhance-prompt/routes/enhance.ts
  • apps/ui/src/components/views/board-view/dialogs/smart-expand-dialog.tsx
  • apps/server/src/routes/github/routes/validate-issue.ts
  • apps/server/src/routes/backlog-plan/generate-plan.ts
  • apps/server/src/routes/red-giant/routes/list-stars.ts
  • apps/server/src/services/ai-gateway.ts
  • apps/server/src/services/red-giant-service.ts
  • apps/server/src/routes/app-spec/generate-spec.ts
  • apps/ui/src/components/views/graph-view/hooks/use-graph-nodes.ts
  • apps/ui/src/components/views/board-view.tsx
🧠 Learnings (4)
📚 Learning: 2025-12-28T05:07:48.147Z
Learnt from: CR
Repo: AutoMaker-Org/automaker PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-28T05:07:48.147Z
Learning: Frontend UI must use TanStack Router for file-based routing, organize components in components/views/, implement stores with Zustand, and use custom hooks in hooks/ directory

Applied to files:

  • apps/ui/src/routes/world-model.tsx
📚 Learning: 2025-12-30T01:02:07.114Z
Learnt from: illia1f
Repo: AutoMaker-Org/automaker PR: 324
File: apps/ui/src/components/views/board-view/components/kanban-card/kanban-card.tsx:122-131
Timestamp: 2025-12-30T01:02:07.114Z
Learning: Tailwind CSS v4 uses postfix syntax for the important modifier: append ! at the end of the utility class (e.g., backdrop-blur-[0px]! or hover:bg-red-500!). The older v3 style used a prefix (!) at the start (e.g., !backdrop-blur-[0px]); prefer the postfix form for consistency across TSX files.

Applied to files:

  • apps/ui/src/routes/world-model.tsx
  • apps/ui/src/components/views/graph-view/context/graph-actions-context.tsx
  • apps/ui/src/components/views/board-view/components/kanban-card/kanban-card.tsx
  • apps/ui/src/components/views/graph-view/graph-canvas.tsx
  • apps/ui/src/components/views/graph-view/components/dependency-edge.tsx
  • apps/ui/src/components/views/board-view/dialogs/smart-expand-dialog.tsx
  • apps/ui/src/components/views/board-view.tsx
📚 Learning: 2025-12-28T05:07:48.147Z
Learnt from: CR
Repo: AutoMaker-Org/automaker PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-28T05:07:48.147Z
Learning: Applies to apps/server/src/**/*.{ts,tsx} : Use `createEventEmitter()` from `lib/events.ts` for all server operations to emit events that stream to frontend via WebSocket

Applied to files:

  • apps/server/src/index.ts
📚 Learning: 2025-12-28T05:07:48.147Z
Learnt from: CR
Repo: AutoMaker-Org/automaker PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-28T05:07:48.147Z
Learning: Applies to **/*.{ts,tsx} : Use `resolveModelString()` from automaker/model-resolver to convert model aliases (haiku, sonnet, opus) to full model names

Applied to files:

  • apps/ui/src/store/app-store.ts
  • apps/server/src/services/auto-mode-service.ts
  • apps/server/src/services/ideation-service.ts
  • apps/server/src/routes/github/routes/validate-issue.ts
  • apps/server/src/routes/backlog-plan/generate-plan.ts
  • apps/ui/src/components/views/board-view.tsx
🧬 Code graph analysis (18)
apps/ui/src/routes/world-model.tsx (1)
apps/ui/src/routes/__root.tsx (1)
  • Route (552-554)
apps/server/src/routes/red-giant/routes/create-star.ts (1)
apps/server/src/services/red-giant-service.ts (1)
  • RedGiantService (54-432)
apps/ui/src/components/views/graph-view/context/graph-actions-context.tsx (1)
apps/ui/src/components/views/graph-view/hooks/use-graph-nodes.ts (1)
  • NodeActionCallbacks (40-49)
apps/server/src/services/agent-service.ts (1)
apps/server/src/services/ai-gateway.ts (1)
  • aiGateway (301-301)
apps/server/src/routes/context/routes/describe-image.ts (3)
apps/server/src/services/ai-gateway.ts (1)
  • aiGateway (301-301)
apps/server/src/providers/claude-provider.ts (1)
  • executeQuery (55-169)
apps/server/src/providers/cursor-provider.ts (1)
  • executeQuery (649-796)
apps/ui/src/components/views/graph-view/graph-canvas.tsx (3)
apps/ui/src/lib/utils.ts (1)
  • cn (6-8)
apps/ui/src/components/views/graph-view/index.ts (1)
  • GraphCanvas (2-2)
apps/ui/src/components/views/graph-view/context/graph-actions-context.tsx (1)
  • GraphActionsContext (4-4)
apps/server/src/index.ts (7)
apps/server/src/services/settings-service.ts (1)
  • SettingsService (107-797)
apps/server/src/routes/pipeline/common.ts (1)
  • logger (11-11)
apps/server/src/routes/settings/common.ts (1)
  • logger (12-12)
apps/server/src/routes/templates/common.ts (1)
  • logger (8-8)
apps/server/src/services/ai-gateway.ts (1)
  • aiGateway (301-301)
apps/server/src/services/universal-gateway.ts (1)
  • universalGateway (498-498)
apps/server/src/routes/setup/index.ts (1)
  • createSetupRoutes (41-83)
apps/server/src/routes/app-spec/generate-features-from-spec.ts (2)
apps/server/src/services/ai-gateway.ts (1)
  • aiGateway (301-301)
apps/server/src/providers/claude-provider.ts (1)
  • executeQuery (55-169)
libs/provider-zai/src/zai-query-service.ts (1)
libs/providers-core/src/query-service.ts (3)
  • QueryService (50-88)
  • QueryOptions (14-27)
  • QueryResult (32-42)
apps/server/src/services/ideation-service.ts (4)
libs/model-resolver/src/resolver.ts (1)
  • resolveModelString (36-120)
apps/server/src/services/ai-gateway.ts (1)
  • aiGateway (301-301)
libs/types/src/index.ts (2)
  • AnalysisSuggestion (224-224)
  • IdeaCategory (211-211)
apps/server/src/lib/sdk-options.ts (2)
  • validateWorkingDirectory (107-119)
  • createSuggestionsOptions (422-443)
apps/ui/src/components/views/graph-view/components/dependency-edge.tsx (2)
apps/ui/src/store/app-store.ts (1)
  • Feature (334-350)
apps/ui/src/components/views/graph-view/context/graph-actions-context.tsx (1)
  • useGraphActions (6-8)
apps/server/src/routes/enhance-prompt/routes/enhance.ts (4)
apps/server/src/services/ai-gateway.ts (1)
  • aiGateway (301-301)
libs/types/src/index.ts (1)
  • CLAUDE_MODEL_MAP (58-58)
libs/model-resolver/src/resolver.ts (1)
  • resolveModelString (36-120)
libs/providers-core/src/provider-registry.ts (1)
  • getQueryService (114-142)
apps/ui/src/components/views/board-view/dialogs/smart-expand-dialog.tsx (3)
apps/ui/src/store/app-store.ts (1)
  • Feature (334-350)
apps/ui/src/lib/electron.ts (1)
  • AnalysisSuggestion (54-54)
apps/ui/src/lib/http-api-client.ts (1)
  • getHttpApiClient (2318-2323)
apps/server/src/routes/github/routes/validate-issue.ts (4)
apps/server/src/services/ai-gateway.ts (1)
  • aiGateway (301-301)
apps/server/src/routes/github/routes/validation-schema.ts (1)
  • ISSUE_VALIDATION_SYSTEM_PROMPT (89-153)
apps/server/src/lib/json-extractor.ts (1)
  • extractJson (49-177)
libs/types/src/issue-validation.ts (1)
  • IssueValidationResult (80-99)
apps/server/src/routes/backlog-plan/generate-plan.ts (1)
apps/server/src/services/ai-gateway.ts (1)
  • aiGateway (301-301)
apps/server/src/routes/red-giant/routes/list-stars.ts (1)
apps/server/src/services/red-giant-service.ts (1)
  • RedGiantService (54-432)
apps/server/src/services/red-giant-service.ts (1)
apps/server/src/lib/events.ts (1)
  • EventEmitter (13-16)
apps/server/src/routes/app-spec/generate-spec.ts (4)
apps/server/src/services/ai-gateway.ts (1)
  • aiGateway (301-301)
libs/types/src/index.ts (2)
  • specOutputSchema (76-76)
  • SpecOutput (75-75)
apps/server/src/lib/app-spec-format.ts (2)
  • specOutputSchema (10-10)
  • SpecOutput (9-9)
apps/server/src/lib/json-extractor.ts (1)
  • extractJson (49-177)
🪛 Biome (2.1.2)
apps/ui/src/components/views/board-view.tsx

[error] 1413-1415: This code is unreachable

(lint/correctness/noUnreachable)

Comment on lines 178 to 206
// Initialize services
(async () => {
// Enforce Z.AI priority by disabling Claude if Z.AI key is present
if (process.env.ZAI_API_KEY) {
if (process.env.ANTHROPIC_API_KEY) {
logger.info(
'Detected Z.AI Key: Explicitly disabling Claude provider by unsetting ANTHROPIC_API_KEY'
);
delete process.env.ANTHROPIC_API_KEY;
}
}

// Load providers dynamically to ensure they see the updated environment
await import('@automaker/provider-claude');
await import('@automaker/provider-zai');

// Initialize AI Gateway (must be after providers are registered)
const { aiGateway } = await import('./services/ai-gateway.js');
await aiGateway.init();
logger.info('AI Gateway initialized');

// Initialize Universal Gateway (Vercel AI SDK)
const { universalGateway } = await import('./services/universal-gateway.js');
await universalGateway.init();
logger.info('Universal Gateway initialized');

await agentService.initialize();
logger.info('Agent service initialized');
})();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Major: server can accept requests before gateways/providers finish initializing
The async init IIFE (Line 178-206) is not awaited before startServer(PORT) (Line 590). Any early request hitting aiGateway.execute(...) can fail with “Not initialized”.

Proposed fix (start listening after init completes)
-startServer(PORT);
-
-// Initialize services
-(async () => {
+// Initialize services, then start server
+(async () => {
   // Enforce Z.AI priority...
   ...
   await agentService.initialize();
   logger.info('Agent service initialized');
+
+  startServer(PORT);
 })();

Also applies to: 540-591

🤖 Prompt for AI Agents
In @apps/server/src/index.ts around lines 178 - 206, The init IIFE runs
asynchronously and is not awaited before the server begins listening, so
requests can hit aiGateway.execute/universalGateway or agentService before
aiGateway.init/universalGateway.init/agentService.initialize complete; modify
startup so that startServer(PORT) is only called after awaiting the providers
and gateways initialization (await the async IIFE or refactor it into an
exported async function like initServices that sequentially awaits
aiGateway.init(), universalGateway.init(), and agentService.initialize()) and
then call startServer(PORT) once that promise resolves, ensuring aiGateway,
universalGateway, and agentService are fully initialized before accepting
requests.

Comment on lines +8 to 15
import { executeQuery } from '../../lib/execute-query.js';
import * as secureFs from '../../lib/secure-fs.js';
import type { EventEmitter } from '../../lib/events.js';
import { createLogger } from '@automaker/utils';
import { DEFAULT_PHASE_MODELS, isCursorModel, stripProviderPrefix } from '@automaker/types';
import { resolvePhaseModel } from '@automaker/model-resolver';
import { createFeatureGenerationOptions } from '../../lib/sdk-options.js';
import { ProviderFactory } from '../../providers/provider-factory.js';
import { logAuthStatus } from './common.js';
import { aiGateway } from '../../services/ai-gateway.js';
import { parseAndCreateFeatures } from './parse-and-create-features.js';
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# First, check the file exists and get its overall structure
wc -l apps/server/src/routes/app-spec/generate-features-from-spec.ts
echo "---"
# Read the imports section (lines 1-20)
head -20 apps/server/src/routes/app-spec/generate-features-from-spec.ts | cat -n

Repository: AutoMaker-Org/automaker

Length of output: 1228


🏁 Script executed:

#!/bin/bash
# Read the critical sections mentioned in the review
echo "=== Lines 100-120 (around line 112 where thinkingLevel is resolved) ==="
sed -n '100,120p' apps/server/src/routes/app-spec/generate-features-from-spec.ts | cat -n
echo ""
echo "=== Lines 130-170 (Cursor execution path) ==="
sed -n '130,170p' apps/server/src/routes/app-spec/generate-features-from-spec.ts | cat -n
echo ""
echo "=== Lines 160-220 (Non-Cursor execution path) ==="
sed -n '160,220p' apps/server/src/routes/app-spec/generate-features-from-spec.ts | cat -n

Repository: AutoMaker-Org/automaker

Length of output: 5563


🏁 Script executed:

#!/bin/bash
# Check executeQuery function definition and signature
echo "=== Looking for executeQuery definition ==="
rg -n "export.*function executeQuery|export.*const executeQuery" apps/server/src/lib/ -A 5 --type ts --type js

Repository: AutoMaker-Org/automaker

Length of output: 110


🏁 Script executed:

#!/bin/bash
# Check the aiGateway execute function signature
echo "=== Looking for aiGateway definition ==="
find apps/server/src -name "*ai-gateway*" -type f

Repository: AutoMaker-Org/automaker

Length of output: 146


🏁 Script executed:

#!/bin/bash
# Check if thinkingLevel is actually being used in the file
echo "=== Search for thinkingLevel in the file ==="
rg -n "thinkingLevel" apps/server/src/routes/app-spec/generate-features-from-spec.ts -B 2 -A 2

Repository: AutoMaker-Org/automaker

Length of output: 365


🏁 Script executed:

#!/bin/bash
# Find and examine executeQuery function
find apps/server/src -name "execute-query*" -type f

Repository: AutoMaker-Org/automaker

Length of output: 49


🏁 Script executed:

#!/bin/bash
# Check executeQuery function signature
cat -n apps/server/src/lib/execute-query.ts | head -60

Repository: AutoMaker-Org/automaker

Length of output: 135


🏁 Script executed:

#!/bin/bash
# Check aiGateway.execute method signature
rg -n "execute\s*\(" apps/server/src/services/ai-gateway.ts -A 10 | head -40

Repository: AutoMaker-Org/automaker

Length of output: 1506


🏁 Script executed:

#!/bin/bash
# Check if thinkingLevel is used anywhere in similar code paths
rg -n "thinkingLevel" apps/server/src/services/ -B 3 -A 3

Repository: AutoMaker-Org/automaker

Length of output: 11952


🏁 Script executed:

#!/bin/bash
# Check resolvePhaseModel to understand what it returns
rg -n "export.*resolvePhaseModel|function resolvePhaseModel" -A 5 | head -30

Repository: AutoMaker-Org/automaker

Length of output: 540


🏁 Script executed:

#!/bin/bash
# Search for executeQuery more broadly in js files
find apps/server/src -name "*.js" -o -name "*.ts" | xargs grep -l "executeQuery" | head -5

Repository: AutoMaker-Org/automaker

Length of output: 287


🏁 Script executed:

#!/bin/bash
# Look for the actual definition/export of executeQuery with broader search
rg -n "executeQuery" apps/server/src/lib/ -B 2 -A 8 | head -50

Repository: AutoMaker-Org/automaker

Length of output: 49


🏁 Script executed:

#!/bin/bash
# Check what files are in the lib directory
ls -la apps/server/src/lib/ | head -20

Repository: AutoMaker-Org/automaker

Length of output: 1334


🏁 Script executed:

#!/bin/bash
# Check if it's in a .js file with different naming
find apps/server/src/lib -type f \( -name "*.ts" -o -name "*.js" \) | xargs ls -lh

Repository: AutoMaker-Org/automaker

Length of output: 1523


🏁 Script executed:

#!/bin/bash
# Search for where executeQuery is actually imported from in other files
rg "from.*execute-query|import.*executeQuery" apps/server/src -A 1 | head -40

Repository: AutoMaker-Org/automaker

Length of output: 955


🏁 Script executed:

#!/bin/bash
# Search for executeQuery definition without file path restrictions
rg "export.*function executeQuery|export.*const executeQuery" -A 5

Repository: AutoMaker-Org/automaker

Length of output: 49


🏁 Script executed:

#!/bin/bash
# Check if execute-query might be exported from a different file
rg "executeQuery" apps/server/src -B 3 -A 3 | grep "export\|function executeQuery\|const executeQuery" | head -20

Repository: AutoMaker-Org/automaker

Length of output: 49


🏁 Script executed:

#!/bin/bash
# Let's try a direct search for the actual definition
ast-grep --pattern 'function executeQuery' -A 5

Repository: AutoMaker-Org/automaker

Length of output: 49


🏁 Script executed:

#!/bin/bash
# Check if it's in node_modules or a generated file
find . -path ./node_modules -prune -o -name "*execute*query*" -type f -print

Repository: AutoMaker-Org/automaker

Length of output: 49


🏁 Script executed:

#!/bin/bash
# Check the git index to see what files should exist
git ls-files | grep -i "execute-query"

Repository: AutoMaker-Org/automaker

Length of output: 49


🏁 Script executed:

#!/bin/bash
# Check the providers to see if executeQuery might be defined there
rg "executeQuery" apps/server/src/providers/ -B 5 -A 10

Repository: AutoMaker-Org/automaker

Length of output: 15352


🏁 Script executed:

#!/bin/bash
# Check if executeQuery might be in ai-gateway or other services
rg "function executeQuery|export.*executeQuery" apps/server/src/services/ -B 2 -A 5

Repository: AutoMaker-Org/automaker

Length of output: 49


🏁 Script executed:

#!/bin/bash
# Check git history - maybe the file was deleted
git log --all --full-history --oneline -- "*execute-query*" | head -10

Repository: AutoMaker-Org/automaker

Length of output: 49


🏁 Script executed:

#!/bin/bash
# Let's check what the actual import resolves to
rg "from.*'.*execute-query" apps/server/src -A 5 | head -30

Repository: AutoMaker-Org/automaker

Length of output: 2512


🏁 Script executed:

#!/bin/bash
# Check if there's a re-export from ai-gateway or base-provider
cat -n apps/server/src/services/ai-gateway.ts | head -80

Repository: AutoMaker-Org/automaker

Length of output: 3049


🏁 Script executed:

#!/bin/bash
# Let's check if executeQuery might be defined as a method within a module
rg "executeQuery\s*=" apps/server/src -B 2 -A 3

Repository: AutoMaker-Org/automaker

Length of output: 49


🏁 Script executed:

#!/bin/bash
# Check the ai-gateway file more thoroughly to see if executeQuery is exported
rg -n "executeQuery" apps/server/src/services/ai-gateway.ts

Repository: AutoMaker-Org/automaker

Length of output: 141


🏁 Script executed:

#!/bin/bash
# Let's see if there's an index file that re-exports executeQuery
find apps/server/src/lib -name "index.*"

Repository: AutoMaker-Org/automaker

Length of output: 49


🏁 Script executed:

#!/bin/bash
# Let's check what a file that imports executeQuery actually does with it
cat -n apps/server/src/routes/app-spec/generate-spec.ts | head -100

Repository: AutoMaker-Org/automaker

Length of output: 4507


🏁 Script executed:

#!/bin/bash
# Check if executeQuery might be the aiGateway.execute method aliased
rg -n "const executeQuery|export.*executeQuery" apps/server/src -B 3 -A 5

Repository: AutoMaker-Org/automaker

Length of output: 49


🏁 Script executed:

#!/bin/bash
# Let's search for where executeQuery might be coming from - check if it's an alias or wrapper
rg "executeQuery" apps/server/src -l | xargs -I {} sh -c 'echo "=== {} ===" && head -30 {}'

Repository: AutoMaker-Org/automaker

Length of output: 16560


🏁 Script executed:

#!/bin/bash
# Check if the import path resolves at all in a compiled/built version
find . -path ./node_modules -prune -o -path ./dist -prune -o -path ./.next -prune -o -name "*execute-query*" -type f -print

Repository: AutoMaker-Org/automaker

Length of output: 49


🏁 Script executed:

#!/bin/bash
# Let's see what GatewayOptions includes to check if thinkingLevel is a valid parameter
rg -n "interface GatewayOptions|interface ExecuteOptions" apps/server/src/services/ai-gateway.ts -A 10

Repository: AutoMaker-Org/automaker

Length of output: 491


🏁 Script executed:

#!/bin/bash
# Check @automaker/types to see ExecuteOptions definition
rg -n "export.*interface ExecuteOptions|export.*type ExecuteOptions" --type ts -A 15

Repository: AutoMaker-Org/automaker

Length of output: 1347


🏁 Script executed:

#!/bin/bash
# Let's check the complete ExecuteOptions definition
rg -n "export interface ExecuteOptions" libs/types/src/provider.ts -A 40

Repository: AutoMaker-Org/automaker

Length of output: 2151


🏁 Script executed:

#!/bin/bash
# Now let's search for where executeQuery might actually be defined - check if it's in a function wrapper
grep -n "executeQuery" apps/server/src/routes/app-spec/generate-spec.ts | head -20

Repository: AutoMaker-Org/automaker

Length of output: 373


🏁 Script executed:

#!/bin/bash
# Let's look for the actual call to executeQuery in generate-spec.ts to understand usage
sed -n '150,250p' apps/server/src/routes/app-spec/generate-spec.ts | cat -n

Repository: AutoMaker-Org/automaker

Length of output: 4801


🏁 Script executed:

#!/bin/bash
# Search for any file containing the word executeQuery definition (not import)
rg "executeQuery\s*\(" apps/server/src -B 5 | grep -A 5 "function\|const.*=.*async"

Repository: AutoMaker-Org/automaker

Length of output: 49


🏁 Script executed:

#!/bin/bash
# Search comprehensively for executeQuery as a function definition or export across all source files
find apps/server/src libs -name "*.ts" -o -name "*.js" | xargs grep -l "executeQuery\|execute-query" | while read file; do echo "=== $file ==="; grep -n "executeQuery" "$file" | head -10; done

Repository: AutoMaker-Org/automaker

Length of output: 4236


🏁 Script executed:

#!/bin/bash
# Check if executeQuery might be defined as part of a module export in provider.ts
rg -n "executeQuery" libs/types/src/provider.ts -B 5 -A 5

Repository: AutoMaker-Org/automaker

Length of output: 49


🏁 Script executed:

#!/bin/bash
# Let's look at what the actual function signature is by checking usage context in generate-features-from-spec.ts
sed -n '179,210p' apps/server/src/routes/app-spec/generate-features-from-spec.ts | cat -n

Repository: AutoMaker-Org/automaker

Length of output: 1644


🏁 Script executed:

#!/bin/bash
# Check if there's a barrel export or wrapper
rg "export.*from.*execute" apps/server/src -R

Repository: AutoMaker-Org/automaker

Length of output: 471


🏁 Script executed:

#!/bin/bash
# Let's check if executeQuery might be generated during build - look for build config
find apps/server -name "tsconfig*" -o -name "esbuild*" -o -name "vite*" | head -5

Repository: AutoMaker-Org/automaker

Length of output: 152


🏁 Script executed:

#!/bin/bash
# Check what executeQuery actually is - maybe it's an alias for aiGateway.execute
rg -n "const executeQuery\|let executeQuery" apps/server/src -B 2 -A 5

Repository: AutoMaker-Org/automaker

Length of output: 49


🏁 Script executed:

#!/bin/bash
# Let's look at auto-mode-service where executeQuery is dynamically imported
sed -n '3050,3075p' apps/server/src/services/auto-mode-service.ts | cat -n

Repository: AutoMaker-Org/automaker

Length of output: 1418


🏁 Script executed:

#!/antml:bash
# Check if there's a lib/execute-query in libs folder instead
find libs -name "*execute-query*" -type f

Repository: AutoMaker-Org/automaker

Length of output: 49


🏁 Script executed:

#!/bin/bash
# Verify that thinkingLevel is being passed in auto-mode-service (which works correctly) vs generate-features-from-spec (which doesn't)
rg -n "aiGateway.execute\|executeQuery" apps/server/src/services/auto-mode-service.ts -A 12 | grep -E "thinkingLevel|aiGateway.execute|executeQuery\(" -A 10 | head -60

Repository: AutoMaker-Org/automaker

Length of output: 49


🏁 Script executed:

#!/bin/bash
# Let's check if execute-query might be a symlink or if there are similar files
find apps/server/src -name "*execute*" -type f

Repository: AutoMaker-Org/automaker

Length of output: 49


🏁 Script executed:

#!/bin/bash
# Check git status to see if the file is missing/staged
git status | grep -i "execute-query\|deleted\|modified" | head -20

Repository: AutoMaker-Org/automaker

Length of output: 49


🏁 Script executed:

#!/bin/bash
# Let's check if this file was recently deleted or if it's supposed to be created
git log --all --full-history --oneline -- "*/execute-query*" | head -5

Repository: AutoMaker-Org/automaker

Length of output: 49


🏁 Script executed:

#!/bin/bash
# Since executeQuery is used but file doesn't exist, let's check how it's supposed to be implemented
# by looking at what auto-mode-service does when using executeQuery vs aiGateway
sed -n '1405,1430p' apps/server/src/services/auto-mode-service.ts | cat -n

Repository: AutoMaker-Org/automaker

Length of output: 1120


🏁 Script executed:

#!/antml:bash
# Check if there's a wrapper function that should be execute-query - maybe it's in another location
rg -n "export.*function.*execute\|export.*const.*execute" apps/server/src/lib/ apps/server/src/services/ | grep -v "executeQuery\|executeText" | head -20

Repository: AutoMaker-Org/automaker

Length of output: 49


🏁 Script executed:

#!/bin/bash
# Let's verify that thinkingLevel is NOT passed in the Cursor path either
sed -n '133,155p' apps/server/src/routes/app-spec/generate-features-from-spec.ts | cat -n

Repository: AutoMaker-Org/automaker

Length of output: 1046


🏁 Script executed:

#!/antml:bash
# Check if aiGateway.execute indeed supports thinkingLevel by checking its signature
rg -n "async \*execute" apps/server/src/services/ai-gateway.ts -A 3

Repository: AutoMaker-Org/automaker

Length of output: 268


🏁 Script executed:

#!/bin/bash
# Final verification - exact line numbers and context for both paths
echo "=== Cursor path (aiGateway.execute) ===" 
sed -n '130,147p' apps/server/src/routes/app-spec/generate-features-from-spec.ts | cat -n
echo ""
echo "=== Non-Cursor path (executeQuery) ===" 
sed -n '175,205p' apps/server/src/routes/app-spec/generate-features-from-spec.ts | cat -n

Repository: AutoMaker-Org/automaker

Length of output: 2315


Add thinkingLevel parameter to both execution paths

thinkingLevel is extracted at line 112 via resolvePhaseModel() but never passed to either aiGateway.execute() or executeQuery(), preventing extended thinking configurations from taking effect. Both ExecuteOptions and GatewayOptions support this parameter (defined in libs/types/src/provider.ts:126). Auto-mode-service correctly demonstrates this usage.

Proposed changes

Add thinkingLevel to the Cursor execution path:

 for await (const msg of aiGateway.execute({
   prompt: cursorPrompt,
   model,
   cwd: projectPath,
   maxTurns: 250,
   allowedTools: ['Read', 'Glob', 'Grep'],
   abortController,
   readOnly: true,
+  thinkingLevel,
 })) {

Add thinkingLevel to the non-Cursor execution path:

 for await (const msg of executeQuery({
   prompt: structuredPrompt,
   model,
   cwd: projectPath,
   maxTurns: 250,
   allowedTools: ['Read', 'Glob', 'Grep'],
   abortController,
   readOnly: true,
+  thinkingLevel,
 })) {
🤖 Prompt for AI Agents
In @apps/server/src/routes/app-spec/generate-features-from-spec.ts around lines
8 - 15, resolvePhaseModel is returning a thinkingLevel that is extracted but
never forwarded; update both execution branches so thinkingLevel is passed into
the gateway and query calls. Specifically, when isCursorModel(...) branch
invokes aiGateway.execute, include thinkingLevel in the GatewayOptions passed to
aiGateway.execute; and when the non-cursor path calls executeQuery, include
thinkingLevel in the ExecuteOptions passed to executeQuery. Locate
resolvePhaseModel, isCursorModel, aiGateway.execute, and executeQuery in this
file and add the thinkingLevel field to the respective options objects so
extended thinking configurations are honored.

Comment on lines +34 to +36
} catch (error) {
res.status(500).json({ success: false, error: String(error) });
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Distinguish client errors from server errors.

The service's evolveStar method throws when the star doesn't exist or is inactive (per the service documentation). These are client errors that should return 4xx status codes, not 500. Currently, all exceptions return 500, which misrepresents the error type.

🔧 Suggested improvement for error handling
     } catch (error) {
-      res.status(500).json({ success: false, error: String(error) });
+      const message = String(error);
+      // Star not found or inactive are client errors
+      if (message.includes('not found') || message.includes('not active')) {
+        res.status(404).json({ success: false, error: message });
+      } else {
+        res.status(500).json({ success: false, error: message });
+      }
     }

Alternatively, consider having the service throw typed errors (e.g., NotFoundError) that can be caught and mapped to appropriate HTTP status codes more reliably.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
} catch (error) {
res.status(500).json({ success: false, error: String(error) });
}
} catch (error) {
const message = String(error);
// Star not found or inactive are client errors
if (message.includes('not found') || message.includes('not active')) {
res.status(404).json({ success: false, error: message });
} else {
res.status(500).json({ success: false, error: message });
}
}
🤖 Prompt for AI Agents
In @apps/server/src/routes/red-giant/routes/evolve-star.ts around lines 34 - 36,
The catch-all 500 response in the evolveStar route masks client errors from the
service; update the error handling in the evolveStar route handler to
distinguish client vs server errors by inspecting the thrown error (e.g., check
error instanceof NotFoundError / BadRequestError or error.name) and map those to
appropriate 4xx responses (404/400) while preserving a 500 for unexpected
errors; if the service currently throws plain Errors, either add / use typed
errors (e.g., NotFoundError) from the evolveStar service method or check for
specific error messages to decide the 4xx status, and return
res.status(appropriateCode).json({ success: false, error: String(error) })
accordingly.

Comment on lines +64 to +69
constructor(dataDir?: string) {
this.dataDir =
dataDir || process.env.APPDATA
? path.join(process.env.APPDATA!, 'Automaker')
: path.join(process.env.HOME || '', '.automaker');
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: constructor has operator-precedence bug (ignores dataDir on most platforms)
dataDir || process.env.APPDATA ? ... : ... evaluates as (dataDir || process.env.APPDATA) ? ... : ..., so any truthy dataDir will still select the APPDATA path branch.

Proposed fix
   constructor(dataDir?: string) {
-    this.dataDir =
-      dataDir || process.env.APPDATA
-        ? path.join(process.env.APPDATA!, 'Automaker')
-        : path.join(process.env.HOME || '', '.automaker');
+    this.dataDir =
+      dataDir ??
+      (process.env.APPDATA
+        ? path.join(process.env.APPDATA, 'Automaker')
+        : path.join(process.env.HOME || '', '.automaker'));
   }
🤖 Prompt for AI Agents
In @apps/server/src/services/ai-gateway.ts around lines 64 - 69, The
constructor's assignment to this.dataDir uses a ternary with incorrect operator
precedence so a truthy APPDATA can be chosen even when dataDir is provided;
update the constructor in the class (the constructor method that sets
this.dataDir) to first check dataDir explicitly and return it if present,
otherwise perform the APPDATA vs HOME ternary (or rewrite as a clear if/else) so
dataDir is honored; ensure you reference the same property this.dataDir and use
process.env.APPDATA and process.env.HOME in the fallback logic.

Comment on lines +171 to +225
async *execute(options: GatewayOptions): AsyncGenerator<ProviderMessage> {
if (!this.initialized) {
throw new Error('[AIGateway] Not initialized. Call init() first.');
}

const { model: modelInput = 'default', cwd = process.cwd(), ...restOptions } = options;

// Resolve model string (handles 'default', 'opus', 'sonnet', etc.)
const resolvedModel = resolveModelString(modelInput);

// Get provider name for this model
const providerName = ProviderFactory.getProviderForModelName(resolvedModel);

// Audit: Start timing
const startTime = Date.now();
const callId = `${Date.now()}-${Math.random().toString(36).substring(7)}`;

logger.info(`[AIGateway] [${callId}] START: ${resolvedModel} via ${providerName}`);

// Get provider instance (stateless lookup)
const provider = ProviderFactory.getProviderByName(providerName);

if (!provider) {
logger.error(`[AIGateway] [${callId}] FAIL: Provider '${providerName}' not found`);
throw new Error(
`[AIGateway] Provider '${providerName}' not found. Available: ${ProviderFactory.getRegisteredProviderNames().join(', ')}`
);
}

// Build execute options
const executeOptions: ExecuteOptions = {
...restOptions,
model: resolvedModel,
cwd,
};

// Execute and yield with audit tracking
let messageCount = 0;
let hasError = false;

try {
for await (const msg of provider.executeQuery(executeOptions)) {
messageCount++;
if (msg.type === 'error') {
hasError = true;
}
yield msg;
}
} finally {
// Audit: Log completion
const elapsed = Date.now() - startTime;
const status = hasError ? 'ERROR' : 'OK';
logger.info(`[AIGateway] [${callId}] END: ${status} in ${elapsed}ms (${messageCount} msgs)`);
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Audit status can log OK even if execute() throws
hasError only flips on streamed msg.type === 'error', not on exceptions thrown by provider.executeQuery(...). That can produce misleading END: OK ... logs.

Proposed fix
-    try {
+    try {
       for await (const msg of provider.executeQuery(executeOptions)) {
         messageCount++;
         if (msg.type === 'error') {
           hasError = true;
         }
         yield msg;
       }
+    } catch (e) {
+      hasError = true;
+      throw e;
     } finally {
       const elapsed = Date.now() - startTime;
       const status = hasError ? 'ERROR' : 'OK';
       logger.info(`[AIGateway] [${callId}] END: ${status} in ${elapsed}ms (${messageCount} msgs)`);
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async *execute(options: GatewayOptions): AsyncGenerator<ProviderMessage> {
if (!this.initialized) {
throw new Error('[AIGateway] Not initialized. Call init() first.');
}
const { model: modelInput = 'default', cwd = process.cwd(), ...restOptions } = options;
// Resolve model string (handles 'default', 'opus', 'sonnet', etc.)
const resolvedModel = resolveModelString(modelInput);
// Get provider name for this model
const providerName = ProviderFactory.getProviderForModelName(resolvedModel);
// Audit: Start timing
const startTime = Date.now();
const callId = `${Date.now()}-${Math.random().toString(36).substring(7)}`;
logger.info(`[AIGateway] [${callId}] START: ${resolvedModel} via ${providerName}`);
// Get provider instance (stateless lookup)
const provider = ProviderFactory.getProviderByName(providerName);
if (!provider) {
logger.error(`[AIGateway] [${callId}] FAIL: Provider '${providerName}' not found`);
throw new Error(
`[AIGateway] Provider '${providerName}' not found. Available: ${ProviderFactory.getRegisteredProviderNames().join(', ')}`
);
}
// Build execute options
const executeOptions: ExecuteOptions = {
...restOptions,
model: resolvedModel,
cwd,
};
// Execute and yield with audit tracking
let messageCount = 0;
let hasError = false;
try {
for await (const msg of provider.executeQuery(executeOptions)) {
messageCount++;
if (msg.type === 'error') {
hasError = true;
}
yield msg;
}
} finally {
// Audit: Log completion
const elapsed = Date.now() - startTime;
const status = hasError ? 'ERROR' : 'OK';
logger.info(`[AIGateway] [${callId}] END: ${status} in ${elapsed}ms (${messageCount} msgs)`);
}
}
async *execute(options: GatewayOptions): AsyncGenerator<ProviderMessage> {
if (!this.initialized) {
throw new Error('[AIGateway] Not initialized. Call init() first.');
}
const { model: modelInput = 'default', cwd = process.cwd(), ...restOptions } = options;
// Resolve model string (handles 'default', 'opus', 'sonnet', etc.)
const resolvedModel = resolveModelString(modelInput);
// Get provider name for this model
const providerName = ProviderFactory.getProviderForModelName(resolvedModel);
// Audit: Start timing
const startTime = Date.now();
const callId = `${Date.now()}-${Math.random().toString(36).substring(7)}`;
logger.info(`[AIGateway] [${callId}] START: ${resolvedModel} via ${providerName}`);
// Get provider instance (stateless lookup)
const provider = ProviderFactory.getProviderByName(providerName);
if (!provider) {
logger.error(`[AIGateway] [${callId}] FAIL: Provider '${providerName}' not found`);
throw new Error(
`[AIGateway] Provider '${providerName}' not found. Available: ${ProviderFactory.getRegisteredProviderNames().join(', ')}`
);
}
// Build execute options
const executeOptions: ExecuteOptions = {
...restOptions,
model: resolvedModel,
cwd,
};
// Execute and yield with audit tracking
let messageCount = 0;
let hasError = false;
try {
for await (const msg of provider.executeQuery(executeOptions)) {
messageCount++;
if (msg.type === 'error') {
hasError = true;
}
yield msg;
}
} catch (e) {
hasError = true;
throw e;
} finally {
// Audit: Log completion
const elapsed = Date.now() - startTime;
const status = hasError ? 'ERROR' : 'OK';
logger.info(`[AIGateway] [${callId}] END: ${status} in ${elapsed}ms (${messageCount} msgs)`);
}
}

Comment on lines +1145 to +1154
// Z.AI profiles
{
id: 'profile-zai-reasoning',
name: 'Z.AI Reasoning',
description: 'Z.AI GLM-4.7 for reasoning and coding.',
model: 'GLM-4.7',
provider: 'zai',
isBuiltIn: true,
icon: 'Zai',
},
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check how other Z.AI model references are defined
rg -n "GLM-4|glm-4" --type=ts -C2

# Check if there's a Z.AI model list or resolver
rg -n "zai.*model|ZAI.*MODEL" --type=ts -C2

Repository: AutoMaker-Org/automaker

Length of output: 42885


🏁 Script executed:

#!/bin/bash
# Find and examine the AIProfile type definition
rg -n "interface AIProfile|type AIProfile" --type=ts -A10

# Check how model field is used in other profiles
rg -n "model:\s*['\"]" apps/ui/src/store/app-store.ts | head -20

Repository: AutoMaker-Org/automaker

Length of output: 868


🏁 Script executed:

#!/bin/bash
# Examine the model-resolver to understand resolveModelString
fd "model-resolver" --type=f

Repository: AutoMaker-Org/automaker

Length of output: 116


🏁 Script executed:

#!/bin/bash
# Check if resolveModelString is imported or used in profile-related files
rg -n "resolveModelString" --type=ts

# Check profile handling and model resolution
rg -n "profile.*model|getModel.*profile" apps/ui/src/store/app-store.ts -A2 -B2

Repository: AutoMaker-Org/automaker

Length of output: 7874


🏁 Script executed:

#!/bin/bash
# Check where profiles are used and how model field is accessed
rg -n "\.model" apps/ui/src/store/app-store.ts | head -20

# Check how Claude profiles vs Z.AI profiles differ in their model field
rg -n "profile-.*model.*:" apps/ui/src/store/app-store.ts -B2 -A2

Repository: AutoMaker-Org/automaker

Length of output: 90


🏁 Script executed:

#!/bin/bash
# Check the model resolver implementation for profile handling
cat libs/model-resolver/src/resolver.ts | head -100

Repository: AutoMaker-Org/automaker

Length of output: 3513


Change Z.AI profile model to lowercase alias matching the type definition.

The profile uses 'GLM-4.7' but should use 'glm-4.7' (lowercase) to be consistent with:

  • The ZAI_MODEL_MAP keys ('glm-4.7': 'glm-4.7')
  • Other built-in profile aliases ('opus', 'sonnet', 'haiku')
  • The ModelAlias type definition which references these lowercase keys
  • The resolveModelString() resolver which checks if (modelKey in ZAI_MODEL_MAP) for lowercase identifiers
Diff
  {
    id: 'profile-zai-reasoning',
    name: 'Z.AI Reasoning',
    description: 'Z.AI GLM-4.7 for reasoning and coding.',
-   model: 'GLM-4.7',
+   model: 'glm-4.7',
    provider: 'zai',
    isBuiltIn: true,
    icon: 'Zai',
  },
🤖 Prompt for AI Agents
In @apps/ui/src/store/app-store.ts around lines 1145 - 1154, The built-in Z.AI
profile object with id 'profile-zai-reasoning' uses an uppercase model string
'GLM-4.7'; change the model field to the lowercase alias 'glm-4.7' so it matches
the keys in ZAI_MODEL_MAP, the ModelAlias type, and the behavior of
resolveModelString (which checks modelKey in ZAI_MODEL_MAP). Ensure the 'model'
property value in that profile is exactly 'glm-4.7'.

autoModeByProject: {},
autoModeActivityLog: [],
maxConcurrency: 3, // Default to 3 concurrent agents
maxConcurrency: 24, // Performance Workstation: Match 24-Core CPU
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Default maxConcurrency of 24 may be too aggressive.

The default was increased from 3 to 24 with a comment referencing "24-Core CPU". This default will apply to all users regardless of their hardware. A value of 24 concurrent agent tasks could overwhelm machines with fewer cores or limited memory, potentially causing:

  • High CPU/memory usage
  • API rate limiting issues
  • Degraded user experience on typical hardware

Consider keeping a more conservative default (e.g., 3-6) that works well across different hardware configurations, or dynamically adjusting based on detected system resources.

🤖 Prompt for AI Agents
In @apps/ui/src/store/app-store.ts at line 1181, The default maxConcurrency set
to 24 is too aggressive for typical users; change the default value of the
maxConcurrency setting (the maxConcurrency property in the app store) to a
conservative number such as 3–6 (recommend 4) so it won't overwhelm machines,
and optionally implement dynamic adjustment by detecting available CPU
cores/memory (e.g., using navigator.hardwareConcurrency or a platform-specific
check) to scale up only when appropriate; update any related docs/comments to
explain the conservative default and the dynamic fallback logic.

Comment on lines +1193 to +1194
enhancementModel: 'GLM-4.7', // Default to GLM-4.7 for feature enhancement
validationModel: 'GLM-4.7', // Default to GLM-4.7 for GitHub issue validation
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Breaking change: Default models changed from Claude to Z.AI.

Changing enhancementModel from 'sonnet' to 'GLM-4.7' and validationModel from 'opus' to 'GLM-4.7' will break functionality for users who don't have Z.AI credentials configured. These are default values that apply to all users.

The previous defaults ('sonnet', 'opus') are model aliases that work with the existing Claude integration. Switching to Z.AI-specific models as defaults could cause:

  • Feature enhancement failures for users without Z.AI API keys
  • GitHub issue validation failures
  • Confusion for existing users upgrading to this version

Consider keeping Claude models as defaults, or implementing fallback logic.

🔧 Suggested fix: Restore Claude defaults
-  enhancementModel: 'GLM-4.7', // Default to GLM-4.7 for feature enhancement
-  validationModel: 'GLM-4.7', // Default to GLM-4.7 for GitHub issue validation
+  enhancementModel: 'sonnet', // Default to Sonnet for feature enhancement
+  validationModel: 'opus', // Default to Opus for GitHub issue validation
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
enhancementModel: 'GLM-4.7', // Default to GLM-4.7 for feature enhancement
validationModel: 'GLM-4.7', // Default to GLM-4.7 for GitHub issue validation
enhancementModel: 'sonnet', // Default to Sonnet for feature enhancement
validationModel: 'opus', // Default to Opus for GitHub issue validation
🤖 Prompt for AI Agents
In @apps/ui/src/store/app-store.ts around lines 1193 - 1194, Restore the
previous Claude-safe defaults by changing the store defaults: set
enhancementModel back to 'sonnet' and validationModel back to 'opus' (replace
the GLM-4.7 assignments for enhancementModel and validationModel), or
alternatively implement fallback logic in the code that reads these values so if
Z.AI credentials are not present it falls back to 'sonnet' and 'opus' when using
the enhancementModel or validationModel config keys.

Comment on lines +1141 to +1144
.graph-zoomed-out .react-flow__edge.graph-edge-highlighted {
opacity: 1 !important;
stroke-width: 2px !important;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

stroke-width won't apply to the edge container element.

The stroke-width property only affects SVG path elements, not their container. Compare with the existing .graph-edge-highlighted path rule on lines 1061-1065 which correctly targets the path.

🔧 Proposed fix
-.graph-zoomed-out .react-flow__edge.graph-edge-highlighted {
-  opacity: 1 !important;
-  stroke-width: 2px !important;
-}
+.graph-zoomed-out .react-flow__edge.graph-edge-highlighted {
+  opacity: 1 !important;
+}
+
+.graph-zoomed-out .react-flow__edge.graph-edge-highlighted path {
+  stroke-width: 2px !important;
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
.graph-zoomed-out .react-flow__edge.graph-edge-highlighted {
opacity: 1 !important;
stroke-width: 2px !important;
}
.graph-zoomed-out .react-flow__edge.graph-edge-highlighted {
opacity: 1 !important;
}
.graph-zoomed-out .react-flow__edge.graph-edge-highlighted path {
stroke-width: 2px !important;
}
🤖 Prompt for AI Agents
In @apps/ui/src/styles/global.css around lines 1141 - 1144, The CSS rule targets
the edge container (.react-flow__edge.graph-edge-highlighted) but stroke-width
only applies to SVG path elements; update the selector to target the path node
instead, e.g., change or add a rule for .graph-zoomed-out
.react-flow__edge.graph-edge-highlighted path and set stroke-width: 2px
!important there (keep opacity on the container if desired) so the stroke width
actually takes effect.

Comment on lines +76 to +81
const response = await this.client.chat.completions.create({
model: this.resolveModel(options?.model),
messages,
max_tokens: options?.maxTokens || 8192,
temperature: options?.temperature,
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Missing support for abortController and timeout from QueryOptions.

The QueryOptions interface includes abortController and timeout parameters, but neither is passed to the OpenAI SDK. This prevents request cancellation and could cause requests to hang indefinitely.

🔧 Proposed fix
       const response = await this.client.chat.completions.create({
         model: this.resolveModel(options?.model),
         messages,
         max_tokens: options?.maxTokens || 8192,
         temperature: options?.temperature,
-      });
+      }, {
+        signal: options?.abortController?.signal,
+        timeout: options?.timeout,
+      });

The same pattern should be applied to streamQuery (lines 110-116) and query (lines 167-172) methods.

🤖 Prompt for AI Agents
In @libs/provider-zai/src/zai-query-service.ts around lines 76 - 81, The create
call to this.client.chat.completions.create is missing support for
QueryOptions.abortController and QueryOptions.timeout, so update the options
passed in (in the methods this.client.chat.completions.create, streamQuery, and
query) to include the abortController.signal (e.g., abortController?.signal) and
the timeout value from QueryOptions (e.g., timeout) alongside model
(resolveModel), messages, max_tokens and temperature; ensure you thread the same
abort/timeout handling into streamQuery and query so requests can be cancelled
and will honor timeouts.

@GhostDragonAlpha GhostDragonAlpha marked this pull request as draft January 14, 2026 11:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants