diff --git a/docs/codex-config-proxy-plan.md b/docs/codex-config-proxy-plan.md
deleted file mode 100644
index d504b11..0000000
--- a/docs/codex-config-proxy-plan.md
+++ /dev/null
@@ -1,158 +0,0 @@
-# Codex Config And Proxy Plan
-
-## Goals
-
-- Launch Codex from Hermes without mutating the user's real `~/.codex`.
-- Keep model/provider config isolated by Hermes profile and provider.
-- Support OpenAI Responses providers directly.
-- Add a local adapter for providers that only expose OpenAI Chat Completions.
-- Keep Codex resume/history stable by using a consistent model provider id.
-
-## Directory Layout
-
-All generated Codex state should live under the Web UI home:
-
-```text
-~/.hermes-web-ui/coding-agent/
- model/{profile}/{provider}/codex/
- config.toml
- auth.json
- AGENTS.md
- workspace/{profile}/{provider}/
-```
-
-Launch command shape:
-
-```bash
-cd ~/.hermes-web-ui/coding-agent/workspace/{profile}/{provider} \
- && CODEX_HOME=~/.hermes-web-ui/coding-agent/model/{profile}/{provider}/codex \
- codex --model {model}
-```
-
-## Current MVP Config
-
-For Responses-compatible providers, generate:
-
-```toml
-model_provider = "custom"
-model = "provider-model-id"
-disable_response_storage = true
-
-[model_providers.custom]
-name = "provider-id"
-base_url = "https://provider.example/v1"
-wire_api = "responses"
-requires_openai_auth = false
-experimental_bearer_token = "provider-api-key"
-```
-
-Keep `auth.json` empty for third-party providers:
-
-```json
-{}
-```
-
-Reason: avoid overwriting the user's official Codex / ChatGPT login cache.
-
-## Stable Provider Id
-
-Use `model_provider = "custom"` for third-party providers.
-
-Codex history and resume behavior can depend on provider identity. Keeping a stable provider id avoids making history appear to move between provider-specific ids.
-
-Provider identity remains visible in:
-
-- `[model_providers.custom].name`
-- the generated directory path
-- the UI launch result
-
-## Local Proxy Plan
-
-Some providers expose only OpenAI Chat Completions:
-
-```text
-/v1/chat/completions
-```
-
-Codex prefers Responses:
-
-```text
-/v1/responses
-```
-
-Add a local proxy endpoint:
-
-```text
-/api/codex-proxy/{routeKey}/v1/responses
-```
-
-The `routeKey` should encode:
-
-```text
-profile + "\0" + provider + "\0" + model
-```
-
-Authentication should use a generated `hwui_...` token, not the upstream provider key.
-
-## Responses To Chat Mapping
-
-When the upstream provider is Chat Completions only:
-
-- Convert Responses `input` items to Chat `messages`.
-- Convert Responses `tools` to Chat `tools`.
-- Convert `max_output_tokens` to `max_tokens`.
-- Preserve `stream: true`.
-- Map function calls and function outputs both ways.
-
-Response event mapping for streaming:
-
-```text
-chat delta.content -> response.output_text.delta
-chat delta.tool_calls -> response.function_call_arguments.delta
-finish -> response.completed
-```
-
-Non-streaming mapping:
-
-```text
-chat.choices[0].message.content -> output message/content
-chat.choices[0].message.tool_calls -> output function_call items
-chat.usage -> response.usage
-```
-
-## Config Generation With Proxy
-
-For Chat-only providers, generated Codex config should point at Hermes:
-
-```toml
-model_provider = "custom"
-model = "provider-model-id"
-disable_response_storage = true
-
-[model_providers.custom]
-name = "provider-id"
-base_url = "http://127.0.0.1:{serverPort}/api/codex-proxy/{routeKey}/v1"
-wire_api = "responses"
-requires_openai_auth = false
-experimental_bearer_token = "hwui_generated_route_token"
-```
-
-The proxy then forwards to the real provider with the real provider key.
-
-## Implementation Tasks
-
-1. Add a Codex proxy service parallel to the Claude Code proxy service.
-2. Register route targets in memory for launch-time provider/model selection.
-3. Add `/api/codex-proxy/:key/v1/responses`.
-4. Implement Responses to Chat conversion.
-5. Implement Chat to Responses conversion for streaming and non-streaming.
-6. Add launch-time api mode selection for Codex providers.
-7. Generate Codex `base_url` against the local proxy when api mode is Chat Completions.
-8. Add server tests for config generation, auth rejection, streaming conversion, tool call conversion, and error passthrough.
-
-## Open Questions
-
-- Whether to expose Codex protocol selection in the UI immediately or infer it from provider preset metadata.
-- Whether to persist proxy targets or require relaunch after server restart.
-- Whether model catalog generation is needed for the first Codex MVP.
-- Whether MCP and `AGENTS.md` should be copied from a template or edited only through the advanced config editor.
diff --git a/docs/plans/2026-05-23-latex-rendering.md b/docs/plans/2026-05-23-latex-rendering.md
deleted file mode 100644
index 6c14d5b..0000000
--- a/docs/plans/2026-05-23-latex-rendering.md
+++ /dev/null
@@ -1,491 +0,0 @@
-# LaTeX Rendering Implementation Plan
-
-> **For Hermes:** Use subagent-driven-development skill to implement this plan task-by-task.
-
-**Goal:** Add reliable LaTeX/math rendering to Hermes Web UI chat Markdown messages so formulas render visually instead of appearing as raw TeX, including explicit fenced math blocks like ```latex.
-
-**Architecture:** Keep the existing `markdown-it` renderer in `MarkdownRenderer.vue` and add a KaTeX-backed math plugin there, plus a small fence override for explicit LaTeX code fences. Load KaTeX styles globally once, cover inline, display, and fenced math in component tests, then verify with a production build and a local Web UI smoke test.
-
-**Tech Stack:** Vue 3, TypeScript, Vite, markdown-it, KaTeX, Vitest, @vue/test-utils.
-
----
-
-## Acceptance Criteria
-
-- Chat messages render inline math like `$x^2 + y^2 = z^2$` as KaTeX HTML.
-- Chat messages render block math like `$$\n\\int_0^1 x^2 dx = \\frac{1}{3}\n$$` as display KaTeX HTML.
-- Chat messages render explicit fenced math blocks like ```latex, ```tex, or ```math as display KaTeX HTML.
-- Existing Markdown features still work: code fences, Mermaid fences, headings, local file links, mentions.
-- Ordinary fenced code blocks stay literal and are not rendered as math.
-- Invalid/unsupported math syntax does not crash the chat renderer.
-- `npm run test -- tests/client/markdown-rendering.test.ts` passes.
-- `npm run build` passes.
-- Local `hermes-web-ui.service` can be restarted onto the built fork and displays formulas in the real panel.
-
-## Recommended Dependency Choice
-
-Use `markdown-it-katex` + `katex` because the app already uses `markdown-it`, and this is the smallest change surface.
-
-Install as runtime dependencies, not dev-only, because the renderer runs in the browser bundle:
-
-```bash
-npm install katex markdown-it-katex
-npm install -D @types/katex
-```
-
-If TypeScript reports no module declaration for `markdown-it-katex`, add a local declaration file instead of weakening `tsconfig`.
-
----
-
-## Task 1: Add KaTeX dependencies
-
-**Objective:** Make the required renderer and styles available to the client bundle.
-
-**Files:**
-- Modify: `package.json`
-- Modify: `package-lock.json` or lockfile generated by npm if present
-
-**Step 1: Install packages**
-
-Run from repo root:
-
-```bash
-cd /home/werserk/2-kira/hermes-web-ui
-npm install katex markdown-it-katex
-npm install -D @types/katex
-```
-
-**Expected:** `package.json` includes `katex` and `markdown-it-katex`; lockfile is updated.
-
-**Step 2: Verify dependency tree**
-
-```bash
-npm ls katex markdown-it-katex
-```
-
-**Expected:** both packages resolve without `UNMET DEPENDENCY`.
-
-**Step 3: Commit**
-
-```bash
-git add package.json package-lock.json
-git commit -m "chore: add latex rendering dependencies"
-```
-
----
-
-## Task 2: Add TypeScript module declaration if needed
-
-**Objective:** Keep TypeScript strict and avoid `any` leaking into the renderer setup.
-
-**Files:**
-- Create if needed: `packages/client/src/types/markdown-it-katex.d.ts`
-
-**Step 1: Run typecheck to discover whether declaration is needed**
-
-```bash
-npm run build
-```
-
-**Expected before implementation:** build may still fail later because code is not changed yet. For this task, only check whether TypeScript knows the `markdown-it-katex` module after it is imported in Task 3.
-
-**Step 2: If TS2307/TS7016 appears, create declaration**
-
-Create `packages/client/src/types/markdown-it-katex.d.ts`:
-
-```ts
-declare module 'markdown-it-katex' {
- import type MarkdownIt from 'markdown-it'
-
- interface MarkdownItKatexOptions {
- throwOnError?: boolean
- errorColor?: string
- strict?: boolean | string | ((errorCode: string) => boolean | string)
- output?: 'html' | 'mathml' | 'htmlAndMathml'
- trust?: boolean | ((context: unknown) => boolean)
- macros?: Record
- }
-
- const markdownItKatex: MarkdownIt.PluginWithOptions
- export default markdownItKatex
-}
-```
-
-**Step 3: Commit only if file was needed**
-
-```bash
-git add packages/client/src/types/markdown-it-katex.d.ts
-git commit -m "chore: add markdown-it-katex types"
-```
-
----
-
-## Task 3: Wire KaTeX into MarkdownRenderer
-
-**Objective:** Render math in the same path that currently renders Markdown chat messages.
-
-**Files:**
-- Modify: `packages/client/src/components/hermes/chat/MarkdownRenderer.vue`
-
-**Step 1: Import the plugin**
-
-Near existing Markdown imports:
-
-```ts
-import markdownItKatex from 'markdown-it-katex'
-```
-
-**Step 2: Register the plugin after creating `md`**
-
-After the `new MarkdownItConstructor(...)` block and before custom fence renderer setup:
-
-```ts
-md.use(markdownItKatex, {
- throwOnError: false,
- errorColor: '#cc3344',
- output: 'htmlAndMathml',
-})
-```
-
-**Step 3: Preserve existing Mermaid fence behavior**
-
-Do not move or remove this existing code:
-
-```ts
-const defaultFenceRenderer = md.renderer.rules.fence?.bind(md.renderer.rules)
-
-md.renderer.rules.fence = (tokens, idx, options, env, self) => {
- const token = tokens[idx]
- if (isMermaidFence(token.info)) {
- return renderMermaidPlaceholder(token.content)
- }
-
- if (defaultFenceRenderer) {
- return defaultFenceRenderer(tokens, idx, options, env, self)
- }
-
- return self.renderToken(tokens, idx, options)
-}
-```
-
-**Step 4: Run focused test suite**
-
-```bash
-npm run test -- tests/client/markdown-rendering.test.ts
-```
-
-**Expected:** existing tests still pass or only fail because new math tests are not yet added.
-
-**Step 5: Commit**
-
-```bash
-git add packages/client/src/components/hermes/chat/MarkdownRenderer.vue
-git commit -m "feat: enable latex rendering in chat markdown"
-```
-
----
-
-## Task 4: Load and tune KaTeX CSS
-
-**Objective:** Ensure rendered math is visually correct in the Web UI.
-
-**Files:**
-- Modify: `packages/client/src/main.ts`
-- Optionally modify: `packages/client/src/styles/global.scss`
-
-**Step 1: Import KaTeX CSS once globally**
-
-In `packages/client/src/main.ts`, add after global styles import:
-
-```ts
-import 'katex/dist/katex.min.css'
-```
-
-Current import area should become:
-
-```ts
-import App from './App.vue'
-import './styles/global.scss'
-import 'katex/dist/katex.min.css'
-```
-
-**Step 2: Add Hermes-specific layout tweaks only if needed**
-
-If visual smoke test shows cramped block equations, append to `packages/client/src/styles/global.scss`:
-
-```scss
-.markdown-body {
- .katex-display {
- overflow-x: auto;
- overflow-y: hidden;
- padding: 0.25rem 0;
- }
-}
-```
-
-Do not restyle KaTeX itself unless there is a concrete visual bug.
-
-**Step 3: Commit**
-
-```bash
-git add packages/client/src/main.ts packages/client/src/styles/global.scss
-git commit -m "style: load katex styles for markdown math"
-```
-
----
-
-## Task 5: Add regression tests for math rendering
-
-**Objective:** Prove formulas render, code fences stay literal, and malformed math is safe.
-
-**Files:**
-- Modify: `tests/client/markdown-rendering.test.ts`
-
-**Step 1: Add inline math test**
-
-Inside `describe('MarkdownRenderer', () => { ... })`:
-
-```ts
-it('renders inline latex math with katex', () => {
- const wrapper = mount(MarkdownRenderer, {
- props: {
- content: 'Pythagoras: $x^2 + y^2 = z^2$.',
- },
- })
-
- const body = wrapper.find('.markdown-body')
- expect(body.find('.katex').exists()).toBe(true)
- expect(body.html()).toContain('x')
- expect(body.html()).toContain('z')
- expect(body.text()).not.toContain('$x^2 + y^2 = z^2$')
-})
-```
-
-**Step 2: Add block math test**
-
-```ts
-it('renders display latex math with katex', () => {
- const wrapper = mount(MarkdownRenderer, {
- props: {
- content: '$$\n\\int_0^1 x^2 dx = \\frac{1}{3}\n$$',
- },
- })
-
- const body = wrapper.find('.markdown-body')
- expect(body.find('.katex-display').exists()).toBe(true)
- expect(body.find('.katex').exists()).toBe(true)
- expect(body.text()).not.toContain('$$')
-})
-```
-
-**Step 3: Add fenced-code non-rendering test**
-
-```ts
-it('does not render latex inside fenced code blocks', () => {
- const wrapper = mount(MarkdownRenderer, {
- props: {
- content: '```md\n$x^2 + y^2 = z^2$\n```',
- },
- })
-
- expect(wrapper.find('.markdown-body').find('.katex').exists()).toBe(false)
- expect(wrapper.find('code.hljs').text()).toContain('$x^2 + y^2 = z^2$')
-})
-```
-
-**Step 4: Add invalid math safety test**
-
-```ts
-it('keeps rendering when latex syntax is invalid', () => {
- const wrapper = mount(MarkdownRenderer, {
- props: {
- content: 'Before $\\notacommand{ after',
- },
- })
-
- expect(wrapper.find('.markdown-body').text()).toContain('Before')
-})
-```
-
-**Step 5: Run focused tests**
-
-```bash
-npm run test -- tests/client/markdown-rendering.test.ts
-```
-
-**Expected:** all tests in the file pass.
-
-**Step 6: Commit**
-
-```bash
-git add tests/client/markdown-rendering.test.ts
-git commit -m "test: cover latex rendering in markdown"
-```
-
----
-
-## Task 6: Run build and broader regression checks
-
-**Objective:** Catch TypeScript, Vite, and existing client regressions before deploying locally.
-
-**Files:**
-- No source changes expected unless checks fail.
-
-**Step 1: Run focused tests**
-
-```bash
-npm run test -- tests/client/markdown-rendering.test.ts tests/client/markdown-rendering-mermaid-import-timeout.test.ts
-```
-
-**Expected:** pass.
-
-**Step 2: Run all tests if practical**
-
-```bash
-npm run test
-```
-
-**Expected:** pass. If unrelated pre-existing failures appear, record them in the PR body with evidence.
-
-**Step 3: Run production build**
-
-```bash
-npm run build
-```
-
-**Expected:** pass and update `dist/` only as build output. Do not commit `dist/` unless the package/release workflow requires it.
-
-**Step 4: Commit fixes only if needed**
-
-```bash
-git add
-git commit -m "fix: stabilize latex markdown rendering"
-```
-
----
-
-## Task 7: Deploy to Maxim's local Hermes Web UI instance
-
-**Objective:** Start using the feature on `https://hermes.kiraproject.ru/` before upstream PR review completes.
-
-**Files:**
-- Local runtime install under npm/global prefix may change outside the repo.
-- Do not commit generated deployment artifacts unless intentionally part of the repo.
-
-**Step 1: Build from the fork checkout**
-
-```bash
-cd /home/werserk/2-kira/hermes-web-ui
-npm run build
-```
-
-**Step 2: Replace currently installed package using npm link or local global install**
-
-Preferred reversible approach:
-
-```bash
-cd /home/werserk/2-kira/hermes-web-ui
-npm install -g --prefix /home/werserk/.npm-global .
-```
-
-**Step 3: Restart the Web UI service**
-
-```bash
-systemctl --user restart hermes-web-ui.service
-systemctl --user status hermes-web-ui.service --no-pager --lines=20
-```
-
-**Step 4: Smoke test HTTP response**
-
-```bash
-curl -sS -I --max-time 5 http://127.0.0.1:8648/ | head -20
-```
-
-**Expected:** HTTP 200/304 or another successful static response, not connection refused.
-
-**Step 5: Browser smoke test**
-
-Open the chat panel and send/render a message containing:
-
-```md
-Inline: $x^2 + y^2 = z^2$
-
-Block:
-$$
-\\int_0^1 x^2 dx = \\frac{1}{3}
-$$
-
-Code fence should stay literal:
-```md
-$x^2 + y^2 = z^2$
-```
-```
-
-**Expected:** first two formulas render visually; fenced formula stays literal.
-
----
-
-## Task 8: Prepare upstream PR
-
-**Objective:** Make the contribution easy for upstream maintainers to review.
-
-**Files:**
-- No code changes required unless PR polish finds an issue.
-
-**Step 1: Push branch**
-
-```bash
-git push origin feat/latex-rendering
-```
-
-**Step 2: Create PR draft**
-
-```bash
-gh pr create \
- --repo EKKOLearnAI/hermes-web-ui \
- --head kira-project-lab:feat/latex-rendering \
- --base main \
- --title "feat(chat): render LaTeX math in Markdown messages" \
- --body "$(cat <<'EOF'
-## Summary
-- Adds KaTeX-backed LaTeX rendering to chat Markdown messages.
-- Supports inline `$...$` and display `$$...$$` math.
-- Keeps math inside fenced code blocks literal.
-
-## Test Plan
-- [ ] npm run test -- tests/client/markdown-rendering.test.ts
-- [ ] npm run test -- tests/client/markdown-rendering-mermaid-import-timeout.test.ts
-- [ ] npm run build
-- [ ] Manual browser smoke test with inline, block, and fenced math
-
-## Notes
-This uses the existing markdown-it rendering path and keeps HTML disabled.
-EOF
-)" \
- --draft
-```
-
-**Step 3: Attach visual proof**
-
-Add before/after screenshots in the PR body or a PR comment:
-
-- Before: raw `$...$` / `$$...$$` text.
-- After: rendered KaTeX inline and display equations.
-
----
-
-## Risks and Guardrails
-
-- **Delimiter ambiguity:** `$` is common in shell snippets and prices. The fenced-code test protects code blocks, but inline text like `cost is $5 and $6` may be parsed depending on plugin behavior. If this is noisy, switch to `markdown-it-texmath` with explicit delimiter options or configure delimiters to prioritize `\(...\)` / `\[...\]`.
-- **Security:** keep `html: false` in `markdown-it`; do not enable arbitrary HTML. KaTeX should run with `trust: false` unless a clear need appears.
-- **Bundle size:** KaTeX adds client bundle weight. Acceptable for this feature, but mention it in PR if maintainers ask.
-- **CSS scope:** KaTeX CSS is global. Import once in `main.ts`; avoid duplicating it inside component styles.
-- **Invalid math:** `throwOnError: false` should prevent chat crashes. Regression test required.
-
-## Definition of Done
-
-- Branch `feat/latex-rendering` contains committed implementation.
-- Local Web UI renders inline and block formulas.
-- Focused Markdown tests pass.
-- Production build passes.
-- PR draft exists against `EKKOLearnAI/hermes-web-ui:main` or is ready to create.
diff --git a/packages/server/src/services/coding-agents.ts b/packages/server/src/services/coding-agents.ts
index 14eb0bf..a250c6e 100644
--- a/packages/server/src/services/coding-agents.ts
+++ b/packages/server/src/services/coding-agents.ts
@@ -2,7 +2,7 @@ import { execFile } from 'child_process'
import { existsSync, realpathSync } from 'fs'
import { mkdir, readFile, stat, writeFile } from 'fs/promises'
import { homedir } from 'os'
-import { delimiter, dirname, join } from 'path'
+import { delimiter, dirname, extname, join } from 'path'
import { promisify } from 'util'
import { getWebUiHome } from '../config'
import { registerClaudeCodeProxyTarget, type ApiMode } from './claude-code-proxy'
@@ -12,9 +12,6 @@ import { getModelContextLength } from './hermes/model-context'
const execFileAsync = promisify(execFile)
const LAUNCH_API_MODES = new Set(['chat_completions', 'codex_responses', 'anthropic_messages'])
-const CLAUDE_PROXY_HAIKU_MODEL = 'claude-haiku-4-5'
-const CLAUDE_PROXY_SONNET_MODEL = 'claude-sonnet-4-6'
-const CLAUDE_PROXY_OPUS_MODEL = 'claude-opus-4-7'
const CODING_AGENT_HOME_DIR = 'coding-agent'
const CODEX_MODEL_CATALOG_FILE = 'codex-model-catalog.json'
const CODEX_CATALOG_BASE_INSTRUCTIONS = 'You are Codex, a coding agent. Be precise, safe, and helpful.'
@@ -167,12 +164,15 @@ function getNpmBin() {
}
function normalizeScopeSegment(value: string | undefined, fallback: string, label: string): string {
- const segment = String(value || '').trim() || fallback
+ // Replace invalid filename characters with underscores
+ // Windows invalid chars: < > : " / \ | ? *
+ // Additional problematic chars: control characters
+ const sanitizedValue = String(value || '').trim().replace(/[<>:"/\\|?*\x00-\x1f]/g, '_')
+ const segment = sanitizedValue || fallback
+
if (
segment === '.' ||
segment === '..' ||
- segment.includes('/') ||
- segment.includes('\\') ||
segment.includes('\0')
) {
const err = new Error(`Invalid ${label}`)
@@ -309,6 +309,10 @@ function powerShellQuote(value: string): string {
return `'${value.replace(/'/g, "''")}'`
}
+function cmdQuote(value: string): string {
+ return `"${value.replace(/"/g, '""')}"`
+}
+
function buildLaunchShellCommand(input: {
workspaceDir: string
env: Record
@@ -357,11 +361,11 @@ function isDockerRuntime(): boolean {
async function openNativeTerminal(shellCommand: string): Promise {
if (process.platform === 'win32') {
+ const escapedCommand = shellCommand.replace(/"/g, '""').replace(/\$/g, '`$')
await execFileAsync('powershell.exe', [
'-NoProfile',
'-Command',
- "Start-Process -FilePath powershell.exe -ArgumentList @('-NoExit', '-Command', $args[0])",
- shellCommand,
+ `Start-Process -FilePath powershell.exe -ArgumentList @('-NoExit', '-Command', "${escapedCommand}")`,
], {
encoding: 'utf-8',
timeout: 8000,
@@ -508,6 +512,34 @@ async function findCommandPaths(command: string, env: NodeJS.ProcessEnv): Promis
}
}
+function windowsCommandNeedsShell(command: string): boolean {
+ const extension = extname(command).toLowerCase()
+ return extension === '.cmd' || extension === '.bat'
+}
+
+async function resolveCommandForExecution(command: string, env: NodeJS.ProcessEnv): Promise {
+ if (process.platform !== 'win32') return command
+ const paths = await findCommandPaths(command, env)
+ // On Windows, prioritize paths with .cmd or .bat extensions since where may return
+ // both the unix-style script (without extension) and the Windows shim (.cmd)
+ const windowsPath = paths.find(path => windowsCommandNeedsShell(path))
+ return windowsPath || paths[0] || command
+}
+
+function commandExecution(command: string, args: string[]): { command: string; args: string[] } {
+ if (process.platform === 'win32' && windowsCommandNeedsShell(command)) {
+ // For CMD /C, the command and args need to be passed as a single string
+ // The command path should be quoted if it contains spaces, but args are joined directly
+ const commandArg = / /.test(command) ? `"${command}"` : command
+ const argsString = args.map(arg => / /.test(arg) ? `"${arg}"` : arg).join(' ')
+ return {
+ command: 'cmd.exe',
+ args: ['/d', '/s', '/c', `${commandArg} ${argsString}`],
+ }
+ }
+ return { command, args }
+}
+
function packageParts(packageName: string): string[] {
return packageName.split('/').filter(Boolean)
}
@@ -601,11 +633,14 @@ export function getCodingAgentConfigFileDefinitions(id: string): CodingAgentConf
export async function getCodingAgentStatus(definition: CodingAgentDefinition): Promise {
try {
- const { stdout, stderr } = await execFileAsync(definition.command, ['--version'], {
+ const env = await commandEnv()
+ const resolvedCommand = await resolveCommandForExecution(definition.command, env)
+ const execution = commandExecution(resolvedCommand, ['--version'])
+ const { stdout, stderr } = await execFileAsync(execution.command, execution.args, {
encoding: 'utf-8',
timeout: 8000,
windowsHide: true,
- env: await commandEnv(),
+ env,
})
const rawVersion = `${stdout || ''}${stderr || ''}`.trim()
return {
@@ -864,16 +899,21 @@ export async function prepareCodingAgentLaunch(id: string, input: CodingAgentLau
: null
const claudeBaseUrl = proxyTarget?.baseUrl || baseUrl
const claudeApiKey = proxyTarget?.token || apiKey
+ const modelName = displayNameForModel(model)
const settings = {
+ model,
env: {
...(claudeApiKey ? { ANTHROPIC_API_KEY: claudeApiKey } : {}),
...(claudeBaseUrl ? { ANTHROPIC_BASE_URL: claudeBaseUrl } : {}),
- ANTHROPIC_DEFAULT_HAIKU_MODEL: CLAUDE_PROXY_HAIKU_MODEL,
- ANTHROPIC_DEFAULT_HAIKU_MODEL_NAME: model,
- ANTHROPIC_DEFAULT_SONNET_MODEL: CLAUDE_PROXY_SONNET_MODEL,
- ANTHROPIC_DEFAULT_SONNET_MODEL_NAME: model,
- ANTHROPIC_DEFAULT_OPUS_MODEL: CLAUDE_PROXY_OPUS_MODEL,
- ANTHROPIC_DEFAULT_OPUS_MODEL_NAME: model,
+ ANTHROPIC_MODEL: model,
+ ANTHROPIC_CUSTOM_MODEL_OPTION: model,
+ ANTHROPIC_CUSTOM_MODEL_OPTION_NAME: modelName,
+ ANTHROPIC_DEFAULT_HAIKU_MODEL: model,
+ ANTHROPIC_DEFAULT_HAIKU_MODEL_NAME: modelName,
+ ANTHROPIC_DEFAULT_SONNET_MODEL: model,
+ ANTHROPIC_DEFAULT_SONNET_MODEL_NAME: modelName,
+ ANTHROPIC_DEFAULT_OPUS_MODEL: model,
+ ANTHROPIC_DEFAULT_OPUS_MODEL_NAME: modelName,
},
}
await writeScopedFile('settings', `${JSON.stringify(settings, null, 2)}\n`)
diff --git a/tests/server/coding-agents-launch.test.ts b/tests/server/coding-agents-launch.test.ts
index 08263c6..127b54b 100644
--- a/tests/server/coding-agents-launch.test.ts
+++ b/tests/server/coding-agents-launch.test.ts
@@ -108,17 +108,21 @@ describe('coding agent launch preparation', () => {
expect(result.shellCommand).not.toContain('--model')
const settings = JSON.parse(readFileSync(join(result.rootDir, 'settings.json'), 'utf-8'))
+ expect(settings.model).toBe('cognitivecomputations/dolphin-mistral-24b-venice-edition:free')
expect(settings.env.ANTHROPIC_API_KEY).toMatch(/^hwui_/)
expect(settings.env.ANTHROPIC_BASE_URL).toMatch(/^http:\/\/127\.0\.0\.1:\d+\/api\/claude-code-proxy\/.+$/)
expect(settings.env).toMatchObject({
- ANTHROPIC_DEFAULT_HAIKU_MODEL: 'claude-haiku-4-5',
- ANTHROPIC_DEFAULT_HAIKU_MODEL_NAME: 'cognitivecomputations/dolphin-mistral-24b-venice-edition:free',
- ANTHROPIC_DEFAULT_SONNET_MODEL: 'claude-sonnet-4-6',
- ANTHROPIC_DEFAULT_SONNET_MODEL_NAME: 'cognitivecomputations/dolphin-mistral-24b-venice-edition:free',
- ANTHROPIC_DEFAULT_OPUS_MODEL: 'claude-opus-4-7',
- ANTHROPIC_DEFAULT_OPUS_MODEL_NAME: 'cognitivecomputations/dolphin-mistral-24b-venice-edition:free',
+ ANTHROPIC_MODEL: 'cognitivecomputations/dolphin-mistral-24b-venice-edition:free',
+ ANTHROPIC_CUSTOM_MODEL_OPTION: 'cognitivecomputations/dolphin-mistral-24b-venice-edition:free',
+ ANTHROPIC_CUSTOM_MODEL_OPTION_NAME: 'Dolphin Mistral 24b Venice Edition:Free',
+ ANTHROPIC_DEFAULT_HAIKU_MODEL: 'cognitivecomputations/dolphin-mistral-24b-venice-edition:free',
+ ANTHROPIC_DEFAULT_HAIKU_MODEL_NAME: 'Dolphin Mistral 24b Venice Edition:Free',
+ ANTHROPIC_DEFAULT_SONNET_MODEL: 'cognitivecomputations/dolphin-mistral-24b-venice-edition:free',
+ ANTHROPIC_DEFAULT_SONNET_MODEL_NAME: 'Dolphin Mistral 24b Venice Edition:Free',
+ ANTHROPIC_DEFAULT_OPUS_MODEL: 'cognitivecomputations/dolphin-mistral-24b-venice-edition:free',
+ ANTHROPIC_DEFAULT_OPUS_MODEL_NAME: 'Dolphin Mistral 24b Venice Edition:Free',
})
- expect(settings.env).not.toHaveProperty('ANTHROPIC_MODEL')
+ expect(settings.env.ANTHROPIC_DEFAULT_SONNET_MODEL).not.toBe('claude-sonnet-4-6')
})
it('keeps Claude Code protocol overrides behind the local proxy', async () => {