@lazyingart/agintiflow 0.20.7 → 0.20.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -69,10 +69,15 @@ Start an interactive Codex-style CLI chat from any project folder:
69
69
  aginti
70
70
  # or explicitly:
71
71
  aginti chat
72
+ # choose a UI language, or omit it to follow your system locale:
73
+ aginti --language ja
74
+ aginti --language zh-Hans
72
75
  ```
73
76
 
74
77
  Inside chat, type normal requests such as `write a small Python CLI app with tests`. The default is Docker workspace mode with approved package installs, so coding, plotting, and LaTeX tasks can set up project-local tools without touching the host. Use `/help` for commands, `/login` or `/auth` to paste a provider key, `/instructions` to inspect `AGINTI.md`, `/latex on` for PDF work, `/docker off` only when you intentionally want host mode, `/sessions` to list project runs, and `/resume latest` or `/resume <session-id>` to continue work. Type `/` then Tab for command completion. `Ctrl+J` inserts a new line in the colored input panel, Enter sends, arrow keys move through wrapped multiline input, and `Ctrl+A`/`Ctrl+E` jump to the current line start/end. During an active run, Enter sends the draft as an ASAP pipe message (`→`) and Tab queues it for after the run (`↳`); ASAP messages are consumed before after-finish queued prompts, Alt+Up edits the last piped message, and Shift+Left edits the last after-finish queued message. Idle Esc is ignored so it does not disturb the input panel; during a run, Esc waits when `→` messages are pending and otherwise stops the run cleanly. Ctrl+C always stops and prints the resume command. The input panel always shows the current `cwd` footer and a single live status row, so long goals and tool updates are compacted instead of flooding the transcript. Assistant responses start on a fresh line after the `aginti>` header with a colored response gutter and render common Markdown, including headings, inline code, bold text, lists, quotes, code fences, tables, and red/green patch diff lines. Resuming a session prints the full saved chat history with wrapped messages before the prompt.
75
78
 
79
+ CLI and app language can follow the system locale or be set with `--language`, `--lang`, `-L`, or the interactive `/language` command. Supported codes are `en`, `ja`, `zh-Hans`, `zh-Hant`, `ko`, `fr`, `es`, `ar`, `vi`, `de`, and `ru`; longer names plus old `jp` and `cn-*` aliases still work. See [docs/cli-i18n.md](docs/cli-i18n.md).
80
+
76
81
  `aginti init` creates `AGINTI.md` at the project root. This is the editable project-instruction file for both CLI and web runs, similar to `AGENTS.md` or project memory in other agents. Keep durable preferences, commands, and constraints there, but never secrets. You can edit it manually or ask in chat, for example: `update AGINTI.md to remember that this project uses pytest and npm run check`.
77
82
 
78
83
  For code edits, AgInTiFlow routes patch/refactor/database-style tasks to DeepSeek v4 pro by default and exposes `apply_patch` as a deterministic workspace tool. It supports exact replacements, Codex-style patch envelopes, and unified diffs, with preflight checks, path guardrails, hashes, and compact per-file diffs. See [docs/patch-tools.md](docs/patch-tools.md).
@@ -0,0 +1,84 @@
1
+ # CLI And App Language
2
+
3
+ AgInTiFlow supports a shared language preference for the interactive CLI, one-shot CLI runs, web defaults, and model prompt context.
4
+
5
+ ## Supported Canonical Languages
6
+
7
+ | Canonical | Language | Common aliases |
8
+ | --- | --- |
9
+ | `en` | English | `english` |
10
+ | `ja` | Japanese | `jp`, `japanese` |
11
+ | `zh-Hans` | Simplified Chinese | `zh-s`, `zh s`, `cn`, `cn-s`, `cn s`, `zh-cn`, `simplified` |
12
+ | `zh-Hant` | Traditional Chinese | `zh-t`, `zh t`, `cn-t`, `cn t`, `zh-tw`, `zh-hk`, `traditional` |
13
+ | `ko` | Korean | `korean` |
14
+ | `fr` | French | `french` |
15
+ | `es` | Spanish | `spanish` |
16
+ | `ar` | Arabic | `arabic` |
17
+ | `vi` | Vietnamese | `vietnamese` |
18
+ | `de` | German | `deutsch`, `german` |
19
+ | `ru` | Russian | `russian` |
20
+
21
+ ## Defaults
22
+
23
+ If no language is passed, AgInTiFlow follows the system language from:
24
+
25
+ ```text
26
+ AGINTI_LANGUAGE -> LANGUAGE -> LC_ALL -> LC_MESSAGES -> LANG
27
+ ```
28
+
29
+ If none of those map to a supported locale, English is used.
30
+
31
+ ## CLI Usage
32
+
33
+ Start the interactive CLI in a specific language:
34
+
35
+ ```bash
36
+ aginti --language ja
37
+ aginti --language zh-Hans
38
+ aginti --language zh-Hant
39
+ aginti --language ko
40
+ aginti --language fr
41
+ aginti --language es
42
+ aginti --language ar
43
+ aginti --language vi
44
+ aginti --language de
45
+ aginti --language ru
46
+ ```
47
+
48
+ Inside interactive chat:
49
+
50
+ ```text
51
+ /language
52
+ /language auto
53
+ /language en
54
+ /language ja
55
+ /language zh-Hans
56
+ /language zh-Hant
57
+ /language ko
58
+ ```
59
+
60
+ `/lang` is an alias for `/language`.
61
+
62
+ One-shot tasks also accept the same option:
63
+
64
+ ```bash
65
+ aginti --language zh-Hans "list files and summarize this project"
66
+ ```
67
+
68
+ The selected language localizes the launch banner subtitle, prompt hint, help text, status labels, and language status. It also adds language guidance to the model context so the assistant usually replies in the selected UI language unless the user asks for another language.
69
+
70
+ ## Web App
71
+
72
+ The web app already has an 11-language dropdown. Launching web with a language seeds the project-local default:
73
+
74
+ ```bash
75
+ aginti web --language de --port 3210
76
+ ```
77
+
78
+ The web preference is stored in the project-local `.sessions/web-state.sqlite` database, so the CLI and web app can share sessions while each frontend keeps its own UI controls.
79
+
80
+ ## Notes
81
+
82
+ - `zh-Hans` and `zh-Hant` are the canonical Chinese values. `zh-s`, `zh-t`, `cn-s`, `cn-t`, `cn s`, and `cn t` remain compatibility aliases.
83
+ - API keys and `.aginti/.env` values are never translated, printed, or exposed through the language system.
84
+ - Model output language is guidance, not a hard safety filter. If the user asks in a different language or explicitly requests another output language, the model should follow that request.
@@ -48,12 +48,15 @@ Interactive commands:
48
48
 
49
49
  In the interactive CLI, `/provider`, `/route`, `/model`, `/spare`, and `/auxiliary model` without arguments open selectors. Use Up/Down/Left/Right to move through choices, Enter to confirm, and Esc to cancel. Slash-command hints use the same arrow selection behavior: type a prefix such as `/mo`, use arrows to choose `/model` or `/models`, then press Enter or Tab.
50
50
 
51
+ `/route`, `/model`, and `/spare` intentionally share the same text-model selector so users do not need to learn three different catalogs. The shared list is grouped as DeepSeek, Venice Uncensored, Venice GPT/Claude/Gemma/Qwen, OpenAI, Qwen, and Mock. OpenAI entries show the recommended default reasoning effort in the description; `/spare` stores that reasoning value when selected.
52
+
51
53
  `/venice` opens a two-step selector for the Venice route and main models. The current text choices are:
52
54
 
53
55
  ```text
54
56
  venice/venice-uncensored-1-2
55
57
  venice/venice-uncensored
56
58
  venice/gemma-4-uncensored
59
+ Disable Venice
57
60
  ```
58
61
 
59
62
  For scripts or non-interactive terminals, `/venice` uses Venice 1.2 for both roles. You can also set both roles directly with `/venice 1.2`, `/venice 1.1`, or `/venice gemma`. Use two values to set route and main separately, for example `/venice 1.2 gemma`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lazyingart/agintiflow",
3
- "version": "0.20.7",
3
+ "version": "0.20.9",
4
4
  "type": "module",
5
5
  "description": "AgInTiFlow is a web-first coding agent and CLI with DeepSeek routing, sandboxed tools, model providers, canvas artifacts, and optional wrappers.",
6
6
  "license": "Apache-2.0",
@@ -53,7 +53,9 @@ Interactive equivalents:
53
53
  /auxiliary model grsai/nano-banana-2
54
54
  ```
55
55
 
56
- `/venice` opens a route/main selector for Venice text models. The selector includes `venice/venice-uncensored-1-2` (Venice 1.2), `venice/venice-uncensored` (Venice 1.1), and `venice/gemma-4-uncensored` (Gemma 4). In non-interactive shells, `/venice` keeps script compatibility by selecting `venice/venice-uncensored-1-2` for both roles. `/venice 1.2 gemma` sets route to Venice 1.2 and main to Gemma 4; `/venice off` restores `deepseek/deepseek-v4-flash` for route and `deepseek/deepseek-v4-pro` for main.
56
+ `/route`, `/model`, and `/spare` share a single text-model selector. The selector is grouped as DeepSeek, Venice Uncensored, Venice GPT/Claude/Gemma/Qwen, OpenAI, Qwen, and Mock so the user sees the same union of route/main/spare-capable models in every role.
57
+
58
+ `/venice` opens a route/main selector for Venice text models. The selector includes `venice/venice-uncensored-1-2` (Venice 1.2), `venice/venice-uncensored` (Venice 1.1), `venice/gemma-4-uncensored` (Gemma 4), and a Disable Venice option. In non-interactive shells, `/venice` keeps script compatibility by selecting `venice/venice-uncensored-1-2` for both roles. `/venice 1.2 gemma` sets route to Venice 1.2 and main to Gemma 4; `/venice off` or the Disable Venice selector option restores `deepseek/deepseek-v4-flash` for route and `deepseek/deepseek-v4-pro` for main.
57
59
 
58
60
  The web UI should expose model names as dropdowns, not free-text fields. The left panel should stay focused on common daily controls, while model-role editing and less-used switches live in an Advanced settings modal. The terminal-like capability panels belong after the runtime log so the left control panel remains short.
59
61
 
@@ -30,6 +30,7 @@ function runCli(args, inputText) {
30
30
  env: {
31
31
  ...process.env,
32
32
  AGINTIFLOW_RUNTIME_DIR: "",
33
+ AGINTI_LANGUAGE: "en",
33
34
  },
34
35
  });
35
36
 
@@ -153,6 +154,15 @@ try {
153
154
  ) {
154
155
  throw new Error("terminal prompt layout did not render visual-only input padding safely");
155
156
  }
157
+ const zhPromptLayout = buildPromptLayout("", 0, 90, 24, { language: "zh-Hans", commandCwd: "/tmp/aginti-project" });
158
+ const zhPromptText = zhPromptLayout.renderedRows.map((line) => line.replace(/\x1b\[[0-9;?]*[ -/]*[@-~]/g, "")).join("\n");
159
+ if (!zhPromptText.includes("输入任务")) {
160
+ throw new Error("terminal prompt layout did not localize the empty input hint");
161
+ }
162
+ const jaLaunchHeader = buildLaunchHeaderLines({ width: 120, packageVersion: "0.0.0", animated: false, language: "ja" }).join("\n");
163
+ if (!jaLaunchHeader.includes("Web ファースト")) {
164
+ throw new Error("launch header did not localize by language option");
165
+ }
156
166
  const hugePromptLayout = buildPromptLayout(Array.from({ length: 30 }, (_unused, index) => `line ${index + 1}`).join("\n"), 120, 80, 20);
157
167
  if (hugePromptLayout.renderedRows.length > 12 || !hugePromptLayout.renderedRows.some((line) => line.includes("earlier input row"))) {
158
168
  throw new Error("terminal prompt layout did not bound redraw size for large prompts");
@@ -220,6 +230,10 @@ try {
220
230
  if (!helpResult.stdout.includes("/auxiliary") || helpResult.stdout.includes(misspelledAuxiliary)) {
221
231
  throw new Error("interactive help did not expose only the correctly spelled /auxiliary command");
222
232
  }
233
+ const zhHelpResult = await runCli(["chat", "--language", "zh-Hans"], "/help\n/exit\n");
234
+ if (!zhHelpResult.stdout.includes("命令:") || !zhHelpResult.stdout.includes("输入普通任务")) {
235
+ throw new Error("interactive --language zh-Hans did not localize CLI help");
236
+ }
223
237
  const skillsResult = await runChat("/skills website\n/exit\n");
224
238
  if (!skillsResult.stdout.includes("website-app") || !skillsResult.stdout.includes("Website And App Builder")) {
225
239
  throw new Error("interactive /skills did not show matching built-in skills");
@@ -283,6 +297,7 @@ try {
283
297
  "large-launch-header",
284
298
  "prompt-layout",
285
299
  "prompt-redraw-fast-path",
300
+ "cli-i18n",
286
301
  "user-prompt-label",
287
302
  "escape-policy",
288
303
  "live-input-status-layout",
@@ -10,6 +10,7 @@ import {
10
10
  selectModelRoute,
11
11
  } from "../src/model-routing.js";
12
12
  import { parseTextToolCalls, usesTextToolProtocol } from "../src/model-client.js";
13
+ import { modelRoleChoices } from "../src/interactive-cli.js";
13
14
 
14
15
  const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
15
16
 
@@ -111,6 +112,32 @@ assert(MODEL_PROVIDER_GROUPS["venice-gpt"].provider === "venice", "venice-gpt gr
111
112
  assert(modelsForProviderGroup("venice-gemma").some((item) => item.id === "gemma-4-uncensored"), "venice-gemma bucket missing Gemma");
112
113
  assert(modelsForProviderGroup("venice-uncensored").some((item) => item.id === "e2ee-venice-uncensored-24b-p"), "venice-uncensored bucket missing Venice 1.1");
113
114
  assert(AUXILIARY_MODEL_CATALOG["venice-image"].some((item) => item.id === "gpt-image-2"), "Venice image catalog missing GPT Image 2");
115
+ const routeChoices = modelRoleChoices("route").map((item) => `${item.provider}/${item.model}`);
116
+ const mainChoices = modelRoleChoices("main").map((item) => `${item.provider}/${item.model}`);
117
+ const spareChoices = modelRoleChoices("spare").map((item) => `${item.provider}/${item.model}`);
118
+ assert(JSON.stringify(routeChoices) === JSON.stringify(mainChoices), "route and main selectors should share the same text-model list");
119
+ assert(JSON.stringify(routeChoices) === JSON.stringify(spareChoices), "route and spare selectors should share the same text-model list");
120
+ for (const expected of [
121
+ "deepseek/deepseek-v4-flash",
122
+ "deepseek/deepseek-v4-pro",
123
+ "venice/venice-uncensored-1-2",
124
+ "venice/venice-uncensored",
125
+ "venice/gemma-4-uncensored",
126
+ "venice/openai-gpt-55",
127
+ "venice/claude-sonnet-4-6",
128
+ "venice/qwen3-6-27b",
129
+ "openai/gpt-5.5",
130
+ "openai/gpt-5.4",
131
+ "openai/gpt-5.4-mini",
132
+ "openai/gpt-5.3-codex",
133
+ "openai/gpt-5.3-codex-spark",
134
+ "qwen/qwen-plus",
135
+ "mock/mock-agent",
136
+ ]) {
137
+ assert(routeChoices.includes(expected), `shared model selector missing ${expected}`);
138
+ }
139
+ assert(!routeChoices.includes("venice/e2ee-venice-uncensored-24b-p"), "shared model selector should hide unstable E2EE Venice 1.1");
140
+ assert(modelRoleChoices("auxiliary").some((item) => item.provider === "grsai"), "auxiliary selector missing GRS AI");
114
141
  const parsedTextToolCalls = parseTextToolCalls('[TOOL_CALLS]list_files[ARGS]call_123[ARGS]{"path":".","maxDepth":1}');
115
142
  assert(parsedTextToolCalls.length === 1, "Venice text tool-call parser did not detect encoded tool call");
116
143
  assert(parsedTextToolCalls[0].function.name === "list_files", "Venice text tool-call parser returned wrong tool name");
@@ -151,6 +178,7 @@ console.log(
151
178
  "route-overrides",
152
179
  "provider-groups",
153
180
  "auxiliary-catalog",
181
+ "shared-model-selectors",
154
182
  "venice-text-tool-parser",
155
183
  "cli-models-command",
156
184
  "venice-shortcut",
@@ -25,6 +25,7 @@ import { readProjectInstructions } from "./project.js";
25
25
  import { formatSkillsForPrompt, selectSkillsForGoal } from "./skill-library.js";
26
26
  import { hostShellOption, platformInfo, platformLabel } from "./platform.js";
27
27
  import { captureTmuxPane, listTmuxSessions, sendTmuxKeys, startTmuxSession } from "./tmux-tools.js";
28
+ import { languageInstruction } from "./i18n.js";
28
29
 
29
30
  const exec = promisify(execCallback);
30
31
  const BROWSER_TOOLS = new Set(["open_url", "open_workspace_file", "preview_workspace", "click", "type", "scroll", "press", "back"]);
@@ -300,6 +301,7 @@ async function createInitialState(config, sessionId) {
300
301
  "Prefer short, deliberate actions over guessing.",
301
302
  "Never navigate outside the allowed domains when an allowlist exists.",
302
303
  "Avoid destructive actions, purchases, account changes, and sensitive workflows.",
304
+ languageInstruction(config.language || "en"),
303
305
  projectInstructionContext,
304
306
  "Treat AGINTI.md as durable project memory and operating instructions for this project. The user can edit it manually or ask you in chat to update it; use workspace file tools for that and never store secrets there.",
305
307
  config.allowShellTool
@@ -351,6 +353,7 @@ async function createInitialState(config, sessionId) {
351
353
  role: "user",
352
354
  content: [
353
355
  `Goal: ${config.goal}`,
356
+ languageInstruction(config.language || "en"),
354
357
  config.startUrl ? `Suggested start URL: ${config.startUrl}` : "",
355
358
  config.allowedDomains.length > 0 ? `Allowed domains: ${config.allowedDomains.join(", ")}` : "",
356
359
  config.allowShellTool
package/src/cli.js CHANGED
@@ -25,6 +25,7 @@ import { listTaskProfiles } from "./task-profiles.js";
25
25
  import { recommendedMaxStepsForTask } from "./engineering-guidance.js";
26
26
  import { normalizeAuthProvider, promptHidden, runAuthWizard, shouldPromptForDeepSeek } from "./auth-onboarding.js";
27
27
  import { listSkills, selectSkillsForGoal } from "./skill-library.js";
28
+ import { languageLabel, resolveLanguage } from "./i18n.js";
28
29
  import fs from "node:fs/promises";
29
30
  import path from "node:path";
30
31
  import { fileURLToPath } from "node:url";
@@ -87,6 +88,7 @@ export function parseArgs(argv) {
87
88
  listSkills: false,
88
89
  latex: false,
89
90
  image: false,
91
+ language: "",
90
92
  };
91
93
 
92
94
  const parts = [];
@@ -110,6 +112,18 @@ export function parseArgs(argv) {
110
112
  i += 1;
111
113
  continue;
112
114
  }
115
+ if (arg === "--language" || arg === "--lang" || arg === "-L") {
116
+ const first = readOption(argv, i);
117
+ const second = argv[i + 2] && !String(argv[i + 2]).startsWith("--") ? argv[i + 2] : "";
118
+ if (["cn", "zh"].includes(String(first || "").toLowerCase()) && ["s", "t"].includes(String(second || "").toLowerCase())) {
119
+ result.language = `${first}-${second}`;
120
+ i += 2;
121
+ } else {
122
+ result.language = first;
123
+ i += 1;
124
+ }
125
+ continue;
126
+ }
113
127
  if (arg === "--start-url") {
114
128
  result.startUrl = readOption(argv, i);
115
129
  i += 1;
@@ -345,8 +359,9 @@ export function parseArgs(argv) {
345
359
 
346
360
  function printUsage() {
347
361
  console.log(
348
- 'Usage: aginti [chat] OR aginti web [--port 3210] OR aginti models OR aginti skills [query] OR aginti auth [deepseek|openai|qwen|venice|grsai] OR aginti resume [latest|<session-id>] ["prompt"] OR aginti queue <session-id> "message" OR aginti [--image] [--latex] [--routing smart|fast|complex|manual] [--provider deepseek|openai|qwen|venice|mock] [--model MODEL] [--route-model MODEL] [--main-model MODEL] [--spare-model MODEL --spare-reasoning medium] [--aux-provider grsai|venice --aux-model MODEL] [--sandbox-mode host|docker-readonly|docker-workspace] [--package-install-policy block|prompt|allow] [--approve-package-installs] [--allow-shell|--no-shell] [--allow-file-tools|--no-file-tools] [--web-search|--no-web-search] [--parallel-scouts|--no-parallel-scouts --scout-count 1..10] [--allow-auxiliary-tools|--no-auxiliary-tools] [--allow-wrappers --wrapper codex --wrapper-model gpt-5.5] [--list-models|--list-routes] "your task"'
362
+ 'Usage: aginti [chat] OR aginti web [--port 3210] OR aginti models OR aginti skills [query] OR aginti auth [deepseek|openai|qwen|venice|grsai] OR aginti resume [latest|<session-id>] ["prompt"] OR aginti queue <session-id> "message" OR aginti [--language en|ja|zh-Hans|zh-Hant|ko|fr|es|ar|vi|de|ru] [--image] [--latex] [--routing smart|fast|complex|manual] [--provider deepseek|openai|qwen|venice|mock] [--model MODEL] [--route-model MODEL] [--main-model MODEL] [--spare-model MODEL --spare-reasoning medium] [--aux-provider grsai|venice --aux-model MODEL] [--sandbox-mode host|docker-readonly|docker-workspace] [--package-install-policy block|prompt|allow] [--approve-package-installs] [--allow-shell|--no-shell] [--allow-file-tools|--no-file-tools] [--web-search|--no-web-search] [--parallel-scouts|--no-parallel-scouts --scout-count 1..10] [--allow-auxiliary-tools|--no-auxiliary-tools] [--allow-wrappers --wrapper codex --wrapper-model gpt-5.5] [--list-models|--list-routes] "your task"'
349
363
  );
364
+ console.log(`Languages: ${["en", "ja", "zh-Hans", "zh-Hant", "ko", "fr", "es", "ar", "vi", "de", "ru"].map((code) => `${code}=${languageLabel(code)}`).join(", ")}`);
350
365
  }
351
366
 
352
367
  function providerLabel(provider) {
@@ -361,6 +376,7 @@ function providerLabel(provider) {
361
376
  function agentDefaults(args) {
362
377
  const defaults = {
363
378
  ...args,
379
+ language: resolveLanguage(args.language || process.env.AGINTI_LANGUAGE || ""),
364
380
  allowShellTool: args.allowShellTool ?? true,
365
381
  allowFileTools: args.allowFileTools ?? true,
366
382
  allowAuxiliaryTools: args.allowAuxiliaryTools ?? true,
@@ -728,6 +744,7 @@ export async function main(argv = process.argv.slice(2)) {
728
744
  if (args.web) {
729
745
  if (args.port) process.env.PORT = String(args.port);
730
746
  if (args.host) process.env.HOST = String(args.host);
747
+ if (args.language) process.env.AGINTI_LANGUAGE = resolveLanguage(args.language);
731
748
  process.env.AGINTIFLOW_PACKAGE_DIR = packageDir;
732
749
  await import("../web.js");
733
750
  return;
package/src/config.js CHANGED
@@ -6,6 +6,7 @@ import { normalizeWrapperName } from "./tool-wrappers.js";
6
6
  import { loadProjectEnv, resolveProjectRoot } from "./project.js";
7
7
  import { normalizeTaskProfile } from "./task-profiles.js";
8
8
  import { recommendedMaxStepsForTask } from "./engineering-guidance.js";
9
+ import { resolveLanguage } from "./i18n.js";
9
10
 
10
11
  function parseBoolean(value, fallback) {
11
12
  if (value === undefined) return fallback;
@@ -47,6 +48,7 @@ export function resolveRuntimeConfig(args, overrides = {}) {
47
48
  : "deepseek");
48
49
  const routingMode = normalizeRoutingMode(overrides.routingMode || args.routingMode || process.env.AGENT_ROUTING_MODE || "smart");
49
50
  const taskProfile = normalizeTaskProfile(overrides.taskProfile || args.taskProfile || process.env.AGINTI_TASK_PROFILE || "auto");
51
+ const language = resolveLanguage(overrides.language || args.language || process.env.AGINTI_LANGUAGE || "");
50
52
  const route = selectModelRoute({
51
53
  routingMode,
52
54
  provider: requestedProvider,
@@ -94,6 +96,7 @@ export function resolveRuntimeConfig(args, overrides = {}) {
94
96
  sessionId: overrides.sessionId || args.sessionId || process.env.SESSION_ID || `web-agent-${crypto.randomUUID()}`,
95
97
  routingMode,
96
98
  taskProfile,
99
+ language,
97
100
  routeReason: route.reason,
98
101
  routeComplexityScore: route.complexityScore,
99
102
  modelRoles,