diff --git a/src/main/services/task-execution-service.ts b/src/main/services/task-execution-service.ts index b6ae93f9..9e1aaa28 100644 --- a/src/main/services/task-execution-service.ts +++ b/src/main/services/task-execution-service.ts @@ -131,6 +131,7 @@ export class TaskExecutionService { const connectorNames = new ConnectorGrantsService(this.db) .toolkitsForAgent(orgId, agent.id) .map(connectorDisplayName); + const language = this.appLanguage(); const externalTools = await loadComposioTools(this.db, orgId, agent.id); const result = await runAgentLoop( { @@ -142,7 +143,7 @@ export class TaskExecutionService { orgId, providerId: provider.id, workspaceId: task.workspace_id, - instructions: buildAgentInstructions({ agent, companyName: this.orgName(orgId), isCeo, hasReports, connectorNames }), + instructions: buildAgentInstructions({ agent, companyName: this.orgName(orgId), isCeo, hasReports, connectorNames, language }), initialUserTurn: buildTaskPrompt({ task, agent, @@ -418,6 +419,11 @@ export class TaskExecutionService { return row.name; } + private appLanguage(): "en" | "zh" { + const row = this.db.prepare("select language from app_state where id = 1").get() as { language: string } | undefined; + return row?.language === "zh" ? "zh" : "en"; + } + private completeRun(orgId: string, runId: string, providerRequestId: string): void { this.db.prepare(` update task_runs diff --git a/src/main/services/task-prompts.ts b/src/main/services/task-prompts.ts index 69a25fae..a5b02de2 100644 --- a/src/main/services/task-prompts.ts +++ b/src/main/services/task-prompts.ts @@ -78,6 +78,8 @@ export interface AgentInstructionContext { hasReports: boolean; /** Display names of the connected toolkits this agent carries in this run. */ connectorNames: string[]; + /** The operator's app language; agents speak it back. */ + language: "en" | "zh"; } /** @@ -93,6 +95,9 @@ export function buildAgentInstructions(input: AgentInstructionContext): string { roleVoice(input), "Work like a sharp person: take in what is actually in front of you, decide the smallest real next step, do it, then tell whoever needs to know. Do not narrate busywork.", `Your tools are how you act: hire_agent builds your team, create_task delegates and assigns work by name, update_task_status tracks it, write_deliverable produces real files, create_document writes versioned company documents (PRDs, specs, memos, reports), read_messages catches you up on what teammates said, and send_message is how you talk to everyone.`, + ...(input.language === "zh" + ? ["公司的工作语言是简体中文:你写的每一条消息、任务标题与描述、文档和交付物都用中文表达(代码、命令、API 名称等技术标识保持原样)。工具名称和工具参数的字段名仍按定义使用英文。"] + : []), capabilityBriefing(input), "Talk like a person on a team, not a status bot: address teammates by @name, say what you genuinely think, ask when you are unsure, and reply when someone asks you something. Before starting work, check your messages with read_messages to see if anyone needs you. Post where the work lives — 'engineering', 'product', or otherwise 'general' — and keep a back-and-forth in one thread by passing the thread_id a previous send_message returned. Hand work to a teammate with /handoff @Name and the file_id write_deliverable gives you.", ].join("\n"); diff --git a/src/renderer/src/app/Onboarding.tsx b/src/renderer/src/app/Onboarding.tsx index 73d39a24..fdf07c8f 100644 --- a/src/renderer/src/app/Onboarding.tsx +++ b/src/renderer/src/app/Onboarding.tsx @@ -3,8 +3,9 @@ import { PROVIDER_AUTH_TYPE, type AppSnapshot, type ProviderCatalogEntry, + type UiLanguage, } from "../../../shared/domain"; -import { Button, Field, Icon, IconButton, Pill } from "../shared/ui"; +import { Button, Field, Icon, IconButton, Pill, Segmented } from "../shared/ui"; import type { SnapshotActions } from "./useSnapshot"; import { ProviderStep } from "./OnboardingProviderStep"; import { ConnectorsStep } from "./OnboardingConnectorsStep"; @@ -178,7 +179,11 @@ export function Onboarding({
{step === "welcome" && ( - setStep("provider")} /> + void actions.run(() => window.agentCompany.setUiPreferences({ language }))} + onNext={() => setStep("provider")} + /> )} {step === "provider" && ( void; } -function WelcomeStep({ onNext }: { onNext: () => void }) { +function WelcomeStep({ + language, + onLanguage, + onNext, +}: { + language: UiLanguage; + onLanguage: (language: UiLanguage) => void; + onNext: () => void; +}) { return (
-

{t("Welcome")}

+
+

{t("Welcome")}

+ { if (next !== language) onLanguage(next); }} + options={[{ id: "en", label: "English" }, { id: "zh", label: "中文" }]} + /> +

{t("AgentCompany sets up your operating loop in six steps. Bring an API key, pick a runtime for the founding CEO, and we'll wire up the first goal, project, and task.")}

diff --git a/tests/unit/agent-instructions.test.ts b/tests/unit/agent-instructions.test.ts index f64f793f..79bfa992 100644 --- a/tests/unit/agent-instructions.test.ts +++ b/tests/unit/agent-instructions.test.ts @@ -35,6 +35,7 @@ describe("agent instructions", () => { isCeo: true, hasReports: false, connectorNames: ["GitHub", "Slack"], + language: "en", }); expect(text).toContain("every connected service and every company skill"); expect(text).toContain("grant_connector"); @@ -51,6 +52,7 @@ describe("agent instructions", () => { isCeo: false, hasReports: false, connectorNames: ["Stripe"], + language: "en", }); expect(text).toContain("Connected external services you carry in this run: Stripe"); expect(text).toContain("composio_ prefix"); @@ -66,11 +68,33 @@ describe("agent instructions", () => { isCeo: false, hasReports: false, connectorNames: [], + language: "en", }); expect(text).toContain("You currently hold no connector grants"); expect(text).toContain("message the CEO"); expect(text).not.toContain("grant before you assign"); }); + + test("Chinese app language directs agents to work in Chinese", () => { + const zh = buildAgentInstructions({ + agent: agentRow({}), + companyName: "OrbitOps", + isCeo: false, + hasReports: false, + connectorNames: [], + language: "zh", + }); + expect(zh).toContain("公司的工作语言是简体中文"); + const en = buildAgentInstructions({ + agent: agentRow({}), + companyName: "OrbitOps", + isCeo: false, + hasReports: false, + connectorNames: [], + language: "en", + }); + expect(en).not.toContain("简体中文"); + }); }); describe("agent instruction wiring", () => { @@ -123,5 +147,11 @@ describe("agent instruction wiring", () => { expect(capturedInstructions).not.toContain("You run this company"); expect(capturedInstructions).toContain("Connected external services you carry in this run: GitHub"); expect(capturedInstructions).toContain("list_connectors"); + + // Switching the app language redirects the whole company to Chinese. + organization.updateUiPreferences({ language: "zh" }); + connection.db.prepare("update task_runs set state = 'failed' where org_id = ?").run(orgId); + await execution.runTask({ orgId, taskId }); + expect(capturedInstructions).toContain("公司的工作语言是简体中文"); }); });