Skip to content

feat(local-ai): expose compact service gateway and request logs#210

Open
awsl233777 wants to merge 11 commits into
mainfrom
feat/local-ai-service-provider-console
Open

feat(local-ai): expose compact service gateway and request logs#210
awsl233777 wants to merge 11 commits into
mainfrom
feat/local-ai-service-provider-console

Conversation

@awsl233777

@awsl233777 awsl233777 commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

@awsl

Summary

  • Add an independent 本地服务 navigation section with sibling entries 服务 and 请求.
  • Keep 服务 focused on Local AI provider/gateway configuration.
  • Merge client connection info into a compact local gateway row: status, Base URL, POST /chat, GET /models, and copy action.
  • Remove the separate client config block from 请求; keep that page focused on request items and selected request detail.
  • Reuse cached Local AI overview/requestLog when switching between 服务 and 请求; refresh pulls latest data explicitly.
  • Keep request rows itemized: list rows show status/summary; selected detail is rendered only for the selected request.
  • Keep Local AI upstream fixes from this PR: optional API key/model, passthrough or mapped model, maxx duplicated /v1 path tolerance, and ignored upstream-error retries.

Validation

  • npm run lint — passed, 579 files.
  • npm run test:unit — passed, 763 tests.
  • npm run test:e2e — passed.
  • Chrome CDP UI regression — passed.
    • Verified compact gateway row on 服务: status, Base URL, POST /chat, GET /models, copy action.
    • Verified 请求 no longer renders client config / gateway summary.
    • Verified manual refresh loads request items.
    • Verified selected request detail renders body only after selection.
    • Verified mock upstream retry chain: 502 -> 502 -> 200.

Screenshot/manual evidence produced

  • tmp/e2e-gateway-compact/screens/01-service-gateway-compact.png
  • tmp/e2e-gateway-compact/screens/02-service-copy-action.png
  • tmp/e2e-gateway-compact/screens/03-requests-without-client-config.png
  • tmp/e2e-gateway-compact/screens/04-requests-selected-detail.png
  • tmp/e2e-gateway-compact/screens/05-mock-upstream-retry-trace.png

Notes

  • This PR does not merge anything automatically.
  • CodeRabbit was not checked or triggered by this update.
  • UI/API status output and screenshots do not expose full API keys.

Summary by CodeRabbit

  • New Features

    • Added a new Local AI tab with provider management, gateway status, model discovery, and request logs.
    • Updated task orchestration to use a more guided “start work” flow with readiness, privacy, and run-status messaging.
    • Added improved provider fallback handling so chat requests can try alternate providers automatically.
  • Bug Fixes

    • Navigation now keeps the app on the correct task-related tabs.
    • Disabled orchestration tabs and side items now behave consistently and are clearly marked.

@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Task-local OpenAI/Claude provider config is now stored and resolved per task, local bridge requests can fail over across upstreams, and the web UI adds a Local AI surface, Kilo webview host state, locale updates, and matching tests.

Changes

Task-local provider and orchestration surface

Layer / File(s) Summary
Task-local provider storage
cli.js
Task-local provider records, webview bridge config, and task OpenAI chat status are read/written from task-local stores, with provider/model normalization and selection helpers.
Task OpenAI chat fallback
cli.js
Task OpenAI chat execution now builds authorized candidates from the task-local store, tries them sequentially, and records fallback attempts and final status metadata.
Local bridge failover
cli/local-bridge.js
Local bridge routing now normalizes eligible paths, logs requests, retries ignored errors, and fails over /bridge/local/v1/* across multiple upstream providers.
Kilo webview host and orchestration wiring
web-ui/app.js, web-ui/modules/app.computed.main-tabs.mjs, web-ui/modules/app.methods.navigation.mjs, web-ui/modules/app.methods.task-orchestration.mjs, web-ui/session-helpers.mjs, web-ui/partials/index/panel-dashboard.html, web-ui/partials/index/panel-orchestration.html, web-ui/partials/index/layout-header.html, web-ui/index.html, web-ui/styles.css, web-ui/styles/layout-shell.css, web-ui/styles/navigation-panels.css, web-ui/styles/local-ai-service.css, web-ui/partials/index/panel-local-ai.html, web-ui/partials/index/panel-local-requests.html
The web UI adds Local AI and local request panels, a Kilo snapshot/bridge host, orchestration message routing and provider setup flows, tab routing changes, panel rewrites, and supporting state, template, and styling wiring.

Locale strings for Local AI, Kilo, and readiness copy

Layer / File(s) Summary
Locale strings for Local AI, Kilo, and readiness copy
web-ui/modules/i18n/locales/en.mjs, web-ui/modules/i18n/locales/ja.mjs, web-ui/modules/i18n/locales/vi.mjs, web-ui/modules/i18n/locales/zh-tw.mjs, web-ui/modules/i18n/locales/zh.mjs
Locale files add Local AI, Kilo, readiness, privacy, and runtime-copy keys and replace several /plan/preview-oriented strings.

Provider, webview, and UI tests

Layer / File(s) Summary
Provider, webview, and UI tests
tests/e2e/test-task-orchestration.js, tests/unit/config-tabs-ui.test.mjs, tests/unit/i18n-locales.test.mjs, tests/unit/local-bridge-claude-target-api.test.mjs, tests/unit/session-tab-switch-performance.test.mjs, tests/unit/task-codex-exec-windows.test.mjs, tests/unit/web-ui-behavior-parity.test.mjs, tests/unit/web-ui-logic.test.mjs, tests/unit/web-ui-runtime-navigation-regression.test.mjs
E2E and unit tests cover task-local provider selection, Local AI gateway behavior, webview host normalization, orchestration UI updates, tab switching, and locale expectations.

Estimated code review effort: 5 (Critical) | ~120 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: exposing the Local AI service gateway and request logs.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/local-ai-service-provider-console

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

🧹 Nitpick comments (1)
web-ui/modules/task-kilo-webview-host.mjs (1)

193-228: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

postMessage pre-normalizes with a default-only config, silently stripping the raw message for anything outside the hardcoded allow-list.

postMessage() calls normalizeTaskKiloWebviewMessage(message) without passing the real bridgeConfig, so it only checks membership against the hardcoded KILO_ALLOWED_WEBVIEW_MESSAGE_TYPES/KILO_BLOCKED_WEBVIEW_MESSAGE_TYPES. normalizeTaskKiloWebviewMessage only attaches message: raw when allowed is true (Line 190). For any type that is only allowed via the documented allowedMessageTypes extension in normalizeTaskKiloBridgeConfig (Lines 35-57), this first pass returns allowed:false and drops the payload. Downstream, handleTaskKiloWebviewMessage re-normalizes using the real state.webviewBridgeConfig (which could allow the type), but by then it only receives decision.message || decision — i.e. the decision object itself, not the original payload (text/content fields are gone).

This defeats the purpose of the allowedMessageTypes extension mechanism for any type not already hardcoded. It's not actively triggered by current in-app usage (the handler's own type dispatch is a closed set), but it's a latent correctness gap in a documented extension point.

♻️ Proposed fix: always retain the raw message on the decision object
 export function normalizeTaskKiloWebviewMessage(message, bridgeConfig = {}) {
     const raw = message && typeof message === 'object' ? message : {};
     const type = String(raw.type || '').trim();
     const config = normalizeTaskKiloBridgeConfig(bridgeConfig);
     const allowedSet = new Set(config.allowedMessageTypes);
     const blockedSet = new Set(config.blockedMessageTypes);
     if (!type) {
         return { allowed: false, blocked: false, type: '', reason: 'missing-type' };
     }
     if (blockedSet.has(type)) {
-        return { allowed: false, blocked: true, type, reason: 'blocked-sensitive-host-capability' };
+        return { allowed: false, blocked: true, type, reason: 'blocked-sensitive-host-capability', message: raw };
     }
     if (!allowedSet.has(type)) {
-        return { allowed: false, blocked: false, type, reason: 'not-whitelisted' };
+        return { allowed: false, blocked: false, type, reason: 'not-whitelisted', message: raw };
     }
     return { allowed: true, blocked: false, type, reason: '', message: raw };
 }

Also applies to: 175-191

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web-ui/modules/task-kilo-webview-host.mjs` around lines 193 - 228,
`createTaskKiloVsCodeApiShim` is dropping the original payload because
`postMessage()` normalizes with no bridge config before
`handleTaskKiloWebviewMessage` can apply the real `state.webviewBridgeConfig`.
Update `normalizeTaskKiloWebviewMessage` so it always preserves the raw
`message` on the returned decision object, even when `allowed` is false, and
keep `postMessage()` passing through that full decision so later
re-normalization in `handleTaskKiloWebviewMessage` can still access the original
fields.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@web-ui/modules/app.methods.task-orchestration.mjs`:
- Around line 246-257: The background refresh in loadTaskOrchestrationOverview()
is overwriting in-progress edits in state.webviewBridgeDraft while the
device-settings panel is open. Update the sync path around
syncTaskWebviewBridgeDraft() and the state.providerSetupSelection reset so they
are skipped or merged when the settings panel is active, instead of blindly
replacing the current draft on each polling refresh.

---

Nitpick comments:
In `@web-ui/modules/task-kilo-webview-host.mjs`:
- Around line 193-228: `createTaskKiloVsCodeApiShim` is dropping the original
payload because `postMessage()` normalizes with no bridge config before
`handleTaskKiloWebviewMessage` can apply the real `state.webviewBridgeConfig`.
Update `normalizeTaskKiloWebviewMessage` so it always preserves the raw
`message` on the returned decision object, even when `allowed` is false, and
keep `postMessage()` passing through that full decision so later
re-normalization in `handleTaskKiloWebviewMessage` can still access the original
fields.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 215367cd-cfb5-4ee2-9733-a66c9e4d0a6a

📥 Commits

Reviewing files that changed from the base of the PR and between a088933 and 35b641f.

📒 Files selected for processing (27)
  • cli.js
  • tests/e2e/test-task-orchestration.js
  • tests/unit/config-tabs-ui.test.mjs
  • tests/unit/i18n-locales.test.mjs
  • tests/unit/session-tab-switch-performance.test.mjs
  • tests/unit/task-codex-exec-windows.test.mjs
  • tests/unit/web-ui-behavior-parity.test.mjs
  • tests/unit/web-ui-logic.test.mjs
  • web-ui/app.js
  • web-ui/index.html
  • web-ui/modules/app.computed.main-tabs.mjs
  • web-ui/modules/app.methods.navigation.mjs
  • web-ui/modules/app.methods.task-orchestration.mjs
  • web-ui/modules/i18n/locales/en.mjs
  • web-ui/modules/i18n/locales/ja.mjs
  • web-ui/modules/i18n/locales/vi.mjs
  • web-ui/modules/i18n/locales/zh-tw.mjs
  • web-ui/modules/i18n/locales/zh.mjs
  • web-ui/modules/task-kilo-webview-host.mjs
  • web-ui/partials/index/layout-header.html
  • web-ui/partials/index/panel-local-ai.html
  • web-ui/partials/index/panel-orchestration.html
  • web-ui/res/web-ui-render.precompiled.js
  • web-ui/session-helpers.mjs
  • web-ui/styles.css
  • web-ui/styles/local-ai-service.css
  • web-ui/styles/task-orchestration.css
📜 Review details
🧰 Additional context used
🪛 ast-grep (0.44.0)
tests/e2e/test-task-orchestration.js

[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: require('child_process')
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process)


[warning] 123-142: Filesystem path is not a string literal; a request-/variable-derived path can enable path traversal. Validate and normalize the path before use.
Context: fs.writeFileSync(scriptPath, const http = require('http'); const fs = require('fs'); const portFile = process.argv[2]; const status = Number(process.argv[3] || 401); const message = process.argv[4] || 'Invalid token'; const server = http.createServer((req, res) => { req.resume(); req.on('end', () => { const body = JSON.stringify({ error: { message } }); res.writeHead(status, { 'Content-Type': 'application/json; charset=utf-8', 'Content-Length': Buffer.byteLength(body, 'utf-8') }); res.end(body, 'utf-8'); }); }); server.listen(0, '127.0.0.1', () => { fs.writeFileSync(portFile, String(server.address().port), 'utf-8'); }); process.on('SIGTERM', () => server.close(() => process.exit(0))); process.on('SIGINT', () => server.close(() => process.exit(0)));, 'utf-8')
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(detect-non-literal-fs-filename)


[warning] 151-151: Filesystem path is not a string literal; a request-/variable-derived path can enable path traversal. Validate and normalize the path before use.
Context: fs.readFileSync(portFile, 'utf-8')
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(detect-non-literal-fs-filename)


[warning] 186-186: Filesystem path is not a string literal; a request-/variable-derived path can enable path traversal. Validate and normalize the path before use.
Context: fs.writeFileSync(path.join(configDir, 'codexmate-init.json'), JSON.stringify({ version: 1, mode: 'task-openai-chat-e2e' }), 'utf-8')
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(detect-non-literal-fs-filename)


[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: require('child_process')
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process)


[warning] 229-229: Filesystem path is not a string literal; a request-/variable-derived path can enable path traversal. Validate and normalize the path before use.
Context: fs.writeFileSync(path.join(configDir, 'codexmate-init.json'), JSON.stringify({ version: 1, mode: 'task-openai-chat-fallback-e2e' }), 'utf-8')
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(detect-non-literal-fs-filename)


[warning] 230-256: Filesystem path is not a string literal; a request-/variable-derived path can enable path traversal. Validate and normalize the path before use.
Context: fs.writeFileSync(path.join(configDir, 'config.toml'), [
'model = "gpt-5.3-codex"',
'model_provider = "local"',
'',
'[model_providers.local]',
'name = "Local Codex"',
'base_url = "http://127.0.0.1:9/v1"',
'wire_api = "responses"',
'preferred_auth_method = "sk-codex-tab-secret"',
'models = ["gpt-5.3-codex"]',
'',
'[model_providers.bad-chat]',
'name = "Bad Chat"',
base_url = "${badBaseUrl}/v1",
'wire_api = "chat_completions"',
'preferred_auth_method = "sk-bad-token"',
'models = ["bad-model"]',
'',
'[model_providers.new-api-chat]',
'name = "New API Chat"',
base_url = "${fallbackBaseUrl}/v1",
'wire_api = "chat_completions"',
'preferred_auth_method = "sk-task-e2e-secret"',
'temperature = 0.7',
'models = ["glm-5.2"]',
''
].join('\n'), 'utf-8')
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(detect-non-literal-fs-filename)


[warning] 257-279: Filesystem path is not a string literal; a request-/variable-derived path can enable path traversal. Validate and normalize the path before use.
Context: fs.writeFileSync(path.join(taskConfigDir, 'task-openai-chat-provider.json'), JSON.stringify({
selectedProvider: 'bad-chat',
selectedModels: { 'bad-chat': 'bad-model', 'new-api-chat': 'glm-5.2' },
providers: {
'bad-chat': {
name: 'Bad Chat',
base_url: ${badBaseUrl}/v1,
wire_api: 'chat_completions',
preferred_auth_method: 'sk-bad-token',
models: ['bad-model'],
selected_model: 'bad-model'
},
'new-api-chat': {
name: 'New API Chat',
base_url: ${fallbackBaseUrl}/v1,
wire_api: 'chat_completions',
preferred_auth_method: 'sk-task-e2e-secret',
temperature: 0.7,
models: ['glm-5.2'],
selected_model: 'glm-5.2'
}
}
}, null, 2), 'utf-8')
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(detect-non-literal-fs-filename)


[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: require('child_process')
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process)


[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: require('child_process')
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process)

🪛 HTMLHint (1.9.2)
web-ui/partials/index/panel-local-ai.html

[error] 1-1: Doctype must be declared before any non-comment content.

(doctype-first)

🪛 Stylelint (17.14.0)
web-ui/styles.css

[error] 14-14: Expected "url('./styles/local-ai-service.css')" to be "'./styles/local-ai-service.css'" (import-notation)

(import-notation)

🔇 Additional comments (51)
cli.js (12)

252-252: LGTM!


12525-12542: LGTM!


16096-16097: LGTM!


16190-16208: LGTM!


16225-16308: LGTM!


16320-16400: LGTM!


16402-16467: LGTM!


16469-16574: LGTM!


16575-16628: LGTM!


16867-16893: LGTM!


17020-17036: LGTM!


16938-16988: 🩺 Stability & Availability

No issue: registerAbort replaces the previous handler, so fallback attempts do not stack abort callbacks.

			> Likely an incorrect or invalid review comment.
tests/e2e/test-task-orchestration.js (5)

121-180: LGTM!


182-223: LGTM!


225-281: LGTM!


485-502: LGTM!


824-852: LGTM!

web-ui/modules/task-kilo-webview-host.mjs (2)

1-57: LGTM!


230-387: LGTM!

web-ui/app.js (1)

479-488: LGTM!

Also applies to: 562-562, 620-622

web-ui/index.html (1)

23-23: LGTM!

web-ui/modules/app.methods.task-orchestration.mjs (2)

1-5: LGTM!

Also applies to: 32-54, 332-510, 618-1131


356-375: 🎯 Functional Correctness

item.key matches the backend shape here. localAiServiceStatus.providerOptions includes key, and selectedProvider is resolved against that same field, so this prefill path is fine.

			> Likely an incorrect or invalid review comment.
web-ui/modules/i18n/locales/en.mjs (1)

135-165: LGTM!

Also applies to: 247-247, 997-1003, 1014-1064, 1080-1115, 1124-1126, 1149-1171, 1192-1200, 1228-1241, 1276-1298

web-ui/modules/i18n/locales/ja.mjs (2)

136-166: 📐 Maintainability & Code Quality | ⚡ Quick win

New Local AI Service / rebuilding strings left untranslated (still English).

Lines 136-166 add the new tab.localAi, localAi.*, orchestration.rebuilding.*, and orchestration.openai.status.present keys, but the Japanese values are copy-pasted English text (e.g. 'title.localAi': 'Local AI Service', 'localAi.body': 'Add Claude or OpenAI-compatible providers...') rather than Japanese translations. Every other new block added in this same file (readiness, kilo, toast, quick-entry copy) is properly translated to Japanese, so this looks like an isolated oversight for this one block. zh.mjs's equivalent block is correctly localized for comparison.


248-248: LGTM!

Also applies to: 986-992, 1001-1063, 1086-1124, 1138-1160, 1181-1190, 1217-1230, 1265-1287

web-ui/modules/i18n/locales/vi.mjs (2)

96-126: 📐 Maintainability & Code Quality | ⚡ Quick win

New Local AI Service / rebuilding strings left untranslated (still English).

Lines 96-126 add the new tab.localAi, localAi.*, orchestration.rebuilding.*, and orchestration.openai.status.present keys, but the Vietnamese values are the raw English text (e.g. 'localAi.title': 'Local AI Service', 'localAi.body': 'Add Claude or OpenAI-compatible providers...') rather than Vietnamese translations. All other new blocks added in this file (readiness, kilo, toast/privacy, quick-entry copy) are properly translated to Vietnamese, so this appears to be an isolated oversight for this one block. zh.mjs's equivalent block is correctly localized for comparison.


262-262: LGTM!

Also applies to: 333-348, 1132-1138, 1149-1209, 1232-1270, 1293-1301, 1329-1342, 1377-1399

web-ui/modules/i18n/locales/zh.mjs (1)

135-165: LGTM!

Also applies to: 247-247, 996-1002, 1013-1063, 1079-1125, 1148-1170, 1191-1199, 1227-1240, 1275-1297

web-ui/modules/app.methods.navigation.mjs (1)

8-21: LGTM!

web-ui/partials/index/layout-header.html (2)

327-339: 🎯 Functional Correctness | ⚡ Quick win

Hardcoded "providers" text bypasses i18n.

The provider count suffix is a literal English string instead of a translation key, so it won't localize for ja/vi/zh-tw/zh users even though this PR adds locale coverage for the Local AI tab.

🌐 Proposed fix
                         <div class="side-item-meta">
                             <span>{{ t('side.localAi.meta') }}</span>
-                            <span>{{ taskOrchestration.localAiServiceStatus && taskOrchestration.localAiServiceStatus.providerOptions ? taskOrchestration.localAiServiceStatus.providerOptions.length : 0 }} providers</span>
+                            <span>{{ t('side.localAi.providerCount', { count: taskOrchestration.localAiServiceStatus && taskOrchestration.localAiServiceStatus.providerOptions ? taskOrchestration.localAiServiceStatus.providerOptions.length : 0 }) }}</span>
                         </div>

76-85: LGTM!

web-ui/partials/index/panel-local-ai.html (1)

1-115: LGTM!

web-ui/styles.css (1)

14-14: LGTM!

web-ui/styles/local-ai-service.css (1)

1-183: LGTM!

web-ui/partials/index/panel-orchestration.html (3)

9-38: LGTM!


41-76: LGTM!

Also applies to: 82-131


105-110: 🩺 Stability & Availability

Guard the .trim() calls if these fields can ever be unset. The current defaults are empty strings, so this only becomes a crash risk if a later code path assigns null or undefined.

web-ui/modules/app.computed.main-tabs.mjs (1)

508-517: 🎯 Functional Correctness

No sync issue here taskOrchestrationProviderConfigRequired() matches taskOpenAiChatConfigRequired(), so the header state stays aligned.

			> Likely an incorrect or invalid review comment.
web-ui/session-helpers.mjs (1)

89-89: LGTM!

Also applies to: 145-150

web-ui/modules/i18n/locales/zh-tw.mjs (1)

135-165: LGTM!

Also applies to: 247-247, 996-1002, 1013-1063, 1079-1125, 1148-1170, 1191-1199, 1227-1240, 1275-1297

tests/unit/config-tabs-ui.test.mjs (3)

9-25: LGTM!


211-220: 🗄️ Data Integrity & Integration

Confirm localAiServiceStatus and openAiChatStatus stay in sync.

The Local AI panel reads provider state from taskOrchestration.localAiServiceStatus.providerOptions (Line 218), while the task-detail/kilo-webview snapshot code paths (per web-ui-logic.test.mjs) read from a separately-named taskOrchestration.openAiChatStatus. Since both surfaces manage the same underlying OpenAI/Claude provider store, please confirm these two status objects are populated from a single source of truth (e.g., both refreshed by the same task-overview response) rather than independently, to avoid the Local AI tab and task tab showing inconsistent provider readiness.


97-210: LGTM!

Also applies to: 221-242

tests/unit/i18n-locales.test.mjs (1)

196-213: LGTM!

Also applies to: 228-250

tests/unit/session-tab-switch-performance.test.mjs (1)

170-192: LGTM!

tests/unit/task-codex-exec-windows.test.mjs (1)

139-192: LGTM!

Also applies to: 194-240, 241-279, 280-311, 313-332, 364-380

tests/unit/web-ui-behavior-parity.test.mjs (2)

794-810: 🎯 Functional Correctness

Verify previewTaskPlan/previewTaskPlanFromChat aren't double-counted across allowlists.

'previewTaskPlan' + 'FromChat' is allowlisted as an "extra" (newly-present) method key at Line 806, but 'previewTaskPlan' and 'previewTaskPlan' + 'FromChat' are also added to the "missing" (allowed-absent) allowlist at Lines 846-847. If both lists diff against the same baseline snapshot, a method name can't legitimately be both newly-added and missing at once — please confirm this isn't a stale/copy-pasted allowlist entry masking a genuine rename/removal that should only appear in one list.

Also applies to: 845-847


821-821: LGTM!

Also applies to: 923-924

tests/unit/web-ui-logic.test.mjs (2)

2185-2261: 🎯 Functional Correctness | ⚡ Quick win

Confirm Local AI Service tab doesn't reuse task-chat-specific save wording.

saveLocalAiProvider is exercised here in the task-orchestration detail context and produces the toast 'orchestration.openai.config.saveSuccess' ("任务聊天 Provider 已保存" / "task chat Provider saved" — Line 2213/2261). The same method is also wired to panel-local-ai.html's form (@submit.prevent="saveLocalAiProvider", per config-tabs-ui.test.mjs), and the new localAi.* locale block added in this PR has no dedicated save-success/failure strings. If the toast text isn't context-aware, users saving a provider from the dedicated "Local AI Service" tab would see a confusing "task chat provider saved" message instead of copy referencing the Local AI Service they're actually configuring.


18-24: LGTM!

Also applies to: 1140-1140, 1308-1345, 1392-1417, 1419-1467, 1477-1477, 1509-1510, 1546-1546, 1695-1697, 1784-1785, 1826-2051, 2082-2105, 2132-2132, 2368-2368

Comment thread web-ui/modules/app.methods.task-orchestration.mjs

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
web-ui/partials/index/layout-header.html (1)

326-336: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Hardcoded "providers" string bypasses i18n.

Unlike the adjacent t('side.localAi.meta'), the provider-count suffix is a raw literal providers, so it won't be translated for ja/vi/zh-tw/zh locales even though this PR adds locale entries for Local AI Service copy elsewhere.

🌐 Proposed fix
-                        <span>{{ taskOrchestration.localAiServiceStatus && taskOrchestration.localAiServiceStatus.providerOptions ? taskOrchestration.localAiServiceStatus.providerOptions.length : 0 }} providers</span>
+                        <span>{{ t('side.localAi.providerCount', { count: taskOrchestration.localAiServiceStatus && taskOrchestration.localAiServiceStatus.providerOptions ? taskOrchestration.localAiServiceStatus.providerOptions.length : 0 }) }}</span>

(requires adding a side.localAi.providerCount key with pluralization support to the locale files)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web-ui/partials/index/layout-header.html` around lines 326 - 336, The Local
AI provider-count label in layout-header.html is hardcoded as a literal string,
so it bypasses i18n. Update the template around the local-ai tab to use a
translation key instead of appending "providers" directly, and wire it to a new
side.localAi.providerCount locale entry with pluralization support across the
existing locale files. Keep the change localized to the local-ai tab markup and
the related locale resources.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@web-ui/modules/app.methods.navigation.mjs`:
- Around line 445-449: The navigation intent clearing logic is using the wrong
tab value after the orchestration redirect. In the `targetTab` flow inside the
navigation method, make sure the later `clearMainTabSwitchIntent` calls use
`targetTab` instead of `normalizedTab`, so the `orchestration` to `local-ai`
mapping is respected and the intent/classes are cleared correctly.

---

Outside diff comments:
In `@web-ui/partials/index/layout-header.html`:
- Around line 326-336: The Local AI provider-count label in layout-header.html
is hardcoded as a literal string, so it bypasses i18n. Update the template
around the local-ai tab to use a translation key instead of appending
"providers" directly, and wire it to a new side.localAi.providerCount locale
entry with pluralization support across the existing locale files. Keep the
change localized to the local-ai tab markup and the related locale resources.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 20f6ec67-2856-4ceb-a6e5-c4cf71c85f2e

📥 Commits

Reviewing files that changed from the base of the PR and between 35b641f and dca75cf.

📒 Files selected for processing (11)
  • tests/unit/config-tabs-ui.test.mjs
  • tests/unit/session-tab-switch-performance.test.mjs
  • tests/unit/web-ui-runtime-navigation-regression.test.mjs
  • web-ui/modules/app.methods.navigation.mjs
  • web-ui/partials/index/layout-header.html
  • web-ui/partials/index/panel-dashboard.html
  • web-ui/res/web-ui-render.precompiled.js
  • web-ui/session-helpers.mjs
  • web-ui/styles/layout-shell.css
  • web-ui/styles/local-ai-service.css
  • web-ui/styles/navigation-panels.css
✅ Files skipped from review due to trivial changes (1)
  • web-ui/partials/index/panel-dashboard.html
🚧 Files skipped from review as they are similar to previous changes (3)
  • web-ui/styles/local-ai-service.css
  • tests/unit/config-tabs-ui.test.mjs
  • web-ui/session-helpers.mjs
📜 Review details
🔇 Additional comments (8)
web-ui/modules/app.methods.navigation.mjs (1)

8-21: LGTM!

web-ui/partials/index/layout-header.html (3)

66-74: LGTM!


76-84: LGTM!


316-325: LGTM!

web-ui/styles/layout-shell.css (1)

377-389: LGTM!

web-ui/styles/navigation-panels.css (1)

308-323: LGTM!

tests/unit/session-tab-switch-performance.test.mjs (1)

170-214: LGTM!

tests/unit/web-ui-runtime-navigation-regression.test.mjs (1)

340-358: LGTM!

Comment thread web-ui/modules/app.methods.navigation.mjs
@awsl233777 awsl233777 changed the title feat(local-ai): add provider service console feat(local-ai): expose local gateway console Jul 3, 2026
@awsl233777

Copy link
Copy Markdown
Collaborator Author

Implemented the Local AI Gateway direction for this round.

What changed:

  • Reworked the Local AI tab into an OpenAI-compatible local Chat gateway console.
  • Removed manual switching from the UI path; the gateway now presents retry / failover / freeze guard as the routing policy.
  • Extended /bridge/local/v1/* passthrough so Chat Completions forwards real request bodies, applies provider defaults like temperature, passes upstream auth, and exposes upstream/attempt headers for traceability.
  • Added E2E coverage for local gateway health, models, chat completions, secret redaction, and automatic failover from a failing provider to a healthy mock provider.

Validation:

  • npm run lint — passed, 231 files.
  • npm run test:unit — passed, unit All 750 tests passed.
  • npm run test:e2e — passed, exited with code 0.
  • git diff --check — passed.

Screenshot evidence was produced locally for:

  • local-ai-gateway-ui.png
  • mock-provider-interactions.png

CodeRabbit was not checked or triggered.

@coderabbitai coderabbitai Bot left a comment

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 comments (1)
web-ui/partials/index/panel-local-ai.html (1)

22-46: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Repeated deep-chained null guards reduce readability.

Lines 29, 35, and 39 each repeat the same taskOrchestration.localAiServiceStatus && ...gateway && ...gateway.X chain with a hardcoded default. Extracting a computed property (e.g. localAiGateway) returning a normalized object with defaults would remove this duplication and reduce risk of the chains drifting out of sync as more gateway fields are added.

Example refactor
-<code>{{ (taskOrchestration.localAiServiceStatus && taskOrchestration.localAiServiceStatus.gateway && taskOrchestration.localAiServiceStatus.gateway.chatCompletionsPath) || '/bridge/local/v1/chat/completions' }}</code>
+<code>{{ localAiGateway.chatCompletionsPath }}</code>
computed: {
  localAiGateway() {
    const gateway = this.taskOrchestration.localAiServiceStatus?.gateway || {};
    return {
      chatCompletionsPath: gateway.chatCompletionsPath || '/bridge/local/v1/chat/completions',
      basePath: gateway.basePath || '/bridge/local/v1',
      modelsPath: gateway.modelsPath || '/bridge/local/v1/models'
    };
  }
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web-ui/partials/index/panel-local-ai.html` around lines 22 - 46, The local AI
endpoint template repeats the same deep null-check chain for gateway paths in
multiple bindings, which hurts readability and makes the defaults easy to drift.
Refactor the panel rendering around a single computed property or helper such as
localAiGateway that normalizes taskOrchestration.localAiServiceStatus.gateway
with fallback values, then update the bindings for chatCompletionsPath,
basePath, and modelsPath to read from that shared object.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@web-ui/partials/index/panel-local-ai.html`:
- Around line 22-46: The local AI endpoint template repeats the same deep
null-check chain for gateway paths in multiple bindings, which hurts readability
and makes the defaults easy to drift. Refactor the panel rendering around a
single computed property or helper such as localAiGateway that normalizes
taskOrchestration.localAiServiceStatus.gateway with fallback values, then update
the bindings for chatCompletionsPath, basePath, and modelsPath to read from that
shared object.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1c9c1b8d-e07a-4e70-9512-f88be72e7cb6

📥 Commits

Reviewing files that changed from the base of the PR and between dca75cf and 6e8ba30.

📒 Files selected for processing (12)
  • cli.js
  • cli/local-bridge.js
  • tests/e2e/test-task-orchestration.js
  • tests/unit/config-tabs-ui.test.mjs
  • web-ui/modules/i18n/locales/en.mjs
  • web-ui/modules/i18n/locales/ja.mjs
  • web-ui/modules/i18n/locales/vi.mjs
  • web-ui/modules/i18n/locales/zh-tw.mjs
  • web-ui/modules/i18n/locales/zh.mjs
  • web-ui/partials/index/panel-local-ai.html
  • web-ui/res/web-ui-render.precompiled.js
  • web-ui/styles/local-ai-service.css
✅ Files skipped from review due to trivial changes (1)
  • web-ui/modules/i18n/locales/zh-tw.mjs
🚧 Files skipped from review as they are similar to previous changes (7)
  • web-ui/styles/local-ai-service.css
  • web-ui/modules/i18n/locales/vi.mjs
  • web-ui/modules/i18n/locales/zh.mjs
  • cli.js
  • web-ui/modules/i18n/locales/en.mjs
  • tests/unit/config-tabs-ui.test.mjs
  • web-ui/modules/i18n/locales/ja.mjs
📜 Review details
🧰 Additional context used
🪛 ast-grep (0.44.0)
tests/e2e/test-task-orchestration.js

[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: require('child_process')
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process)


[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: require('child_process')
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process)


[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: require('child_process')
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process)


[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: require('child_process')
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process)

🔇 Additional comments (12)
web-ui/partials/index/panel-local-ai.html (4)

48-71: LGTM!


122-163: LGTM! Password field uses type="password" with autocomplete="new-password", and JS-side validation is already handled in saveLocalAiProvider.


164-165: LGTM!


10-20: 🎯 Functional Correctness

No fallback to openAiChatStatus is needed here. task-overview always supplies localAiServiceStatus, and this panel is intentionally driven by local-provider state only, so the readiness badge and provider list should stay keyed to taskOrchestration.localAiServiceStatus.

			> Likely an incorrect or invalid review comment.
cli/local-bridge.js (4)

106-109: LGTM!


195-208: LGTM!


157-161: 🎯 Functional Correctness

Remove this warning: preferred_auth_method is the stored API key here, not an auth enum. The non-codexmate/openai-bridge providers in this repo write secrets like sk-* into preferred_auth_method, and this branch correctly forwards that value as Bearer <token>.

			> Likely an incorrect or invalid review comment.

745-836: 🩺 Stability & Availability

No streaming regression here This passthrough already buffered via proxyRequestJson and returned application/json, so it did not preserve SSE or upstream headers before this change.

			> Likely an incorrect or invalid review comment.
tests/e2e/test-task-orchestration.js (4)

2-2: LGTM!


326-352: LGTM!


514-554: LGTM!


876-921: LGTM!

@awsl233777 awsl233777 force-pushed the feat/local-ai-service-provider-console branch from 6e8ba30 to 63dcf74 Compare July 4, 2026 01:29

@coderabbitai coderabbitai Bot left a comment

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.

♻️ Duplicate comments (1)
web-ui/modules/app.methods.navigation.mjs (1)

445-449: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Nav intent still cleared with the wrong tab value.

The orchestrationlocal-ai remap sets state.intent = 'local-ai' via setMainTabSwitchIntent(targetTab) (line 461), but the intent is still cleared using normalizedTab (still 'orchestration') at lines 492, 514, and 531. Since clearMainTabSwitchIntent bails out when expectedTab !== state.intent (lines 411-413), the guard never clears when navigating away from orchestration, leaving nav-intent DOM classes and pending state stuck.

This is the same issue flagged in a prior review round and remains unaddressed.

🐛 Proposed fix
                 this.scheduleAfterFrame(() => {
-                    this.clearMainTabSwitchIntent(normalizedTab);
+                    this.clearMainTabSwitchIntent(targetTab);
                 });
                 return;
             }

Apply the same targetTab substitution at the other two clearMainTabSwitchIntent(normalizedTab) call sites (lines 514 and 531).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web-ui/modules/app.methods.navigation.mjs` around lines 445 - 449, The
navigation intent is still being cleared with the pre-remap tab value, so the
orchestration-to-local-ai flow leaves state stuck. Update the remaining
clearMainTabSwitchIntent call sites in the navigation method to use the same
targetTab value that is passed to setMainTabSwitchIntent, matching the existing
remap logic in the main tab switch path. Use the identifiers normalizedTab,
targetTab, setMainTabSwitchIntent, and clearMainTabSwitchIntent to locate and
align the affected branches.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@web-ui/modules/app.methods.navigation.mjs`:
- Around line 445-449: The navigation intent is still being cleared with the
pre-remap tab value, so the orchestration-to-local-ai flow leaves state stuck.
Update the remaining clearMainTabSwitchIntent call sites in the navigation
method to use the same targetTab value that is passed to setMainTabSwitchIntent,
matching the existing remap logic in the main tab switch path. Use the
identifiers normalizedTab, targetTab, setMainTabSwitchIntent, and
clearMainTabSwitchIntent to locate and align the affected branches.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 647d6976-90a6-4068-b6fa-7cec5c30c2c1

📥 Commits

Reviewing files that changed from the base of the PR and between 6e8ba30 and 63dcf74.

📒 Files selected for processing (32)
  • cli.js
  • cli/local-bridge.js
  • tests/e2e/test-task-orchestration.js
  • tests/unit/config-tabs-ui.test.mjs
  • tests/unit/i18n-locales.test.mjs
  • tests/unit/session-tab-switch-performance.test.mjs
  • tests/unit/task-codex-exec-windows.test.mjs
  • tests/unit/web-ui-behavior-parity.test.mjs
  • tests/unit/web-ui-logic.test.mjs
  • tests/unit/web-ui-runtime-navigation-regression.test.mjs
  • web-ui/app.js
  • web-ui/index.html
  • web-ui/modules/app.computed.main-tabs.mjs
  • web-ui/modules/app.methods.navigation.mjs
  • web-ui/modules/app.methods.task-orchestration.mjs
  • web-ui/modules/i18n/locales/en.mjs
  • web-ui/modules/i18n/locales/ja.mjs
  • web-ui/modules/i18n/locales/vi.mjs
  • web-ui/modules/i18n/locales/zh-tw.mjs
  • web-ui/modules/i18n/locales/zh.mjs
  • web-ui/modules/task-kilo-webview-host.mjs
  • web-ui/partials/index/layout-header.html
  • web-ui/partials/index/panel-dashboard.html
  • web-ui/partials/index/panel-local-ai.html
  • web-ui/partials/index/panel-orchestration.html
  • web-ui/res/web-ui-render.precompiled.js
  • web-ui/session-helpers.mjs
  • web-ui/styles.css
  • web-ui/styles/layout-shell.css
  • web-ui/styles/local-ai-service.css
  • web-ui/styles/navigation-panels.css
  • web-ui/styles/task-orchestration.css
✅ Files skipped from review due to trivial changes (1)
  • web-ui/index.html
🚧 Files skipped from review as they are similar to previous changes (25)
  • web-ui/partials/index/panel-dashboard.html
  • web-ui/styles/navigation-panels.css
  • web-ui/session-helpers.mjs
  • tests/unit/web-ui-runtime-navigation-regression.test.mjs
  • web-ui/app.js
  • tests/unit/session-tab-switch-performance.test.mjs
  • web-ui/styles/layout-shell.css
  • web-ui/partials/index/layout-header.html
  • web-ui/partials/index/panel-orchestration.html
  • tests/unit/i18n-locales.test.mjs
  • web-ui/modules/i18n/locales/en.mjs
  • tests/unit/config-tabs-ui.test.mjs
  • web-ui/modules/i18n/locales/zh-tw.mjs
  • tests/unit/task-codex-exec-windows.test.mjs
  • web-ui/styles/local-ai-service.css
  • cli/local-bridge.js
  • web-ui/modules/i18n/locales/vi.mjs
  • tests/unit/web-ui-behavior-parity.test.mjs
  • web-ui/modules/i18n/locales/zh.mjs
  • cli.js
  • web-ui/modules/i18n/locales/ja.mjs
  • web-ui/modules/task-kilo-webview-host.mjs
  • web-ui/modules/app.computed.main-tabs.mjs
  • tests/unit/web-ui-logic.test.mjs
  • web-ui/modules/app.methods.task-orchestration.mjs
📜 Review details
🧰 Additional context used
🪛 ast-grep (0.44.0)
tests/e2e/test-task-orchestration.js

[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: require('child_process')
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process)


[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: require('child_process')
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process)


[warning] 124-143: Filesystem path is not a string literal; a request-/variable-derived path can enable path traversal. Validate and normalize the path before use.
Context: fs.writeFileSync(scriptPath, const http = require('http'); const fs = require('fs'); const portFile = process.argv[2]; const status = Number(process.argv[3] || 401); const message = process.argv[4] || 'Invalid token'; const server = http.createServer((req, res) => { req.resume(); req.on('end', () => { const body = JSON.stringify({ error: { message } }); res.writeHead(status, { 'Content-Type': 'application/json; charset=utf-8', 'Content-Length': Buffer.byteLength(body, 'utf-8') }); res.end(body, 'utf-8'); }); }); server.listen(0, '127.0.0.1', () => { fs.writeFileSync(portFile, String(server.address().port), 'utf-8'); }); process.on('SIGTERM', () => server.close(() => process.exit(0))); process.on('SIGINT', () => server.close(() => process.exit(0)));, 'utf-8')
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(detect-non-literal-fs-filename)


[warning] 152-152: Filesystem path is not a string literal; a request-/variable-derived path can enable path traversal. Validate and normalize the path before use.
Context: fs.readFileSync(portFile, 'utf-8')
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(detect-non-literal-fs-filename)


[warning] 187-187: Filesystem path is not a string literal; a request-/variable-derived path can enable path traversal. Validate and normalize the path before use.
Context: fs.writeFileSync(path.join(configDir, 'codexmate-init.json'), JSON.stringify({ version: 1, mode: 'task-openai-chat-e2e' }), 'utf-8')
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(detect-non-literal-fs-filename)


[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: require('child_process')
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process)


[warning] 230-230: Filesystem path is not a string literal; a request-/variable-derived path can enable path traversal. Validate and normalize the path before use.
Context: fs.writeFileSync(path.join(configDir, 'codexmate-init.json'), JSON.stringify({ version: 1, mode: 'task-openai-chat-fallback-e2e' }), 'utf-8')
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(detect-non-literal-fs-filename)


[warning] 231-257: Filesystem path is not a string literal; a request-/variable-derived path can enable path traversal. Validate and normalize the path before use.
Context: fs.writeFileSync(path.join(configDir, 'config.toml'), [
'model = "gpt-5.3-codex"',
'model_provider = "local"',
'',
'[model_providers.local]',
'name = "Local Codex"',
'base_url = "http://127.0.0.1:9/v1"',
'wire_api = "responses"',
'preferred_auth_method = "sk-codex-tab-secret"',
'models = ["gpt-5.3-codex"]',
'',
'[model_providers.bad-chat]',
'name = "Bad Chat"',
base_url = "${badBaseUrl}/v1",
'wire_api = "chat_completions"',
'preferred_auth_method = "sk-bad-token"',
'models = ["bad-model"]',
'',
'[model_providers.new-api-chat]',
'name = "New API Chat"',
base_url = "${fallbackBaseUrl}/v1",
'wire_api = "chat_completions"',
'preferred_auth_method = "sk-task-e2e-secret"',
'temperature = 0.7',
'models = ["glm-5.2"]',
''
].join('\n'), 'utf-8')
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(detect-non-literal-fs-filename)


[warning] 258-280: Filesystem path is not a string literal; a request-/variable-derived path can enable path traversal. Validate and normalize the path before use.
Context: fs.writeFileSync(path.join(taskConfigDir, 'task-openai-chat-provider.json'), JSON.stringify({
selectedProvider: 'bad-chat',
selectedModels: { 'bad-chat': 'bad-model', 'new-api-chat': 'glm-5.2' },
providers: {
'bad-chat': {
name: 'Bad Chat',
base_url: ${badBaseUrl}/v1,
wire_api: 'chat_completions',
preferred_auth_method: 'sk-bad-token',
models: ['bad-model'],
selected_model: 'bad-model'
},
'new-api-chat': {
name: 'New API Chat',
base_url: ${fallbackBaseUrl}/v1,
wire_api: 'chat_completions',
preferred_auth_method: 'sk-task-e2e-secret',
temperature: 0.7,
models: ['glm-5.2'],
selected_model: 'glm-5.2'
}
}
}, null, 2), 'utf-8')
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(detect-non-literal-fs-filename)


[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: require('child_process')
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process)


[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: require('child_process')
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process)


[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: require('child_process')
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process)

🪛 HTMLHint (1.9.2)
web-ui/partials/index/panel-local-ai.html

[error] 1-1: Doctype must be declared before any non-comment content.

(doctype-first)

🪛 Stylelint (17.14.0)
web-ui/styles.css

[error] 14-14: Expected "url('./styles/local-ai-service.css')" to be "'./styles/local-ai-service.css'" (import-notation)

(import-notation)

🔇 Additional comments (9)
web-ui/modules/app.methods.navigation.mjs (1)

14-14: LGTM!

tests/e2e/test-task-orchestration.js (4)

122-181: Mock server lifecycle looks solid.

Startup polling, stderr capture, and graceful SIGTERM→timeout→SIGKILL teardown in close() are well structured. Static-analysis flags on require('child_process') / non-literal fs paths here are not applicable since all paths derive from the test's own tmpHome, not external input.


209-282: Fallback config writers look correct and consistent.

writeOpenAiChatConfig and writeOpenAiChatFallbackConfig correctly mirror config.toml provider entries with the task-openai-chat-provider.json selection metadata, matching the assertions made later (assertOpenAiRequests, assertOpenAiRunPayload).


326-352: LGTM!


876-921: 🩺 Stability & Availability

Verify failover retry loop can't spuriously fail on timing.

The loop asserts x-codexmate-upstream-provider === 'new-api-chat' on every one of the (up to) 4 iterations (Line 889) while only using the loop to wait for x-codexmate-attempted-providers to include 'bad-chat' (Line 890). If the freeze/failover logic in cli/local-bridge.js ever skips retrying the previously-failed provider on a later iteration but still reports the healthy provider without ever recording bad-chat in attempted-providers (e.g., due to a circuit-breaker that permanently excludes it after the very first failure without repopulating the header), this loop would exhaust its 4 attempts and fail at Line 895. This is likely fine given the routing design described in the PR, but the coupling between this test and internal freeze/attempt-header semantics in cli/local-bridge.js (not in this review batch) should be double-checked for flakiness.

web-ui/partials/index/panel-local-ai.html (3)

1-46: LGTM!


84-114: 🎯 Functional Correctness | ⚡ Quick win

Verify provider.key is actually populated on provider objects.

The v-for uses :key="provider.key" (Line 85), but the e2e assertions for both providerOptions and routeProviderOptions (in tests/e2e/test-task-orchestration.js) only ever check name, type, and ready — never key. If the underlying provider objects don't carry a key field, every item's key resolves to undefined, which Vue treats as "no key" without any dev warning, risking stale/mismatched DOM (and input state) when the provider list order or contents change (e.g., after adding the Claude provider mid-session, as the E2E test does).

Consider keying on a guaranteed-unique field such as provider.name (or a composite of name+type) instead.

💡 Suggested fix
-                                    <article v-for="provider in (taskOrchestration.localAiServiceStatus.routeProviderOptions && taskOrchestration.localAiServiceStatus.routeProviderOptions.length ? taskOrchestration.localAiServiceStatus.routeProviderOptions : taskOrchestration.localAiServiceStatus.providerOptions)" :key="provider.key" class="provider-card" :data-provider-type="provider.type">
+                                    <article v-for="provider in (taskOrchestration.localAiServiceStatus.routeProviderOptions && taskOrchestration.localAiServiceStatus.routeProviderOptions.length ? taskOrchestration.localAiServiceStatus.routeProviderOptions : taskOrchestration.localAiServiceStatus.providerOptions)" :key="provider.key || provider.name" class="provider-card" :data-provider-type="provider.type">

115-163: LGTM!

web-ui/styles.css (1)

14-14: LGTM!

@awsl233777 awsl233777 changed the title feat(local-ai): expose local gateway console feat(local-ai): isolate local gateway console Jul 4, 2026
@awsl233777

Copy link
Copy Markdown
Collaborator Author

Implemented the recommended A+B direction for this round.

What changed:

  • Reworked the Local AI tab into an endpoint-first console with essential gateway stage, four-step flow, gateway trace, routing policy, and private provider stack.
  • Introduced ~/.codexmate/local-ai-gateway.json as the independent Local AI store.
  • Changed Local AI overview/save/select and /bridge/local/v1/* routing to use the Local AI store instead of Codex config or the legacy Tasks provider store.
  • Added regression traps so Codex/Tasks providers are present but must not be used by Local AI.

Validation:

  • npm run lint — passed, 231 files.
  • npm run test:unit — passed, unit All 750 tests passed.
  • npm run test:e2e — passed, exited with code 0.
  • npm test — passed, unit + e2e.
  • git diff --check — passed.

Screenshot evidence produced locally:

  • local-ai-essential-ui.png
  • mock-provider-independent-traffic.png

CodeRabbit was not checked or triggered.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 2

🧹 Nitpick comments (1)
web-ui/partials/index/panel-local-ai.html (1)

19-21: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Repeated readiness null-check expression.

taskOrchestration.localAiServiceStatus && taskOrchestration.localAiServiceStatus.ready is duplicated across the hero badge and endpoint card. A computed property (e.g. localAiReady) would reduce duplication.

Also applies to: 33-33

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web-ui/partials/index/panel-local-ai.html` around lines 19 - 21, The Local AI
readiness null-check is duplicated in the template, so replace repeated
`taskOrchestration.localAiServiceStatus &&
taskOrchestration.localAiServiceStatus.ready` usage with a single computed
property such as `localAiReady` and reference it in both the hero badge and
endpoint card. Update the `panel-local-ai` template bindings to use that shared
computed value for both the class and label conditions so the readiness logic
lives in one place and stays consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@web-ui/partials/index/panel-local-ai.html`:
- Around line 159-193: The empty state in panel-local-ai.html can render before
the first overview fetch completes because the current v-if only checks
localAiServiceStatus.providerOptions length. Update the conditional around the
provider list/empty-state to account for taskOrchestration.loading (and any
initial undefined state) so the empty-state only shows after loading has
finished and providerOptions is confirmed empty. Use the existing
taskOrchestration.loading and localAiServiceStatus/providerOptions bindings to
keep the initial render from flashing “no providers.”
- Around line 25-55: The readiness indicator in local-ai-endpoint-card is only
changing text, while local-ai-live-dot stays visually green even when
taskOrchestration.localAiServiceStatus.ready is false. Update the template in
panel-local-ai.html so the dot element also gets a readiness-based class/state,
similar to how local-ai-badge-good is conditionally applied, and then wire the
CSS in local-ai-service.css to render the pulse/color differently for ready
versus incomplete.

---

Nitpick comments:
In `@web-ui/partials/index/panel-local-ai.html`:
- Around line 19-21: The Local AI readiness null-check is duplicated in the
template, so replace repeated `taskOrchestration.localAiServiceStatus &&
taskOrchestration.localAiServiceStatus.ready` usage with a single computed
property such as `localAiReady` and reference it in both the hero badge and
endpoint card. Update the `panel-local-ai` template bindings to use that shared
computed value for both the class and label conditions so the readiness logic
lives in one place and stays consistent.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 983d7a7e-36d5-402d-9350-9cf6f2bae6c5

📥 Commits

Reviewing files that changed from the base of the PR and between 63dcf74 and 0ab0b21.

📒 Files selected for processing (11)
  • cli.js
  • tests/e2e/test-task-orchestration.js
  • tests/unit/config-tabs-ui.test.mjs
  • web-ui/modules/i18n/locales/en.mjs
  • web-ui/modules/i18n/locales/ja.mjs
  • web-ui/modules/i18n/locales/vi.mjs
  • web-ui/modules/i18n/locales/zh-tw.mjs
  • web-ui/modules/i18n/locales/zh.mjs
  • web-ui/partials/index/panel-local-ai.html
  • web-ui/res/web-ui-render.precompiled.js
  • web-ui/styles/local-ai-service.css
✅ Files skipped from review due to trivial changes (2)
  • web-ui/modules/i18n/locales/ja.mjs
  • web-ui/modules/i18n/locales/zh-tw.mjs
🚧 Files skipped from review as they are similar to previous changes (5)
  • web-ui/modules/i18n/locales/vi.mjs
  • tests/unit/config-tabs-ui.test.mjs
  • web-ui/modules/i18n/locales/en.mjs
  • web-ui/modules/i18n/locales/zh.mjs
  • cli.js
📜 Review details
🧰 Additional context used
🪛 ast-grep (0.44.0)
tests/e2e/test-task-orchestration.js

[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: require('child_process')
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process)


[warning] 208-222: Filesystem path is not a string literal; a request-/variable-derived path can enable path traversal. Validate and normalize the path before use.
Context: fs.writeFileSync(path.join(taskConfigDir, 'local-ai-gateway.json'), JSON.stringify({
selectedProvider: 'local-ai-chat',
selectedModels: { 'local-ai-chat': 'glm-5.2' },
providers: {
'local-ai-chat': {
name: 'Local AI Chat',
base_url: ${baseUrl}/v1,
wire_api: 'chat_completions',
preferred_auth_method: 'sk-local-ai-secret',
temperature: 0.7,
models: ['glm-5.2', 'glm-5.2-flash'],
selected_model: 'glm-5.2'
}
}
}, null, 2), 'utf-8')
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(detect-non-literal-fs-filename)


[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: require('child_process')
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process)


[warning] 267-289: Filesystem path is not a string literal; a request-/variable-derived path can enable path traversal. Validate and normalize the path before use.
Context: fs.writeFileSync(path.join(taskConfigDir, 'local-ai-gateway.json'), JSON.stringify({
selectedProvider: 'bad-chat',
selectedModels: { 'bad-chat': 'bad-model', 'local-ai-chat': 'glm-5.2' },
providers: {
'bad-chat': {
name: 'Bad Chat',
base_url: ${badBaseUrl}/v1,
wire_api: 'chat_completions',
preferred_auth_method: 'sk-bad-token',
models: ['bad-model'],
selected_model: 'bad-model'
},
'local-ai-chat': {
name: 'Local AI Chat',
base_url: ${fallbackBaseUrl}/v1,
wire_api: 'chat_completions',
preferred_auth_method: 'sk-local-ai-secret',
temperature: 0.7,
models: ['glm-5.2'],
selected_model: 'glm-5.2'
}
}
}, null, 2), 'utf-8')
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(detect-non-literal-fs-filename)


[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: require('child_process')
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process)


[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: require('child_process')
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process)


[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: require('child_process')
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process)


[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: require('child_process')
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process)

🔇 Additional comments (10)
tests/e2e/test-task-orchestration.js (7)

157-170: LGTM!


200-223: LGTM!


259-290: LGTM!


331-335: LGTM!


549-582: LGTM!


584-594: LGTM!


917-965: LGTM!

web-ui/partials/index/panel-local-ai.html (2)

196-235: LGTM!


105-140: 🎯 Functional Correctness

This panel is intentionally mock-ready; the hardcoded rows are descriptive example content, not live trace output.

			> Likely an incorrect or invalid review comment.
web-ui/styles/local-ai-service.css (1)

1-432: LGTM!

Comment thread web-ui/partials/index/panel-local-ai.html Outdated
Comment thread web-ui/partials/index/panel-local-ai.html
@awsl233777 awsl233777 changed the title feat(local-ai): isolate local gateway console style(local-ai): apply calm gateway console Jul 4, 2026
@awsl233777

Copy link
Copy Markdown
Collaborator Author

Implemented the selected 方案 1 / Calm Console layout.

What changed:

  • Reduced the Local AI first screen to a restrained hero, one primary endpoint card, routing policy, and gateway trace.
  • Moved provider stack and provider setup lower so they no longer dominate the main visual hierarchy.
  • Kept the previous Local AI config isolation: ~/.codexmate/local-ai-gateway.json remains the store, and Codex/Tasks trap-provider E2E coverage remains intact.

Validation:

  • npm run lint — passed, 231 files.
  • npm run test:unit — passed, unit All 750 tests passed.
  • npm run test:e2e — passed, exited with code 0.
  • npm test — passed, unit + e2e.
  • git diff --check — passed.

Screenshot evidence produced locally:

  • local-ai-calm-console-ui.png
  • mock-provider-calm-traffic.png

CodeRabbit was not checked or triggered.

@coderabbitai coderabbitai Bot left a comment

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 comments (1)
web-ui/styles/local-ai-service.css (1)

140-147: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Hardcoded hex colors bypass the theme token system.

The rest of the file consistently uses var(--color-*) tokens, but .local-ai-live-dot::before (line 145) and .local-ai-trace-row span (line 294) use raw hex colors (#22a05a, #168a45). This breaks theme consistency (e.g. dark mode) established elsewhere in this stylesheet.

♻️ Suggested fix
 .local-ai-live-dot::before {
     content: "";
     width: 8px;
     height: 8px;
     border-radius: 50%;
-    background: `#22a05a`;
-    box-shadow: 0 0 0 6px rgba(34, 160, 90, 0.12);
+    background: var(--color-success, `#22a05a`);
+    box-shadow: 0 0 0 6px color-mix(in srgb, var(--color-success, `#22a05a`) 12%, transparent);
 }
 .local-ai-trace-row span {
     justify-self: end;
-    color: `#168a45`;
+    color: var(--color-success, `#168a45`);
     font-size: 12px;
     font-weight: 850;
 }

Also applies to: 292-297

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web-ui/styles/local-ai-service.css` around lines 140 - 147, Replace the
hardcoded hex colors in the local AI status styles with existing theme tokens so
the stylesheet stays consistent with the rest of the file. Update the
`.local-ai-live-dot::before` rule and the `.local-ai-trace-row span` styling to
use `var(--color-*)` values instead of `#22a05a` and `#168a45`, reusing the
appropriate green token or defining a matching token if needed. Keep the same
visual intent, but route all color values through the theme token system.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@web-ui/styles/local-ai-service.css`:
- Around line 140-147: Replace the hardcoded hex colors in the local AI status
styles with existing theme tokens so the stylesheet stays consistent with the
rest of the file. Update the `.local-ai-live-dot::before` rule and the
`.local-ai-trace-row span` styling to use `var(--color-*)` values instead of
`#22a05a` and `#168a45`, reusing the appropriate green token or defining a
matching token if needed. Keep the same visual intent, but route all color
values through the theme token system.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 211eaa16-4695-48e8-a5ef-b9d0ccf8c70c

📥 Commits

Reviewing files that changed from the base of the PR and between 0ab0b21 and bf2dad5.

📒 Files selected for processing (8)
  • web-ui/modules/i18n/locales/en.mjs
  • web-ui/modules/i18n/locales/ja.mjs
  • web-ui/modules/i18n/locales/vi.mjs
  • web-ui/modules/i18n/locales/zh-tw.mjs
  • web-ui/modules/i18n/locales/zh.mjs
  • web-ui/partials/index/panel-local-ai.html
  • web-ui/res/web-ui-render.precompiled.js
  • web-ui/styles/local-ai-service.css
🚧 Files skipped from review as they are similar to previous changes (6)
  • web-ui/partials/index/panel-local-ai.html
  • web-ui/modules/i18n/locales/en.mjs
  • web-ui/modules/i18n/locales/zh.mjs
  • web-ui/modules/i18n/locales/zh-tw.mjs
  • web-ui/modules/i18n/locales/ja.mjs
  • web-ui/modules/i18n/locales/vi.mjs
📜 Review details
🔇 Additional comments (2)
web-ui/styles/local-ai-service.css (2)

5-59: LGTM!

Also applies to: 96-415


16-21: 🎯 Functional Correctness

These selectors are scoped to the Local AI panel, so the leakage concern doesn’t apply.

			> Likely an incorrect or invalid review comment.

@awsl233777 awsl233777 force-pushed the feat/local-ai-service-provider-console branch 2 times, most recently from 586af60 to ef09f77 Compare July 4, 2026 03:56

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

🧹 Nitpick comments (2)
web-ui/partials/index/panel-local-ai.html (1)

56-58: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

"Form Open" button doesn't actually toggle the form closed.

Text switches to localAi.action.formOpen when open, but the click handler still calls openTaskProviderSetup unconditionally — clicking again won't close the form via this control, only via the separate Cancel button. Consider toggling directly.

💡 Proposed fix
-                                    <button type="button" class="primary-button" `@click`="openTaskProviderSetup" aria-controls="local-ai-provider-form" :aria-expanded="taskOrchestration.providerSetupOpen ? 'true' : 'false'">
+                                    <button type="button" class="primary-button" `@click`="taskOrchestration.providerSetupOpen ? closeTaskProviderSetup() : openTaskProviderSetup()" aria-controls="local-ai-provider-form" :aria-expanded="taskOrchestration.providerSetupOpen ? 'true' : 'false'">
                                        {{ taskOrchestration.providerSetupOpen ? t('localAi.action.formOpen') : t('localAi.action.newProvider') }}
                                    </button>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web-ui/partials/index/panel-local-ai.html` around lines 56 - 58, The button
in panel-local-ai.html uses openTaskProviderSetup unconditionally, so when
taskOrchestration.providerSetupOpen is already true it cannot toggle the form
closed. Update the click handling on this primary-button to switch behavior
based on providerSetupOpen: openTaskProviderSetup when closed and the
corresponding close/toggle action when open, keeping the existing aria-expanded
and label logic in sync.
tests/e2e/test-task-orchestration.js (1)

361-387: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Optional: guard the response stream against mid-flight errors.

requestJson handles req errors and request timeouts, but not a res stream error. If the upstream socket drops after headers arrive, neither resolve nor reject fires and the promise hangs until the outer harness timeout. Since this helper is now used inside a polling loop, adding a res.on('error', reject) keeps failures fast and observable.

♻️ Proposed guard
         }, (res) => {
             let raw = '';
             res.setEncoding('utf-8');
+            res.on('error', reject);
             res.on('data', chunk => { raw += chunk; });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/e2e/test-task-orchestration.js` around lines 361 - 387, The requestJson
helper can hang if the response stream fails after headers are received because
only req errors and timeouts are handled. Update requestJson to also listen for
res stream errors and reject the Promise when they occur, using the existing
request/response flow in requestJson so polling failures surface immediately
instead of waiting for the outer harness timeout.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@web-ui/partials/index/panel-local-ai.html`:
- Around line 127-133: The endpoint and model input placeholders in the local AI
panel are hardcoded English strings, which breaks i18n consistency with the rest
of the form. Update the placeholders in the relevant input fields within the
panel-local-ai template to use translated keys via t(...) like the existing
provider name placeholder, and add the corresponding locale entries for the
endpoint and model examples.

---

Nitpick comments:
In `@tests/e2e/test-task-orchestration.js`:
- Around line 361-387: The requestJson helper can hang if the response stream
fails after headers are received because only req errors and timeouts are
handled. Update requestJson to also listen for res stream errors and reject the
Promise when they occur, using the existing request/response flow in requestJson
so polling failures surface immediately instead of waiting for the outer harness
timeout.

In `@web-ui/partials/index/panel-local-ai.html`:
- Around line 56-58: The button in panel-local-ai.html uses
openTaskProviderSetup unconditionally, so when
taskOrchestration.providerSetupOpen is already true it cannot toggle the form
closed. Update the click handling on this primary-button to switch behavior
based on providerSetupOpen: openTaskProviderSetup when closed and the
corresponding close/toggle action when open, keeping the existing aria-expanded
and label logic in sync.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: dddd2ee6-80f9-4c43-ba71-bb3b4cf305fc

📥 Commits

Reviewing files that changed from the base of the PR and between bf2dad5 and ef09f77.

📒 Files selected for processing (32)
  • cli.js
  • cli/local-bridge.js
  • tests/e2e/test-task-orchestration.js
  • tests/unit/config-tabs-ui.test.mjs
  • tests/unit/i18n-locales.test.mjs
  • tests/unit/session-tab-switch-performance.test.mjs
  • tests/unit/task-codex-exec-windows.test.mjs
  • tests/unit/web-ui-behavior-parity.test.mjs
  • tests/unit/web-ui-logic.test.mjs
  • tests/unit/web-ui-runtime-navigation-regression.test.mjs
  • web-ui/app.js
  • web-ui/index.html
  • web-ui/modules/app.computed.main-tabs.mjs
  • web-ui/modules/app.methods.navigation.mjs
  • web-ui/modules/app.methods.task-orchestration.mjs
  • web-ui/modules/i18n/locales/en.mjs
  • web-ui/modules/i18n/locales/ja.mjs
  • web-ui/modules/i18n/locales/vi.mjs
  • web-ui/modules/i18n/locales/zh-tw.mjs
  • web-ui/modules/i18n/locales/zh.mjs
  • web-ui/modules/task-kilo-webview-host.mjs
  • web-ui/partials/index/layout-header.html
  • web-ui/partials/index/panel-dashboard.html
  • web-ui/partials/index/panel-local-ai.html
  • web-ui/partials/index/panel-orchestration.html
  • web-ui/res/web-ui-render.precompiled.js
  • web-ui/session-helpers.mjs
  • web-ui/styles.css
  • web-ui/styles/layout-shell.css
  • web-ui/styles/local-ai-service.css
  • web-ui/styles/navigation-panels.css
  • web-ui/styles/task-orchestration.css
✅ Files skipped from review due to trivial changes (3)
  • web-ui/styles/navigation-panels.css
  • web-ui/index.html
  • web-ui/modules/i18n/locales/zh.mjs
🚧 Files skipped from review as they are similar to previous changes (23)
  • web-ui/partials/index/panel-dashboard.html
  • web-ui/app.js
  • tests/unit/web-ui-runtime-navigation-regression.test.mjs
  • web-ui/styles/layout-shell.css
  • tests/unit/session-tab-switch-performance.test.mjs
  • web-ui/modules/app.methods.navigation.mjs
  • tests/unit/web-ui-behavior-parity.test.mjs
  • tests/unit/i18n-locales.test.mjs
  • web-ui/session-helpers.mjs
  • tests/unit/config-tabs-ui.test.mjs
  • web-ui/partials/index/layout-header.html
  • web-ui/partials/index/panel-orchestration.html
  • cli/local-bridge.js
  • web-ui/modules/i18n/locales/zh-tw.mjs
  • web-ui/modules/i18n/locales/en.mjs
  • tests/unit/task-codex-exec-windows.test.mjs
  • web-ui/modules/task-kilo-webview-host.mjs
  • cli.js
  • tests/unit/web-ui-logic.test.mjs
  • web-ui/modules/i18n/locales/vi.mjs
  • web-ui/modules/app.computed.main-tabs.mjs
  • web-ui/modules/i18n/locales/ja.mjs
  • web-ui/modules/app.methods.task-orchestration.mjs
📜 Review details
🧰 Additional context used
🪛 ast-grep (0.44.0)
tests/e2e/test-task-orchestration.js

[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: require('child_process')
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process)


[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: require('child_process')
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process)


[warning] 124-143: Filesystem path is not a string literal; a request-/variable-derived path can enable path traversal. Validate and normalize the path before use.
Context: fs.writeFileSync(scriptPath, const http = require('http'); const fs = require('fs'); const portFile = process.argv[2]; const status = Number(process.argv[3] || 401); const message = process.argv[4] || 'Invalid token'; const server = http.createServer((req, res) => { req.resume(); req.on('end', () => { const body = JSON.stringify({ error: { message } }); res.writeHead(status, { 'Content-Type': 'application/json; charset=utf-8', 'Content-Length': Buffer.byteLength(body, 'utf-8') }); res.end(body, 'utf-8'); }); }); server.listen(0, '127.0.0.1', () => { fs.writeFileSync(portFile, String(server.address().port), 'utf-8'); }); process.on('SIGTERM', () => server.close(() => process.exit(0))); process.on('SIGINT', () => server.close(() => process.exit(0)));, 'utf-8')
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(detect-non-literal-fs-filename)


[warning] 152-152: Filesystem path is not a string literal; a request-/variable-derived path can enable path traversal. Validate and normalize the path before use.
Context: fs.readFileSync(portFile, 'utf-8')
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(detect-non-literal-fs-filename)


[warning] 187-187: Filesystem path is not a string literal; a request-/variable-derived path can enable path traversal. Validate and normalize the path before use.
Context: fs.writeFileSync(path.join(configDir, 'codexmate-init.json'), JSON.stringify({ version: 1, mode: 'task-openai-chat-e2e' }), 'utf-8')
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(detect-non-literal-fs-filename)


[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: require('child_process')
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process)


[warning] 208-222: Filesystem path is not a string literal; a request-/variable-derived path can enable path traversal. Validate and normalize the path before use.
Context: fs.writeFileSync(path.join(taskConfigDir, 'local-ai-gateway.json'), JSON.stringify({
selectedProvider: 'local-ai-chat',
selectedModels: { 'local-ai-chat': 'glm-5.2' },
providers: {
'local-ai-chat': {
name: 'Local AI Chat',
base_url: ${baseUrl}/v1,
wire_api: 'chat_completions',
preferred_auth_method: 'sk-local-ai-secret',
temperature: 0.7,
models: ['glm-5.2', 'glm-5.2-flash'],
selected_model: 'glm-5.2'
}
}
}, null, 2), 'utf-8')
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(detect-non-literal-fs-filename)


[warning] 224-238: Filesystem path is not a string literal; a request-/variable-derived path can enable path traversal. Validate and normalize the path before use.
Context: fs.writeFileSync(path.join(taskConfigDir, 'task-openai-chat-provider.json'), JSON.stringify({
selectedProvider: 'new-api-chat',
selectedModels: { 'new-api-chat': 'glm-5.2' },
providers: {
'new-api-chat': {
name: 'New API Chat',
base_url: ${baseUrl}/v1,
wire_api: 'chat_completions',
preferred_auth_method: 'sk-task-e2e-secret',
temperature: 0.7,
models: ['glm-5.2', 'glm-5.2-flash'],
selected_model: 'glm-5.2'
}
}
}, null, 2), 'utf-8')
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(detect-non-literal-fs-filename)


[warning] 246-246: Filesystem path is not a string literal; a request-/variable-derived path can enable path traversal. Validate and normalize the path before use.
Context: fs.writeFileSync(path.join(configDir, 'codexmate-init.json'), JSON.stringify({ version: 1, mode: 'task-openai-chat-fallback-e2e' }), 'utf-8')
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(detect-non-literal-fs-filename)


[warning] 247-265: Filesystem path is not a string literal; a request-/variable-derived path can enable path traversal. Validate and normalize the path before use.
Context: fs.writeFileSync(path.join(configDir, 'config.toml'), [
'model = "gpt-5.3-codex"',
'model_provider = "local"',
'',
'[model_providers.local]',
'name = "Local Codex"',
'base_url = "http://127.0.0.1:9/v1"',
'wire_api = "responses"',
'preferred_auth_method = "sk-codex-tab-secret"',
'models = ["gpt-5.3-codex"]',
'',
'[model_providers.codex-coupling-trap]',
'name = "Codex Coupling Trap"',
'base_url = "http://127.0.0.1:9/v1"',
'wire_api = "chat_completions"',
'preferred_auth_method = "sk-codex-tab-secret"',
'models = ["codex-trap-model"]',
''
].join('\n'), 'utf-8')
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(detect-non-literal-fs-filename)


[warning] 267-289: Filesystem path is not a string literal; a request-/variable-derived path can enable path traversal. Validate and normalize the path before use.
Context: fs.writeFileSync(path.join(taskConfigDir, 'local-ai-gateway.json'), JSON.stringify({
selectedProvider: 'bad-chat',
selectedModels: { 'bad-chat': 'bad-model', 'local-ai-chat': 'glm-5.2' },
providers: {
'bad-chat': {
name: 'Bad Chat',
base_url: ${badBaseUrl}/v1,
wire_api: 'chat_completions',
preferred_auth_method: 'sk-bad-token',
models: ['bad-model'],
selected_model: 'bad-model'
},
'local-ai-chat': {
name: 'Local AI Chat',
base_url: ${fallbackBaseUrl}/v1,
wire_api: 'chat_completions',
preferred_auth_method: 'sk-local-ai-secret',
temperature: 0.7,
models: ['glm-5.2'],
selected_model: 'glm-5.2'
}
}
}, null, 2), 'utf-8')
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(detect-non-literal-fs-filename)


[warning] 291-313: Filesystem path is not a string literal; a request-/variable-derived path can enable path traversal. Validate and normalize the path before use.
Context: fs.writeFileSync(path.join(taskConfigDir, 'task-openai-chat-provider.json'), JSON.stringify({
selectedProvider: 'bad-chat',
selectedModels: { 'bad-chat': 'bad-model', 'new-api-chat': 'glm-5.2' },
providers: {
'bad-chat': {
name: 'Bad Chat',
base_url: ${badBaseUrl}/v1,
wire_api: 'chat_completions',
preferred_auth_method: 'sk-bad-token',
models: ['bad-model'],
selected_model: 'bad-model'
},
'new-api-chat': {
name: 'New API Chat',
base_url: ${fallbackBaseUrl}/v1,
wire_api: 'chat_completions',
preferred_auth_method: 'sk-task-e2e-secret',
temperature: 0.7,
models: ['glm-5.2'],
selected_model: 'glm-5.2'
}
}
}, null, 2), 'utf-8')
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(detect-non-literal-fs-filename)


[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: require('child_process')
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process)


[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: require('child_process')
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process)


[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: require('child_process')
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process)


[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: require('child_process')
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process)

🪛 HTMLHint (1.9.2)
web-ui/partials/index/panel-local-ai.html

[error] 1-1: Doctype must be declared before any non-comment content.

(doctype-first)

🪛 Stylelint (17.14.0)
web-ui/styles.css

[error] 14-14: Expected "url('./styles/local-ai-service.css')" to be "'./styles/local-ai-service.css'" (import-notation)

(import-notation)

🔇 Additional comments (10)
web-ui/partials/index/panel-local-ai.html (2)

62-96: Empty-state can still flash before data loads.

The provider-list conditional still only checks providerOptions && ...length, with no guard for taskOrchestration.loading/initial undefined state, so "no providers" can flash before the first overview fetch resolves.


1-153: HTMLHint doctype hint is a false positive.

This is a Vue partial included into index.html, not a standalone document; no doctype is expected here.

Source: Linters/SAST tools

web-ui/styles/local-ai-service.css (1)

1-286: LGTM!

tests/e2e/test-task-orchestration.js (6)

2-2: LGTM!


122-181: LGTM!


183-240: LGTM!

Also applies to: 242-315


327-342: LGTM!


549-595: LGTM!


917-965: LGTM!

web-ui/styles.css (1)

14-14: LGTM!

Comment thread web-ui/partials/index/panel-local-ai.html
@awsl233777 awsl233777 force-pushed the feat/local-ai-service-provider-console branch from ef09f77 to ab398be Compare July 4, 2026 04:02
@awsl233777

Copy link
Copy Markdown
Collaborator Author

Pushed 7d9b380.

Changed:

  • Provider setup button now toggles open/closed.
  • Empty provider state waits until loading finishes.
  • Endpoint/model placeholders use existing i18n keys.
  • E2E requestJson rejects response stream errors.

Validation:

  • npm run lint passed.
  • npm run test:unit passed, 750 tests.
  • npm run test:e2e passed.
  • git diff --check passed.

CodeRabbit not triggered.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

🧹 Nitpick comments (3)
web-ui/styles/local-ai-service.css (3)

85-94: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Non-standard font-weight values.

font-weight: 760 (line 90) and font-weight: 740 (line 188) aren't standard weight steps; static (non-variable) fonts will just snap to the nearest defined weight (usually 700 or 800), making these specific values misleading/no-op in most environments.

💅 Suggested fix
 .local-ai-button {
     min-height: 38px;
     border-radius: 12px;
     padding: 9px 14px;
     font-size: 13px;
-    font-weight: 760;
+    font-weight: 700;
     letter-spacing: -0.01em;
 .local-ai-brief-item code {
     display: inline-flex;
     max-width: 100%;
     margin-top: 12px;
     border-radius: 12px;
     padding: 8px 10px;
     background: var(--color-brand-dark);
     color: `#ffe5ce`;
     font-size: 13px;
-    font-weight: 740;
+    font-weight: 700;

Also applies to: 179-190

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web-ui/styles/local-ai-service.css` around lines 85 - 94, The
local-ai-service button styles use non-standard font-weight values that will be
ignored or snapped in many fonts, so update the font-weight declarations in the
local-ai-button and related local AI service CSS rules to standard numeric steps
that are supported consistently. Review the affected selectors in
local-ai-service.css, especially local-ai-button and the other nearby local AI
service button/text styles referenced in the comment, and replace the 760/740
values with an appropriate standard weight such as 700 or 800 to match the
intended visual emphasis.

145-156: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Hardcoded hex colors bypass the design-token system.

.status-pill.success/.local-ai-badge-good (Lines 145-150), .status-pill.warn (Lines 152-156), and the code chip text color #ffe5ce (Line 186) use raw hex/rgba values while the rest of the file consistently uses var(--color-*) tokens. This will diverge from theme changes (e.g., dark mode) that the token-based rules automatically pick up.

Also applies to: 179-190

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web-ui/styles/local-ai-service.css` around lines 145 - 156, Hardcoded color
values are bypassing the design-token system in the local AI service styles.
Update the selectors around .local-ai-service-badges .local-ai-badge-good,
.status-pill.success, and .status-pill.warn to use existing var(--color-*)
tokens instead of raw hex/rgba values, and apply the same token-based cleanup to
the code chip text styling in the related 179-190 rules. Keep the intent of the
current success/warn visuals, but express all colors through theme tokens so
dark mode and future palette changes continue to work automatically.

283-291: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Inconsistent focus state: use :focus-visible for consistency.

.local-ai-upstream-card uses :focus while .local-ai-button (Lines 104-107) uses :focus-visible. Mouse clicks will now show a persistent outline on this card, unlike buttons elsewhere in the panel.

💅 Suggested fix
-.local-ai-upstream-card:focus {
+.local-ai-upstream-card:focus-visible {
     outline: 2px solid color-mix(in srgb, var(--color-brand-dark) 32%, transparent);
     outline-offset: 3px;
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web-ui/styles/local-ai-service.css` around lines 283 - 291, The
`.local-ai-upstream-card` focus styling is inconsistent with `.local-ai-button`
because it uses `:focus` instead of `:focus-visible`, which causes mouse clicks
to show the outline. Update the focus rule on `.local-ai-upstream-card` in the
same stylesheet to use `:focus-visible` so it matches the panel’s other
interactive elements and only appears for keyboard focus.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@web-ui/partials/index/panel-local-ai.html`:
- Line 24: The aria-label on the Local AI summary section is hardcoded English
and should be localized like the rest of the panel. Update the
local-ai-brief-row markup in panel-local-ai.html to route the label through the
existing t(...) translation helper so the accessible text is translated
consistently with other user-facing copy.

---

Nitpick comments:
In `@web-ui/styles/local-ai-service.css`:
- Around line 85-94: The local-ai-service button styles use non-standard
font-weight values that will be ignored or snapped in many fonts, so update the
font-weight declarations in the local-ai-button and related local AI service CSS
rules to standard numeric steps that are supported consistently. Review the
affected selectors in local-ai-service.css, especially local-ai-button and the
other nearby local AI service button/text styles referenced in the comment, and
replace the 760/740 values with an appropriate standard weight such as 700 or
800 to match the intended visual emphasis.
- Around line 145-156: Hardcoded color values are bypassing the design-token
system in the local AI service styles. Update the selectors around
.local-ai-service-badges .local-ai-badge-good, .status-pill.success, and
.status-pill.warn to use existing var(--color-*) tokens instead of raw hex/rgba
values, and apply the same token-based cleanup to the code chip text styling in
the related 179-190 rules. Keep the intent of the current success/warn visuals,
but express all colors through theme tokens so dark mode and future palette
changes continue to work automatically.
- Around line 283-291: The `.local-ai-upstream-card` focus styling is
inconsistent with `.local-ai-button` because it uses `:focus` instead of
`:focus-visible`, which causes mouse clicks to show the outline. Update the
focus rule on `.local-ai-upstream-card` in the same stylesheet to use
`:focus-visible` so it matches the panel’s other interactive elements and only
appears for keyboard focus.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 7d14df91-46d5-4de1-abea-81ae5e87e23b

📥 Commits

Reviewing files that changed from the base of the PR and between ef09f77 and 7d9b380.

📒 Files selected for processing (32)
  • cli.js
  • cli/local-bridge.js
  • tests/e2e/test-task-orchestration.js
  • tests/unit/config-tabs-ui.test.mjs
  • tests/unit/i18n-locales.test.mjs
  • tests/unit/session-tab-switch-performance.test.mjs
  • tests/unit/task-codex-exec-windows.test.mjs
  • tests/unit/web-ui-behavior-parity.test.mjs
  • tests/unit/web-ui-logic.test.mjs
  • tests/unit/web-ui-runtime-navigation-regression.test.mjs
  • web-ui/app.js
  • web-ui/index.html
  • web-ui/modules/app.computed.main-tabs.mjs
  • web-ui/modules/app.methods.navigation.mjs
  • web-ui/modules/app.methods.task-orchestration.mjs
  • web-ui/modules/i18n/locales/en.mjs
  • web-ui/modules/i18n/locales/ja.mjs
  • web-ui/modules/i18n/locales/vi.mjs
  • web-ui/modules/i18n/locales/zh-tw.mjs
  • web-ui/modules/i18n/locales/zh.mjs
  • web-ui/modules/task-kilo-webview-host.mjs
  • web-ui/partials/index/layout-header.html
  • web-ui/partials/index/panel-dashboard.html
  • web-ui/partials/index/panel-local-ai.html
  • web-ui/partials/index/panel-orchestration.html
  • web-ui/res/web-ui-render.precompiled.js
  • web-ui/session-helpers.mjs
  • web-ui/styles.css
  • web-ui/styles/layout-shell.css
  • web-ui/styles/local-ai-service.css
  • web-ui/styles/navigation-panels.css
  • web-ui/styles/task-orchestration.css
✅ Files skipped from review due to trivial changes (1)
  • web-ui/index.html
🚧 Files skipped from review as they are similar to previous changes (25)
  • web-ui/partials/index/panel-dashboard.html
  • web-ui/styles/layout-shell.css
  • web-ui/session-helpers.mjs
  • web-ui/app.js
  • tests/unit/web-ui-runtime-navigation-regression.test.mjs
  • tests/unit/i18n-locales.test.mjs
  • tests/unit/session-tab-switch-performance.test.mjs
  • web-ui/styles/navigation-panels.css
  • tests/unit/config-tabs-ui.test.mjs
  • web-ui/modules/i18n/locales/ja.mjs
  • tests/unit/task-codex-exec-windows.test.mjs
  • web-ui/modules/i18n/locales/zh.mjs
  • web-ui/partials/index/panel-orchestration.html
  • web-ui/partials/index/layout-header.html
  • web-ui/modules/i18n/locales/zh-tw.mjs
  • web-ui/modules/i18n/locales/vi.mjs
  • web-ui/modules/task-kilo-webview-host.mjs
  • web-ui/modules/i18n/locales/en.mjs
  • tests/unit/web-ui-behavior-parity.test.mjs
  • cli/local-bridge.js
  • tests/unit/web-ui-logic.test.mjs
  • web-ui/modules/app.methods.navigation.mjs
  • cli.js
  • web-ui/modules/app.computed.main-tabs.mjs
  • web-ui/modules/app.methods.task-orchestration.mjs
📜 Review details
🧰 Additional context used
🪛 ast-grep (0.44.0)
tests/e2e/test-task-orchestration.js

[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: require('child_process')
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process)


[warning] 124-143: Filesystem path is not a string literal; a request-/variable-derived path can enable path traversal. Validate and normalize the path before use.
Context: fs.writeFileSync(scriptPath, const http = require('http'); const fs = require('fs'); const portFile = process.argv[2]; const status = Number(process.argv[3] || 401); const message = process.argv[4] || 'Invalid token'; const server = http.createServer((req, res) => { req.resume(); req.on('end', () => { const body = JSON.stringify({ error: { message } }); res.writeHead(status, { 'Content-Type': 'application/json; charset=utf-8', 'Content-Length': Buffer.byteLength(body, 'utf-8') }); res.end(body, 'utf-8'); }); }); server.listen(0, '127.0.0.1', () => { fs.writeFileSync(portFile, String(server.address().port), 'utf-8'); }); process.on('SIGTERM', () => server.close(() => process.exit(0))); process.on('SIGINT', () => server.close(() => process.exit(0)));, 'utf-8')
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(detect-non-literal-fs-filename)


[warning] 152-152: Filesystem path is not a string literal; a request-/variable-derived path can enable path traversal. Validate and normalize the path before use.
Context: fs.readFileSync(portFile, 'utf-8')
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(detect-non-literal-fs-filename)


[warning] 187-187: Filesystem path is not a string literal; a request-/variable-derived path can enable path traversal. Validate and normalize the path before use.
Context: fs.writeFileSync(path.join(configDir, 'codexmate-init.json'), JSON.stringify({ version: 1, mode: 'task-openai-chat-e2e' }), 'utf-8')
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(detect-non-literal-fs-filename)


[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: require('child_process')
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process)


[warning] 208-222: Filesystem path is not a string literal; a request-/variable-derived path can enable path traversal. Validate and normalize the path before use.
Context: fs.writeFileSync(path.join(taskConfigDir, 'local-ai-gateway.json'), JSON.stringify({
selectedProvider: 'local-ai-chat',
selectedModels: { 'local-ai-chat': 'glm-5.2' },
providers: {
'local-ai-chat': {
name: 'Local AI Chat',
base_url: ${baseUrl}/v1,
wire_api: 'chat_completions',
preferred_auth_method: 'sk-local-ai-secret',
temperature: 0.7,
models: ['glm-5.2', 'glm-5.2-flash'],
selected_model: 'glm-5.2'
}
}
}, null, 2), 'utf-8')
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(detect-non-literal-fs-filename)


[warning] 224-238: Filesystem path is not a string literal; a request-/variable-derived path can enable path traversal. Validate and normalize the path before use.
Context: fs.writeFileSync(path.join(taskConfigDir, 'task-openai-chat-provider.json'), JSON.stringify({
selectedProvider: 'new-api-chat',
selectedModels: { 'new-api-chat': 'glm-5.2' },
providers: {
'new-api-chat': {
name: 'New API Chat',
base_url: ${baseUrl}/v1,
wire_api: 'chat_completions',
preferred_auth_method: 'sk-task-e2e-secret',
temperature: 0.7,
models: ['glm-5.2', 'glm-5.2-flash'],
selected_model: 'glm-5.2'
}
}
}, null, 2), 'utf-8')
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(detect-non-literal-fs-filename)


[warning] 246-246: Filesystem path is not a string literal; a request-/variable-derived path can enable path traversal. Validate and normalize the path before use.
Context: fs.writeFileSync(path.join(configDir, 'codexmate-init.json'), JSON.stringify({ version: 1, mode: 'task-openai-chat-fallback-e2e' }), 'utf-8')
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(detect-non-literal-fs-filename)


[warning] 247-265: Filesystem path is not a string literal; a request-/variable-derived path can enable path traversal. Validate and normalize the path before use.
Context: fs.writeFileSync(path.join(configDir, 'config.toml'), [
'model = "gpt-5.3-codex"',
'model_provider = "local"',
'',
'[model_providers.local]',
'name = "Local Codex"',
'base_url = "http://127.0.0.1:9/v1"',
'wire_api = "responses"',
'preferred_auth_method = "sk-codex-tab-secret"',
'models = ["gpt-5.3-codex"]',
'',
'[model_providers.codex-coupling-trap]',
'name = "Codex Coupling Trap"',
'base_url = "http://127.0.0.1:9/v1"',
'wire_api = "chat_completions"',
'preferred_auth_method = "sk-codex-tab-secret"',
'models = ["codex-trap-model"]',
''
].join('\n'), 'utf-8')
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(detect-non-literal-fs-filename)


[warning] 267-289: Filesystem path is not a string literal; a request-/variable-derived path can enable path traversal. Validate and normalize the path before use.
Context: fs.writeFileSync(path.join(taskConfigDir, 'local-ai-gateway.json'), JSON.stringify({
selectedProvider: 'bad-chat',
selectedModels: { 'bad-chat': 'bad-model', 'local-ai-chat': 'glm-5.2' },
providers: {
'bad-chat': {
name: 'Bad Chat',
base_url: ${badBaseUrl}/v1,
wire_api: 'chat_completions',
preferred_auth_method: 'sk-bad-token',
models: ['bad-model'],
selected_model: 'bad-model'
},
'local-ai-chat': {
name: 'Local AI Chat',
base_url: ${fallbackBaseUrl}/v1,
wire_api: 'chat_completions',
preferred_auth_method: 'sk-local-ai-secret',
temperature: 0.7,
models: ['glm-5.2'],
selected_model: 'glm-5.2'
}
}
}, null, 2), 'utf-8')
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(detect-non-literal-fs-filename)


[warning] 291-313: Filesystem path is not a string literal; a request-/variable-derived path can enable path traversal. Validate and normalize the path before use.
Context: fs.writeFileSync(path.join(taskConfigDir, 'task-openai-chat-provider.json'), JSON.stringify({
selectedProvider: 'bad-chat',
selectedModels: { 'bad-chat': 'bad-model', 'new-api-chat': 'glm-5.2' },
providers: {
'bad-chat': {
name: 'Bad Chat',
base_url: ${badBaseUrl}/v1,
wire_api: 'chat_completions',
preferred_auth_method: 'sk-bad-token',
models: ['bad-model'],
selected_model: 'bad-model'
},
'new-api-chat': {
name: 'New API Chat',
base_url: ${fallbackBaseUrl}/v1,
wire_api: 'chat_completions',
preferred_auth_method: 'sk-task-e2e-secret',
temperature: 0.7,
models: ['glm-5.2'],
selected_model: 'glm-5.2'
}
}
}, null, 2), 'utf-8')
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(detect-non-literal-fs-filename)


[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: require('child_process')
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process)


[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: require('child_process')
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process)


[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: require('child_process')
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process)


[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: require('child_process')
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process)


[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: require('child_process')
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process)

🪛 HTMLHint (1.9.2)
web-ui/partials/index/panel-local-ai.html

[error] 1-1: Doctype must be declared before any non-comment content.

(doctype-first)

🪛 Stylelint (17.14.0)
web-ui/styles.css

[error] 14-14: Expected "url('./styles/local-ai-service.css')" to be "'./styles/local-ai-service.css'" (import-notation)

(import-notation)

🔇 Additional comments (8)
web-ui/styles/local-ai-service.css (1)

1-345: Structure, layout grids, responsive breakpoints, and token usage otherwise look solid and match the panel-local-ai.html consumer structure.

web-ui/partials/index/panel-local-ai.html (3)

93-96: Previously flagged issues now resolved.

The empty-state loading race and untranslated endpoint/model placeholders flagged in earlier reviews are fixed here — v-else-if now guards on !taskOrchestration.loading (Line 93), and placeholders use t('placeholder.apiEndpointExample') / t('placeholder.modelExample') (Lines 128, 132).

Also applies to: 128-128, 132-132


1-22: LGTM!


45-150: LGTM!

tests/e2e/test-task-orchestration.js (3)

2-2: LGTM!

Also applies to: 122-181, 183-240, 242-315, 327-341, 361-389


917-965: 🎯 Functional Correctness

No issue here: cli.js emits the fallback log line, and cli/local-bridge.js already sets X-Codexmate-Attempted-Providers on bridge responses.

			> Likely an incorrect or invalid review comment.

549-595: 🎯 Functional Correctness

No issue: local-ai-service-overview, /bridge/local/v1/*, and x-codexmate-upstream-provider are implemented in cli.js/cli/local-bridge.js.

web-ui/styles.css (1)

14-14: LGTM!

</div>
</section>

<section class="local-ai-brief-row" aria-label="Local AI gateway summary">

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Untranslated aria-label.

aria-label="Local AI gateway summary" is hardcoded English, inconsistent with the rest of the panel which routes all user-facing copy through t(...).

💡 Proposed fix
-                        <section class="local-ai-brief-row" aria-label="Local AI gateway summary">
+                        <section class="local-ai-brief-row" :aria-label="t('localAi.brief.ariaLabel')">
📝 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.

Suggested change
<section class="local-ai-brief-row" aria-label="Local AI gateway summary">
<section class="local-ai-brief-row" :aria-label="t('localAi.brief.ariaLabel')">
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web-ui/partials/index/panel-local-ai.html` at line 24, The aria-label on the
Local AI summary section is hardcoded English and should be localized like the
rest of the panel. Update the local-ai-brief-row markup in panel-local-ai.html
to route the label through the existing t(...) translation helper so the
accessible text is translated consistently with other user-facing copy.

@awsl233777

Copy link
Copy Markdown
Collaborator Author

Pushed 47af59d.

Changed:

  • Merged Endpoint Gateway, routing policy, and Gateway trace into one local-ai-gateway-summary card.
  • Updated Local AI responsive layout styles.
  • Updated UI assertions and regenerated precompiled render output.

Validation:

  • npm run lint passed.
  • npm run test:unit passed, 750 tests.
  • npm run test:e2e passed.
  • git diff --check passed.

CodeRabbit not manually triggered.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@web-ui/styles/local-ai-service.css`:
- Around line 169-185: Add the missing shrink rule to the
`.local-ai-summary-meta` grid item used by `.local-ai-gateway-summary` so long
localized text cannot force the two-column layout wider than intended. Update
the same responsive sizing rule set that already includes
`.local-ai-summary-main` and `.local-ai-summary-meta > div` so
`.local-ai-summary-meta` itself also gets `min-width: 0`, keeping the nested
grid from overflowing.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f6b0a1eb-5ff3-4eb6-b3b1-ebb3d1c4b050

📥 Commits

Reviewing files that changed from the base of the PR and between 7d9b380 and 47af59d.

📒 Files selected for processing (4)
  • tests/unit/config-tabs-ui.test.mjs
  • web-ui/partials/index/panel-local-ai.html
  • web-ui/res/web-ui-render.precompiled.js
  • web-ui/styles/local-ai-service.css
🚧 Files skipped from review as they are similar to previous changes (2)
  • tests/unit/config-tabs-ui.test.mjs
  • web-ui/partials/index/panel-local-ai.html
📜 Review details
🔇 Additional comments (2)
web-ui/styles/local-ai-service.css (2)

158-162: LGTM!


335-366: LGTM!

Comment on lines +169 to +185
.local-ai-gateway-summary {
display: grid;
grid-template-columns: minmax(260px, 1.2fr) minmax(320px, 1.8fr);
gap: 18px;
}

.local-ai-summary-main,
.local-ai-summary-meta > div {
min-width: 0;
}

.local-ai-summary-meta {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 14px;
}

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Confirm no ancestor rule already resets min-width for nested grid items.
rg -n 'local-ai-brief-item|local-ai-summary-meta|min-width' web-ui/styles/local-ai-service.css

Repository: SakuraByteCore/codexmate

Length of output: 512


🏁 Script executed:

#!/bin/bash
sed -n '150,190p' web-ui/styles/local-ai-service.css
printf '\n---\n'
sed -n '332,360p' web-ui/styles/local-ai-service.css

Repository: SakuraByteCore/codexmate

Length of output: 1486


Add min-width: 0 to .local-ai-summary-meta

.local-ai-summary-meta is the direct grid item inside .local-ai-gateway-summary, so long localized policy/trace text can still push the two-column layout wider than intended. Add it to the same shrink rule:

🩹 Proposed fix
 .local-ai-summary-main,
+.local-ai-summary-meta,
 .local-ai-summary-meta > div {
     min-width: 0;
 }
📝 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.

Suggested change
.local-ai-gateway-summary {
display: grid;
grid-template-columns: minmax(260px, 1.2fr) minmax(320px, 1.8fr);
gap: 18px;
}
.local-ai-summary-main,
.local-ai-summary-meta > div {
min-width: 0;
}
.local-ai-summary-meta {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 14px;
}
.local-ai-gateway-summary {
display: grid;
grid-template-columns: minmax(260px, 1.2fr) minmax(320px, 1.8fr);
gap: 18px;
}
.local-ai-summary-main,
.local-ai-summary-meta,
.local-ai-summary-meta > div {
min-width: 0;
}
.local-ai-summary-meta {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 14px;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web-ui/styles/local-ai-service.css` around lines 169 - 185, Add the missing
shrink rule to the `.local-ai-summary-meta` grid item used by
`.local-ai-gateway-summary` so long localized text cannot force the two-column
layout wider than intended. Update the same responsive sizing rule set that
already includes `.local-ai-summary-main` and `.local-ai-summary-meta > div` so
`.local-ai-summary-meta` itself also gets `min-width: 0`, keeping the nested
grid from overflowing.

@awsl233777

Copy link
Copy Markdown
Collaborator Author

Pushed ffe1677.

Changed:

  • Added an Edit action to each Local AI provider card.
  • Edit opens the provider form with existing provider values.
  • Existing API key can be left blank and is preserved server-side.
  • Saving edits updates endpoint/model and keeps the provider ready.
  • Added UI/API regression coverage and regenerated precompiled render output.

Validation:

  • npm run lint passed.
  • npm run test:unit passed, 750 tests.
  • npm run test:e2e passed.
  • git diff --check passed.
  • Browser E2E passed for add → edit → save with preserved key.

CodeRabbit not manually triggered.

@coderabbitai coderabbitai Bot left a comment

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 comments (1)
tests/e2e/test-task-orchestration.js (1)

597-604: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Optional: also cover the rename path.

This new edit-flow test keeps originalName and name identical ('claude-local'), validating key preservation and field updates but not an actual rename (originalName differing from name). Since the commit introduces provider editing generally, consider adding a follow-up case where the name changes to ensure the backend correctly resolves the old entry via originalName and persists it under the new name.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/e2e/test-task-orchestration.js` around lines 597 - 604, The
save-local-ai-provider edit-flow test currently only covers updating an existing
provider without changing its name, so it misses the rename path. Add a
follow-up case in test-task-orchestration.js around the save-local-ai-provider
call to set originalName and name to different values, then verify the backend
resolves the old provider by originalName and persists the edited provider under
the new name while preserving the expected fields. Use the existing
save-local-ai-provider helper and the claude-local test setup to keep the new
rename coverage close to the current edit-flow assertions.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@tests/e2e/test-task-orchestration.js`:
- Around line 597-604: The save-local-ai-provider edit-flow test currently only
covers updating an existing provider without changing its name, so it misses the
rename path. Add a follow-up case in test-task-orchestration.js around the
save-local-ai-provider call to set originalName and name to different values,
then verify the backend resolves the old provider by originalName and persists
the edited provider under the new name while preserving the expected fields. Use
the existing save-local-ai-provider helper and the claude-local test setup to
keep the new rename coverage close to the current edit-flow assertions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 0bfa3fea-c8d6-4382-bc61-d93f6b2f5f2a

📥 Commits

Reviewing files that changed from the base of the PR and between 47af59d and ffe1677.

📒 Files selected for processing (8)
  • cli.js
  • tests/e2e/test-task-orchestration.js
  • tests/unit/config-tabs-ui.test.mjs
  • tests/unit/web-ui-logic.test.mjs
  • web-ui/modules/app.methods.task-orchestration.mjs
  • web-ui/partials/index/panel-local-ai.html
  • web-ui/res/web-ui-render.precompiled.js
  • web-ui/styles/local-ai-service.css
🚧 Files skipped from review as they are similar to previous changes (6)
  • web-ui/partials/index/panel-local-ai.html
  • tests/unit/config-tabs-ui.test.mjs
  • web-ui/styles/local-ai-service.css
  • cli.js
  • web-ui/modules/app.methods.task-orchestration.mjs
  • tests/unit/web-ui-logic.test.mjs
📜 Review details
🧰 Additional context used
🪛 ast-grep (0.44.0)
tests/e2e/test-task-orchestration.js

[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: require('child_process')
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process)

🔇 Additional comments (1)
tests/e2e/test-task-orchestration.js (1)

597-611: LGTM!

@awsl233777

Copy link
Copy Markdown
Collaborator Author

Pushed 914421a.

Changed:

  • Replaced the internal gateway/route/trace copy with a client configuration card.
  • Shows the full client Base URL computed from the current Codexmate host, e.g. http://host/bridge/local/v1.
  • Shows API key guidance: local loopback can be blank; remote clients use CODEXMATE_HTTP_TOKEN as bearer token.
  • Keeps Chat Completions endpoint only as verification, not as the field users should paste.
  • Added computed URL regression coverage and updated UI assertions/locales/precompiled render.

Validation:

  • npm run lint passed.
  • npm run test:unit passed, 751 tests.
  • npm run test:e2e passed.
  • git diff --check passed.
  • Browser E2E passed: client Base URL visible, provider save works, mock upstream receives models/chat requests.

CodeRabbit not manually triggered.

@awsl233777

Copy link
Copy Markdown
Collaborator Author

Pushed 4209db3.

Changed:

  • Raised Local AI description text contrast.
  • Made Base URL/code text higher contrast and easier to verify before copying.
  • Made section kickers use the brand-dark text color for clearer grouping.
  • Muted the green ready status while keeping it readable.
  • Kept secondary/edit action text visibly clickable.

Validation:

  • npm run lint passed.
  • npm run test:unit passed, 751 tests.
  • npm run test:e2e passed.
  • git diff --check passed.
  • Browser E2E passed: Base URL visible, provider save works, mock upstream receives models/chat requests.

CodeRabbit not manually triggered.

@awsl233777

Copy link
Copy Markdown
Collaborator Author

Pushed ec9a388.

Changed:

  • Forced the “Independent config” badge foreground to #fff.
  • Forced the primary Local AI button foreground, including “Add upstream”, to #fff.

Validation:

  • npm run lint passed.
  • npm run test:unit passed, 751 tests.
  • npm run test:e2e passed.
  • git diff --check passed.
  • Browser E2E passed: Base URL visible, provider save works, mock upstream receives models/chat requests.

CodeRabbit not manually triggered.

@coderabbitai coderabbitai Bot left a comment

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 comments (2)
web-ui/modules/app.methods.task-orchestration.mjs (2)

657-662: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Consider skipping the model refetch on each poll.

loadTaskOrchestrationOverview runs on the 4s polling loop while a run is active, so fetchLocalAiGatewayModels({ silent: true }) fires a /models GET every cycle even though detected models rarely change. Consider only fetching when the model list is empty or the gateway endpoint/readiness actually changed. Note: the silent option is passed but fetchLocalAiGatewayModels never reads it.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web-ui/modules/app.methods.task-orchestration.mjs` around lines 657 - 662,
The polling path in loadTaskOrchestrationOverview is refetching local AI gateway
models on every cycle via fetchLocalAiGatewayModels even though the models
rarely change, and the silent option is unused. Update the task orchestration
flow to skip this GET unless localAiDetectedModels is empty or the
localAiServiceStatus/readiness or gateway endpoint has actually changed, and
either remove or wire up the silent parameter in fetchLocalAiGatewayModels so
the call sites reflect real behavior.

436-475: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Localize the model-fetch error strings.

state.localAiModelsError is set to hardcoded English ('Models endpoint unavailable', 'No models returned', 'Models endpoint timed out', 'Failed to fetch models', 'Models endpoint returned invalid JSON'). Every other user-facing message in this module routes through showTaskOrchestrationMessage with i18n keys, so these will render untranslated when this field is displayed. Prefer storing an i18n key (or a translated string via this.t) instead of raw text.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web-ui/modules/app.methods.task-orchestration.mjs` around lines 436 - 475,
The model-fetch path in `fetchLocalAiGatewayModels` is assigning hardcoded
English strings directly to `state.localAiModelsError`, which bypasses the
module’s i18n flow. Update the error handling in this method to store an i18n
key or translate via `this.t` for every message (`Models endpoint unavailable`,
`No models returned`, `Models endpoint timed out`, `Failed to fetch models`,
`Models endpoint returned invalid JSON`, and the HTTP error case) so any UI
displaying `localAiModelsError` renders localized text consistently.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@web-ui/modules/app.methods.task-orchestration.mjs`:
- Around line 657-662: The polling path in loadTaskOrchestrationOverview is
refetching local AI gateway models on every cycle via fetchLocalAiGatewayModels
even though the models rarely change, and the silent option is unused. Update
the task orchestration flow to skip this GET unless localAiDetectedModels is
empty or the localAiServiceStatus/readiness or gateway endpoint has actually
changed, and either remove or wire up the silent parameter in
fetchLocalAiGatewayModels so the call sites reflect real behavior.
- Around line 436-475: The model-fetch path in `fetchLocalAiGatewayModels` is
assigning hardcoded English strings directly to `state.localAiModelsError`,
which bypasses the module’s i18n flow. Update the error handling in this method
to store an i18n key or translate via `this.t` for every message (`Models
endpoint unavailable`, `No models returned`, `Models endpoint timed out`,
`Failed to fetch models`, `Models endpoint returned invalid JSON`, and the HTTP
error case) so any UI displaying `localAiModelsError` renders localized text
consistently.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: fd5d9690-c536-4af0-a7ef-2e941aaecf3d

📥 Commits

Reviewing files that changed from the base of the PR and between 200963b and dcaa08f.

📒 Files selected for processing (13)
  • tests/unit/config-tabs-ui.test.mjs
  • tests/unit/web-ui-behavior-parity.test.mjs
  • tests/unit/web-ui-logic.test.mjs
  • web-ui/app.js
  • web-ui/modules/app.computed.main-tabs.mjs
  • web-ui/modules/app.methods.task-orchestration.mjs
  • web-ui/modules/i18n/locales/en.mjs
  • web-ui/modules/i18n/locales/ja.mjs
  • web-ui/modules/i18n/locales/vi.mjs
  • web-ui/modules/i18n/locales/zh-tw.mjs
  • web-ui/modules/i18n/locales/zh.mjs
  • web-ui/partials/index/panel-local-ai.html
  • web-ui/res/web-ui-render.precompiled.js
✅ Files skipped from review due to trivial changes (1)
  • web-ui/modules/i18n/locales/zh.mjs
🚧 Files skipped from review as they are similar to previous changes (10)
  • web-ui/app.js
  • web-ui/partials/index/panel-local-ai.html
  • tests/unit/web-ui-behavior-parity.test.mjs
  • web-ui/modules/i18n/locales/en.mjs
  • web-ui/modules/i18n/locales/vi.mjs
  • tests/unit/config-tabs-ui.test.mjs
  • web-ui/modules/i18n/locales/zh-tw.mjs
  • web-ui/modules/i18n/locales/ja.mjs
  • tests/unit/web-ui-logic.test.mjs
  • web-ui/modules/app.computed.main-tabs.mjs
📜 Review details
🔇 Additional comments (6)
web-ui/modules/app.methods.task-orchestration.mjs (6)

33-58: LGTM!


81-97: LGTM!


379-419: LGTM!


482-527: LGTM!


539-612: LGTM!


1182-1238: LGTM!

@awsl233777 awsl233777 changed the title style(local-ai): apply calm gateway console feat(local-ai): expose client gateway model mapping Jul 4, 2026

@coderabbitai coderabbitai Bot left a comment

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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
web-ui/modules/app.methods.task-orchestration.mjs (1)

426-480: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Unguarded concurrent fetch can overwrite fresh model list with stale data.

fetchLocalAiGatewayModels has no protection against overlapping invocations. It's fired-and-forgotten (void this.fetchLocalAiGatewayModels(...)) from loadTaskOrchestrationOverview on every poll cycle (every 4s per syncTaskOrchestrationPolling), while its own default timeout is 5000ms — so a newer call can start before an older one resolves. If the older (stale) request resolves after the newer one, its state.localAiDetectedModels/state.localAiModelsError/state.localAiModelsLoading writes silently clobber the fresher result. The file already has a proven fix for exactly this pattern via the detailRequestToken counter in loadTaskRunDetail (Lines 939-983); the same technique should be applied here.

🔧 Proposed fix using a request token
         async fetchLocalAiGatewayModels(options = {}) {
             const state = this.ensureTaskOrchestrationState();
             const status = state.localAiServiceStatus && typeof state.localAiServiceStatus === 'object'
                 ? state.localAiServiceStatus
                 : {};
             if (!status.ready) {
                 state.localAiDetectedModels = [];
                 state.localAiModelsError = '';
                 return [];
             }
             const modelsUrl = String(this.localAiGatewayModelsUrl || '').trim();
             if (!modelsUrl || typeof fetchImpl !== 'function') {
                 state.localAiDetectedModels = [];
                 state.localAiModelsError = 'Models endpoint unavailable';
                 return [];
             }
+            const requestToken = (state.localAiModelsRequestToken || 0) + 1;
+            state.localAiModelsRequestToken = requestToken;
             state.localAiModelsLoading = true;
             state.localAiModelsError = '';
             const timeoutMs = Number.isFinite(Number(options.timeoutMs)) ? Number(options.timeoutMs) : 5000;
             const controller = typeof AbortController === 'function' ? new AbortController() : null;
             const timeoutId = controller ? setTimeout(() => controller.abort(), timeoutMs) : 0;
             try {
                 const res = await fetchImpl(modelsUrl, {
                     method: 'GET',
                     headers: { Accept: 'application/json' },
                     ...(controller ? { signal: controller.signal } : {})
                 });
                 const bodyText = typeof res.text === 'function' ? await res.text() : '';
                 let payload = {};
                 if (bodyText.trim()) {
                     try {
                         payload = JSON.parse(bodyText);
                     } catch (_error) {
                         throw new Error('Models endpoint returned invalid JSON');
                     }
                 }
                 if (!res.ok) {
                     const message = payload && payload.error ? String(payload.error) : `Models endpoint returned HTTP ${res.status}`;
                     throw new Error(message);
                 }
                 const models = normalizeLocalAiGatewayModelIds(payload);
+                if (state.localAiModelsRequestToken !== requestToken) return models;
                 state.localAiDetectedModels = models;
                 state.localAiModelsError = models.length ? '' : 'No models returned';
                 return models;
             } catch (error) {
+                if (state.localAiModelsRequestToken !== requestToken) return [];
                 state.localAiDetectedModels = [];
                 state.localAiModelsError = error && error.name === 'AbortError'
                     ? 'Models endpoint timed out'
                     : (error && error.message ? error.message : 'Failed to fetch models');
                 return [];
             } finally {
                 if (timeoutId) clearTimeout(timeoutId);
-                state.localAiModelsLoading = false;
+                if (state.localAiModelsRequestToken === requestToken) {
+                    state.localAiModelsLoading = false;
+                }
             }
         },

Also applies to: 654-662

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web-ui/modules/app.methods.task-orchestration.mjs` around lines 426 - 480,
`fetchLocalAiGatewayModels` can race with overlapping poll-triggered calls and
let stale responses overwrite newer model data. Add a request token/counter to
this method, similar to `loadTaskRunDetail`’s `detailRequestToken`, and only
apply `state.localAiDetectedModels`, `state.localAiModelsError`, and
`state.localAiModelsLoading` updates if the current token is still the latest
invocation. Use the unique symbols `fetchLocalAiGatewayModels`,
`loadTaskOrchestrationOverview`, and `syncTaskOrchestrationPolling` to locate
the polling path and ensure older in-flight requests are ignored.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@web-ui/modules/app.methods.task-orchestration.mjs`:
- Around line 426-480: `fetchLocalAiGatewayModels` can race with overlapping
poll-triggered calls and let stale responses overwrite newer model data. Add a
request token/counter to this method, similar to `loadTaskRunDetail`’s
`detailRequestToken`, and only apply `state.localAiDetectedModels`,
`state.localAiModelsError`, and `state.localAiModelsLoading` updates if the
current token is still the latest invocation. Use the unique symbols
`fetchLocalAiGatewayModels`, `loadTaskOrchestrationOverview`, and
`syncTaskOrchestrationPolling` to locate the polling path and ensure older
in-flight requests are ignored.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 4a090863-3b67-4407-8a00-d699ea5d767c

📥 Commits

Reviewing files that changed from the base of the PR and between dcaa08f and cb5b150.

📒 Files selected for processing (13)
  • cli.js
  • cli/local-bridge.js
  • tests/unit/config-tabs-ui.test.mjs
  • tests/unit/local-bridge-claude-target-api.test.mjs
  • tests/unit/web-ui-logic.test.mjs
  • web-ui/modules/app.methods.task-orchestration.mjs
  • web-ui/modules/i18n/locales/en.mjs
  • web-ui/modules/i18n/locales/ja.mjs
  • web-ui/modules/i18n/locales/vi.mjs
  • web-ui/modules/i18n/locales/zh-tw.mjs
  • web-ui/modules/i18n/locales/zh.mjs
  • web-ui/partials/index/panel-local-ai.html
  • web-ui/res/web-ui-render.precompiled.js
✅ Files skipped from review due to trivial changes (1)
  • web-ui/modules/i18n/locales/ja.mjs
🚧 Files skipped from review as they are similar to previous changes (9)
  • tests/unit/config-tabs-ui.test.mjs
  • web-ui/partials/index/panel-local-ai.html
  • web-ui/modules/i18n/locales/en.mjs
  • web-ui/modules/i18n/locales/zh.mjs
  • web-ui/modules/i18n/locales/vi.mjs
  • cli/local-bridge.js
  • cli.js
  • tests/unit/web-ui-logic.test.mjs
  • web-ui/modules/i18n/locales/zh-tw.mjs
📜 Review details
🔇 Additional comments (4)
tests/unit/local-bridge-claude-target-api.test.mjs (1)

203-305: LGTM!

web-ui/modules/app.methods.task-orchestration.mjs (3)

439-439: 📐 Maintainability & Code Quality | ⚡ Quick win

Untranslated error strings break i18n consistency.

fetchLocalAiGatewayModels sets state.localAiModelsError to plain English literals (e.g. 'Models endpoint unavailable', 'No models returned', 'Models endpoint timed out'), while the rest of the file consistently routes user-facing text through t()/i18n keys (see formatTaskNodeDependencies/formatTaskNodeLogs/formatTaskNodeOutputText and all the showTaskOrchestrationMessage(..., 'orchestration.toast...') calls). If this error string is rendered in the UI it will not be localized.

Also applies to: 459-459, 464-464, 468-468, 472-474


286-378: LGTM!

Also applies to: 482-612, 614-1181, 1217-1260


1182-1215: 🎯 Functional Correctness

No change needed. taskOrchestrationWorkspacePath is already defined in the main-tabs computed state, and startNewTaskWorkspaceSession() falls back to the current workspace anyway. abort is intentionally routed to the currently selected orchestration run; this webview contract only sends the message type, not a run/session id.

			> Likely an incorrect or invalid review comment.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@web-ui/partials/index/panel-local-ai.html`:
- Around line 54-56: The request log in the panel-local-ai template mixes
translated and hardcoded labels, because the provider label uses
t('localAi.client.requestLog.provider') while the model label is still hardcoded
as “model:”. Update the span in the request log section to use the same i18n
pattern as the rest of this panel, adding or reusing the appropriate translation
key for the model label and referencing the existing template bindings around
item.provider, item.model, and item.at.
- Line 54: The request log provider label in the local AI panel is using the
initial provider selection, which can be wrong after failover. Update the
display logic in the panel-local-ai template to show the serving upstream first
by using item.upstreamProvider when present, and fall back to item.provider only
if it is missing. Keep the change focused on the provider span in the local AI
request log row.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 9c026306-2d7d-49c5-820d-8915d6a1a9a7

📥 Commits

Reviewing files that changed from the base of the PR and between 7f87f44 and e205442.

📒 Files selected for processing (16)
  • cli.js
  • cli/local-bridge.js
  • tests/unit/config-tabs-ui.test.mjs
  • tests/unit/local-bridge-claude-target-api.test.mjs
  • tests/unit/web-ui-behavior-parity.test.mjs
  • tests/unit/web-ui-logic.test.mjs
  • web-ui/modules/app.computed.main-tabs.mjs
  • web-ui/modules/app.methods.task-orchestration.mjs
  • web-ui/modules/i18n/locales/en.mjs
  • web-ui/modules/i18n/locales/ja.mjs
  • web-ui/modules/i18n/locales/vi.mjs
  • web-ui/modules/i18n/locales/zh-tw.mjs
  • web-ui/modules/i18n/locales/zh.mjs
  • web-ui/partials/index/panel-local-ai.html
  • web-ui/res/web-ui-render.precompiled.js
  • web-ui/styles/local-ai-service.css
✅ Files skipped from review due to trivial changes (3)
  • web-ui/modules/i18n/locales/en.mjs
  • web-ui/modules/i18n/locales/vi.mjs
  • web-ui/modules/i18n/locales/zh-tw.mjs
🚧 Files skipped from review as they are similar to previous changes (10)
  • tests/unit/local-bridge-claude-target-api.test.mjs
  • tests/unit/web-ui-behavior-parity.test.mjs
  • tests/unit/config-tabs-ui.test.mjs
  • web-ui/modules/i18n/locales/ja.mjs
  • web-ui/styles/local-ai-service.css
  • web-ui/modules/app.computed.main-tabs.mjs
  • web-ui/modules/i18n/locales/zh.mjs
  • cli.js
  • tests/unit/web-ui-logic.test.mjs
  • web-ui/modules/app.methods.task-orchestration.mjs
📜 Review details
🔇 Additional comments (5)
cli/local-bridge.js (4)

164-186: LGTM!


206-253: LGTM!


269-281: LGTM!


878-936: LGTM!

web-ui/partials/index/panel-local-ai.html (1)

45-66: 🩺 Stability & Availability

No action needed localAiRequestLog and localAiGatewayModelsUrl are both defined as computed properties, and localAiRequestLog falls back to [], so this block is safe as written.

			> Likely an incorrect or invalid review comment.

<span class="local-ai-request-duration">{{ item.durationMs }}ms</span>
</div>
<div class="local-ai-request-log-meta">
<span v-if="item.provider">{{ t('localAi.client.requestLog.provider') }}: {{ item.provider }}</span>

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== panel-local-ai.html ==\n'
grep -n "item\.provider\|item\.upstreamProvider\|requestLog.provider" -n web-ui/partials/index/panel-local-ai.html || true

printf '\n== cli/local-bridge.js relevant lines ==\n'
grep -n "requestLog.provider\|upstreamProvider\|X-Codexmate-Upstream-Provider\|failover\|entry.name" cli/local-bridge.js || true

printf '\n== surrounding context in cli/local-bridge.js ==\n'
python3 - <<'PY'
from pathlib import Path
p = Path('cli/local-bridge.js')
text = p.read_text().splitlines()
for i, line in enumerate(text, 1):
    if any(k in line for k in ['requestLog.provider', 'upstreamProvider', 'X-Codexmate-Upstream-Provider', 'failover', 'entry.name']):
        start = max(1, i-8)
        end = min(len(text), i+12)
        print(f"\n--- lines {start}-{end} around line {i} ---")
        for j in range(start, end+1):
            print(f"{j:4d}: {text[j-1]}")
PY

printf '\n== surrounding context in web-ui/partials/index/panel-local-ai.html ==\n'
python3 - <<'PY'
from pathlib import Path
p = Path('web-ui/partials/index/panel-local-ai.html')
text = p.read_text().splitlines()
for i, line in enumerate(text, 1):
    if 'item.provider' in line or 'item.upstreamProvider' in line:
        start = max(1, i-6)
        end = min(len(text), i+8)
        print(f"\n--- lines {start}-{end} around line {i} ---")
        for j in range(start, end+1):
            print(f"{j:4d}: {text[j-1]}")
PY

Repository: SakuraByteCore/codexmate

Length of output: 37325


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path

files = [
    'cli/local-bridge.js',
    'web-ui/partials/index/panel-local-ai.html',
]
for file in files:
    p = Path(file)
    if not p.exists():
        print(f'MISSING: {file}')
        continue
    print(f'\n== {file} ==')
    text = p.read_text().splitlines()
    keys = ['requestLog.provider', 'upstreamProvider', 'X-Codexmate-Upstream-Provider', 'item.provider', 'item.upstreamProvider', 'failover', 'entry.name']
    for i, line in enumerate(text, 1):
        if any(k in line for k in keys):
            start = max(1, i-6)
            end = min(len(text), i+10)
            print(f'--- around line {i} ---')
            for j in range(start, end+1):
                print(f'{j:4d}: {text[j-1]}')
            print()
PY

Repository: SakuraByteCore/codexmate

Length of output: 29019


Display the serving upstream instead of the initial pick. item.provider is set before failover, so this can mislabel retries; use item.upstreamProvider || item.provider here.

💡 Proposed fix
-                                                    <span v-if="item.provider">{{ t('localAi.client.requestLog.provider') }}: {{ item.provider }}</span>
+                                                    <span v-if="item.upstreamProvider || item.provider">{{ t('localAi.client.requestLog.provider') }}: {{ item.upstreamProvider || item.provider }}</span>
📝 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.

Suggested change
<span v-if="item.provider">{{ t('localAi.client.requestLog.provider') }}: {{ item.provider }}</span>
<span v-if="item.upstreamProvider || item.provider">{{ t('localAi.client.requestLog.provider') }}: {{ item.upstreamProvider || item.provider }}</span>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web-ui/partials/index/panel-local-ai.html` at line 54, The request log
provider label in the local AI panel is using the initial provider selection,
which can be wrong after failover. Update the display logic in the
panel-local-ai template to show the serving upstream first by using
item.upstreamProvider when present, and fall back to item.provider only if it is
missing. Keep the change focused on the provider span in the local AI request
log row.

Comment on lines +54 to +56
<span v-if="item.provider">{{ t('localAi.client.requestLog.provider') }}: {{ item.provider }}</span>
<span v-if="item.model">model: {{ item.model }}</span>
<span>{{ item.at }}</span>

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.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Hardcoded model: label breaks i18n consistency.

Line 54 routes the provider label through t('localAi.client.requestLog.provider'), but Line 55 hardcodes model: in English. Route it through a translation key like the rest of the form, matching the i18n fixes applied earlier in this panel.

💡 Proposed fix
-                                                    <span v-if="item.model">model: {{ item.model }}</span>
+                                                    <span v-if="item.model">{{ t('localAi.client.requestLog.model') }}: {{ item.model }}</span>
📝 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.

Suggested change
<span v-if="item.provider">{{ t('localAi.client.requestLog.provider') }}: {{ item.provider }}</span>
<span v-if="item.model">model: {{ item.model }}</span>
<span>{{ item.at }}</span>
<span v-if="item.provider">{{ t('localAi.client.requestLog.provider') }}: {{ item.provider }}</span>
<span v-if="item.model">{{ t('localAi.client.requestLog.model') }}: {{ item.model }}</span>
<span>{{ item.at }}</span>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web-ui/partials/index/panel-local-ai.html` around lines 54 - 56, The request
log in the panel-local-ai template mixes translated and hardcoded labels,
because the provider label uses t('localAi.client.requestLog.provider') while
the model label is still hardcoded as “model:”. Update the span in the request
log section to use the same i18n pattern as the rest of this panel, adding or
reusing the appropriate translation key for the model label and referencing the
existing template bindings around item.provider, item.model, and item.at.

@coderabbitai coderabbitai Bot left a comment

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.

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 (2)
cli/local-bridge.js (2)

764-770: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Apply configured model mapping to native /responses requests.

The passthrough loop maps chat/completions to attemptEntry.model, but this native Responses path forwards the client model unchanged. With an upstream model configured, Responses clients can still hit upstream “unknown model” failures.

Proposed mapping fix
                 }
-                const responsesRequest = parsed.value;
+                const responsesRequest = parsed.value && typeof parsed.value === 'object' && !Array.isArray(parsed.value)
+                    ? { ...parsed.value }
+                    : parsed.value;
+                if (responsesRequest && typeof responsesRequest === 'object' && !Array.isArray(responsesRequest)) {
+                    if (entry.model) responsesRequest.model = entry.model;
+                    if (responsesRequest.temperature === undefined && entry.temperature !== null && entry.temperature !== undefined) {
+                        responsesRequest.temperature = entry.temperature;
+                    }
+                }
                 const wantsSse = !!(responsesRequest && responsesRequest.stream);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cli/local-bridge.js` around lines 764 - 770, The native Responses passthrough
in local-bridge.js is forwarding the client-supplied model unchanged, unlike the
chat/completions path that applies the configured upstream model mapping. Update
the /responses handling around parsed.value and proxyRequestJson so the request
model is rewritten through the same mapping logic used by the passthrough loop
(for example via attemptEntry.model or the shared model-mapping helper) before
sending to the upstream responses endpoint. Keep the change localized to the
responses request construction so both streaming and non-streaming Requests use
the mapped model consistently.

879-950: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Split audio/* and images/* out of the JSON proxy. This branch routes audio/*, images/*, and chat/completions through proxyRequestJson(), so it parses every body as JSON and always writes JSON back. That breaks multipart uploads and buffers/mislabells streamed or binary responses. Narrow the matcher to JSON-only endpoints, or add raw/streaming passthrough that preserves content type.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cli/local-bridge.js` around lines 879 - 950, The proxy branch in local-bridge
should not treat audio/* and images/* the same as JSON endpoints, because it
currently forces request parsing through proxyRequestJson() and always responds
with JSON. Update the matcher around the failover/proxy path to exclude binary
or multipart routes and handle audio/images with raw or streamed passthrough
instead, preserving the upstream content type and response bytes. Keep the
JSON-specific shaping only for chat/completions and similar JSON-only paths,
using the existing helpers like proxyRequestJson(), joinApiUrl(), and
shouldFailover() to locate the logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cli/local-bridge.js`:
- Around line 693-695: The route check in the local bridge path matcher is too
permissive because `startsWith('v1')` accepts longer prefixes like `v10` and
`v1evil`. Update the `pathname`/`suffix` validation in the local bridge routing
logic so `v1` is matched only as a full path segment in the same place where
`isCodexLocalBridgeSuffix(suffix)` is used, and reject any non-segment matches
before forwarding upstream.
- Around line 190-193: The redactText helper in local-bridge.js only masks a
limited set of secret field names, so common variants like access_token,
refresh_token, id_token, and client_secret can still appear in bodyPreview.
Expand the redaction logic in redactText to cover these additional JSON key
names before preview logging, and keep the existing bearer/sk- masking intact so
all logged previews consistently hide token-like values.

---

Outside diff comments:
In `@cli/local-bridge.js`:
- Around line 764-770: The native Responses passthrough in local-bridge.js is
forwarding the client-supplied model unchanged, unlike the chat/completions path
that applies the configured upstream model mapping. Update the /responses
handling around parsed.value and proxyRequestJson so the request model is
rewritten through the same mapping logic used by the passthrough loop (for
example via attemptEntry.model or the shared model-mapping helper) before
sending to the upstream responses endpoint. Keep the change localized to the
responses request construction so both streaming and non-streaming Requests use
the mapped model consistently.
- Around line 879-950: The proxy branch in local-bridge should not treat audio/*
and images/* the same as JSON endpoints, because it currently forces request
parsing through proxyRequestJson() and always responds with JSON. Update the
matcher around the failover/proxy path to exclude binary or multipart routes and
handle audio/images with raw or streamed passthrough instead, preserving the
upstream content type and response bytes. Keep the JSON-specific shaping only
for chat/completions and similar JSON-only paths, using the existing helpers
like proxyRequestJson(), joinApiUrl(), and shouldFailover() to locate the logic.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: cab2f308-dcfb-4f32-b39a-eec415474774

📥 Commits

Reviewing files that changed from the base of the PR and between e205442 and d91bb56.

📒 Files selected for processing (2)
  • cli/local-bridge.js
  • tests/unit/local-bridge-claude-target-api.test.mjs
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/unit/local-bridge-claude-target-api.test.mjs

Comment thread cli/local-bridge.js
Comment on lines +190 to +193
const redactText = (value) => String(value || '')
.replace(/("(?:api[_-]?key|token|authorization|password|secret)"\s*:\s*")[^"]*(")/ig, '$1***$2')
.replace(/(Bearer\s+)[A-Za-z0-9._~+\-/=]+/ig, '$1***')
.replace(/\bsk-[A-Za-z0-9._-]+\b/g, 'sk-***');

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.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Redact common token key variants before logging previews.

access_token, refresh_token, id_token, and client_secret are not matched here, so they can leak into bodyPreview.

Proposed redaction hardening
     const redactText = (value) => String(value || '')
-        .replace(/("(?:api[_-]?key|token|authorization|password|secret)"\s*:\s*")[^"]*(")/ig, '$1***$2')
+        .replace(/("(?:api[_-]?key|x[_-]?api[_-]?key|authorization|password|secret|client[_-]?secret|access[_-]?token|refresh[_-]?token|id[_-]?token|session[_-]?token|auth[_-]?token)"\s*:\s*")[^"]*(")/ig, '$1***$2')
         .replace(/(Bearer\s+)[A-Za-z0-9._~+\-/=]+/ig, '$1***')
         .replace(/\bsk-[A-Za-z0-9._-]+\b/g, 'sk-***');
📝 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.

Suggested change
const redactText = (value) => String(value || '')
.replace(/("(?:api[_-]?key|token|authorization|password|secret)"\s*:\s*")[^"]*(")/ig, '$1***$2')
.replace(/(Bearer\s+)[A-Za-z0-9._~+\-/=]+/ig, '$1***')
.replace(/\bsk-[A-Za-z0-9._-]+\b/g, 'sk-***');
const redactText = (value) => String(value || '')
.replace(/("(?:api[_-]?key|x[_-]?api[_-]?key|authorization|password|secret|client[_-]?secret|access[_-]?token|refresh[_-]?token|id[_-]?token|session[_-]?token|auth[_-]?token)"\s*:\s*")[^"]*(")/ig, '$1***$2')
.replace(/(Bearer\s+)[A-Za-z0-9._~+\-/=]+/ig, '$1***')
.replace(/\bsk-[A-Za-z0-9._-]+\b/g, 'sk-***');
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cli/local-bridge.js` around lines 190 - 193, The redactText helper in
local-bridge.js only masks a limited set of secret field names, so common
variants like access_token, refresh_token, id_token, and client_secret can still
appear in bodyPreview. Expand the redaction logic in redactText to cover these
additional JSON key names before preview logging, and keep the existing
bearer/sk- masking intact so all logged previews consistently hide token-like
values.

Comment thread cli/local-bridge.js
@awsl233777 awsl233777 force-pushed the feat/local-ai-service-provider-console branch from 79a51ff to 785a422 Compare July 4, 2026 19:15

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
web-ui/session-helpers.mjs (1)

82-91: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Keep the orchestration tab checks aligned with the new local-ai alias. switchMainTab() now normalizes 'orchestration' to 'local-ai', so enteringOrchestrationTab and nextTab !== 'orchestration' no longer fire during normal tab switches. That leaves includeDetail off for the renamed tab and stops orchestration polling as soon as you switch into it; the same stale 'orchestration' guard also exists in web-ui/modules/app.methods.task-orchestration.mjs:1207.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web-ui/session-helpers.mjs` around lines 82 - 91, Align the orchestration tab
logic with the new local-ai alias in switchMainTab() and the task-orchestration
polling code. Update the tab checks so the renamed tab is treated as active when
nextTab is normalized from orchestration to local-ai, and replace any remaining
nextTab !== 'orchestration' style guard in the orchestration methods (including
the one in app.methods.task-orchestration.mjs) so includeDetail and polling
continue to work for the renamed tab.
cli/local-bridge.js (1)

799-868: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Use the same provider failover policy for /responses.

This branch retries only the initially selected provider; network errors or failed chat-completions fallback return immediately instead of trying the next healthy upstream.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cli/local-bridge.js` around lines 799 - 868, The /responses path in
local-bridge.js is only retrying the initially selected upstream, so provider
failover is not applied consistently. Update the logic around
retryLocalBridgeUpstreamRequest, shouldFallbackFromUpstreamResponses, and the
chat/completions fallback so that both network failures and fallback conversion
failures continue through the same provider selection policy used elsewhere
instead of returning immediately. Keep the fix localized to the
upstreamResponsesResult handling and the subsequent chatResult path, and ensure
the next healthy provider is attempted before surfacing an error.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cli/local-bridge.js`:
- Around line 307-309: The upstream error extraction in local-bridge.js only
reads parsed.error as a string, so OpenAI-style responses with error.message end
up logged as empty. Update the error parsing logic around the JSON.parse
handling to detect object-shaped error payloads, especially
parsed.error.message, and fall back to the existing string path when needed.
Keep the extracted message trimmed to the same length limit before assigning it
to item.error, and preserve the current try/catch behavior in the request
logging flow.
- Around line 239-242: The request summary built in the payload handling logic
can leak secrets because it appends raw `messages[].content` and `input` text
even when `bodyPreview` is redacted. Update the summary construction in the code
path that uses `payload.messages` and `payload.input` to redact or omit
sensitive snippets before pushing them into `summaryParts`, preserving only safe
metadata or a sanitized preview.
- Around line 63-72: The route predicate in isCodexLocalBridgeSuffix is too
permissive for embeddings and can match invalid suffixes like embeddingsevil.
Tighten the embeddings check so it only accepts the exact embeddings endpoint or
embeddings followed by a valid path separator/query boundary, matching the
existing style used for responses, chat/completions, and models. Keep the fix
localized to isCodexLocalBridgeSuffix and adjust the embeddings branch
accordingly.
- Around line 796-801: The /responses forwarding path in local-bridge.js is
passing responsesRequest through unchanged, so the configured upstream model
mapping is never applied. Update the branch around proxyRequestJson and
retryLocalBridgeUpstreamRequest to transform the parsed request before sending
it upstream, using the same model-mapping logic already used for other request
types so a configured upstream model overrides the client model. Locate the fix
in the responses handling block that builds upstreamResponsesUrl and sends body:
responsesRequest.

In `@web-ui/styles/local-ai-service.css`:
- Around line 226-228: The grid-column rule is targeting the wrong selector, so
it never matches the actual request section element. Update the selector in
local-ai-service.css from the data-component-style name to the real class used
by panel-local-requests.html, namely local-ai-request-section, so the styling
applies to the section rendered by that component.
- Around line 285-289: The body-preview styling is targeting a nested code
element that does not exist for the local request detail preview, so the
wrapping rules never apply. Update the selector in local-ai-service.css to
target the actual .local-ai-request-body element used by the preview inside
.local-ai-request-detail, and keep the existing block, pre-wrap, and full-width
behavior there so the body text wraps correctly instead of overflowing.

---

Outside diff comments:
In `@cli/local-bridge.js`:
- Around line 799-868: The /responses path in local-bridge.js is only retrying
the initially selected upstream, so provider failover is not applied
consistently. Update the logic around retryLocalBridgeUpstreamRequest,
shouldFallbackFromUpstreamResponses, and the chat/completions fallback so that
both network failures and fallback conversion failures continue through the same
provider selection policy used elsewhere instead of returning immediately. Keep
the fix localized to the upstreamResponsesResult handling and the subsequent
chatResult path, and ensure the next healthy provider is attempted before
surfacing an error.

In `@web-ui/session-helpers.mjs`:
- Around line 82-91: Align the orchestration tab logic with the new local-ai
alias in switchMainTab() and the task-orchestration polling code. Update the tab
checks so the renamed tab is treated as active when nextTab is normalized from
orchestration to local-ai, and replace any remaining nextTab !== 'orchestration'
style guard in the orchestration methods (including the one in
app.methods.task-orchestration.mjs) so includeDetail and polling continue to
work for the renamed tab.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 79008214-b605-4fd8-ad88-de0a89f580ec

📥 Commits

Reviewing files that changed from the base of the PR and between 79a51ff and 785a422.

📒 Files selected for processing (22)
  • cli.js
  • cli/local-bridge.js
  • tests/unit/config-tabs-ui.test.mjs
  • tests/unit/local-bridge-claude-target-api.test.mjs
  • tests/unit/web-ui-behavior-parity.test.mjs
  • tests/unit/web-ui-logic.test.mjs
  • web-ui/app.js
  • web-ui/index.html
  • web-ui/modules/app.computed.main-tabs.mjs
  • web-ui/modules/app.methods.navigation.mjs
  • web-ui/modules/app.methods.task-orchestration.mjs
  • web-ui/modules/i18n/locales/en.mjs
  • web-ui/modules/i18n/locales/ja.mjs
  • web-ui/modules/i18n/locales/vi.mjs
  • web-ui/modules/i18n/locales/zh-tw.mjs
  • web-ui/modules/i18n/locales/zh.mjs
  • web-ui/partials/index/layout-header.html
  • web-ui/partials/index/panel-local-ai.html
  • web-ui/partials/index/panel-local-requests.html
  • web-ui/res/web-ui-render.precompiled.js
  • web-ui/session-helpers.mjs
  • web-ui/styles/local-ai-service.css
✅ Files skipped from review due to trivial changes (2)
  • web-ui/index.html
  • web-ui/modules/i18n/locales/ja.mjs
🚧 Files skipped from review as they are similar to previous changes (14)
  • web-ui/app.js
  • tests/unit/web-ui-behavior-parity.test.mjs
  • web-ui/modules/app.methods.navigation.mjs
  • tests/unit/config-tabs-ui.test.mjs
  • tests/unit/local-bridge-claude-target-api.test.mjs
  • web-ui/modules/i18n/locales/zh-tw.mjs
  • web-ui/modules/i18n/locales/en.mjs
  • web-ui/partials/index/layout-header.html
  • cli.js
  • web-ui/modules/i18n/locales/vi.mjs
  • web-ui/modules/app.computed.main-tabs.mjs
  • web-ui/modules/i18n/locales/zh.mjs
  • tests/unit/web-ui-logic.test.mjs
  • web-ui/modules/app.methods.task-orchestration.mjs
📜 Review details
🧰 Additional context used
🪛 ast-grep (0.44.0)
cli/local-bridge.js

[warning] 156-156: Avoid using the initial state variable in setState
Context: setTimeout(resolve, delay)
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(setstate-same-var)

🪛 HTMLHint (1.9.2)
web-ui/partials/index/panel-local-requests.html

[error] 1-1: Doctype must be declared before any non-comment content.

(doctype-first)

🔇 Additional comments (11)
web-ui/partials/index/panel-local-requests.html (5)

21-21: 📐 Maintainability & Code Quality | ⚡ Quick win

Untranslated aria-label.

aria-label="Local service request configuration" is hardcoded English while the rest of this panel routes copy through t(...). This is the same concern raised previously on the sibling gateway-summary markup (now relocated here).

💡 Proposed fix
-                        <section class="local-ai-brief-row" aria-label="Local service request configuration">
+                        <section class="local-ai-brief-row" :aria-label="t('localAi.requests.briefAriaLabel')">

76-76: 🎯 Functional Correctness | ⚡ Quick win

Provider label should show the serving upstream, not the initial pick.

item.provider (Line 76) and localAiSelectedRequestLogItem.provider (Line 96) reflect the provider chosen before failover. If failover/retry occurs, this mislabels which upstream actually served the request. This is the same issue previously flagged on the (now relocated) request-log markup — prefer item.upstreamProvider || item.provider.

💡 Proposed fix
-                                        <span v-if="item.provider">{{ t('localAi.client.requestLog.provider') }}: {{ item.provider }}</span>
+                                        <span v-if="item.upstreamProvider || item.provider">{{ t('localAi.client.requestLog.provider') }}: {{ item.upstreamProvider || item.provider }}</span>
-                                            <dd>{{ localAiSelectedRequestLogItem.provider || '-' }}</dd>
+                                            <dd>{{ localAiSelectedRequestLogItem.upstreamProvider || localAiSelectedRequestLogItem.provider || '-' }}</dd>

Also applies to: 96-96


77-77: 📐 Maintainability & Code Quality | ⚡ Quick win

Hardcoded model: label breaks i18n consistency.

Line 76 routes the provider label through t('localAi.client.requestLog.provider'), but Line 77 hardcodes model: in English. Same unresolved concern previously flagged, now relocated to this file.

💡 Proposed fix
-                                        <span v-if="item.model">model: {{ item.model }}</span>
+                                        <span v-if="item.model">{{ t('localAi.client.requestLog.model') }}: {{ item.model }}</span>

1-1: Doctype hint is a false positive.

HTMLHint's doctype-first complaint applies since this is a partial fragment injected into a parent document, not a standalone HTML page.

Source: Linters/SAST tools


55-58: LGTM!

Also applies to: 60-118

web-ui/partials/index/panel-local-ai.html (2)

63-76: LGTM!


125-129: LGTM!

web-ui/styles/local-ai-service.css (1)

230-284: LGTM!

Also applies to: 349-354, 416-429, 474-582

cli/local-bridge.js (3)

222-225: Duplicate: broaden token-field redaction variants.

This still misses common JSON secret fields such as access_token, refresh_token, id_token, and client_secret.


725-727: Duplicate: require v1 to be a full path segment.

startsWith('v1') still accepts /bridge/local/v10/... and /bridge/local/v1evil.


38-38: LGTM!

Also applies to: 53-61, 129-167, 259-306, 310-317, 332-345, 760-763, 779-784, 828-836, 892-924, 943-985

Comment thread cli/local-bridge.js
Comment on lines +63 to +72
function isCodexLocalBridgeSuffix(suffix) {
const normalized = normalizeCodexLocalSuffix(`/bridge/local/${suffix || ''}`);
return !normalized
|| normalized === 'responses'
|| normalized === 'chat/completions'
|| normalized === 'models'
|| normalized.startsWith('models?')
|| normalized.startsWith('images/')
|| normalized.startsWith('audio/')
|| normalized.startsWith('embeddings');

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Require embeddings to match as an endpoint.

normalized.startsWith('embeddings') accepts invalid paths like /bridge/local/embeddingsevil and forwards them upstream.

Proposed route predicate tightening
 function isCodexLocalBridgeSuffix(suffix) {
     const normalized = normalizeCodexLocalSuffix(`/bridge/local/${suffix || ''}`);
     return !normalized
         || normalized === 'responses'
         || normalized === 'chat/completions'
         || normalized === 'models'
-        || normalized.startsWith('models?')
         || normalized.startsWith('images/')
         || normalized.startsWith('audio/')
-        || normalized.startsWith('embeddings');
+        || normalized === 'embeddings';
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cli/local-bridge.js` around lines 63 - 72, The route predicate in
isCodexLocalBridgeSuffix is too permissive for embeddings and can match invalid
suffixes like embeddingsevil. Tighten the embeddings check so it only accepts
the exact embeddings endpoint or embeddings followed by a valid path
separator/query boundary, matching the existing style used for responses,
chat/completions, and models. Keep the fix localized to isCodexLocalBridgeSuffix
and adjust the embeddings branch accordingly.

Comment thread cli/local-bridge.js
Comment thread cli/local-bridge.js
Comment on lines +307 to +309
const parsed = JSON.parse(Buffer.isBuffer(chunk) ? chunk.toString('utf-8') : String(chunk));
item.error = typeof parsed.error === 'string' ? parsed.error.slice(0, 240) : '';
} catch (_) { }

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Extract object-shaped upstream errors too.

OpenAI-style errors usually use error.message, so the request log can show an empty error for failed upstream calls.

Proposed error extraction fix
                         const parsed = JSON.parse(Buffer.isBuffer(chunk) ? chunk.toString('utf-8') : String(chunk));
-                        item.error = typeof parsed.error === 'string' ? parsed.error.slice(0, 240) : '';
+                        const errorValue = parsed && parsed.error;
+                        item.error = typeof errorValue === 'string'
+                            ? errorValue.slice(0, 240)
+                            : String((errorValue && (errorValue.message || errorValue.type)) || '').slice(0, 240);
📝 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.

Suggested change
const parsed = JSON.parse(Buffer.isBuffer(chunk) ? chunk.toString('utf-8') : String(chunk));
item.error = typeof parsed.error === 'string' ? parsed.error.slice(0, 240) : '';
} catch (_) { }
const parsed = JSON.parse(Buffer.isBuffer(chunk) ? chunk.toString('utf-8') : String(chunk));
const errorValue = parsed && parsed.error;
item.error = typeof errorValue === 'string'
? errorValue.slice(0, 240)
: String((errorValue && (errorValue.message || errorValue.type)) || '').slice(0, 240);
} catch (_) { }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cli/local-bridge.js` around lines 307 - 309, The upstream error extraction in
local-bridge.js only reads parsed.error as a string, so OpenAI-style responses
with error.message end up logged as empty. Update the error parsing logic around
the JSON.parse handling to detect object-shaped error payloads, especially
parsed.error.message, and fall back to the existing string path when needed.
Keep the extracted message trimmed to the same length limit before assigning it
to item.error, and preserve the current try/catch behavior in the request
logging flow.

Comment thread cli/local-bridge.js
Comment on lines 796 to 801
const responsesRequest = parsed.value;
const wantsSse = !!(responsesRequest && responsesRequest.stream);
const upstreamResponsesUrl = joinApiUrl(upstreamBase, 'responses');
const upstreamResponsesResult = await retryTransientRequest(() => proxyRequestJson(upstreamResponsesUrl, {
const upstreamResponsesResult = await retryLocalBridgeUpstreamRequest(entry, () => proxyRequestJson(upstreamResponsesUrl, {
method: 'POST',
body: responsesRequest,

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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Apply configured model mapping to /responses requests.

A filled upstream model should override the client model, but this branch forwards/converts the original responsesRequest unchanged.

Proposed mapping fix
-                const responsesRequest = parsed.value;
+                let responsesRequest = parsed.value;
+                if (responsesRequest && typeof responsesRequest === 'object' && !Array.isArray(responsesRequest)) {
+                    responsesRequest = { ...responsesRequest };
+                    if (entry.model) responsesRequest.model = entry.model;
+                    if (responsesRequest.temperature === undefined && entry.temperature !== null && entry.temperature !== undefined) {
+                        responsesRequest.temperature = entry.temperature;
+                    }
+                }
                 const wantsSse = !!(responsesRequest && responsesRequest.stream);
📝 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.

Suggested change
const responsesRequest = parsed.value;
const wantsSse = !!(responsesRequest && responsesRequest.stream);
const upstreamResponsesUrl = joinApiUrl(upstreamBase, 'responses');
const upstreamResponsesResult = await retryTransientRequest(() => proxyRequestJson(upstreamResponsesUrl, {
const upstreamResponsesResult = await retryLocalBridgeUpstreamRequest(entry, () => proxyRequestJson(upstreamResponsesUrl, {
method: 'POST',
body: responsesRequest,
let responsesRequest = parsed.value;
if (responsesRequest && typeof responsesRequest === 'object' && !Array.isArray(responsesRequest)) {
responsesRequest = { ...responsesRequest };
if (entry.model) responsesRequest.model = entry.model;
if (responsesRequest.temperature === undefined && entry.temperature !== null && entry.temperature !== undefined) {
responsesRequest.temperature = entry.temperature;
}
}
const wantsSse = !!(responsesRequest && responsesRequest.stream);
const upstreamResponsesUrl = joinApiUrl(upstreamBase, 'responses');
const upstreamResponsesResult = await retryLocalBridgeUpstreamRequest(entry, () => proxyRequestJson(upstreamResponsesUrl, {
method: 'POST',
body: responsesRequest,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cli/local-bridge.js` around lines 796 - 801, The /responses forwarding path
in local-bridge.js is passing responsesRequest through unchanged, so the
configured upstream model mapping is never applied. Update the branch around
proxyRequestJson and retryLocalBridgeUpstreamRequest to transform the parsed
request before sending it upstream, using the same model-mapping logic already
used for other request types so a configured upstream model overrides the client
model. Locate the fix in the responses handling block that builds
upstreamResponsesUrl and sends body: responsesRequest.

Comment on lines +226 to +228
.local-ai-request-info {
grid-column: 1 / -1;
}

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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

rg -n "local-ai-request-info|local-ai-request-section|data-component=\"local-ai-request-info\"|local-ai-calm-console" web-ui/styles/local-ai-service.css web-ui -S

Repository: SakuraByteCore/codexmate

Length of output: 1484


Use the actual section class here
.local-ai-request-info is only a data-component value on panel-local-requests.html; the element’s class is local-ai-request-section, so this grid-column: 1 / -1 rule never applies. Change the selector to .local-ai-request-section.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web-ui/styles/local-ai-service.css` around lines 226 - 228, The grid-column
rule is targeting the wrong selector, so it never matches the actual request
section element. Update the selector in local-ai-service.css from the
data-component-style name to the real class used by panel-local-requests.html,
namely local-ai-request-section, so the styling applies to the section rendered
by that component.

Comment on lines +285 to +289
.local-ai-brief-item .local-ai-request-body code {
display: block;
white-space: pre-wrap;
width: 100%;
}

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map the relevant files and inspect the exact surrounding lines.
git ls-files 'web-ui/styles/local-ai-service.css' 'web-ui/**/*.html' | sed -n '1,200p'

printf '\n--- local-ai-service.css (around lines 260-310) ---\n'
sed -n '260,310p' web-ui/styles/local-ai-service.css

printf '\n--- panel-local-requests.html: selector references ---\n'
rg -n 'local-ai-request-body|local-ai-brief-item|local-ai-request-detail|local-ai-request' web-ui -g '!*node_modules*'

printf '\n--- panel-local-requests.html (around matched lines) ---\n'
sed -n '1,260p' web-ui/panel-local-requests.html

Repository: SakuraByteCore/codexmate

Length of output: 11989


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n--- panel-local-requests.html (lines 80-120) ---\n'
sed -n '80,120p' web-ui/partials/index/panel-local-requests.html

printf '\n--- local-ai-service.css (lines 1-120) ---\n'
sed -n '1,120p' web-ui/styles/local-ai-service.css

printf '\n--- local-ai-service.css (lines 520-610) ---\n'
sed -n '520,610p' web-ui/styles/local-ai-service.css

Repository: SakuraByteCore/codexmate

Length of output: 7798


Scope the body-preview rule to .local-ai-request-body

In web-ui/partials/index/panel-local-requests.html, the body preview is rendered as <pre class="local-ai-request-body"> inside .local-ai-request-detail, so .local-ai-brief-item .local-ai-request-body code never matches that element. The preview keeps the default <pre> whitespace behavior and can overflow instead of wrapping.

Proposed fix
-.local-ai-brief-item .local-ai-request-body code {
+.local-ai-request-body code {
     display: block;
     white-space: pre-wrap;
     width: 100%;
 }
📝 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.

Suggested change
.local-ai-brief-item .local-ai-request-body code {
display: block;
white-space: pre-wrap;
width: 100%;
}
.local-ai-request-body code {
display: block;
white-space: pre-wrap;
width: 100%;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web-ui/styles/local-ai-service.css` around lines 285 - 289, The body-preview
styling is targeting a nested code element that does not exist for the local
request detail preview, so the wrapping rules never apply. Update the selector
in local-ai-service.css to target the actual .local-ai-request-body element used
by the preview inside .local-ai-request-detail, and keep the existing block,
pre-wrap, and full-width behavior there so the body text wraps correctly instead
of overflowing.

@awsl233777 awsl233777 force-pushed the feat/local-ai-service-provider-console branch from 785a422 to c94f00d Compare July 4, 2026 19:33
@awsl233777 awsl233777 changed the title feat(local-ai): expose client gateway model mapping feat(local-ai): expose service console and cached request logs Jul 4, 2026
@awsl233777 awsl233777 force-pushed the feat/local-ai-service-provider-console branch from c94f00d to 38a5aec Compare July 5, 2026 01:11
@awsl233777 awsl233777 changed the title feat(local-ai): expose service console and cached request logs feat(local-ai): expose compact service gateway and request logs Jul 5, 2026

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
web-ui/modules/app.methods.task-orchestration.mjs (1)

663-670: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Background/poll refresh unconditionally wipes fetched local AI models.

state.localAiDetectedModels/localAiModelsError are reset to empty on every successful loadTaskOrchestrationOverview() call, including silent background polling and the "请求" page's explicit refresh button. Unlike providerSetupDraft/webviewBridgeDraft (which merge/preserve current values via syncTaskProviderSelectionFromStatus/syncTaskWebviewBridgeDraft, addressing the earlier "background refresh overwrites in-progress edits" issue), this field has no such guard — a user who fetched models via fetchLocalAiGatewayModels() and is mid-selection on the provider form will silently lose the populated dropdown as soon as any overview refresh fires.

♻️ Suggested fix: only clear when provider identity actually changes
-                state.localAiDetectedModels = [];
-                state.localAiModelsError = '';
+                const previousProviderKey = state.localAiServiceStatus && state.localAiServiceStatus.selectedProvider;
+                const nextProviderKey = res && res.localAiServiceStatus && res.localAiServiceStatus.selectedProvider;
+                if (previousProviderKey !== nextProviderKey) {
+                    state.localAiDetectedModels = [];
+                    state.localAiModelsError = '';
+                }

Also applies to: 690-698

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web-ui/modules/app.methods.task-orchestration.mjs` around lines 663 - 670,
The overview refresh logic is wiping fetched local AI models on every successful
load, which can clear the provider form during background polling or manual
refresh. Update loadTaskOrchestrationOverview() so state.localAiDetectedModels
and state.localAiModelsError are only reset when the active provider identity
actually changes, using the same kind of preservation pattern already used by
syncTaskProviderSelectionFromStatus and syncTaskWebviewBridgeDraft. Keep
fetchLocalAiGatewayModels() results intact across ordinary refreshes, and only
clear them when a real provider switch makes the cached models invalid.
web-ui/session-helpers.mjs (1)

82-91: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

local-ai/local-requests never restart orchestration polling

switchMainTab('orchestration') now lands on local-ai, so enteringOrchestrationTab is dead here and includeDetail is always false on this path. More importantly, syncTaskOrchestrationPolling() still returns unless this.mainTab === 'orchestration', so any timer stopped on tab change never comes back while viewing local-ai or local-requests. Update the polling gate to the new tab set.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web-ui/session-helpers.mjs` around lines 82 - 91, The polling logic in
switchMainTab now maps orchestration to local-ai, but
syncTaskOrchestrationPolling still gates on this.mainTab === 'orchestration', so
polling never restarts from local-ai or local-requests. Update the tab checks in
session-helpers.mjs to use the new tab set consistently, remove or replace the
now-dead enteringOrchestrationTab branch, and make sure the restart condition in
syncTaskOrchestrationPolling matches local-ai/local-requests so timers resume
after tab changes.
🧹 Nitpick comments (2)
web-ui/partials/index/panel-local-requests.html (2)

43-43: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Duplicated status-pill class logic.

The item.status >= 200 && item.status < 400 ? 'success' : 'warn' ternary is duplicated between the list row (Line 43) and the detail header (Line 62). Consider extracting a small helper (e.g., localAiRequestStatusTone(status)) alongside the other request-log helpers in app.methods.task-orchestration.mjs.

Also applies to: 62-62

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web-ui/partials/index/panel-local-requests.html` at line 43, The
status-to-tone ternary is duplicated in the request log UI, so extract it into a
shared helper instead of repeating it. Add a small function like
localAiRequestStatusTone(status) alongside the other request-log helpers in
app.methods.task-orchestration.mjs, then update both the list row and detail
header bindings in the local requests panel to call that helper for the
status-pill class.

33-53: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

role="listitem" on native <button> may conflict with implicit button semantics.

Each list row is a <button> with an explicit role="listitem". Per ARIA guidance, an explicit role generally overrides an element's implicit role, so assistive tech may announce these as list items rather than actionable buttons, even though click/keyboard activation still works via the native <button>. Consider dropping role="listitem" (since <button> inside a role="list" container doesn't require it) or restructuring so the list semantics live on a wrapping non-interactive element.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web-ui/partials/index/panel-local-requests.html` around lines 33 - 53, The
local request rows in panel-local-requests.html use a native button element with
an explicit role="listitem", which overrides the button’s implicit semantics.
Update the button rendered by localAiRequestLog to remove the conflicting role,
or move the list semantics to a non-interactive wrapper if true list structure
is needed. Keep the existing button behavior and bindings in
localAiRequestLogItemKey, selectLocalAiRequestLogItem, and the current
active/status classes unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@web-ui/partials/index/panel-local-requests.html`:
- Around line 40-51: The request log template in panel-local-requests should
avoid rendering raw undefined values and should localize the model label. Update
the local-ai request display logic so the `item.durationMs` field in the request
header/footer falls back to a placeholder like `-` when missing, and replace the
hardcoded `model:` text with the appropriate `t('localAi.client.requestLog...')`
translation key used alongside `localAi.client.requestLog.provider`. Apply the
same fix wherever the request log item is rendered so
`local-ai-request-duration` and the `item.model` label behave consistently.

---

Outside diff comments:
In `@web-ui/modules/app.methods.task-orchestration.mjs`:
- Around line 663-670: The overview refresh logic is wiping fetched local AI
models on every successful load, which can clear the provider form during
background polling or manual refresh. Update loadTaskOrchestrationOverview() so
state.localAiDetectedModels and state.localAiModelsError are only reset when the
active provider identity actually changes, using the same kind of preservation
pattern already used by syncTaskProviderSelectionFromStatus and
syncTaskWebviewBridgeDraft. Keep fetchLocalAiGatewayModels() results intact
across ordinary refreshes, and only clear them when a real provider switch makes
the cached models invalid.

In `@web-ui/session-helpers.mjs`:
- Around line 82-91: The polling logic in switchMainTab now maps orchestration
to local-ai, but syncTaskOrchestrationPolling still gates on this.mainTab ===
'orchestration', so polling never restarts from local-ai or local-requests.
Update the tab checks in session-helpers.mjs to use the new tab set
consistently, remove or replace the now-dead enteringOrchestrationTab branch,
and make sure the restart condition in syncTaskOrchestrationPolling matches
local-ai/local-requests so timers resume after tab changes.

---

Nitpick comments:
In `@web-ui/partials/index/panel-local-requests.html`:
- Line 43: The status-to-tone ternary is duplicated in the request log UI, so
extract it into a shared helper instead of repeating it. Add a small function
like localAiRequestStatusTone(status) alongside the other request-log helpers in
app.methods.task-orchestration.mjs, then update both the list row and detail
header bindings in the local requests panel to call that helper for the
status-pill class.
- Around line 33-53: The local request rows in panel-local-requests.html use a
native button element with an explicit role="listitem", which overrides the
button’s implicit semantics. Update the button rendered by localAiRequestLog to
remove the conflicting role, or move the list semantics to a non-interactive
wrapper if true list structure is needed. Keep the existing button behavior and
bindings in localAiRequestLogItemKey, selectLocalAiRequestLogItem, and the
current active/status classes unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 394b0202-a3f1-4a47-bf7c-7d9cc752fc47

📥 Commits

Reviewing files that changed from the base of the PR and between 785a422 and 38a5aec.

📒 Files selected for processing (23)
  • cli.js
  • cli/local-bridge.js
  • tests/unit/config-tabs-ui.test.mjs
  • tests/unit/local-bridge-claude-target-api.test.mjs
  • tests/unit/session-tab-switch-performance.test.mjs
  • tests/unit/web-ui-behavior-parity.test.mjs
  • tests/unit/web-ui-logic.test.mjs
  • web-ui/app.js
  • web-ui/index.html
  • web-ui/modules/app.computed.main-tabs.mjs
  • web-ui/modules/app.methods.navigation.mjs
  • web-ui/modules/app.methods.task-orchestration.mjs
  • web-ui/modules/i18n/locales/en.mjs
  • web-ui/modules/i18n/locales/ja.mjs
  • web-ui/modules/i18n/locales/vi.mjs
  • web-ui/modules/i18n/locales/zh-tw.mjs
  • web-ui/modules/i18n/locales/zh.mjs
  • web-ui/partials/index/layout-header.html
  • web-ui/partials/index/panel-local-ai.html
  • web-ui/partials/index/panel-local-requests.html
  • web-ui/res/web-ui-render.precompiled.js
  • web-ui/session-helpers.mjs
  • web-ui/styles/local-ai-service.css
🚧 Files skipped from review as they are similar to previous changes (17)
  • web-ui/index.html
  • web-ui/app.js
  • web-ui/partials/index/layout-header.html
  • web-ui/partials/index/panel-local-ai.html
  • web-ui/modules/app.methods.navigation.mjs
  • tests/unit/config-tabs-ui.test.mjs
  • tests/unit/web-ui-behavior-parity.test.mjs
  • tests/unit/local-bridge-claude-target-api.test.mjs
  • web-ui/modules/i18n/locales/en.mjs
  • web-ui/modules/i18n/locales/zh.mjs
  • web-ui/styles/local-ai-service.css
  • web-ui/modules/i18n/locales/ja.mjs
  • web-ui/modules/i18n/locales/vi.mjs
  • web-ui/modules/app.computed.main-tabs.mjs
  • web-ui/modules/i18n/locales/zh-tw.mjs
  • cli.js
  • tests/unit/web-ui-logic.test.mjs
📜 Review details
🧰 Additional context used
🪛 ast-grep (0.44.1)
cli/local-bridge.js

[warning] 156-156: Avoid using the initial state variable in setState
Context: setTimeout(resolve, delay)
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(setstate-same-var)

🪛 HTMLHint (1.9.2)
web-ui/partials/index/panel-local-requests.html

[error] 1-1: Doctype must be declared before any non-comment content.

(doctype-first)

🔇 Additional comments (13)
cli/local-bridge.js (9)

63-72: normalized.startsWith('embeddings') on Line 72 still accepts invalid suffixes like embeddingsevil and forwards them upstream. This matches the earlier finding and remains unaddressed.


222-225: redactText still omits common token variants (access_token, refresh_token, id_token, client_secret), so they can leak into bodyPreview. Same as the prior finding.


239-242: summary still pushes raw messages[].content/input (Lines 240, 242) without redactText, so secrets can surface even when bodyPreview is redacted. Same as the prior finding.


305-309: Line 308 still reads only string parsed.error, so OpenAI-style error.message payloads log an empty error. Same as the prior finding.


725-727: Line 727 still uses !suffix.startsWith('v1'), which accepts /bridge/local/v10/... and /bridge/local/v1evil and forwards them upstream. This was previously marked addressed, but the current snapshot shows the permissive check is still present.


796-801: The /responses branch still forwards responsesRequest unchanged (Line 796), so a configured upstream model/temperature never overrides the client request. Same as the prior finding.


139-167: LGTM!


269-317: LGTM!


943-986: LGTM!

web-ui/partials/index/panel-local-requests.html (1)

1-1: Doctype hint is a false positive.

This is an HTML partial included into a larger document, not a standalone page; the doctype-first HTMLHint rule doesn't apply here.

web-ui/modules/app.methods.task-orchestration.mjs (2)

32-61: LGTM!

Also applies to: 437-492, 704-763, 1243-1299


1226-1241: 🩺 Stability & Availability

Polling guard still has a reachable orchestration state mainTab can still be 'orchestration' via nav-state restore, and it’s only rewritten to config when taskOrchestrationTabEnabled is false.

			> Likely an incorrect or invalid review comment.
tests/unit/session-tab-switch-performance.test.mjs (1)

170-242: LGTM!

Comment on lines +40 to +51
<span class="local-ai-request-log-head">
<span class="local-ai-request-method">{{ item.method }}</span>
<code>{{ item.path }}</code>
<span :class="['status-pill', item.status >= 200 && item.status < 400 ? 'success' : 'warn']">{{ item.status || '-' }}</span>
<span class="local-ai-request-duration">{{ item.durationMs }}ms</span>
</span>
<span class="local-ai-request-log-meta">
<span v-if="item.provider">{{ t('localAi.client.requestLog.provider') }}: {{ item.provider }}</span>
<span v-if="item.model">model: {{ item.model }}</span>
<span>{{ item.at }}</span>
</span>
<span v-if="item.summary" class="local-ai-request-summary">{{ item.summary }}</span>

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Unset durationMs/model renders literal undefinedms/-.

When item.durationMs is not yet populated (e.g., request still in flight or logging omitted it), {{ item.durationMs }}ms (Lines 44, 75) will literally render undefinedms instead of a placeholder. The model label at Lines 48 and 70 is also hardcoded in English rather than using t(...), unlike the surrounding labels (t('localAi.client.requestLog.provider'), etc.), which will look inconsistent in non-English locales given this PR explicitly ships zh/ja/vi locale updates.

🩹 Suggested fixes
-                                        <span class="local-ai-request-duration">{{ item.durationMs }}ms</span>
+                                        <span class="local-ai-request-duration">{{ item.durationMs != null ? `${item.durationMs}ms` : '-' }}</span>
-                                            <span v-if="item.model">model: {{ item.model }}</span>
+                                            <span v-if="item.model">{{ t('localAi.client.requestLog.model') }}: {{ item.model }}</span>
-                                            <dt>model</dt>
+                                            <dt>{{ t('localAi.client.requestLog.model') }}</dt>

Also applies to: 64-80

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web-ui/partials/index/panel-local-requests.html` around lines 40 - 51, The
request log template in panel-local-requests should avoid rendering raw
undefined values and should localize the model label. Update the local-ai
request display logic so the `item.durationMs` field in the request
header/footer falls back to a placeholder like `-` when missing, and replace the
hardcoded `model:` text with the appropriate `t('localAi.client.requestLog...')`
translation key used alongside `localAi.client.requestLog.provider`. Apply the
same fix wherever the request log item is rendered so
`local-ai-request-duration` and the `item.model` label behave consistently.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant