Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions apps/desktop/src/main/agent/agent-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,11 @@ export class AgentManager extends EventEmitter {
return accounts.length > 0;
}

private getSettingsLanguage(): string | undefined {
const settings = readSettingsFile();
return settings?.language as string | undefined;
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

/**
* Resolve auth using the provider accounts priority queue.
* Falls back to legacy Claude profile if no provider accounts exist.
Expand Down Expand Up @@ -389,6 +394,7 @@ export class AgentManager extends EventEmitter {
maxSteps: 1000,
specDir: resolvedSpecDir,
projectDir: projectPath,
language: this.getSettingsLanguage(),
provider: resolved.provider,
modelId: resolved.modelId,
apiKey: resolved.auth?.apiKey,
Expand Down Expand Up @@ -510,6 +516,7 @@ export class AgentManager extends EventEmitter {
maxSteps: 1000,
specDir: worktreeSpecDir,
projectDir: effectiveProjectDir,
language: this.getSettingsLanguage(),
// When running in a worktree, sourceSpecDir points to the main project spec dir
// so the subtask iterator can sync phase updates in real time (not just on exit).
sourceSpecDir: worktreePath ? specDir : undefined,
Expand Down Expand Up @@ -616,6 +623,7 @@ export class AgentManager extends EventEmitter {
maxSteps: 1000,
specDir: effectiveSpecDir,
projectDir: effectiveProjectDir,
language: this.getSettingsLanguage(),
provider: resolved.provider,
modelId: resolved.modelId,
apiKey: resolved.auth?.apiKey,
Expand Down
2 changes: 2 additions & 0 deletions apps/desktop/src/main/ai/agent/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ export interface SerializableSessionConfig {
};
/** Enable agentic orchestration mode where the AI drives the pipeline via SpawnSubagent tool */
useAgenticOrchestration?: boolean;
/** UI language code for agent response language (e.g., 'en', 'ru') */
language?: string;
/** Tool context serialized fields */
toolContext: {
cwd: string;
Expand Down
1 change: 1 addition & 0 deletions apps/desktop/src/main/ai/agent/worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@ async function assemblePrompt(
specDir: session.specDir,
projectDir: session.projectDir,
projectInstructions: cachedProjectInstructions,
language: session.language,
});
}

Expand Down
15 changes: 14 additions & 1 deletion apps/desktop/src/main/ai/client/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
import type { Tool as AITool } from 'ai';

import { resolveAuth, resolveAuthFromQueue, buildDefaultQueueConfig } from '../auth/resolver';
import { readSettingsFile } from '../../settings-utils';
import { buildLanguageInstruction } from '../prompts/language-utils';
import {
getDefaultThinkingLevel,
getRequiredMcpServers,
Expand Down Expand Up @@ -192,6 +194,17 @@ export async function createAgentClient(
};
}

// =============================================================================
// Language Injection
// =============================================================================

function injectLanguageInstruction(prompt: string): string {
const settings = readSettingsFile();
const lang = (settings?.language as string | undefined) ?? 'en';
const instruction = buildLanguageInstruction(lang);
return instruction ? instruction + prompt : prompt;
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Comment on lines +201 to +206
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify sync I/O and call-path coupling in current code.
rg -n -C3 '\breadSettingsFile\s*\(' apps/desktop/src/main/ai/client/factory.ts apps/desktop/src/main/settings-utils.ts
rg -n -C3 '\breadFileSync\s*\(' apps/desktop/src/main/settings-utils.ts
rg -n -C3 '\binjectLanguageInstruction\s*\(' apps/desktop/src/main/ai/client/factory.ts

Repository: AndyMik90/Aperant

Length of output: 2129


🏁 Script executed:

#!/bin/bash
# Find all usages of createSimpleClient, injectLanguageInstruction, and readSettingsFile
echo "=== createSimpleClient calls ==="
rg -n 'createSimpleClient\s*\(' apps/desktop/src/main/ai/ --type ts

echo "=== readSettingsFile calls ==="
rg -n 'readSettingsFile\s*\(' apps/desktop/src/main/ --type ts

echo "=== Check TypeScript strict mode ==="
jq '.compilerOptions.strict' apps/desktop/tsconfig.json

Repository: AndyMik90/Aperant

Length of output: 8762


🏁 Script executed:

#!/bin/bash
# Check file imports and dependencies to understand context
echo "=== Imports in factory.ts ==="
head -30 apps/desktop/src/main/ai/client/factory.ts | grep -E '^import|^export'

echo "=== Full function signature of createSimpleClient ==="
rg -A 15 'export\s+(?:async\s+)?function\s+createSimpleClient' apps/desktop/src/main/ai/client/factory.ts

Repository: AndyMik90/Aperant

Length of output: 1378


Consider passing language as a parameter instead of reading settings on each call.

Line 202 performs a synchronous disk read via readSettingsFile() on every createSimpleClient() invocation. With 29+ callsites across runners and validators, this adds unnecessary I/O. Additionally, line 203's type assertion (settings?.language as string | undefined) bypasses TypeScript's strict type checking.

The refactor is straightforward: accept language as a parameter in SimpleClientConfig and pass it down, avoiding the per-call settings read.

-function injectLanguageInstruction(prompt: string): string {
-  const settings = readSettingsFile();
-  const lang = (settings?.language as string | undefined) ?? 'en';
+function injectLanguageInstruction(prompt: string, lang: string): string {
   const instruction = buildLanguageInstruction(lang);
   return instruction ? instruction + prompt : prompt;
 }

 const result = await streamText({
   model,
   tools,
-  systemPrompt: injectLanguageInstruction(systemPrompt),
+  systemPrompt: injectLanguageInstruction(systemPrompt, language ?? 'en'),
 });

Add language?: string to SimpleClientConfig interface.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/main/ai/client/factory.ts` around lines 201 - 206, The
helper injectLanguageInstruction currently reads settings from disk each call;
change it to accept a language parameter and stop calling readSettingsFile()
inside it (update signature injectLanguageInstruction(prompt: string, language?:
string)), use buildLanguageInstruction(language ?? 'en') and remove the type
assertion. Add language?: string to the SimpleClientConfig interface and
propagate that value into createSimpleClient so callers pass config.language to
injectLanguageInstruction (fall back to 'en' where unspecified). Update all
callsites of injectLanguageInstruction/createSimpleClient to pass the new
language parameter and remove per-call reads of readSettingsFile().


// =============================================================================
// createSimpleClient
// =============================================================================
Expand Down Expand Up @@ -289,7 +302,7 @@ export async function createSimpleClient(
model,
resolvedModelId,
tools,
systemPrompt,
systemPrompt: injectLanguageInstruction(systemPrompt),
maxSteps,
thinkingLevel: resolvedThinkingLevel,
...(queueAuth ? { queueAuth } : {}),
Expand Down
11 changes: 11 additions & 0 deletions apps/desktop/src/main/ai/prompts/language-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { AVAILABLE_LANGUAGES } from '../../../shared/constants/i18n';

export function buildLanguageInstruction(language: string): string | null {
if (!language || language === 'en') return null;
const entry = AVAILABLE_LANGUAGES.find((l) => l.value === language);
const langName = entry?.label ?? language;
return (
`**LANGUAGE**: Always respond in ${langName}. All code comments, ` +
`documentation, commit messages, and explanations must be in ${langName}.\n\n`
);
}
13 changes: 10 additions & 3 deletions apps/desktop/src/main/ai/prompts/prompt-loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { join } from 'node:path';
import { execSync } from 'node:child_process';

import type { ProjectCapabilities, PromptContext, PromptValidationResult } from './types';
import { buildLanguageInstruction } from './language-utils';

// =============================================================================
// Expected prompt files (used for startup validation)
Expand Down Expand Up @@ -223,7 +224,13 @@ export function injectContext(promptTemplate: string, context: PromptContext): s
sections.push(context.recoveryContext);
}

// 3. Human input
// 3. Language instruction (non-English only)
if (context.language) {
const instruction = buildLanguageInstruction(context.language);
if (instruction) sections.push(instruction);
}

// 4. Human input
if (context.humanInput) {
sections.push(
`## HUMAN INPUT (READ THIS FIRST!)\n\n` +
Comment on lines 226 to 236
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Bug: The QA status check in readQAStatus() only recognizes English, but the QA agent is instructed to respond in the user's selected language, causing a validation mismatch and build failure.
Severity: CRITICAL

Suggested Fix

Update the readQAStatus() function to support localized status keywords for all supported languages. Alternatively, explicitly instruct the QA agent to always write the status line in English, regardless of the user's language setting, or exclude the status line from the scope of the language instruction.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.

Location: apps/desktop/src/main/ai/prompts/prompt-loader.ts#L224-L236

Potential issue: When a user selects a non-English language, a language instruction is
injected into the QA agent's prompt, causing it to write its status report in that
language (e.g., `Статус: ПРОЙДЕНО`). However, the `readQAStatus()` function in
`build-orchestrator.ts` only performs case-insensitive matching for hardcoded English
strings like `status: passed`. This mismatch causes the status to be read as `unknown`,
leading to repeated retries and an eventual build failure. This breaks the entire
automated build pipeline for any user operating in a non-English language.

Did we get this right? 👍 / 👎 to inform future reviews.

Expand All @@ -234,7 +241,7 @@ export function injectContext(promptTemplate: string, context: PromptContext): s
);
}

// 4. Project instructions (AGENTS.md or CLAUDE.md fallback)
// 5. Project instructions (AGENTS.md or CLAUDE.md fallback)
if (context.projectInstructions) {
sections.push(
`## PROJECT INSTRUCTIONS\n\n` +
Expand All @@ -243,7 +250,7 @@ export function injectContext(promptTemplate: string, context: PromptContext): s
);
}

// 5. Base prompt
// 6. Base prompt
sections.push(promptTemplate);

return sections.join('');
Expand Down
2 changes: 2 additions & 0 deletions apps/desktop/src/main/ai/prompts/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ export interface PromptContext {
recoveryHints?: string[];
/** Phase-specific planning retry context */
planningRetryContext?: string;
/** UI language code for agent response language (e.g., 'en', 'ru') */
language?: string;
}

// =============================================================================
Expand Down
5 changes: 3 additions & 2 deletions apps/desktop/src/shared/constants/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@
* Available languages and display labels
*/

export type SupportedLanguage = 'en' | 'fr';
export type SupportedLanguage = 'en' | 'fr' | 'ru';

export const AVAILABLE_LANGUAGES = [
{ value: 'en' as const, label: 'English', nativeLabel: 'English' },
{ value: 'fr' as const, label: 'French', nativeLabel: 'Français' }
{ value: 'fr' as const, label: 'French', nativeLabel: 'Français' },
{ value: 'ru' as const, label: 'Russian', nativeLabel: 'Русский' }
] as const;

export const DEFAULT_LANGUAGE: SupportedLanguage = 'en';
26 changes: 26 additions & 0 deletions apps/desktop/src/shared/i18n/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,19 @@ import frTaskReview from './locales/fr/taskReview.json';
import frTerminal from './locales/fr/terminal.json';
import frErrors from './locales/fr/errors.json';

// Import Russian translation resources
import ruCommon from './locales/ru/common.json';
import ruNavigation from './locales/ru/navigation.json';
import ruSettings from './locales/ru/settings.json';
import ruTasks from './locales/ru/tasks.json';
import ruWelcome from './locales/ru/welcome.json';
import ruOnboarding from './locales/ru/onboarding.json';
import ruDialogs from './locales/ru/dialogs.json';
import ruGitlab from './locales/ru/gitlab.json';
import ruTaskReview from './locales/ru/taskReview.json';
import ruTerminal from './locales/ru/terminal.json';
import ruErrors from './locales/ru/errors.json';

export const defaultNS = 'common';

export const resources = {
Expand Down Expand Up @@ -55,6 +68,19 @@ export const resources = {
taskReview: frTaskReview,
terminal: frTerminal,
errors: frErrors
},
ru: {
common: ruCommon,
navigation: ruNavigation,
settings: ruSettings,
tasks: ruTasks,
welcome: ruWelcome,
onboarding: ruOnboarding,
dialogs: ruDialogs,
gitlab: ruGitlab,
taskReview: ruTaskReview,
terminal: ruTerminal,
errors: ruErrors
}
} as const;

Expand Down
Loading
Loading