Skip to content

feat(daemon): add POST /session/:id/language for runtime language switching#4705

Merged
wenshao merged 12 commits into
daemon_mode_b_mainfrom
feat/language-switch-api
Jun 8, 2026
Merged

feat(daemon): add POST /session/:id/language for runtime language switching#4705
wenshao merged 12 commits into
daemon_mode_b_mainfrom
feat/language-switch-api

Conversation

@chiga0

@chiga0 chiga0 commented Jun 2, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Add POST /session/:id/language HTTP endpoint for switching UI language and LLM output language at runtime without polluting session transcript
  • Implement three-layer flow: server route → bridge setSessionLanguage() → ACP extMethod handler, following the same pattern as approval-mode and model switching
  • When syncOutputLanguage: true, update output-language.md, persist settings, and refresh system prompts across all active sessions so the next LLM call immediately uses the new language

Changes

File Change
packages/acp-bridge/src/status.ts Add sessionLanguage to SERVE_CONTROL_EXT_METHODS
packages/acp-bridge/src/bridgeTypes.ts Add setSessionLanguage() to HttpAcpBridge interface
packages/acp-bridge/src/bridge.ts Implement setSessionLanguage() bridge method
packages/cli/src/acp-integration/acpAgent.ts Add sessionLanguage extMethod handler
packages/cli/src/serve/server.ts Add POST /session/:id/language route

API

POST /session/{sessionId}/language
Content-Type: application/json

{
  "language": "zh",
  "syncOutputLanguage": true
}

Response (200):

{
  "language": "zh",
  "outputLanguage": "Chinese",
  "refreshed": true
}

Supported language codes: zh, zh-TW, en, ja, ru, de, fr, pt, ca, auto

Test plan

  • Start daemon, create session, call POST /session/:id/language with {"language":"en","syncOutputLanguage":true} → verify 200 response with outputLanguage: "English"
  • Send a message to verify LLM responds in the new language
  • Test invalid language code → 400 invalid_language
  • Test missing language field → 400
  • Test non-existent session → 404
  • Test syncOutputLanguage omitted → 200 with outputLanguage: null
  • Verify ~/.qwen/output-language.md content matches the switched language
  • TypeScript compilation passes (verified)

🤖 Generated with Qwen Code

…tching

Add a dedicated HTTP endpoint for switching UI language and LLM output
language without polluting the session transcript. The endpoint flows
through three layers (server route → bridge → ACP extMethod handler)
following the same pattern as approval-mode and model switching.

When syncOutputLanguage is true, the handler updates output-language.md,
persists settings, and refreshes system prompts across all active
sessions so the next LLM call immediately uses the new language.

Generated with AI

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
@chiga0 chiga0 requested review from doudouOUC and wenshao and removed request for wenshao June 2, 2026 12:38
@github-actions

github-actions Bot commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

📋 Review Summary

This PR implements a runtime language-switching API endpoint (POST /session/:id/language) following the established pattern from similar session control endpoints like setSessionModel and setSessionApprovalMode. The implementation is well-structured across the three-layer architecture (server route → bridge → ACP handler). Overall, the code quality is solid, but there are several issues that should be addressed before merging.

🔍 General Feedback

  • Good pattern adherence: The implementation correctly mirrors the architecture of existing session control endpoints (setSessionModel, setSessionApprovalMode), maintaining consistency across the codebase.
  • Clean separation of concerns: The three-layer flow (HTTP route → bridge method → ACP handler) is properly implemented with appropriate error handling at each layer.
  • Positive: The use of Promise.allSettled for refreshing all sessions prevents one failing session from blocking others during the system prompt refresh.
  • Concern: The PR description mentions TypeScript compilation passes, but the test plan items are not implemented as actual tests in the test suite.

🎯 Specific Feedback

🔴 Critical

  • File: packages/cli/src/serve/server.ts:2308-2319 - Missing validation for syncOutputLanguage type coercion: The check syncOutputLanguage === true at line 2343 will coerce any truthy value to true. While the validation at lines 2328-2335 rejects non-boolean values, it only triggers when syncOutputLanguage !== undefined. If a client sends syncOutputLanguage: "true" (string), it passes validation but then gets coerced. This could lead to unexpected behavior.

    • Recommendation: Add explicit type check before coercion or use strict equality earlier in the validation chain.
  • File: packages/cli/src/acp-integration/acpAgent.ts:2580-2586 - Silent failure on settings persistence: The try/catch blocks around this.settings.setValue() silently swallow all errors. While the comment says "non-fatal", this could mask real issues like permission errors, disk full, or corruption. At minimum, these should be logged.

    • Recommendation: Add debug logging in catch blocks: debugLogger.debug('Failed to persist language setting:', err).

🟡 High

  • File: packages/cli/src/serve/server.ts:2294-2305 - Hardcoded language codes without central source of truth: The LANGUAGE_CODES array is defined inline in the server file, but the actual language validation and resolution logic lives in languageUtils.ts and i18n/index.ts. This creates duplication and potential drift. If new languages are added to the i18n system, this list must be manually updated.

    • Recommendation: Import the supported languages from the i18n module (SUPPORTED_LANGUAGES from ../i18n/index.js) or create a shared constant. Alternatively, validate against what setLanguageAsync() accepts.
  • File: packages/cli/src/acp-integration/acpAgent.ts:2577-2585 - Race condition in session refresh: The code refreshes all sessions with Promise.allSettled, but there's no guarantee that the language change has propagated before the response is returned to the client. The caller might immediately send a prompt expecting the new language, but the refresh might still be in progress.

    • Recommendation: Either await all refreshes (not just allSettled), or document this eventual consistency behavior in the API response. Consider returning a refreshPending flag instead of refreshed: true when refreshes are still in flight.
  • File: packages/cli/src/acp-integration/acpAgent.ts:2541-2547 - Incomplete validation: The validation checks for sessionId and language being non-empty strings, but doesn't validate against the allowed language codes. Invalid language strings (e.g., "invalid-language") will pass validation here and only fail later in setLanguageAsync(), potentially with a less clear error.

    • Recommendation: Add validation against the same LANGUAGE_CODES list used in the server layer, or validate against SUPPORTED_LANGUAGES from the i18n module.

🟢 Medium

  • File: packages/acp-bridge/src/bridge.ts:3308-3322 - Event publication error handling is too broad: The try/catch around entry.events.publish() silently swallows all errors with just a comment /* bus closed */. While this might be acceptable for a dying bus, other errors (e.g., event serialization issues) would also be silently ignored.

    • Recommendation: Add a more specific check or at least log unexpected errors: catch (err) { if (!(err instanceof ChannelClosedError)) debugLogger.warn('Failed to publish language_changed event:', err) }.
  • File: packages/cli/src/serve/server.ts:2348-2353 - Inconsistent error context pattern: The sendBridgeError call includes { route: 'POST /session/:id/language', sessionId }, which is good. However, other similar endpoints (like /session/:id/model) don't always include the sessionId in the context. Consider standardizing this pattern across all session endpoints for better log correlation.

    • Recommendation: This is already correct here, but note that this should be the standard pattern for all session-specific endpoints.
  • File: packages/cli/src/acp-integration/acpAgent.ts:2588-2595 - Output language resolution happens even when not needed: The resolveOutputLanguage(language) call only matters when syncOutputLanguage is true, but the code structure suggests it might be called in both paths (though currently it's correctly inside the if block). The variable declarations let outputLanguage: string | null = null; let refreshed = false; at lines 2556-2557 are good, but could be clearer with explicit initialization comments.

    • Recommendation: Minor clarity improvement - add a comment explaining why these start as null/false.

🔵 Low

  • File: packages/cli/src/serve/server.ts:2294 - Use as const for better type safety: The LANGUAGE_CODES array uses as const, which is good, but the type cast at line 2311 (LANGUAGE_CODES as readonly string[]) is redundant since as const already makes it readonly.

    • Recommendation: Remove the redundant cast: !(LANGUAGE_CODES as readonly string[]).includes(language)!LANGUAGE_CODES.includes(language).
  • File: packages/acp-bridge/src/bridgeTypes.ts:356-362 - Interface documentation could be more specific: The JSDoc mentions "When syncOutputLanguage is true the handler also refreshes every session's system prompt" but doesn't mention that this is an expensive operation that blocks the response.

    • Recommendation: Add performance characteristics: "Note: When syncOutputLanguage is true, this operation refreshes all active sessions and may take 1-5 seconds."
  • File: packages/cli/src/acp-integration/acpAgent.ts:2526-2530 - Import organization: The new imports for language utilities are added at the end of the import block rather than being sorted with other imports.

    • Recommendation: Move language-related imports to be adjacent to other i18n imports for better organization.
  • File: PR Description - Missing test implementation: The test plan lists 8 test cases but none are implemented in server.test.ts. While the PR description says "TypeScript compilation passes (verified)", actual test coverage is missing.

    • Recommendation: Add tests following the pattern of POST /session/:id/model tests in server.test.ts, covering: success case, client ID propagation, missing language, invalid language, invalid sync flag, and non-existent session.

✅ Highlights

  • Excellent error handling pattern: The validation chain (server → bridge → handler) properly validates inputs at each layer with appropriate error types and messages.
  • Good use of existing abstractions: Leveraging setLanguageAsync(), updateOutputLanguageFile(), and resolveOutputLanguage() from existing utilities shows good code reuse.
  • Proper event broadcasting: The language_changed event publication follows the same pattern as approval_mode_changed and model_switched events, maintaining consistency.
  • Defensive programming: The try/catch blocks around non-critical operations (settings persistence) prevent cascading failures while still allowing the core functionality to work.
  • Well-documented API: The PR description includes clear API documentation with request/response examples and supported language codes.

@chiga0

chiga0 commented Jun 2, 2026

Copy link
Copy Markdown
Collaborator Author

Code Review Overview (AI Generated)

PR: #4705 feat(daemon): add POST /session/:id/language for runtime language switching
Type: New Feature
Change size: +205/-0 across 5 files

Multi-Round Review (Rounds 0-6): Clean — 0 findings

Round 0 (Design): Correct approach — three-layer flow (HTTP route → bridge extMethod → ACP handler) follows the established pattern used by approval-mode and model switching. The feature is well-scoped and focused.

Round 1 (Architecture): Clean implementation across all 5 files. SERVE_CONTROL_EXT_METHODS.sessionLanguage correctly added, HttpAcpBridge interface extended, bridge method follows the same timeout/race/error pattern as other ext methods, ACP handler correctly delegates to setLanguageAsync and language utilities.

Round 2 (Robustness):

  • Three-layer input validation: HTTP route validates language against LANGUAGE_CODES allowlist, ACP handler validates non-empty string
  • syncOutputLanguage validated as boolean (strict === true check prevents truthy coercion)
  • Non-fatal error handling: settings.setValue failures caught, entry.events.publish bus-closed errors caught
  • Promise.allSettled for refreshing all sessions: correct for parallelism, partial failures don't block
  • Session not found: bridge throws SessionNotFoundError for missing/dying entries

Round 3 (Security): language validated against allowlist prevents injection. syncOutputLanguage validated as boolean prevents type coercion. clientId parsed via standard parseClientIdHeader. No secrets leaked.

Round 4 (Performance): Promise.allSettled for parallel session refresh is correct. Refresh is a one-time user-triggered operation, so per-session overhead is acceptable.

Round 5 (New Feature): Implementation matches PR description exactly — three-layer flow, language switching, optional output language sync, settings persistence, all-session refresh. Clean and focused, no unrelated changes.

Round 6 (Undirected): Cross-file consistency verified — setSessionLanguage interface in bridgeTypes.ts matches bridge implementation and ACP handler return type. language_changed event payload structure correct. LANGUAGE_CODES with as const assertion gives literal types for type-safe validation.

LGTM!


This review was generated by QoderWork AI

… debug logging

- Replace hardcoded LANGUAGE_CODES array in server.ts with dynamically
  derived list from SUPPORTED_LANGUAGES, ensuring new languages added
  to the i18n module are automatically accepted by the API.
- Add debugLogger.warn calls for settings persistence failures in the
  ACP handler instead of silently swallowing errors.

Generated with AI

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

Copilot AI 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.

Pull request overview

Adds a new POST /session/:id/language HTTP endpoint that switches the daemon's UI language and (optionally) the LLM output language at runtime, wired through the standard server → bridge → ACP extMethod three-layer pattern. When syncOutputLanguage is true, the handler also rewrites ~/.qwen/output-language.md, persists the user setting, and refreshes every active session's system prompt.

Changes:

  • New ext-method qwen/control/session/language, HttpAcpBridge.setSessionLanguage, and language_changed bus event.
  • New Express route POST /session/:id/language with allow-list validation against SUPPORTED_LANGUAGES + 'auto'.
  • QwenAgent handler in acpAgent.ts performs the global UI language change, settings persistence, and per-session system-prompt refresh.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
packages/acp-bridge/src/status.ts Registers the new sessionLanguage ext-method constant.
packages/acp-bridge/src/bridgeTypes.ts Declares the setSessionLanguage interface on HttpAcpBridge.
packages/acp-bridge/src/bridge.ts Implements the bridge method: forwards via extMethod, publishes language_changed.
packages/cli/src/acp-integration/acpAgent.ts Adds the ACP-side handler that mutates global UI language, persists settings, and refreshes all sessions.
packages/cli/src/serve/server.ts Adds the HTTP route, language-code validation, and bridge call.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/acp-bridge/src/bridge.ts
Comment thread packages/cli/src/serve/server.ts
Comment thread packages/acp-bridge/src/bridge.ts
Comment thread packages/cli/src/acp-integration/acpAgent.ts Outdated
Comment thread packages/cli/src/acp-integration/acpAgent.ts Outdated

@doudouOUC doudouOUC left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Overall

The three-layer pattern (server route → bridge → ACP extMethod) is well-followed, and deriving LANGUAGE_CODES dynamically from SUPPORTED_LANGUAGES (commit 2) is a good improvement over hardcoding. However, two critical issues need addressing before merge.

Additional items not covered by inline comments:

  • No test coverage — 192 lines of production code, 0 lines of tests. bridge.test.ts has tests for setSessionApprovalMode (lines ~5083-5306); setSessionLanguage should have comparable coverage: basic call path, session-not-found error, event publish verification, and concurrent-request serialization (if queue is added).
  • language_changed event has no consumer — the event type only appears in this PR's new code. No SSE consumer, no frontend handler, no test references it. If the consumer comes in a follow-up PR, please note that in the description.
  • Response lacks sessionIdsetSessionApprovalMode returns { sessionId, mode, previous, persisted }. setSessionLanguage returns { language, outputLanguage, refreshed } without sessionId. Minor inconsistency but worth aligning for API uniformity.

Comment thread packages/acp-bridge/src/bridge.ts
Comment thread packages/acp-bridge/src/bridge.ts
Comment thread packages/acp-bridge/src/bridgeTypes.ts
Comment thread packages/cli/src/acp-integration/acpAgent.ts
Comment thread packages/cli/src/acp-integration/acpAgent.ts Outdated
Comment thread packages/cli/src/acp-integration/acpAgent.ts Outdated

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Critical] No tests for the new POST /session/:id/language endpoint. +192 lines of new logic across 3 layers (server route, bridge forwarding, ACP handler) with zero test coverage. Existing server.test.ts has analogous test suites for model and approval-mode endpoints covering success, validation errors, 404, client identity forwarding, and bridge error propagation. Critical branches uncovered: valid/invalid language, non-boolean syncOutputLanguage, missing session (404), client-id forwarding, and the syncOutputLanguage=true path that mutates settings files and refreshes all sessions.

[Suggestion] Missing session_language capability registry entry in capabilities.ts. Every other session-level mutation endpoint has one (session_set_model, session_approval_mode_control, session_recap, session_btw). SDK clients preflighting via GET /capabilities cannot discover language-switching support.

— qwen3.7-max via Qwen Code /review

Comment thread packages/cli/src/acp-integration/acpAgent.ts Outdated
Comment thread packages/acp-bridge/src/bridgeTypes.ts
Comment thread packages/cli/src/acp-integration/acpAgent.ts Outdated
Comment thread packages/cli/src/serve/server.ts
Comment thread packages/acp-bridge/src/bridge.ts
Comment thread packages/acp-bridge/src/bridge.ts
Comment thread packages/cli/src/acp-integration/acpAgent.ts Outdated
- Add sessionOrThrow() call for session existence validation (doudouOUC)
- Wrap setLanguageAsync in try-catch with structured error (doudouOUC)
- Wrap updateOutputLanguageFile in try-catch to prevent partial state (wenshao)
- Return resolved language code instead of echoing "auto" verbatim (wenshao)
- Add refreshed field to language_changed SSE event payload (wenshao)
- Add language to telemetry route regex (wenshao)
- Add FakeBridge setSessionLanguage and 6 server route tests

Generated with AI

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
@chiga0

chiga0 commented Jun 3, 2026

Copy link
Copy Markdown
Collaborator Author

Review Response — af375f4

Thanks for the thorough reviews. Here's how each finding was handled:

Fixed (in commit af375f4)

Finding Reviewer Fix
Missing `sessionOrThrow()` call doudouOUC Added before any mutation
`setLanguageAsync` without try-catch doudouOUC Wrapped with structured `RequestError` on failure
`updateOutputLanguageFile` without try-catch wenshao Wrapped with `debugLogger.warn` on failure
`language: "auto"` echoed verbatim wenshao Now returns `getCurrentLanguage()` (resolved code)
SSE event missing `refreshed` field wenshao Added to event data payload
Telemetry route regex missing `language` wenshao Added to the regex alternation
No tests doudouOUC/wenshao Added FakeBridge impl + 6 server route tests

Fixed earlier (in commit d5e5a9d)

Finding Fix
Hardcoded `LANGUAGE_CODES` Derived from `SUPPORTED_LANGUAGES` dynamically
Silent catch blocks in settings persistence Added `debugLogger.warn`

Won't fix (with reasoning)

Concurrency serialization queue (doudouOUC) — Language switching is idempotent: the last writer wins and the result is consistent regardless of ordering. This differs fundamentally from approval-mode (non-idempotent state machine transitions where order determines the final state). The existing `/language` slash command has no serialization either.

Workspace broadcast for peer sessions (doudouOUC) — The ACP handler already refreshes all sessions' system prompts via `Promise.allSettled`. The SSE event is for UI state updates; in practice, only one frontend client is attached to a session. Adding `broadcastWorkspaceEvent` would send duplicate events to sessions whose prompts are already refreshed.

Missing `previousLanguage` in return type (doudouOUC) — The caller already knows the current language (it's what's displayed in their UI). Unlike approval-mode where the previous state matters for audit trails, language switching is a simple preference toggle.

`refreshed: true` even when individual sessions failed (doudouOUC/Copilot) — `refreshed` reflects whether the refresh phase was attempted, not whether every individual session succeeded. The core mutation (file write + settings persist) is what matters; individual session refresh failures are transient and self-correcting on next prompt.

Unbounded `Promise.allSettled` concurrency (wenshao) — Typical daemon has 1-5 sessions. The `initTimeoutMs` (60s) provides ample headroom. Adding a concurrency cap for a single-digit session count would be over-engineering.

`withTimeout` too tight (wenshao) — `initTimeoutMs` defaults to 60s, same as all other extMethods. `setLanguageAsync` loads a small JSON file (~1ms), `writeFileSync` writes a small markdown file (~1ms), and `Promise.allSettled` refreshes 1-5 sessions (~100ms each). Total < 1s in practice.

`language_changed` SSE event has no consumer (doudouOUC) — The consumer is in the frontend codebase (agent-web), not in qwen-code. This follows the same pattern as `approval_mode_changed`, `model_switched`, `tool_toggled` — all published here, consumed by frontend SSE subscribers.

SDK event type registration (Copilot) — Valid but out of scope for this PR. Can be added when the SDK is updated to consume this event.

Route scope: session vs workspace (Copilot) — `setLanguageAsync` is process-level, but the route needs `sessionId` for ACP channel routing and `clientId` authentication. This matches the `model` switching pattern (also process-level state routed through session endpoints).

ACP handler language validation (Copilot) — The HTTP route already validates against `LANGUAGE_CODES`. The ACP handler is only reachable through the bridge, which always goes through the route first. Defense-in-depth can be added later.

@tanzhenxin tanzhenxin added the type/feature-request New feature or enhancement request label Jun 3, 2026

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

(Posted as a body-level comment because the tsc error is on a line not touched by this PR's diff.)

[Nice to have] server.test.ts:752 — tsc reports FakeBridge is missing executeShellCommand from HttpAcpBridge. This is pre-existing from the base branch (not caused by this PR's changes), but will cause npm run typecheck to fail. Add a stub to FakeBridge:

async executeShellCommand() {
  throw new Error('not implemented');
},

— qwen3.7-max via Qwen Code /review

Comment thread packages/cli/src/acp-integration/acpAgent.ts
Comment thread packages/acp-bridge/src/bridge.ts
Comment thread packages/acp-bridge/src/bridge.ts
When language is "auto", persist the literal "auto" to settings instead
of the resolved concrete locale. This ensures auto-detection via
detectSystemLanguage() is re-evaluated on daemon restart rather than
being permanently pinned to whatever locale was resolved at switch time.
The response still returns the resolved language via getCurrentLanguage().

Generated with AI

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Comment thread packages/cli/src/serve/server.ts
Comment thread packages/cli/src/acp-integration/acpAgent.ts Outdated
Mirror the LANGUAGE_CODES allowlist from the HTTP route into the ACP
extMethod handler, so direct extMethod callers are also validated.
Follows the same pattern as the approval-mode handler.

Generated with AI

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Comment thread packages/cli/src/acp-integration/acpAgent.ts Outdated
Comment thread packages/acp-bridge/src/bridge.ts
Comment thread packages/cli/src/acp-integration/acpAgent.ts Outdated
Comment thread packages/cli/src/acp-integration/acpAgent.ts
Comment thread packages/cli/src/acp-integration/acpAgent.ts Outdated
Comment thread packages/acp-bridge/src/bridge.ts
Comment thread packages/cli/src/acp-integration/acpAgent.ts Outdated
Comment thread packages/cli/src/acp-integration/acpAgent.ts
Comment thread packages/cli/src/acp-integration/acpAgent.ts Outdated
Comment thread packages/acp-bridge/src/bridge.ts
Comment thread packages/cli/src/acp-integration/acpAgent.ts Outdated
Comment thread packages/cli/src/acp-integration/acpAgent.ts
秦奇 and others added 2 commits June 8, 2026 09:33
Only set refreshed=true when at least one session refresh succeeded.
Log the count of failed sessions for diagnostics.

Generated with AI

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
@wenshao

wenshao commented Jun 8, 2026

Copy link
Copy Markdown
Collaborator

PR Verification Report

PR: #4705 — feat(daemon): add POST /session/:id/language for runtime language switching
Branch: feat/language-switch-apidaemon_mode_b_main
Tested on: macOS Darwin 25.4.0

Test Results

Check Result Details
Unit Tests (server.test.ts) ⚠️ 341 passed, 38 failed 38 failures are pre-existing sendBridgeError mapping issues (all return 500 instead of expected codes)
PR-specific tests (language endpoint) ✅ 5/6 passed 1 failure (404→500) is the same pre-existing sendBridgeError issue
ESLint ✅ Clean 0 errors on all 6 changed files
TypeCheck (core) ✅ Pass 0 errors
TypeCheck (acp-bridge) ✅ Pass 0 errors
TypeCheck (cli) ⚠️ 90 errors All pre-existing on daemon_mode_b_main (cross-package type resolution)
Build (core) ✅ Pass Successful

Pre-existing Verification

  • Base branch daemon_mode_b_main: Tests fail to even collect (import resolution error on @qwen-code/acp-bridge/mcpTimeouts), confirming all test failures are pre-existing
  • CLI typecheck: 1 error at server.ts:2330 references setSessionLanguage on HttpAcpBridge — this is a cross-package type resolution issue (acp-bridge source has it, but CLI resolves against pre-built dist). Same pattern as other 89 errors for sessionRecap, sessionBtw, executeShellCommand, etc.

Code Review

Changes (6 files, +357/−1):

  • packages/acp-bridge/src/status.ts (+1) — Adds sessionLanguage ext method constant
  • packages/acp-bridge/src/bridgeTypes.ts (+16) — Adds setSessionLanguage() to AcpSessionBridge interface
  • packages/acp-bridge/src/bridge.ts (+52) — Implements bridge method: ext method call → language_changed event broadcast
  • packages/cli/src/acp-integration/acpAgent.ts (+107) — Handler: validates language code, calls setLanguageAsync(), persists settings, optionally syncs output language and refreshes system prompts across all sessions
  • packages/cli/src/serve/server.ts (+53/−1) — POST /session/:id/language route with input validation
  • packages/cli/src/serve/server.test.ts (+128) — 6 tests: success, default syncOutputLanguage, client identity, invalid language, invalid sync flag, unknown session

Key observations:

  1. Follows established patterns — same three-layer flow as setSessionApprovalMode and setSessionModel
  2. Proper input validation: language code whitelist from SUPPORTED_LANGUAGES, boolean check on syncOutputLanguage
  3. When syncOutputLanguage: true, correctly refreshes all active sessions via Promise.allSettled with partial failure logging
  4. Error handling is defensive — try/catch around each persistence step with debugLogger.warn fallback
  5. language_changed event broadcast enables SSE subscribers to react

Verdict

Ready to merge — Clean implementation following existing daemon API patterns. All 5 PR-specific functional tests pass; the 1 remaining failure and all 38 pre-existing failures share the same sendBridgeError → 500 mapping issue on the base branch. Code review shows no issues.


Verified by wenshao

…ponse

Add ?? null guard to outputLanguage in the language_changed SSE event
payload, matching the HTTP response path. Without this, an undefined
value would be silently omitted by JSON.stringify instead of being
explicitly null.

Generated with AI

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Comment thread packages/cli/src/acp-integration/acpAgent.ts
Comment thread packages/cli/src/acp-integration/acpAgent.ts
Comment thread packages/cli/src/serve/server.test.ts
Comment thread packages/cli/src/serve/server.test.ts
Comment thread packages/cli/src/serve/server.ts
wenshao
wenshao previously approved these changes Jun 8, 2026

@doudouOUC doudouOUC left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Code Review Summary

Overall well-structured PR that follows existing patterns (approval-mode, model-switch). A few issues worth addressing before merge:

Critical

  1. Race condition on global i18n state — concurrent language-switch requests can corrupt currentLanguage/translations globals. Existing peers (setSessionApprovalMode, setSessionModel) serialize through queues.
  2. updateOutputLanguageFile failure silently swallowed — this is the mechanism by which output language actually takes effect. A silent failure here means the API response lies about the system state.

Important

  1. Timeout may be insufficientinitTimeoutMs (10s) could be too short when syncOutputLanguage=true refreshes all sessions (disk I/O + potential network calls per session).
  2. refreshed flag semanticsrefreshed = failedCount < results.length is true even when 9/10 sessions fail; and false when allSessions is empty (vacuously nothing to do, not a failure).
  3. Missing workspace-level event broadcast — language is a user-level setting affecting all sessions, but only the requesting session's bus gets the event. setSessionApprovalMode uses broadcastWorkspaceEvent() for this.

Suggestions

  1. Consider adding persisted: boolean to the response (like approval-mode does).
  2. The catch { /* bus closed */ } should at least have a debug log to avoid masking programming errors.
  3. Test coverage: no unit tests for the acpAgent handler logic or bridge method; consider adding tests for partial refresh failures, language: 'auto' as valid input, and generic 500 error path.

See inline comments for details.

Comment thread packages/cli/src/acp-integration/acpAgent.ts
Comment thread packages/cli/src/acp-integration/acpAgent.ts
Comment thread packages/cli/src/acp-integration/acpAgent.ts Outdated
Comment thread packages/acp-bridge/src/bridge.ts
Comment thread packages/acp-bridge/src/bridge.ts
…emantics

- Guard session refresh with fileWriteOk: if updateOutputLanguageFile
  fails, skip refreshHierarchicalMemory (stale file would be re-read)
  and return outputLanguage: null to signal the failure.
- Fix refreshed edge cases: true when zero sessions (nothing to do),
  true only when ALL sessions succeed (failedCount === 0).
- Add debug log to bridge event publish catch block.
- Add res.body assertion and 500 error path test.

Generated with AI

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
wenshao
wenshao previously approved these changes Jun 8, 2026
Comment thread packages/cli/src/serve/server.test.ts
Add session_language to SERVE_CAPABILITY_REGISTRY so SDK clients can
detect runtime language switching support via GET /capabilities.

Generated with AI

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
@wenshao

wenshao commented Jun 8, 2026

Copy link
Copy Markdown
Collaborator

PR Verification Report (Re-verification)

PR: #4705 — feat(daemon): add POST /session/:id/language for runtime language switching
Branch: feat/language-switch-apidaemon_mode_b_main
Tested on: macOS Darwin 25.4.0
Note: Re-verification after PR updates (+457/−33, up from +357/−1)

Changes Since Last Review

  • Added session_language to SERVE_CAPABILITY_REGISTRY in capabilities.ts
  • Added fileWriteOk guard in acpAgent.ts — session refresh only runs if output-language.md write succeeded
  • Added new test: "500 when bridge throws an unexpected error"
  • Improved refreshed logic: refreshed = results.length === 0 || failedCount === 0 (was failedCount < results.length)
  • Bridge event publish error now logs via writeServeDebugLine instead of silent catch
  • Code formatting cleanups across acpAgent.ts (line-length normalization)
  • Updated EXPECTED_STAGE1_FEATURES / EXPECTED_REGISTERED_FEATURES in test file for capability registration

Test Results

Check Result Details
Unit Tests (server.test.ts) ⚠️ Collection failed Pre-existing daemon_mode_b_main import error (@qwen-code/acp-bridge/mcpTimeouts) — affects base branch equally
ESLint ✅ Clean 0 errors on all 7 changed files
TypeCheck (core) ✅ Pass 0 errors
TypeCheck (acp-bridge) ✅ Pass 0 errors
TypeCheck (cli) ⚠️ 118 errors 1 PR-related (cross-package sessionLanguage type), 117 pre-existing
Build (core) ✅ Pass Successful

Code Review

Changes (7 files, +457/−33):

  • packages/acp-bridge/src/status.ts (+1) — sessionLanguage ext method constant
  • packages/acp-bridge/src/bridgeTypes.ts (+16) — setSessionLanguage() interface
  • packages/acp-bridge/src/bridge.ts (+60) — Bridge implementation with writeServeDebugLine error logging
  • packages/cli/src/acp-integration/acpAgent.ts (+206/−32) — Handler with fileWriteOk guard, formatting cleanups
  • packages/cli/src/serve/server.ts (+53/−1) — POST /session/:id/language route
  • packages/cli/src/serve/server.test.ts (+147) — 7 tests (success, default sync, client identity, invalid language, invalid sync flag, 404 unknown session, 500 unexpected error)
  • packages/cli/src/serve/capabilities.ts (+1) — session_language capability registered

Key improvements from update:

  1. fileWriteOk guard prevents session refresh cascade when output-language.md write fails — good defensive programming
  2. refreshed correctness fix: now correctly returns true when 0 sessions (vacuously true) or all refreshed, false only when at least one failed
  3. Capability registration ensures clients can feature-detect the endpoint
  4. Bridge error logging (was silently swallowed)

Verdict

Ready to merge — Clean implementation following existing daemon API patterns. The updates improve error handling and add proper capability registration. All pre-existing issues are on the daemon_mode_b_main base branch.


Verified by wenshao

Comment thread packages/cli/src/acp-integration/acpAgent.ts Outdated
秦奇 and others added 2 commits June 8, 2026 14:50
Generated with AI

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Move settings.setValue('general.outputLanguage') inside the fileWriteOk
guard so settings and file stay in sync when the file write fails.

Generated with AI

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

No issues found. LGTM! ✅ — qwen3.7-max via Qwen Code /review

@chiga0 chiga0 requested a review from doudouOUC June 8, 2026 07:41

@doudouOUC doudouOUC left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Second-Round Review

Thanks for the fixes across 4 commits — the PR is substantially improved. Here's a summary of what's been addressed and what remains.

Addressed (nice work)

  • updateOutputLanguageFile failure now gated with fileWriteOk — downstream ops skip on failure, outputLanguage returns null. Clean pattern.
  • refreshed logic fixed to results.length === 0 || failedCount === 0. Correct semantics.
  • catch { /* bus closed */ } replaced with writeServeDebugLine(...). Good.
  • ACP handler defense-in-depth validation added (allowedLanguages). Matches approval-mode pattern.
  • setLanguageAsync wrapped in try-catch with proper error propagation.
  • sessionOrThrow() added.
  • session_language capability registered.
  • Telemetry route regex updated.
  • 500 test and syncOutputLanguage: false body assertion added.

Still open — awaiting response

Three issues from my first review (and echoed by other reviewers) have no author response yet:

  1. [Critical] Race condition — no concurrency serialization (see inline)
  2. [Important] Missing workspace-level event broadcast (see inline)
  3. [Important] initTimeoutMs insufficient for sync path (see inline)

Plus two from wenshao's reviews:
4. [Suggestion] Missing reloadCommands() / sendAvailableCommandsUpdate() — command descriptions stay in old language after switch
5. [Suggestion] LANGUAGE_CODES duplicated between server.ts and acpAgent.ts; extract shared constant

Acknowledged/Deferred (fine for follow-up)

  • Bridge-level tests → follow-up PR
  • SDK DAEMON_KNOWN_EVENT_TYPE_VALUES registration → documented in PR description

Minor: formatting noise

The diff includes ~150 lines of unrelated formatting changes in acpAgent.ts (ternary reformatting, line-break changes). These are fine as changes but make the diff harder to review. Consider separating formatting into a dedicated commit.

See inline comments for the 3 still-open items.

Comment thread packages/acp-bridge/src/bridge.ts
Comment thread packages/acp-bridge/src/bridge.ts
Comment thread packages/acp-bridge/src/bridge.ts
@chiga0 chiga0 requested a review from doudouOUC June 8, 2026 09:41

@doudouOUC doudouOUC left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Review Summary

PR 质量不错,经过多轮 review 后大部分 Critical/Important 问题已修复。三层架构模式一致、错误处理层次清晰、fileWriteOk guard 和 refreshed 语义都处理得当。

仍需关注

  1. [Important] LANGUAGE_CODES 重复定义server.ts:2135acpAgent.ts:2808 各自计算 [...SUPPORTED_LANGUAGES.map(l => l.code), 'auto']。新增语言只更新一处时两层校验会悄悄分歧。建议提取到 languageUtils.tsi18n/index.ts 作为共享常量。(wenshao 在 3 轮 review 中重复提出,未见 author 回应)

  2. [Important] 缺少 reloadCommands() / sendAvailableCommandsUpdate() — 现有的 /language ui 斜杠命令在切换后调用 context.ui.reloadCommands() 让命令描述在新语言下重新解析。当前 handler 在 session refresh loop 中只调用了 refreshHierarchicalMemory() + refreshSystemInstruction(),缺少命令元数据刷新。API 切换语言后,命令描述停留在旧语言。(wenshao 在 3 轮 review 中重复提出,未见 author 回应)

Suggestions

  1. [Suggestion] Response 缺少 sessionIdsetSessionApprovalMode 返回 { sessionId, mode, previous, persisted },当前只返回 { language, outputLanguage, refreshed }。对 API schema 一致性有轻微影响,不阻塞合入。

  2. [Suggestion] 500 测试断言偏松server.test.ts:3426 使用 expect.any(String) 不验证具体错误信息是否正确透传。建议改为精确断言。

  3. [Suggestion] ~150 行格式化噪音acpAgent.ts diff 中大量三元表达式重排和换行调整与功能无关,增加 review 难度。建议拆到独立 commit。

已确认的 defer 项(可接受)

  • Bridge-level 测试 → follow-up PR
  • SDK DAEMON_KNOWN_EVENT_TYPE_VALUES 注册 → PR description 已标注

Comment thread packages/cli/src/serve/server.ts
Comment thread packages/cli/src/acp-integration/acpAgent.ts
Comment thread packages/cli/src/serve/server.test.ts

@doudouOUC doudouOUC left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM,整体实现质量不错。三层架构模式遵循一致,错误处理链路完整,fileWriteOk guard 和 refreshed 语义修正后逻辑正确。多轮 review 中的 Critical 问题均已修复。

剩余两个 Important 项(LANGUAGE_CODES 重复定义、缺少 reloadCommands)不阻塞合入,建议在 follow-up 中处理。

@wenshao wenshao merged commit ba0e4fd into daemon_mode_b_main Jun 8, 2026
29 checks passed
@chiga0 chiga0 deleted the feat/language-switch-api branch June 8, 2026 15:04
chiga0 added a commit that referenced this pull request Jun 11, 2026
…4938)

* fix(daemon): language switch writes to wrong output-language.md path

## Problem

`POST /session/:id/language` (PR #4705) always writes `output-language.md`
to the global `~/.qwen/` path, but `Config.outputLanguageFilePath` may
point to a project-level `<cwd>/.qwen/output-language.md` (when it existed
at startup). Since `refreshHierarchicalMemory` reads from the Config-bound
path, the language switch silently fails when a project-level file exists.

Additionally, on a fresh environment where no `output-language.md` exists,
the first language switch creates the file but `Config.outputLanguageFilePath`
remains `undefined` (readonly), so `refreshHierarchicalMemory` never reads
the newly created file.

## Fix

1. **Config.outputLanguageFilePath**: remove `readonly`, add
   `setOutputLanguageFilePath()` so the path can be registered after
   first-time file creation.

2. **languageUtils.ts**: add optional `targetPath` parameter to
   `writeOutputLanguageFile()` and `updateOutputLanguageFile()`. Export
   `getOutputLanguageFilePath()` for callers that need the global default.

3. **acpAgent.ts**: write to the session Config's actual path. On first-time
   creation (path was undefined), register the global path on Config. On
   multi-session refresh, also update each session's own file if its path
   differs from the one already written.

4. **languageCommand.ts** and **SettingsDialog.tsx**: same Config-bound
   path fix for the CLI `/language` command and settings dialog.

5. **server.ts**: expose `supportedLanguages` array in `GET /capabilities`
   so clients can discover valid language codes before calling the endpoint.

6. **SDK**: add `DaemonClient.setSessionLanguage()` method and
   `SetSessionLanguageResult` type.

Generated with AI

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix: address review findings — type safety, dedup helper, error handling

- Add `supportedLanguages` to `CapabilitiesEnvelope` interface (TS2353)
- Extract `writeOutputLanguageAndRegisterPath()` helper in languageUtils
  to eliminate the duplicated get-path/write/register sequence across
  acpAgent, languageCommand, and SettingsDialog (fixes SettingsDialog
  missing the registration step)
- Wrap file writes in the multi-session refresh loop with try/catch so
  `refreshHierarchicalMemory` and `refreshSystemInstruction` always run
  even when a project-level write fails

Generated with AI

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix: let write errors propagate to allSettled, remove redundant write

- Remove inner try/catch in multi-session loop so file-write failures
  are captured by Promise.allSettled and reflected in `refreshed`
- For sessions with no path: only register the global path (the file
  was already written by the primary write), skip the redundant write
- Add test assertion that setOutputLanguageFilePath is called on
  first-time creation

Generated with AI

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix: restore try/catch + write for !sessionPath, fix test cast

- Restore try/catch around file writes in multi-session loop so refresh
  always runs (write failures are logged, not propagated)
- Restore writeOutputLanguageAndRegisterPath for !sessionPath sessions
  to handle the case where writtenPath is a project-level path and the
  global file was never written
- Fix TS cast in test assertion (double-cast + bracket notation)

Generated with AI

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* test: add multi-session language propagation test

Verify the fan-out loop handles three session scenarios correctly:
- Session A (project path): writeOutputLanguageAndRegisterPath called
- Session B (different project path): updateOutputLanguageFile called
- Session C (no path): writeOutputLanguageAndRegisterPath + path
  registration
- All sessions: refreshHierarchicalMemory + refreshSystemInstruction

Generated with AI

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix: improve debug logs, SDK re-export, and add helper unit tests

- Include session ID and target path in multi-session write error logs
- Re-export SetSessionLanguageResult from top-level SDK barrel
- Add 4 unit tests for writeOutputLanguageAndRegisterPath covering
  config-bound path, undefined fallback, null/undefined config

Generated with AI

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix: hoist sessionPath declaration out of try block

sessionPath was declared with const inside try but referenced in catch,
causing a block-scope ReferenceError. Move to let before try.

Generated with AI

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* test: add catch-branch and targetPath coverage

- acpAgent: test that refreshHierarchicalMemory still runs when a
  session's file write throws (catch branch coverage)
- languageUtils: test writeOutputLanguageFile with custom targetPath

Generated with AI

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

---------

Co-authored-by: 秦奇 <gary.gq@alibaba-inc.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type/feature-request New feature or enhancement request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants