-
Notifications
You must be signed in to change notification settings - Fork 392
feat: Z.AI provider integration, World Model view, enhanced model support #441
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
…port - Add Z.AI provider with full API integration - Implement World Model native graph view (React Flow) - Add ZhiPu AI GLM models support - Enhanced provider factory with Z.AI routing - UI improvements: provider icons, settings tabs - Server: parallel executor, cluster manager support - Fix Agent Runner API signature (thinkingLevel param)
📝 WalkthroughWalkthroughAdds 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
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 }
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
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related PRs
Suggested labels
Suggested reviewers
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
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. Comment |
Summary of ChangesHello @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
🧠 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 AssistThe 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
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 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
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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;
}| 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(); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The 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.',
});
}
}| // 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; | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| // FORCE ROUTER CONFIGURATION | ||
| const forcedEnv = buildEnv(); | ||
| forcedEnv['ANTHROPIC_BASE_URL'] = 'http://127.0.0.1:3457'; | ||
| forcedEnv['ANTHROPIC_API_KEY'] = 'sk-zai-router'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
createCleanupHandlercall here doesn't pass{ webPort, serverPort }, unlike line 95. While the defaults (3007, 3008) matchresolvePortConfiguration'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: Missingzaiin credentials sync function.The
syncCredentialsToServerfunction lackszaiin its apiKeys type signature, whilehydrateStoreFromSettingshandles 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-nocheckor 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
workModelogic andPartial<Feature>typing introduced in this PR. If there are specific type issues, address them individually rather than suppressing all checking.
292-296: ExcludeworkModefrom persisted updates.The spread
...updatesincludes theworkModeproperty, which will be passed toupdateFeatureandpersistFeatureUpdate. SinceworkModeis only used to derivebranchNameand 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. SincehandleRunFeaturederives the workDir fromfeature.branchNameat 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: MissingsetZaiAuthStatusinuseCallbackdependency array.The
checkAllProviderscallback usessetZaiAuthStatus(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: MissingzaiinupdateCredentialsinput type.The
getCredentialsresponse includeszai, but theupdateCredentialsinput type (lines 1920-1921) only acceptsanthropic,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:onSaveis called twice for Codex provider.When the provider is
'codex',onSaveis invoked twice in sequence - first withcodexModel(lines 180-183), then immediately again withopencodeModel(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: UsecreateEventEmitter()fromlib/events.tsinstead of Node's EventEmitter.Per coding guidelines, server operations should use
createEventEmitter()fromlib/events.tsto emit events that stream to the frontend via WebSocket. Using Node'sEventEmitterdirectly 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 actionapps/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: UseExpandOptionstype instead ofany.The
optionsparameter is typed asany, but theExpandOptionstype is already defined and exported fromsmart-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 inuseEffect.The
useEffectincludeszaiAuthStatus?.authenticatedin its dependency array and callssetZaiAuthStatusinside. WhensetZaiAuthStatusis called, it updateszaiAuthStatus, 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?.authenticatedis redundant since the effect's purpose is to set the auth status whenapiKeys.zaiexists. 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.claudeset to a Z.AI model.Setting
DEFAULT_MODELS.claudeto'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:
- Renaming this field to reflect the actual default provider/model
- Keeping
claudedefault as a Claude model and adding a separatezaidefault♻️ 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.idas the key, but when a worker dies and a new one is forked, the new worker receives a fresh ID. This meansworkerRestarts.get(workerId)will always return0orundefinedfor 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 theidfield 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 ModelProvidercasts on lines 111, 118, 125, 132, 139, 146, 153—'zai'is already in theModelProviderunion 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
zaiModelis set to'glm-4.5'(lowercase prefix), butZAI_MODELSfrommodel-constants.tsuses 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) andhandleProviderChange(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 theZAI_MODELSIDs which use PascalCase (e.g.,'GLM-4.5-Flash'). Similarly,isZaiSelectedchecks for'glm-'prefix but model IDs start with'GLM-'.This will cause:
- Default model selection to not highlight correctly
- 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_DEFin this file duplicates definitions fromlibs/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.tsexportsZAI_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_KEYglobally 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:
- Using a provider registry with explicit priority ordering
- Setting a
PREFERRED_PROVIDERenvironment variable- 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 thescan-fileshandler to prevent directory traversal attacks.The handler accepts a
directoryparameter without validating that the path stays within allowed bounds. While the codebase providesvalidatePath()andisPathAllowed()utilities from@automaker/platform, thescan-fileshandler doesn't use them. If this handler is called with untrusted input, an attacker could scan sensitive directories outside the project.Additionally, the default
maxDepthof 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.settingsServiceaccessed without null check.On line 567,
this.settingsService.getGlobalSettings()is called, butsettingsServiceis typed asSettingsService | null(set in constructor line 224). This will throw ifsettingsServiceis 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
patternparameter from user input is passed directly tonew RegExp(). Malicious patterns like(a+)+$can cause catastrophic backtracking, freezing the server.Consider:
- Adding a timeout wrapper around the regex matching
- Validating the regex complexity using a library like
safe-regexorrecheck- 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 everyexecuteQuerycall.When the API key contains
., a new JWT is generated and a newOpenAIclient 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
beforeExitevent only fires when the event loop empties naturally, not when the process receivesSIGTERMorSIGINTsignals. 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
.inglm-4.7is a regex metacharacter that matches any single character. While this likely works in practice, it could cause false positives (e.g., matchingglm-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 unusedtemp_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. Thetemp_zai_verify_live/directory is dynamically created and managed byverify_zai_provider.ts, so it does not need to be committed.Remove
temp_zai_verify_mock/from the PR, and add a pattern to.gitignoreto 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: AddonExpandto theTaskNodeDatainterface to replace the@ts-ignore.The
@ts-ignoreon line 308 suppresses a type error foronExpand, which is called but not defined in theTaskNodeDatainterface. AddonExpand?: () => void;toapps/ui/src/components/views/graph-view/hooks/use-graph-nodes.tsto 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 theTaskNodeDatainterface: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 beitems-centeroritems-startfor 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: UseresolveModelString()to convert the 'default' alias to a full model name.The
model: 'default'value should be resolved usingresolveModelString()from@automaker/model-resolverbefore 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.gitignoreand removing this file from the PR.apps/ui/src/config/api-providers.ts-73-78 (1)
73-78: MissingThe
ProviderConfigParamsinterface requiresbuildProviderConfigsdoesn'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
♻️ 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 withfill="currentColor".The Z.AI icon path
'M4 4h16l-16 16h16'defines strokes/lines, not a filled shape. TheProviderIconcomponent renders paths withfill="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 importsisCursorModelandstripProviderPrefixfrom line 15.The imports
isCursorModelandstripProviderPrefixfrom@automaker/typesare unused in this file and should be removed. Note thatDEFAULT_PHASE_MODELSis 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
// ... importswhich 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, andzaiKeyto the backend viaapi.setup.storeApiKey, butgoogleKeyis 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.errorfor 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(seelibs/provider-claude/src/register.ts).Suggested fix
- priority: 5, // Lower than Claude (10) by default + priority: 5, // Lower than Claude (100) by defaultapps/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.IrYFbzCIvcUJ0zoxwhich 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: Updateopenaito a more recent version.The package specifies
openai: ^4.77.0, but the latest version is6.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 issettings.phaseModels?.enhancementModel. This mismatch means the Claude success check will likely always fail sinceenhancementModeldoesn'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 usesisZaiSelected. This could cause inconsistent behavior ifgetModelProvider()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. IfgetName()in the subclass relies on any subclass-specific initialization, it may not behave correctly.Consider initializing
this.namelazily or removing it entirely sincegetName()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.readFileSyncwill 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: PassabortControllertosimpleQueryto enable cancellation of long-running queries.The function receives an
abortControllerparameter but doesn't pass it in theQueryOptionsto thesimpleQuerycall. SinceQueryOptionssupports theabortControllerfield, 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
promptis 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 withoutawaitin 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 returnstrue— the try-catch is ineffective.The function contains no code that can throw, so the
catchblock 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:resultoverwrites accumulated streamed content.When a
resultmessage withsubtype === 'success'arrives, the code replaces all previously accumulated text blocks withmsg.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 toinitializeThreadPoolcould create multiple pools.If
initializeThreadPoolis called concurrently before the first call completes, theif (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:settingsServiceis used before being validated.On line 3248,
this.settingsService?.getGlobalSettings()is called, but then lines 3250-3252 throw an error ifsettingsServiceis 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();
| 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; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| // 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, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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:
- Breaks Claude API calls when the local router isn't running
- Ignores the user's actual
ANTHROPIC_API_KEY - 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.
| // 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).
| 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; | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check if ZaiTools has timeout handling
rg -n "timeout|Timeout|TIMEOUT" apps/server/src/providers/zai-tools.tsRepository: 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.tsRepository: 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.tsRepository: AutoMaker-Org/automaker
Length of output: 49
🏁 Script executed:
# Check for resolveModelString usage
rg -n "resolveModelString" apps/server/src/providers/zai-provider.tsRepository: AutoMaker-Org/automaker
Length of output: 49
🏁 Script executed:
# Find lib/events.ts
find . -name "events.ts" -type f | head -20Repository: AutoMaker-Org/automaker
Length of output: 98
🏁 Script executed:
# Check if zai-tools exists with different extensions or locations
find . -name "*zai-tool*" -type fRepository: 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.tsRepository: 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.tsRepository: 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 -20Repository: AutoMaker-Org/automaker
Length of output: 49
🏁 Script executed:
# Check lib/events.ts to see createEventEmitter API
cat apps/server/src/lib/events.tsRepository: AutoMaker-Org/automaker
Length of output: 1041
🏁 Script executed:
# Check zai-tools.ts for timeout handling
cat apps/server/src/providers/zai-tools.tsRepository: 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.tsRepository: 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.tsRepository: 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:
-
Missing createEventEmitter(): Line 238+ uses a generator pattern with
yield, but the coding guideline requires usingcreateEventEmitter()fromapps/server/src/lib/events.tsfor all server operations to emit events that stream to frontend via WebSocket. -
Missing resolveModelString(): Lines 242-243 perform local model lookup via
ZAI_MODELS_DEF.find(), but the coding guideline requires usingresolveModelString()from@automaker/model-resolverto 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.
| import glob from 'glob'; | ||
| const globAsync = promisify(glob); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
# Check for glob package version in package.json files
find . -name "package.json" -type f -exec grep -l '"glob"' {} \; | head -10Repository: 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 -20Repository: 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.jsonRepository: AutoMaker-Org/automaker
Length of output: 99
🏁 Script executed:
# Read the full apps/server/package.json to see all dependencies
cat ./apps/server/package.jsonRepository: 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.jsonRepository: 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 -30Repository: AutoMaker-Org/automaker
Length of output: 766
🏁 Script executed:
# Check root package.json for glob dependency
cat ./package.jsonRepository: 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 -20Repository: 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 -20Repository: 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}")
EOFRepository: 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 -40Repository: 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 -40Repository: 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' }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
# Find the ModelAlias type definition and related model types
rg "type ModelAlias|ModelAlias\s*=" --type=ts -A 15Repository: 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 10Repository: 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 2Repository: AutoMaker-Org/automaker
Length of output: 7896
🏁 Script executed:
# Check the PhaseModelEntry type definition
rg "PhaseModelEntry|interface PhaseModelEntry|type PhaseModelEntry" --type=ts -A 5Repository: 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.tsxRepository: 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 -60Repository: 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 -40Repository: 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.
| onSelect={() => { | ||
| onChange({ | ||
| model: model.id as ModelAlias, | ||
| thinkingLevel: 'none', | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 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 -50Repository: 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 3Repository: 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 -60Repository: 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 15Repository: 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 -40Repository: 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 -50Repository: 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 -20Repository: AutoMaker-Org/automaker
Length of output: 473
🏁 Script executed:
#!/bin/bash
# Find PhaseModelEntry type definition
rg -n "type PhaseModelEntry|interface PhaseModelEntry" --type=ts -A 10Repository: 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 -20Repository: 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 10Repository: 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.tsxRepository: 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 -100Repository: 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 2Repository: 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}")
EOFRepository: 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.tsxRepository: 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 -20Repository: 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.tsRepository: 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 -10Repository: 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 1Repository: 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.
| // 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) --- |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| // 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 --- |
| const payload = { | ||
| api_key: id, | ||
| exp: Date.now() + 3600 * 1000, | ||
| timestamp: Date.now(), | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
getUnderlyingModelIcondetects 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_MODELSandSwitchare 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, andsetZaiDefaultModelare 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
loggerinstance 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
loggerutility (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.errorcalls at lines 142, 400, and 457 as well.
364-364: Type assertion workarounds indicate missing type definition.Multiple
as anycasts and@ts-ignore(lines 364, 440-441, 447) suggestplanSpecisn't properly typed in theFeatureinterface. 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
📒 Files selected for processing (3)
apps/ui/src/components/ui/provider-icon.tsxapps/ui/src/components/views/settings-view/providers/zai-settings-tab.tsxapps/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.tsxapps/ui/src/components/ui/provider-icon.tsxapps/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.tsxapps/ui/src/components/ui/provider-icon.tsxapps/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.tsxapps/ui/src/components/ui/provider-icon.tsxapps/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
handleSaveApiKeyfunction correctly validates input, handles the async flow with proper loading state management, and uses afinallyblock to ensureisSavingis reset regardless of outcome.
71-103: LGTM on the UI implementation.The card layout, input handling, and accessibility (label
htmlFor+ inputid) 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_WORKTREESconstant 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
spawnParentFeatureandsmartExpandFeatureon dialog close).
| 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' | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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',
},| const handleRunSmartExpand = useCallback( | ||
| async (seedFeature: Feature, options: any) => { | ||
| try { | ||
| const response = await fetch('http://localhost:3000/api/auto-mode/expand-feature', { | ||
| method: 'POST', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 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 -40Repository: 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 -5Repository: 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.tsxRepository: 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 -100Repository: 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 -30Repository: 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.tsxRepository: 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 -20Repository: 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 -10Repository: 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 -20Repository: 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 -30Repository: 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 -50Repository: 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 -40Repository: 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 -30Repository: 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 -10Repository: 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 -20Repository: 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 -40Repository: 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 -10Repository: 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 -5Repository: 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 2Repository: 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.tsRepository: 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 -20Repository: 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 -10Repository: 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 -100Repository: 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 -50Repository: 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 -10Repository: 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 -20Repository: 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/nullRepository: 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 -10Repository: 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.tsRepository: 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 -5Repository: 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 -5Repository: AutoMaker-Org/automaker
Length of output: 101
🏁 Script executed:
# Check @automaker/model-resolver package
find apps -path "*model-resolver*" -name "*.ts" | head -10Repository: 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 -50Repository: 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 -10Repository: 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 -20Repository: 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 -10Repository: 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 2Repository: 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 -10Repository: 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 -15Repository: 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 -50Repository: 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 -10Repository: 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 2Repository: 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 -60Repository: 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 -20Repository: AutoMaker-Org/automaker
Length of output: 49
Hardcoded localhost URL with wrong port and untyped options parameter.
Two issues in this handler:
- Line 263:
options: anybypasses type safety. Import and useExpandOptionsfrom./board-view/dialogs/smart-expand-dialog. - Line 265: Hardcoded
http://localhost:3000uses the wrong port (should be3008) and will fail when deployed. Use the existinggetServerUrlSync()function from@/lib/http-api-clientinstead, which respects theVITE_SERVER_URLenvironment 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'.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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: Redundantgh --versionexecution.When the secondary check succeeds (line 56-60),
status.versionis already populated. However, lines 82-89 will rungh --versionagain 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
📒 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()fromlib/events.tsfor 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 }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
📒 Files selected for processing (2)
apps/ui/package.jsonpackage.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-corebefore the individual provider implementations (provider-claude,provider-zai), ensuring dependencies are available.apps/ui/package.json (1)
19-19: LGTM!Adding
--openis a minor developer experience improvement that auto-launches the browser when starting the dev server.
| "dev:web": "npm run dev:web --workspace=apps/ui", | ||
| "_dev:web": "npm run dev:web --workspace=apps/ui", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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:
- Line 17 (
_dev:web) already does the same thing - The intended public
dev:webat 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.
| "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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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_KEYis 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_KEYto 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 -->
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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), butzaiModelis typed asstring. Based on the AI summary,ZAI_MODEL_MAPexists inlibs/types/src/model.ts. Consider defining aZaiModelIdtype for consistency and type safety.
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 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
ModelProvidertype 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
trueunconditionally for thezaiprovider. 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.
| if (profile.provider === 'zai') { | ||
| return profile.zaiModel || 'glm-4.5'; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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.
| 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' }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
# Find ModelAlias type definition
rg "type\s+ModelAlias\s*=" --type=ts -A 10Repository: AutoMaker-Org/automaker
Length of output: 715
🏁 Script executed:
# Also search for interface ModelAlias
rg "interface\s+ModelAlias" --type=ts -A 10Repository: 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 2Repository: 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.tsRepository: 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 -100Repository: AutoMaker-Org/automaker
Length of output: 6428
🏁 Script executed:
# Check the model-resolver implementation
cat libs/model-resolver/src/index.tsRepository: 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 3Repository: 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 -20Repository: AutoMaker-Org/automaker
Length of output: 789
🏁 Script executed:
# Get complete CLAUDE_MODEL_MAP definition
rg "export const CLAUDE_MODEL_MAP" --type=ts -A 30Repository: AutoMaker-Org/automaker
Length of output: 1987
🏁 Script executed:
# Get ZAI_MODEL_MAP definition
rg "export const ZAI_MODEL_MAP" --type=ts -A 20Repository: 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 3Repository: AutoMaker-Org/automaker
Length of output: 586
🏁 Script executed:
# Check PhaseModelEntry type definition
rg "type PhaseModelEntry" --type=ts -B 2 -A 10Repository: AutoMaker-Org/automaker
Length of output: 3316
🏁 Script executed:
# Get full PhaseModelEntry definition
rg "type PhaseModelEntry\s*=" --type=ts -B 2 -A 15Repository: AutoMaker-Org/automaker
Length of output: 49
🏁 Script executed:
# Check PhaseModelConfig type
rg "type PhaseModelConfig" --type=ts -B 2 -A 30Repository: AutoMaker-Org/automaker
Length of output: 49
🏁 Script executed:
# Verify the full resolveModelString function signature
rg "export.*function resolveModelString" --type=ts -B 2 -A 20Repository: 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 -5Repository: 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 10Repository: 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 -50Repository: 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 -lRepository: 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.
| enhancementModel: 'GLM-4.7', | ||
| validationModel: 'GLM-4.7', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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.
… 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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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, andisDimmed. 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?.onDeleteDependencywill always beundefinedsinceonDeleteDependencywas removed fromDependencyEdgeData(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: AddzaitosyncCredentialsToServerparameter type and update backend API definitions.The function accepts
anthropic,openaibut omitszai, creating a type mismatch with theApiKeysinterface in the app store (which includeszai). Currently, Z.AI credentials are added to the localapiKeysobject (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.tsandelectron.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
promptContentwith base64 data (lines 318-328), but neither branch uses it:
- Cursor branch (line 367): passes
cursorPrompt(text with file path) toaiGateway.execute()- Non-Cursor branch (line 396): passes
textPrompt(text with file path) toexecuteQuery()— which doesn't exist and would cause a runtime failureCritical Issue: The
executeQueryfunction 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
ExecuteOptionsinterface 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 passpromptContentdirectly:🔧 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
promptContentif the CLI supports base64 image blocks. If not, add a comment explaining the limitation and whycursorPrompt(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)
speccontent, 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: defaultmodel = 'default'is rejected by runtime validation
With Line 289 defaultingmodelto'default', Line 339-348 will 400 every request that omitsmodel(since'default'isn’t inVALID_CLAUDE_MODELSand 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:executeQueryfunction doesn't exist — refactor non-Cursor branch to useaiGateway.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 usesaiGateway.execute(), which also:
- Resolves model aliases internally (
resolveModelStringat 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-existentexecuteQuery(), eliminating the need for manual model resolution. Alternatively, implementexecuteQueryas a wrapper aroundaiGateway.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/eventsand/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 theaiGateway.execute(...)call does not setreadOnly: 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=32768reserves 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
24is 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=65536allocates 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., tomin-w-[260px]), these LOD styles will silently break.💡 Suggested approach
Add a semantic class like
.task-node-containerto 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]: anyloses 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: Usesubstringinstead of deprecatedsubstr.
String.prototype.substris deprecated. Usesubstringfor 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 validatinglimitparameter.The
limitparameter 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'sslice(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 importGitGraph.
GitGraphis 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:subspecTemplatehas no corresponding UI input.The
subspecTemplatestate 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
migrationCompletedcheck 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:extractTextFromStreamis 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:autoLoadClaudeMdandthinkingLevel.
autoLoadClaudeMd(line 336-340) is computed but never passed to the execution options.thinkingLevel(line 346) is destructured but never used—bothaiGateway.executeandexecuteQuerycalls omit it.Based on the provider snippets,
thinkingLevelis a valid option forexecuteQuery. 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+thinkingLevelare computed but unused
autoLoadClaudeMdis read (Line 95-99) but never applied to either gateway path.thinkingLevelis resolved (Line 105) but never passed toaiGateway.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 mutatingprocess.envto “disable” a provider (surprising global side-effect)
Deletingprocess.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: PreferextractJson()over bespoke JSON scraping for learnings
You already haveextractJsonin the repo; this block reimplements JSON extraction/parsing and will be brittle against edge cases.
3197-3366:expandKnowledgeGraph(): resolve model + align execution/auditing withaiGateway
modelConfig.modelcan be'default'and is passed intoqueryService.simpleQuerywithout 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-structuredgenerateSubtasksmethod for Smart Expand feature.The implementation follows established patterns from
generateSuggestionswith 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
AbortControllerinstances are created (lines 770 and 781). The one passed tocreateSuggestionsOptionsis unused sinceaiGateway.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
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (36)
apps/server/package.jsonapps/server/src/index.tsapps/server/src/routes/app-spec/generate-features-from-spec.tsapps/server/src/routes/app-spec/generate-spec.tsapps/server/src/routes/backlog-plan/generate-plan.tsapps/server/src/routes/context/routes/describe-image.tsapps/server/src/routes/enhance-prompt/routes/enhance.tsapps/server/src/routes/github/routes/validate-issue.tsapps/server/src/routes/red-giant/routes/create-star.tsapps/server/src/routes/red-giant/routes/delete-star.tsapps/server/src/routes/red-giant/routes/evolve-star.tsapps/server/src/routes/red-giant/routes/get-events.tsapps/server/src/routes/red-giant/routes/get-metrics.tsapps/server/src/routes/red-giant/routes/get-star.tsapps/server/src/routes/red-giant/routes/list-stars.tsapps/server/src/services/agent-service.tsapps/server/src/services/ai-gateway.tsapps/server/src/services/auto-mode-service.tsapps/server/src/services/ideation-service.tsapps/server/src/services/red-giant-service.tsapps/server/src/services/universal-gateway.tsapps/ui/package.jsonapps/ui/src/components/views/board-view.tsxapps/ui/src/components/views/board-view/components/kanban-card/kanban-card.tsxapps/ui/src/components/views/board-view/dialogs/smart-expand-dialog.tsxapps/ui/src/components/views/graph-view/components/dependency-edge.tsxapps/ui/src/components/views/graph-view/components/task-node.tsxapps/ui/src/components/views/graph-view/context/graph-actions-context.tsxapps/ui/src/components/views/graph-view/graph-canvas.tsxapps/ui/src/components/views/graph-view/hooks/use-graph-nodes.tsapps/ui/src/hooks/use-settings-migration.tsapps/ui/src/main.tsapps/ui/src/routes/world-model.tsxapps/ui/src/store/app-store.tsapps/ui/src/styles/global.csslibs/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.tsapps/server/src/routes/red-giant/routes/evolve-star.tsapps/server/src/routes/red-giant/routes/get-metrics.tsapps/ui/src/routes/world-model.tsxapps/server/src/routes/red-giant/routes/delete-star.tsapps/server/src/routes/red-giant/routes/create-star.tsapps/ui/src/components/views/graph-view/context/graph-actions-context.tsxapps/ui/src/components/views/board-view/components/kanban-card/kanban-card.tsxapps/server/src/services/agent-service.tsapps/server/src/routes/red-giant/routes/get-events.tsapps/server/src/routes/context/routes/describe-image.tsapps/ui/src/components/views/graph-view/graph-canvas.tsxapps/server/src/services/universal-gateway.tsapps/server/src/index.tsapps/server/src/routes/app-spec/generate-features-from-spec.tslibs/provider-zai/src/zai-query-service.tsapps/ui/src/hooks/use-settings-migration.tsapps/ui/src/store/app-store.tsapps/ui/src/main.tsapps/server/src/services/auto-mode-service.tsapps/server/src/services/ideation-service.tsapps/ui/src/components/views/graph-view/components/dependency-edge.tsxapps/server/src/routes/enhance-prompt/routes/enhance.tsapps/ui/src/components/views/board-view/dialogs/smart-expand-dialog.tsxapps/server/src/routes/github/routes/validate-issue.tsapps/server/src/routes/backlog-plan/generate-plan.tsapps/server/src/routes/red-giant/routes/list-stars.tsapps/server/src/services/ai-gateway.tsapps/server/src/services/red-giant-service.tsapps/server/src/routes/app-spec/generate-spec.tsapps/ui/src/components/views/graph-view/hooks/use-graph-nodes.tsapps/ui/src/components/views/board-view.tsx
apps/server/src/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use
createEventEmitter()fromlib/events.tsfor all server operations to emit events that stream to frontend via WebSocket
Files:
apps/server/src/routes/red-giant/routes/get-star.tsapps/server/src/routes/red-giant/routes/evolve-star.tsapps/server/src/routes/red-giant/routes/get-metrics.tsapps/server/src/routes/red-giant/routes/delete-star.tsapps/server/src/routes/red-giant/routes/create-star.tsapps/server/src/services/agent-service.tsapps/server/src/routes/red-giant/routes/get-events.tsapps/server/src/routes/context/routes/describe-image.tsapps/server/src/services/universal-gateway.tsapps/server/src/index.tsapps/server/src/routes/app-spec/generate-features-from-spec.tsapps/server/src/services/auto-mode-service.tsapps/server/src/services/ideation-service.tsapps/server/src/routes/enhance-prompt/routes/enhance.tsapps/server/src/routes/github/routes/validate-issue.tsapps/server/src/routes/backlog-plan/generate-plan.tsapps/server/src/routes/red-giant/routes/list-stars.tsapps/server/src/services/ai-gateway.tsapps/server/src/services/red-giant-service.tsapps/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.tsapps/server/src/routes/red-giant/routes/evolve-star.tsapps/server/src/routes/red-giant/routes/get-metrics.tsapps/ui/src/routes/world-model.tsxapps/server/src/routes/red-giant/routes/delete-star.tsapps/server/src/routes/red-giant/routes/create-star.tsapps/ui/src/components/views/graph-view/context/graph-actions-context.tsxapps/ui/src/components/views/board-view/components/kanban-card/kanban-card.tsxapps/server/src/services/agent-service.tsapps/server/src/routes/red-giant/routes/get-events.tsapps/server/src/routes/context/routes/describe-image.tsapps/ui/src/components/views/graph-view/graph-canvas.tsxapps/server/src/services/universal-gateway.tsapps/server/src/index.tsapps/server/src/routes/app-spec/generate-features-from-spec.tslibs/provider-zai/src/zai-query-service.tsapps/ui/src/hooks/use-settings-migration.tsapps/ui/src/store/app-store.tsapps/ui/src/main.tsapps/server/src/services/auto-mode-service.tsapps/server/src/services/ideation-service.tsapps/ui/src/components/views/graph-view/components/dependency-edge.tsxapps/server/src/routes/enhance-prompt/routes/enhance.tsapps/ui/src/components/views/board-view/dialogs/smart-expand-dialog.tsxapps/server/src/routes/github/routes/validate-issue.tsapps/server/src/routes/backlog-plan/generate-plan.tsapps/server/src/routes/red-giant/routes/list-stars.tsapps/server/src/services/ai-gateway.tsapps/server/src/services/red-giant-service.tsapps/server/src/routes/app-spec/generate-spec.tsapps/ui/src/components/views/graph-view/hooks/use-graph-nodes.tsapps/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.tsxapps/ui/src/components/views/graph-view/context/graph-actions-context.tsxapps/ui/src/components/views/board-view/components/kanban-card/kanban-card.tsxapps/ui/src/components/views/graph-view/graph-canvas.tsxapps/ui/src/components/views/graph-view/components/dependency-edge.tsxapps/ui/src/components/views/board-view/dialogs/smart-expand-dialog.tsxapps/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.tsapps/server/src/services/auto-mode-service.tsapps/server/src/services/ideation-service.tsapps/server/src/routes/github/routes/validate-issue.tsapps/server/src/routes/backlog-plan/generate-plan.tsapps/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)
| // 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'); | ||
| })(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 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 -nRepository: 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 -nRepository: 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 jsRepository: 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 fRepository: 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 2Repository: AutoMaker-Org/automaker
Length of output: 365
🏁 Script executed:
#!/bin/bash
# Find and examine executeQuery function
find apps/server/src -name "execute-query*" -type fRepository: 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 -60Repository: 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 -40Repository: 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 3Repository: 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 -30Repository: 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 -5Repository: 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 -50Repository: 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 -20Repository: 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 -lhRepository: 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 -40Repository: 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 5Repository: 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 -20Repository: 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 5Repository: 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 -printRepository: 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 10Repository: 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 5Repository: 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 -10Repository: 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 -30Repository: 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 -80Repository: 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 3Repository: 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.tsRepository: 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 -100Repository: 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 5Repository: 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 -printRepository: 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 10Repository: 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 15Repository: 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 40Repository: 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 -20Repository: 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 -nRepository: 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; doneRepository: 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 5Repository: 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 -nRepository: 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 -RRepository: 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 -5Repository: 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 5Repository: 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 -nRepository: 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 fRepository: 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 -60Repository: 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 fRepository: 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 -20Repository: 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 -5Repository: 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 -nRepository: 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 -20Repository: 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 -nRepository: 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 3Repository: 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 -nRepository: 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.
| } catch (error) { | ||
| res.status(500).json({ success: false, error: String(error) }); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| } 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.
| constructor(dataDir?: string) { | ||
| this.dataDir = | ||
| dataDir || process.env.APPDATA | ||
| ? path.join(process.env.APPDATA!, 'Automaker') | ||
| : path.join(process.env.HOME || '', '.automaker'); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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)`); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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)`); | |
| } | |
| } |
| // 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', | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 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 -C2Repository: 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 -20Repository: AutoMaker-Org/automaker
Length of output: 868
🏁 Script executed:
#!/bin/bash
# Examine the model-resolver to understand resolveModelString
fd "model-resolver" --type=fRepository: 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 -B2Repository: 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 -A2Repository: 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 -100Repository: 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_MAPkeys ('glm-4.7':'glm-4.7') - Other built-in profile aliases (
'opus','sonnet','haiku') - The
ModelAliastype definition which references these lowercase keys - The
resolveModelString()resolver which checksif (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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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.
| .graph-zoomed-out .react-flow__edge.graph-edge-highlighted { | ||
| opacity: 1 !important; | ||
| stroke-width: 2px !important; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| .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.
| const response = await this.client.chat.completions.create({ | ||
| model: this.resolveModel(options?.model), | ||
| messages, | ||
| max_tokens: options?.maxTokens || 8192, | ||
| temperature: options?.temperature, | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Summary
This PR adds comprehensive Z.AI integration and UI improvements:
Features
Technical Changes
Files Changed
76 files, +4512/-267 lines
Submitted from GhostDragonAlpha/automaker fork
Summary by CodeRabbit
New Features
Changes
Tests
✏️ Tip: You can customize this high-level summary in your review settings.