Skip to content

fix(core): honor output language in side queries#4519

Open
Jerry2003826 wants to merge 16 commits into
QwenLM:mainfrom
Jerry2003826:codex/fix-side-query-output-language
Open

fix(core): honor output language in side queries#4519
Jerry2003826 wants to merge 16 commits into
QwenLM:mainfrom
Jerry2003826:codex/fix-side-query-output-language

Conversation

@Jerry2003826

@Jerry2003826 Jerry2003826 commented May 25, 2026

Copy link
Copy Markdown
Contributor

What this PR does

Honors the configured output language for user-visible side queries without duplicating the language instruction in project-summary prompts.

Why it's needed

Some side-query results are shown directly to users, such as session titles, session recaps, tool-use summaries, suggestions, web-fetch summaries, generated subagent artifacts, arena summaries, and /insight reports. These should follow output-language.md. Internal structured side queries and summary prompts that already carry the language instruction should not receive duplicate language rules.

Reviewer Test Plan

How to verify

Review the changed runSideQuery call sites and confirm only user-visible side queries pass respectOutputLanguagePreference: true, while internal classifier/selector-style calls and project-summary prompts do not. Then run the targeted checks below.

npm run test:ci --workspace=packages/core -- src/permissions/classifier.test.ts src/services/sessionRecap.test.ts src/utils/sideQuery.test.ts
npm run test --workspace=packages/cli -- src/services/insight/generators/DataProcessor.test.ts
npx prettier --check packages/core/src/services/sessionRecap.test.ts packages/cli/src/services/insight/generators/DataProcessor.ts packages/cli/src/services/insight/generators/DataProcessor.test.ts
npx eslint packages/core/src/services/sessionRecap.test.ts packages/cli/src/services/insight/generators/DataProcessor.ts packages/cli/src/services/insight/generators/DataProcessor.test.ts
npm run typecheck --workspace=packages/core
git diff --check

Expected result: the core side-query tests pass, the insight generator tests pass, and the DataProcessor calls for insight-session-analysis and insight-qualitative-generate both include respectOutputLanguagePreference: true.

Evidence (Before & After)

Before: switching runSideQuery from unconditional output-language injection to opt-in could make /insight reports ignore output-language.md, and the session recap path lacked direct coverage that generateSessionRecap() wires the recap side query with output-language opt-in.

After: DataProcessor opts in for both user-visible /insight JSON side queries, DataProcessor.test.ts verifies both call boundaries, and sessionRecap.test.ts verifies generateSessionRecap() calls the recap side query with respectOutputLanguagePreference: true and extracts the tagged recap. This is prompt-construction behavior, so TUI screenshots are N/A.

Tested on

OS Status
macOS CI
Windows tested locally
Linux CI

Environment (optional)

Local Windows/PowerShell checkout with repository npm workspaces. Full local packages/cli typecheck is not listed as a passing local check because this checkout currently reports unrelated generated/package-link errors in serve/acp/channel/web-template areas; the changed files pass targeted tests and eslint, and CI runs the repository matrix.

Risk & Scope

  • Main risk or tradeoff: The opt-in policy must include all user-visible side-query callers without broadening to internal classifier/selector calls.
  • Not validated / out of scope: No TUI screenshot because the change is prompt construction and side-query routing, not a visible TUI state. No unrelated compression, provider, or UI behavior changes.
  • Breaking changes / migration notes: None expected.

Linked Issues

Fixes #4494

中文说明

这个 PR 做了什么

让用户可见的 side query 遵守 output-language.md,同时避免在 project summary prompt 里重复注入语言要求。

为什么需要

session title、session recap、tool-use summary、follow-up suggestion、web-fetch summary、subagent artifact、arena summary 和 /insight report 这些内容会展示给用户,因此应该遵守输出语言偏好。内部 classifier/selector 类 side query 不应该被扩大影响。

Reviewer Test Plan

审核时可以检查用户可见的 runSideQuery 调用是否传入 respectOutputLanguagePreference: true,内部结构化调用是否没有 opt in。已在 Windows 本地验证 core side-query/sessionRecap 组合测试、CLI DataProcessor 测试、changed-file prettier/eslint、core typecheck 和 git diff --check

证据

修改前:opt-in 切换后 /insight 这类用户可见报告可能漏掉输出语言偏好,session recap 外层调用也缺少直接覆盖。修改后:DataProcessor 两个 /insight side query 都 opt in,测试覆盖 call boundary;generateSessionRecap() 测试确认 recap side query opt in 并提取 <recap> 内容。该改动是 prompt 构造逻辑,TUI 截图不适用。

风险和范围

风险主要在 opt-in 覆盖范围:需要覆盖用户可见 side query,但不扩大到内部 classifier/selector。没有公共 API 或迁移影响。

@Jerry2003826 Jerry2003826 marked this pull request as ready for review May 25, 2026 22:07

@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.

[Suggestion] Missed callers that produce user-visible text

Three additional runSideQuery callers produce user-visible output but were not updated with respectOutputLanguagePreference: true:

  1. packages/core/src/tools/web-fetch.ts:154 — web content summaries shown to users
  2. packages/core/src/agents/arena/ArenaManager.ts:1690 — agent approach summaries for side-by-side comparison
  3. packages/core/src/utils/subagentGenerator.ts:129 — generated agent name/description visible to users

Consider adding the flag to these call sites for consistency with the four callers already updated.

[Suggestion] Test gaps in sideQuery.test.ts

The new tests cover the happy path (flag=true, valid file with content) but miss three realistic edge cases:

  • File does not exist (exercises the catch {} path at sideQuery.ts:157)
  • File is empty or whitespace-only (exercises content.trim() || undefined)
  • getOutputLanguageFilePath() returns undefined with respectOutputLanguagePreference: true (exercises the early return at sideQuery.ts:152)

— qwen3.7-max via Qwen Code /review

config: Config,
): Promise<string | undefined> {
const filePath = config.getOutputLanguageFilePath?.();
if (!filePath) return undefined;

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.

[Suggestion] readOutputLanguagePreference calls readFile on every runSideQuery invocation with no caching. The file path is static for the Config instance lifetime, and generateToolUseSummary fires on every completed tool batch — so the same small file is read from disk dozens of times per session.

Consider memoizing the result (keyed by filePath):

let cachedPreference: { filePath: string; value: string | undefined } | undefined;

async function readOutputLanguagePreference(
  config: Config,
): Promise<string | undefined> {
  const filePath = config.getOutputLanguageFilePath?.();
  if (!filePath) return undefined;
  if (cachedPreference?.filePath === filePath) return cachedPreference.value;
  try {
    const content = await readFile(filePath, 'utf8');
    const value = content.trim() || undefined;
    cachedPreference = { filePath, value };
    return value;
  } catch {
    cachedPreference = { filePath, value: undefined };
    return undefined;
  }
}

— qwen3.7-max via Qwen Code /review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Handled in 84eec74. readOutputLanguagePreference now memoizes by file path, and the new regression mutates the same output-language.md after the first side query to verify subsequent calls reuse the cached preference instead of re-reading.

} as unknown as Config;
});

function writeOutputLanguageFile(content: string): string {

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.

[Suggestion] writeOutputLanguageFile is duplicated verbatim across 5 test files (sideQuery.test.ts, sessionRecap.test.ts, sessionTitle.test.ts, toolUseSummary.test.ts, suggestionGenerator.test.ts). Only the temp directory prefix string differs.

Consider extracting to a shared test utility:

// packages/core/src/test-utils/outputLanguage.ts
export function writeOutputLanguageFile(content: string): string {
  const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'qwen-output-lang-'));
  const file = path.join(dir, 'output-language.md');
  fs.writeFileSync(file, content, 'utf8');
  return file;
}

— qwen3.7-max via Qwen Code /review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I left this one out of the focused bug fix for now. Extracting the duplicated test helper would touch several unrelated test files, while this PR's behavioral changes stay limited to side-query output-language plumbing and its direct coverage.

{ role: 'user', parts: [{ text: RECAP_USER_PROMPT }] },
],
systemInstruction: RECAP_SYSTEM_PROMPT,
respectOutputLanguagePreference: true,

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.

[Suggestion] RECAP_SYSTEM_PROMPT (line 20) and TITLE_SYSTEM_PROMPT (line 26 in sessionTitle.ts) both contain the instruction "Match the dominant language of the conversation (English or Chinese)." When respectOutputLanguagePreference: true appends a conflicting directive like "You MUST always respond in Chinese", the model receives two contradictory language instructions. The outcome is model-dependent and non-deterministic.

Consider either removing the "Match the dominant language" sentence from these prompts, or appending an explicit override (e.g., "This overrides any prior language instructions in this prompt.").

— qwen3.7-max via Qwen Code /review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Handled in 84eec74. The output-language append now includes an explicit override sentence, so a configured output-language preference supersedes earlier language-selection guidance in prompts like recap/title without editing each caller prompt separately.

@pomelo-nwu pomelo-nwu 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.

Hi @Jerry2003826, thank you for your continued contributions — 9 PRs in a short time is impressive! 🎉

As we review your changes, we'd like to ask you to update each PR to follow the latest PR template on the main branch. The most important section is the Reviewer Test Plan, which significantly accelerates the review and merge process.

Specifically, for each PR please include:

  • How to verify — clear reproduction steps so a reviewer can confirm the fix/feature
  • Evidence (Before & After) — use the tmux-real-user-testing skill (or manual tmux capture) to show before/after screenshots of the TUI behavior. Side-by-side evidence makes it much faster for maintainers to validate and merge
  • Tested on — fill in the OS table (macOS / Windows / Linux)

PRs with a complete Reviewer Test Plan are prioritized for review — without it, review may be delayed.

You can see the full template at: .github/pull_request_template.md

Thanks again for your effort — looking forward to getting these merged! 🚀

中文说明

你好 @Jerry2003826,感谢你的持续贡献——短时间内提交了 9 个 PR,非常高效!🎉

在 review 过程中,我们希望你能按照 main 分支上最新的 PR 模版更新每个 PR 的描述。其中最关键的部分是 Reviewer Test Plan,它能显著加速审核和合并流程。

具体来说,请为每个 PR 补充:

  • How to verify — 清晰的复现步骤,让 reviewer 能确认修复/功能的效果
  • Evidence (Before & After) — 使用 tmux-real-user-testing skill(或手动 tmux 截取)展示修改前后的 TUI 截图对比,前后对比能让维护者更快地验证和合并
  • Tested on — 填写操作系统测试表格(macOS / Windows / Linux)

有完整 Reviewer Test Plan 的 PR 会被优先审核——缺少该部分可能会导致审核延迟。

完整模版见:.github/pull_request_template.md

再次感谢你的付出,期待尽快把这些 PR 合并!🚀

— Qwen Code

@wenshao wenshao added the type/bug Something isn't working as expected label May 26, 2026
@Jerry2003826

Copy link
Copy Markdown
Contributor Author

Updated in a22d233. This addresses the review comments by adding respectOutputLanguagePreference: true to the remaining user-visible runSideQuery callers:

  • web-fetch summaries
  • Arena approach summaries
  • generated subagent artifacts

I also added coverage for the requested sideQuery edge cases: missing output-language file, blank output-language file, and getOutputLanguageFilePath() returning undefined while the flag is enabled. The Arena summary test was adjusted to return mock summaries based on each prompt's agentId instead of concurrent call order, because output-language file reads make the two summary calls race in a realistic way.

Validation run locally on Windows:

npm run test --workspace=packages/core -- src/tools/web-fetch.test.ts -t "output language"
npm run test --workspace=packages/core -- src/utils/subagentGenerator.test.ts -t "output language"
npm run test --workspace=packages/core -- src/agents/arena/ArenaManager.test.ts -t "routes all approach summaries"
npm run test --workspace=packages/core -- src/utils/sideQuery.test.ts -t "output language path|output language file is blank"
npm run test --workspace=packages/core -- src/utils/sideQuery.test.ts src/services/sessionTitle.test.ts src/services/sessionRecap.test.ts src/services/toolUseSummary.test.ts src/followup/suggestionGenerator.test.ts src/tools/web-fetch.test.ts src/agents/arena/ArenaManager.test.ts src/utils/subagentGenerator.test.ts
npx prettier --check packages/core/src/utils/sideQuery.test.ts packages/core/src/tools/web-fetch.ts packages/core/src/tools/web-fetch.test.ts packages/core/src/agents/arena/ArenaManager.ts packages/core/src/agents/arena/ArenaManager.test.ts packages/core/src/utils/subagentGenerator.ts packages/core/src/utils/subagentGenerator.test.ts
npx eslint packages/core/src/utils/sideQuery.test.ts packages/core/src/tools/web-fetch.ts packages/core/src/tools/web-fetch.test.ts packages/core/src/agents/arena/ArenaManager.ts packages/core/src/agents/arena/ArenaManager.test.ts packages/core/src/utils/subagentGenerator.ts packages/core/src/utils/subagentGenerator.test.ts
npm run lint --workspace=packages/core
npm run typecheck --workspace=packages/core
git diff --check

Note: when I initially ran several vitest commands in parallel locally on Windows, two runs hit the known coverage/.tmp race after tests had passed. I reran those checks serially, and they passed.

@Jerry2003826

Copy link
Copy Markdown
Contributor Author

Added aaf6883 for the remaining sideQuery.test.ts missing-file edge case.

I also rechecked the three user-visible call sites from the review against the current branch head; they already pass respectOutputLanguagePreference: true:

  • packages/core/src/tools/web-fetch.ts
  • packages/core/src/agents/arena/ArenaManager.ts
  • packages/core/src/utils/subagentGenerator.ts

Validation:

npm run test --workspace=@qwen-code/qwen-code-core -- src/utils/sideQuery.test.ts -t "output language"
npm run test --workspace=@qwen-code/qwen-code-core -- src/utils/sideQuery.test.ts
npx eslint packages/core/src/utils/sideQuery.ts packages/core/src/utils/sideQuery.test.ts packages/core/src/tools/web-fetch.ts packages/core/src/agents/arena/ArenaManager.ts packages/core/src/utils/subagentGenerator.ts
npx prettier --check packages/core/src/utils/sideQuery.ts packages/core/src/utils/sideQuery.test.ts packages/core/src/tools/web-fetch.ts packages/core/src/agents/arena/ArenaManager.ts packages/core/src/utils/subagentGenerator.ts
npm run lint --workspace=@qwen-code/qwen-code-core
npm run typecheck --workspace=@qwen-code/qwen-code-core
git diff --check

);
});

it('leaves JSON system instruction unchanged when output language path is missing', async () => {

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.

[Suggestion] JSON-mode negative test coverage is asymmetric with text mode. Text mode has three negative tests (path undefined, file missing, file blank), but JSON mode has only one (file missing). Also: the test name says "path is missing" when the file at the path is missing — the text-mode tests correctly distinguish these cases. And the bare path.join(os.tmpdir(), 'qwen-side-query-missing.md') is not parallel-safe; the text-mode "file is missing" test at line ~515 correctly uses fs.mkdtempSync for isolation.

Consider adding two JSON-mode tests mirroring text mode, and using mkdtempSync for the existing one:

it('leaves JSON system instruction unchanged when output language path is undefined', async () => {
  vi.mocked(mockBaseLlmClient.generateJson).mockResolvedValue({ ok: true });
  // mockConfig already returns undefined for getOutputLanguageFilePath
  await runSideQuery<{ ok: boolean }>(mockConfig, { /* ... */ respectOutputLanguagePreference: true });
  expect(callArg.systemInstruction).toBe('custom JSON side query prompt');
});

it('leaves JSON system instruction unchanged when output language file is missing', async () => {
  vi.mocked(mockBaseLlmClient.generateJson).mockResolvedValue({ ok: true });
  const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'qwen-side-query-missing-'));
  vi.mocked(mockConfig.getOutputLanguageFilePath).mockReturnValue(
    path.join(dir, 'output-language.md'),
  );
  // ...
});

it('leaves JSON system instruction unchanged when output language file is blank', async () => {
  vi.mocked(mockBaseLlmClient.generateJson).mockResolvedValue({ ok: true });
  vi.mocked(mockConfig.getOutputLanguageFilePath).mockReturnValue(
    writeOutputLanguageFile(' \n\t '),
  );
  // ...
});

— qwen3.7-max via Qwen Code /review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added in e0c28da. JSON mode now mirrors the text-mode negative coverage: undefined output-language path, missing file using an isolated temp dir, and blank file all leave the original JSON systemInstruction unchanged.

const callArg = mockGenerateContent.mock.calls[0]?.[0];
expect(callArg.systemInstruction).toContain(
'You MUST always respond in Chinese.',
);

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.

[Suggestion] This assertion only checks that the language preference is present, but doesn't verify the original systemInstruction is preserved. Compare with existing tests in toolUseSummary.test.ts and sessionRecap.test.ts which assert both:

// Established pattern (e.g. toolUseSummary.test.ts:322-326):
expect(options.systemInstruction).toContain(TOOL_USE_SUMMARY_SYSTEM_PROMPT);
expect(options.systemInstruction).toContain('You MUST always answer in Chinese.');

If appendSystemInstructionText had a bug that replaced rather than appended, this test would still pass. Add:

Suggested change
);
expect(callArg.systemInstruction).toContain(
'Extract and summarize the requested information',
);
expect(callArg.systemInstruction).toContain(
'You MUST always answer in Chinese.',
);

— qwen3.7-max via Qwen Code /review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added in e0c28da. The web-fetch output-language test now asserts both that the original web-fetch system instruction (Extract and summarize the requested information...) is preserved and that the configured language preference is appended.

const generateJsonCall = (mockClient.generateJson as Mock).mock.calls[0];
const callParams = generateJsonCall[0];
expect(callParams.systemInstruction).toContain(
'You MUST always respond in Chinese.',

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.

[Suggestion] Same as web-fetch.test.ts — this only asserts the language preference is present. The established pattern in toolUseSummary.test.ts and sessionRecap.test.ts also verifies the original systemInstruction is preserved:

Suggested change
'You MUST always respond in Chinese.',
expect(callParams.systemInstruction).toContain(
'You are an elite AI agent architect',
);
expect(callParams.systemInstruction).toContain(
'You MUST always answer in Chinese.',
);

— qwen3.7-max via Qwen Code /review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added in e0c28da. The subagent output-language test now follows the same pattern: it verifies the original You are an elite AI agent architect system prompt remains present and the configured language preference is appended.

@Jerry2003826 Jerry2003826 force-pushed the codex/fix-side-query-output-language branch from aaf6883 to e0c28da Compare May 26, 2026 09:00
@Jerry2003826

Copy link
Copy Markdown
Contributor Author

Updated in e0c28da for the latest review suggestions.

Changes made:

  • Mirrored text-mode negative coverage for JSON side queries: undefined output-language path, missing file, and blank file all preserve the original JSON systemInstruction.
  • Strengthened the web-fetch output-language test to assert the original system instruction is preserved while the language directive is appended.
  • Strengthened the subagent output-language test with the same preserve-and-append assertion pattern.

Validation run locally on Windows:

npm run test --workspace=packages/core -- src/utils/sideQuery.test.ts src/tools/web-fetch.test.ts src/utils/subagentGenerator.test.ts -t "output language|JSON system instruction"
npm run test --workspace=packages/core -- src/utils/sideQuery.test.ts src/tools/web-fetch.test.ts src/utils/subagentGenerator.test.ts
npx prettier --check packages/core/src/utils/sideQuery.test.ts packages/core/src/tools/web-fetch.test.ts packages/core/src/utils/subagentGenerator.test.ts
npx eslint packages/core/src/utils/sideQuery.test.ts packages/core/src/tools/web-fetch.test.ts packages/core/src/utils/subagentGenerator.test.ts
npm run typecheck --workspace=packages/core
npm run lint --workspace=packages/core
git diff --check

wenshao
wenshao previously approved these changes May 26, 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.

R3: All R2 suggestions addressed. No new issues found. ✅ — qwen3.7-max via Qwen Code /review

@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.

⚠️ Downgraded from Approve to Comment: CI failing (Post Coverage Comment, Lint, CodeQL, Test, Classify PR).

R4 incremental review (delta since R3: sideQuery.ts memoization + override sentence, sideQuery.test.ts new tests). The memoization and override sentence address the remaining R1 Suggestions. One new Suggestion below regarding cache invalidation for long-running sessions.

— qwen3.7-max via Qwen Code /review

'User output language preference from output-language.md:';
const OUTPUT_LANGUAGE_PREFERENCE_OVERRIDE =
'This preference overrides any earlier language-selection rule in this system instruction.';
const outputLanguagePreferenceCache = new Map<

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.

[Suggestion] outputLanguagePreferenceCache is a module-level Map with no invalidation path. When a user runs /language mid-session, languageCommand.ts rewrites the file and refreshes the main conversation's system instruction — but this cache still holds the old resolved Promise. All 7 respectOutputLanguagePreference: true consumers (session title, recap, tool-use summary, follow-up suggestions, web-fetch, subagent generation, Arena) will serve the stale language rule for the remainder of the process.

The new memoization test (sideQuery.test.ts) correctly asserts this behavior, but it codifies a regression for daemon mode or any long-lived session where the user changes language.

Suggested fix: Export a clearOutputLanguagePreferenceCache() function and call it from the /language command handler (alongside the existing config.refreshHierarchicalMemory() call), or scope the cache to the Config instance lifetime rather than the module:

export function clearOutputLanguagePreferenceCache(): void {
  outputLanguagePreferenceCache.clear();
}

Then in languageCommand.ts:

import { clearOutputLanguagePreferenceCache } from '../utils/sideQuery.js';
// ...after updateOutputLanguageFile():
clearOutputLanguagePreferenceCache();

— qwen3.7-max via Qwen Code /review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 28aee11. Side-query output-language caching now invalidates on file signature changes, and /language output ... explicitly clears the cached path after rewriting output-language.md. Added regressions for both the side-query refresh and the live /language command cache clear.

@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.

R5 incremental review (delta since R4: cache invalidation via stat-based signatures + clearOutputLanguagePreferenceCache export). CI 10/10 all pass. 75 tests pass.

Missed caller (out of diff scope): packages/cli/src/ui/commands/summaryCommand.ts:107 calls runSideQuery to generate a user-visible project summary (displayed in TUI and saved to .qwen/PROJECT_SUMMARY.md) but does not set respectOutputLanguagePreference: true. If the user configures output language to Chinese, the project summary would still be generated in English. Consider adding respectOutputLanguagePreference: true to match the PR's stated goal.

— qwen3.7-max via Qwen Code /review


// Update the rule file with the resolved language
updateOutputLanguageFile(settingValue);
clearOutputLanguagePreferenceCache(config?.getOutputLanguageFilePath?.());

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.

[Suggestion] Path mismatch between write target and cache invalidation target.

updateOutputLanguageFile(settingValue) writes to the global path (~/.qwen/output-language.md, hardcoded in languageUtils.ts), but clearOutputLanguagePreferenceCache(config?.getOutputLanguageFilePath?.()) clears the cache for the config's path, which may be a project-level path (<cwd>/.qwen/output-language.md). When a project-level file exists, the write modifies the global file while side queries re-read the unchanged project-level file — the user's /language command has no visible effect on side-query outputs.

Additionally, when config?.getOutputLanguageFilePath?.() returns undefined (e.g., config lacks the method), clearOutputLanguagePreferenceCache(undefined) fires the clear-all branch, wiping every cached path — including entries from other sessions in daemon mode.

Suggested change
clearOutputLanguagePreferenceCache(config?.getOutputLanguageFilePath?.());
clearOutputLanguagePreferenceCache();

Using the no-arg version clears all entries as a belt-and-suspenders approach. The stat-based invalidation in readOutputLanguagePreference handles correctness automatically on the next call, so the only cost is one extra readFile per session.

— qwen3.7-max via Qwen Code /review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in the current branch. /language output now calls clearOutputLanguagePreferenceCache() without a path after updateOutputLanguageFile(), so project/global path differences cannot leave a stale side-query language preference cached.

}
>();

export function clearOutputLanguagePreferenceCache(filePath?: string): void {

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.

[Suggestion] This new public API has two branches (specific-path deletion and clear-all) but no behavioral unit tests. It is only verified via vi.fn() mock in languageCommand.test.ts, which confirms the function was called but not that it works.

Consider adding tests in sideQuery.test.ts:

  1. Specific-path deletion: populate cache via runSideQuery, call clearOutputLanguagePreferenceCache(path), verify next call re-reads the file.
  2. Clear-all: populate cache with entries, call clearOutputLanguagePreferenceCache(), verify all entries are removed.

Also consider adding a JSDoc note that this function is a performance hint — stat-based invalidation in readOutputLanguagePreference handles correctness automatically.

— qwen3.7-max via Qwen Code /review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Covered in the current branch. sideQuery.test.ts now exercises both cache-clear branches: specific-path deletion preserves other cached entries, and no-argument clearing refreshes all cached preferences. Verified with npm run test --workspace=packages/core -- src/utils/sideQuery.test.ts.

],
},
],
respectOutputLanguagePreference: true,

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.

[Suggestion] Output language rule is appended twice to this side query. The main session's systemInstruction (extracted at line 99-105 via getGenerationConfig().systemInstruction) already embeds output-language.md content as part of userMemory (loaded through getExtensionContextFilePaths()loadServerHierarchicalMemory()). Setting respectOutputLanguagePreference: true causes applyOutputLanguagePreference in sideQuery.ts to read the same file and append the language rule a second time.

The model receives the instruction duplicated in every summary generation — redundant tokens and a confusing artifact for anyone debugging system instructions.

Consider either:

  • Removing respectOutputLanguagePreference: true here since the carried-over chatSystemInstruction already contains the rule, or
  • Stripping the output-language section from chatSystemInstruction before passing it, and keeping the explicit flag.

— qwen3.7-max via Qwen Code /review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in the current branch. summaryCommand no longer sets respectOutputLanguagePreference: true for the project-summary side query because it carries over the main chat system instruction, which already includes the configured output-language rule through user memory.

expect(secondCallArg.systemInstruction).not.toContain('Chinese');
});

it('clears one cached output language preference by file path', async () => {

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.

[Suggestion] This test creates only a single file and verifies its cache was cleared, but does not include a negative assertion — it never checks that a different cached file's entry survived the selective clear. If clearOutputLanguagePreferenceCache(file) were accidentally changed to call .clear() instead of .delete(filePath), this test would still pass.

Consider creating a second file, caching it via runSideQuery, then after clearOutputLanguagePreferenceCache(firstFile), running a side query for the second file and asserting it still contains the original (stale) preference — proving its cache was not disturbed. This would make the two new tests properly complementary (one proves selective clear, the other proves blanket clear).

— qwen3.7-max via Qwen Code /review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Covered in the current branch. The specific-path cache test now populates two files, clears only the first path, and asserts the second cached preference survives instead of being globally cleared. Verified with npm run test --workspace=packages/core -- src/utils/sideQuery.test.ts.

Comment thread packages/core/src/utils/sideQuery.ts Outdated
signature,
preference: readFile(filePath, 'utf8')
.then((content) => content.trim() || undefined)
.catch(() => undefined),

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.

[Suggestion] Transient readFile failure permanently caches undefined until the file's mtime or size changes.

When stat() succeeds but readFile() fails (EMFILE, permission flap, transient IO error), .catch(() => undefined) stores a Promise<undefined> as cached.preference with the current signature. On subsequent calls, stat() returns the same mtimeMs:size, the cache hits, and the language preference is silently lost.

Suggested change
.catch(() => undefined),
.catch(() => {
outputLanguagePreferenceCache.delete(filePath);
return undefined;
}),

— qwen3.7-max via Qwen Code /review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in the current branch. readOutputLanguagePreference() now deletes the cache entry when readFile() fails for the current signature, so transient read failures do not permanently cache undefined.

@wenshao

wenshao commented May 27, 2026

Copy link
Copy Markdown
Collaborator

Verification Report — PR #4519

Reviewer: wenshao
Branch: codex/fix-side-query-output-languagemain
Environment: macOS Darwin 25.4.0, Node v22.17.0


1. Build & Type Check

Check Result
npm install ✅ Pass
tsc --noEmit (packages/core) ✅ Pass (zero errors)
tsc --noEmit (packages/cli) ✅ Pass (zero errors)
eslint (changed files) ✅ Pass
prettier --check (changed files) ✅ Pass

2. Unit Tests

PR-related tests (all pass):

Test File Result
sideQuery.test.ts ✅ 29/29 passed
languageCommand.test.ts ✅ 48/48 passed
ArenaManager.test.ts ✅ 22/22 passed
sessionRecap.test.ts ✅ new test passed
sessionTitle.test.ts ✅ all passed
toolUseSummary.test.ts ✅ all passed
web-fetch.test.ts ✅ all passed
suggestionGenerator.test.ts ✅ all passed
subagentGenerator.test.ts ✅ all passed
Total across 9 files 149/149 passed

Full core suite: 9438 passed, 1 failed, 3 skipped (344 files). The single failure (anthropicContentGenerator.test.ts: treats unset baseURL as Anthropic-native) is pre-existing on main — verified by checking out the main version of that test file and confirming the same failure. Unrelated to this PR.

3. Code Review

Architecture (sideQuery.ts)

The design is clean and well-layered:

  1. readOutputLanguagePreference(config) — reads output-language.md with a stat-signature-based cache (mtimeMs:size). Cache is a Map<filePath, {signature, preference}>. The preference value is a Promise<string | undefined> — concurrent callers reuse the same in-flight read.

  2. appendSystemInstructionText(systemInstruction, text) — handles all 4 systemInstruction type variants: string, Part[], Content (with .parts), and single Part. Correctly creates a new object/array without mutating the original.

  3. applyOutputLanguagePreference(config, systemInstruction, flag) — wires the above together; early-returns when respectOutputLanguagePreference is falsy or no preference exists.

  4. clearOutputLanguagePreferenceCache(filePath?) — selective or full cache invalidation, called from languageCommand.ts when the user changes their output language.

Callers — Correct Opt-in

7 side-query callers correctly opt in with respectOutputLanguagePreference: true:

  • sessionRecap, sessionTitle, toolUseSummary — session management UI text
  • suggestionGenerator — follow-up suggestions
  • web-fetch — web content summaries
  • subagentGenerator — subagent spec generation
  • ArenaManager — arena approach summaries

summaryCommand.ts correctly does NOT opt in — its comment explains the system instruction already includes the output-language rule via user memory. This avoids the duplicate-language-instruction problem stated in the PR.

Test Coverage

The new tests thoroughly cover:

  • Append to string / Part[] / Content with .parts / single Part
  • Undefined / missing / blank output-language file → no-op
  • Cache hit / miss / invalidation (selective and full)
  • File content change detection via stat signature
  • Each caller tested for output-language presence in the prompt

4. Issues Found

Severity Issue
None No issues found in this PR's changes.

5. Summary

This is a well-designed, well-tested fix. The opt-in respectOutputLanguagePreference flag is the right approach — it avoids changing default behavior while allowing user-visible side queries to honor the configured output language. The stat-based caching is efficient and the cache invalidation is properly wired into the language change flow. Test coverage is comprehensive at 149 new/updated test cases.

Recommendation: ✅ Approve.


Verified locally on 2026-05-28

@Jerry2003826

Copy link
Copy Markdown
Contributor Author

Updated in b86309b for the transient read failure cache issue.

If stat() succeeds but readFile() fails, the cached entry is now removed before returning undefined, so a later side query with the same file signature retries the preference read instead of permanently treating the preference as absent.

Validation run locally on Windows:

npm run test --workspace=packages/core -- src/utils/sideQuery.test.ts -t "output language"
npm run test --workspace=packages/core -- src/utils/sideQuery.test.ts
npx prettier --check packages/core/src/utils/sideQuery.ts packages/core/src/utils/sideQuery.test.ts
npx eslint packages/core/src/utils/sideQuery.ts packages/core/src/utils/sideQuery.test.ts
npm run typecheck --workspace=packages/core

@Jerry2003826

Copy link
Copy Markdown
Contributor Author

CI note for the latest run: the macOS failure appears unrelated to this PR's side-query change.

The failed test is in packages/cli/src/ui/components/messages/AskUserQuestionDialog.test.tsx:

<AskUserQuestionDialog /> > single-select interaction > keeps bare k/j in custom input while Ctrl+P/N still navigates options
AssertionError: expected rendered output to contain '? 4.'

This PR only changes packages/core/src/utils/sideQuery.ts and sideQuery.test.ts. The targeted side-query checks and the failed CLI dialog test both pass locally on Windows:

npm run test --workspace=packages/core -- src/utils/sideQuery.test.ts -t "output language"
npm run test --workspace=packages/core -- src/utils/sideQuery.test.ts
npm run test --workspace=packages/cli -- src/ui/components/messages/AskUserQuestionDialog.test.tsx -t "keeps bare k/j"

I tried to rerun the failed workflow, but GitHub rejected it because I do not have repository admin rights. Could a maintainer rerun the failed macOS job?

BZ-D
BZ-D previously approved these changes Jun 1, 2026

@BZ-D BZ-D 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. The respectOutputLanguagePreference plumbing is thorough — correctly wired into session title, recap, tool-use summary, suggestion generator, web fetch, subagent generator, and arena manager. The stat-based cache with mtime+size signature is a good balance of freshness vs performance, and clearOutputLanguagePreferenceCache gives the /language command an explicit flush path. Edge cases (missing file, blank content, transient read failures) are all covered.

@Jerry2003826 Jerry2003826 force-pushed the codex/fix-side-query-output-language branch 3 times, most recently from 6ae137a to 21f8a79 Compare June 1, 2026 20:15
@Jerry2003826 Jerry2003826 force-pushed the codex/fix-side-query-output-language branch from 21f8a79 to 63c46de Compare June 1, 2026 20:34

import type { Content } from '@google/genai';
import { describe, expect, it } from 'vitest';
import { buildSessionRecapSideQueryOptions } from './sessionRecap.js';

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.

[Suggestion] The test was refactored from calling generateSessionRecap end-to-end to only testing buildSessionRecapSideQueryOptions. The old test exercised the full pipeline — config resolution, file read, system-instruction append, LLM call, extractRecap tag parsing, and the result.text / abort / error-swallowing branches. None of those paths are covered now.

Consider keeping the old integration test alongside the new unit test, or adding at least one test that calls generateSessionRecap through to the mocked LLM and asserts the final returned string (including the <recap> tag unwrap). The describe block name ('generateSessionRecap') also no longer matches what is actually tested.

— qwen3.7-max via Qwen Code /review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in the current branch. sessionRecap.test.ts now keeps an end-to-end generateSessionRecap() test that exercises the side-query call and verifies tagged <recap> extraction, alongside the focused options-builder coverage for output-language opt-in.

BZ-D
BZ-D previously approved these changes Jun 2, 2026

@BZ-D BZ-D 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.

Reviewed the diff and ran the targeted side-query language preference tests; no blocking issues found.

wenshao
wenshao previously approved these changes Jun 2, 2026
@wenshao

wenshao commented Jun 2, 2026

Copy link
Copy Markdown
Collaborator

Possible regression: /insight reports stop honoring output-language.md after this change

The opt-in switch (unconditional injection → respectOutputLanguagePreference) is the right call and correctly fixes the duplicated-instruction case in summaryCommand. But the same switch is global to every runSideQuery call site, and two user-visible ones in the insight generator were not opted in, so they regress:

  • packages/cli/src/services/insight/generators/DataProcessor.ts:201purpose: 'insight-session-analysis'
  • packages/cli/src/services/insight/generators/DataProcessor.ts:402purpose: 'insight-qualitative-generate'

Why these are user-visible (not internal/structured like the forget / classifier / relevanceSelector / nextSpeakerChecker queries that are correctly skipped):

  • Their schemas emit natural-language fields — intro, title, description, whats_possible, how_to_try, brief_summary, etc.
  • Those fields are rendered into the /insight HTML report via StaticInsightGeneratorTemplateRenderer.renderInsightHTML() and opened for the user (insightCommand.ts:219 await open(outputPath)).
  • getInsightPrompt() itself carries no language instruction — it relied entirely on runSideQuery's previously-unconditional injection to control output language.

Before this PR, these JSON side queries received the output-language.md instruction unconditionally (the injection ran before the isJsonOptions branch). After the opt-in switch they no longer do, so /insight output reverts to the model's default language regardless of the user's configured preference — the opposite of this PR's stated goal.

Suggested fix — add the opt-in to both calls:

// DataProcessor.ts — both insight side queries
const result = await runSideQuery<Record<string, unknown>>(this.config, {
  purpose: 'insight-session-analysis', // and 'insight-qualitative-generate'
  model: this.config.getModel(),
  contents: [{ role: 'user', parts: [{ text: prompt }] }],
  schema: INSIGHT_SCHEMA,
  respectOutputLanguagePreference: true, // <-- add
  config: { thinkingConfig: { includeThoughts: true } },
  abortSignal: AbortSignal.timeout(600000),
});

A regression test asserting the injected output-language.md text reaches the insight systemInstruction (mirroring the new sessionTitle / toolUseSummary tests) would lock this in.


(Minor, not blocking) chatCompressionService.ts:336 is also not opted in. Its summary is injected back into history for the model rather than shown directly, so leaving it off is probably fine — just flagging it in case any UI surfaces the compression summary.

@Jerry2003826 Jerry2003826 dismissed stale reviews from wenshao and BZ-D via e7e0e4b June 2, 2026 06:15
@Jerry2003826 Jerry2003826 force-pushed the codex/fix-side-query-output-language branch 3 times, most recently from 7ef958e to f9c3199 Compare June 2, 2026 07:27
@Jerry2003826 Jerry2003826 force-pushed the codex/fix-side-query-output-language branch from f9c3199 to 52f785e Compare June 2, 2026 07:36
wenshao
wenshao previously approved these changes Jun 2, 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.

No high-confidence review findings. The implementation is sound: stat-based cache invalidation is correct, Promise deduplication across concurrent callers is efficient, and the opt-in flag is consistently applied to all user-visible callers. The session recap E2E test coverage gap flagged in R9 has been addressed in this commit. 255 tests pass, ESLint and TypeScript clean.

Needs Human Review (low-confidence, terminal-only):

  1. readOutputLanguagePreference catch blocks silently return undefined with no debug logging — consider adding createDebugLogger for troubleshooting.
  2. Em-dash regressed to ASCII hyphen in sessionRecap.ts comment during extraction.
  3. languageCommand.ts comment slightly overstates clearOutputLanguagePreferenceCache necessity.

Note: PR has merge conflicts with main that need resolution before merging.

— qwen3.7-max via Qwen Code /review

…output-language

# Conflicts:
#	packages/cli/src/services/insight/generators/DataProcessor.test.ts
xaelistic pushed a commit to xaelistic/qwen-code that referenced this pull request Jun 7, 2026
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>

@DragonnZhang DragonnZhang 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

Verdict: No new findings. This PR has been reviewed exhaustively (8+ prior review rounds, 23 inline comments, 9 issue comments). After reading all 24 changed files in full and cross-referencing every existing comment, I found no high-confidence issues that are not already tracked.

What was checked

  • sideQuery.ts core logic: respectOutputLanguagePreference opt-in gate, readOutputLanguagePreference caching with stat-based invalidation, appendSystemInstructionText for all 5 systemInstruction variants, clearOutputLanguagePreferenceCache selective and full clear, isJsonOptions type guard, concurrent-call safety via shared Promise deduplication. Implementation is correct.
  • All 11 caller sites: sessionTitle, sessionRecap, toolUseSummary, suggestionGenerator, web-fetch, subagentGenerator, ArenaManager, DataProcessor (both analyzeSession and generateQualitativeInsights), LlmRewriter, summaryCommand (correctly skipped with explanatory comment). Each user-visible caller opts in; internal/classifier callers do not.
  • Test coverage: ~512 new lines of tests covering positive opt-in for text and JSON modes, negative paths (missing file, blank file, undefined path), Part[]/Content/single-Part system instruction variants, cache refresh on file change, selective and full cache clear, transient read failure recovery, and opt-in gate negative test. Coverage is thorough.
  • languageCommand.ts: clearOutputLanguagePreferenceCache() called after updateOutputLanguageFile() with clear-all (no path arg) to handle global-vs-project path differences. Correct.
  • config.ts: getOutputLanguageFilePath relocated for grouping; no behavioral change.

CI status

CI is currently failing (Lint, CodeQL, Test, Post Coverage Comment, Classify PR). Some failures appear environment-related (the macOS AskUserQuestionDialog test failure mentioned by the author). Approve downgraded to Comment per presubmit policy.

Open items from prior reviewers

Several open inline comments remain valid and tracked:

  • Path mismatch between updateOutputLanguageFile (global) and getOutputLanguageFilePath (project-scoped) — mitigated by clear-all strategy
  • sessionRecap.test.ts reduced from end-to-end to builder-only testing
  • summaryCommand.ts double-injection concern — addressed by inline comment explaining the carried system instruction already includes the language rule

— qwen-code via Qwen Code /review

@Jerry2003826

Copy link
Copy Markdown
Contributor Author

Updated this branch against current origin/main in a57ac5d81 and re-verified the insight output-language path.

The /insight regression mentioned in the review is covered on the current branch:

  • DataProcessor.ts passes respectOutputLanguagePreference: true for both insight-session-analysis and insight-qualitative-generate.
  • DataProcessor.test.ts has regression coverage for both side-query call sites.

Local validation on Windows:

  • npm run test --workspace=packages/cli -- src/services/insight/generators/DataProcessor.test.ts -t "output language" - 2 passed
  • npm run test --workspace=packages/cli -- src/services/insight/generators/DataProcessor.test.ts - 47 passed
  • npm run test --workspace=packages/core -- src/utils/sideQuery.test.ts -t "output language" - 18 passed
  • npm run test --workspace=packages/core -- src/utils/sideQuery.test.ts - 36 passed
  • npm run build --workspace=packages/core - passed
  • npm run typecheck --workspace=packages/core - passed
  • npx eslint --max-warnings 0 --no-warn-ignored packages/core/src/utils/sideQuery.ts packages/core/src/utils/sideQuery.test.ts packages/cli/src/services/insight/generators/DataProcessor.ts packages/cli/src/services/insight/generators/DataProcessor.test.ts packages/core/src/config/config.ts packages/cli/src/ui/commands/summaryCommand.ts - passed
  • npx prettier --check packages/core/src/utils/sideQuery.ts packages/core/src/utils/sideQuery.test.ts packages/cli/src/services/insight/generators/DataProcessor.ts packages/cli/src/services/insight/generators/DataProcessor.test.ts packages/core/src/config/config.ts packages/cli/src/ui/commands/summaryCommand.ts - passed

Note: full packages/cli typecheck now gets past core/acp/channel package references after building those packages, but still stops on @qwen-code/web-templates because npm run build --workspace=packages/web-templates fails locally on this Windows path with a space (C:\Users\Jiarui Li\...). The failure happens inside packages/web-templates/build.mjs before this PR's files are typechecked and is unrelated to the side-query output-language change.

@DragonnZhang DragonnZhang 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.

Incremental Review (R2) — delta since R1

HEAD: 8730d9f06579 (two new commits: merge from origin/main + test: normalize dev launcher paths on Windows)
CI: 13/13 checks pass.

Delta reviewed

  • scripts/tests/dev.test.js — Adds normalizePath helper that converts Windows backslashes to forward slashes before substring assertions. Correct fix: String(filePath).endsWith('node_modules/tsx/dist/cli.mjs') would fail on native Windows paths where filePath contains backslashes. The refactor to destructure spawnMock.mock.calls[0] and assert each field individually is clearer than the prior monolithic toHaveBeenCalledWith.

Verdict

No high-confidence findings in the delta. The Windows path normalization is a clean, well-scoped test fix.

The six stale inline comments from prior reviewers (IDs 3300572305, 3301123073, 3307003468, 3308736776, 3312297210, 3337943627) are all addressed in the current code:

  • sessionRecap.ts language override — the appended text includes an explicit override sentence.
  • JSON negative coverage — mirrored text-mode negative tests now present.
  • languageCommand path mismatchclearOutputLanguagePreferenceCache() (no-arg) correctly clears the entire map.
  • summaryCommand double injection — correctly omits respectOutputLanguagePreference: true since the carried-over system instruction already embeds the rule.
  • Transient readFile caching — the .catch handler now deletes the cache entry on read failure.
  • sessionRecap test refactor — the generateSessionRecap end-to-end test is retained with runSideQuery mocked; buildSessionRecapSideQueryOptions gets separate unit coverage.

— qwen3-coder via Qwen Code /review

…output-language

# Conflicts:
#	scripts/tests/dev.test.js

@DragonnZhang DragonnZhang 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: APPROVE

Scope: 30 files, +1151/-101 — adds opt-in respectOutputLanguagePreference to runSideQuery for user-visible side queries (session title, recap, tool-use summary, suggestions, web-fetch summaries, subagent artifacts, arena summaries, /insight reports, LLM rewriter). Includes stat-based file cache with invalidation on language change. Bundles two small fixes (cronScheduler creation-minute guard, chatCompression hard-threshold formula) from upstream main merges.

CI: All checks pass (Lint, Test x3 platforms, CodeQL).

Findings: No high-confidence bugs, security issues, or critical logic errors identified.

Analysis

  • Side-query opt-in coverage: Correctly applied to all 10 user-visible call sites (sessionTitle, sessionRecap, toolUseSummary, suggestionGenerator, web-fetch, subagentGenerator, ArenaManager, LlmRewriter, DataProcessor x2). Intentionally omitted from internal classifiers (permissions/classifier, relevanceSelector, forget, nextSpeakerChecker, chatCompression) and from summaryCommand (which already carries the language rule through user memory).
  • Caching: Stat-based signature (mtimeMs:size) with module-level Map keyed by file path. Race-safe: concurrent reads share the same Promise; readFile failures clean up only the current entry. clearOutputLanguagePreferenceCache() called on language change in languageCommand, clearing all entries to handle global/project path differences.
  • appendSystemInstructionText: Correctly handles all system instruction shapes (string, Part, Part[], Content with parts array, and single Part fallback).
  • Session recap refactor: Extracted buildSessionRecapSideQueryOptions for testability without behavioral change.
  • Bundled fixes from main merges: cronScheduler lastFiredAt initialization and chatCompression hard threshold formula — both sound and already validated by their own PRs.

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

Labels

type/bug Something isn't working as expected

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Side queries ignore the user's configured output language (recap, title, tool-use summary, suggestions)

5 participants