mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-08-25 08:33:55 +00:00
* refactor(cli): enforce utils leaf-layer dependency direction (#9146) Move domain-coupled modules out of packages/cli/src/utils into the directories that own them: config/ (dialogScopeUtils, settingsUtils), i18n/ (languageUtils), ui/ (handleAutoUpdate, standalone-update, systemInfo, systemInfoFields, update-relaunch, commands, doctorChecks), nonInteractive/ (nonInteractiveHelpers, chat-recording-failure, tool-result-boundary-diagnostics, permission-suggestions), serve/ (sandbox), services/housekeeping/ (scheduler, non-interactive-scheduler), and commands/review/ (findings). Extract the generic normalizePartList helper into utils/normalize-part-list.ts so utils consumers keep importing downward, and move the MergeStrategy enum into utils/deepMerge.ts (its owner). Add an eslint architecture rule (no-utils-upward-import) that forbids value imports from utils/ back up into a domain directory. Type-only imports stay exempt: they are erased at compile time and cannot create a runtime cycle (Settings in modelConfigUtils, CommandContext in sessionPaths). No behavior change: typecheck, build, and the affected unit tests pass. * fix: use Qwen Team 2026 license header on new files (#9146) * chore: refresh stale utils/ path references after leaf-layer move (#9146) * docs: reconcile no-utils-upward-import header with the allowed type-only set (#9146) * fix(cli): allowlist sandbox process.env accesses after leaf-layer move (#9146) * chore(ci): re-record qwen-autofix.yml size baseline after #9677 (#9146) #9677 recorded qwen-autofix.yml at 392111 bytes while the file it committed was already 397656, so every PR that merged main after it tripped the growth ratchet. Re-record the actual size; the file itself is unchanged by this PR. * fix(review): drop the stale utils/findings.ts digest root after the leaf-layer move (#9146) The #9146 move returned findings.ts to commands/review/, but the digest root lists merged from main still pinned it under utils/, where the file no longer exists — the absent root darkened every review's staleness check and failed review-source-digest.test.ts. Drop the stale file-shaped root from both digest copies and their pins; the commands/review/ directory root covers the validator at its new home, and the two utils helpers keep their file-shaped roots. * fix(review): colocate seatbelt profiles with the sandbox module (#9146) * fix(review): exempt inline type-only specifiers from the utils upward-import rule (#9146) * fix(review): report upward inline type-specifier imports under verbatimModuleSyntax (#9146) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(review): pin mixed-specifier and zero-specifier upward imports in the utils rule (#9146) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(review): anchor the nested-checkout utils rule fixture on the last marker (#9146) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(review): pin that the utils/findings.ts digest root stays removed (#9146) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(review): reword stale-bundle SCOPE header to the post-move helper shape (#9146) * test(review): drop the pre-move utils/findings.ts from the skill-parity fixture (#9146) * test(serve): derive the seatbelt colocation tripwire from BUILTIN_SEATBELT_PROFILES (#9146) * fix(architecture): fail closed on computed dynamic imports in the utils leaf rule (#9146) * fix(cli): point settings.test.ts at the post-move settingsUtils path (#9146) main updated settings.test.ts after this branch moved settingsUtils.ts from utils/ into config/, and the merge kept main's old import specifier, which vite fails to resolve. Repoint it at ./settingsUtils.js; every other consumer already uses the new path. * fix(cli): close utils boundary review gaps * test(cli): cover utils boundary allow paths --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
parent
e9ccd3c67e
commit
56db17bd4c
134 changed files with 907 additions and 355 deletions
|
|
@ -221,7 +221,7 @@ function runQwen(options, prompt) {
|
|||
let idleTimedOut = false;
|
||||
let lastOutputAt = Date.now();
|
||||
// The sandbox launcher prints the container name before the container
|
||||
// starts (packages/cli/src/utils/sandbox.ts), so the FIRST match is this
|
||||
// starts (packages/cli/src/serve/sandbox.ts), so the FIRST match is this
|
||||
// run's own container — the kill-path reap below relies on that ownership.
|
||||
let sandboxName = '';
|
||||
let lineCarry = '';
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ The metafile showed eight value-import sites (type-only imports are free):
|
|||
| cli | `commands/channel/proxy.ts` | `EnvHttpProxyAgent`, `setGlobalDispatcher` |
|
||||
| cli | `utils/gitUtils.ts` | `ProxyAgent` |
|
||||
| cli | `services/setup-github.ts` | `ProxyAgent` |
|
||||
| cli | `utils/standalone-update.ts` | `fetch` |
|
||||
| cli | `ui/standalone-update.ts` | `fetch` |
|
||||
|
||||
## Design
|
||||
|
||||
|
|
|
|||
|
|
@ -419,7 +419,7 @@ not be re-plumbed through a running session.
|
|||
### Decision: Reuse the schema's `requiresRestart` flag (single source of truth)
|
||||
|
||||
`settingsSchema.ts` already declares `requiresRestart: boolean` on **every** key,
|
||||
and `packages/cli/src/utils/settingsUtils.ts` already exposes the lookups:
|
||||
and `packages/cli/src/config/settingsUtils.ts` already exposes the lookups:
|
||||
|
||||
- `requiresRestart(key: string): boolean` — flag for a dot-path key
|
||||
- `getFlattenedSchema()` — full flattened `key → definition` map
|
||||
|
|
|
|||
|
|
@ -155,6 +155,13 @@ section; the benefit is that neither document lies about its flow.
|
|||
|
||||
**Decisions** (rationale in the prose below):
|
||||
|
||||
> **Implementation note (2026-08-23):** #9146 moved the existing review
|
||||
> findings schema to `packages/cli/src/commands/review/findings.ts` and made
|
||||
> `packages/cli/src/utils/` a mechanically enforced leaf layer. The proposed
|
||||
> shared-home placement below is retained as design history, not as an
|
||||
> instruction to restore `utils/findings.ts`. A future `/audit` implementation
|
||||
> must revisit the neutral contract ownership explicitly.
|
||||
|
||||
- `/audit` is a new skill with its own SKILL.md; `/review`'s SKILL.md and
|
||||
certifying path stay untouched — no in-place target-kind branches in
|
||||
the files `/review`'s coverage gate recomputes.
|
||||
|
|
|
|||
|
|
@ -577,7 +577,7 @@ const slashCommands = await getAvailableCommands(
|
|||
|
||||
### 9.3 不变的文件
|
||||
|
||||
- `packages/cli/src/utils/commands.ts`(`parseSlashCommand` 无需修改)
|
||||
- `packages/cli/src/ui/commands/commands.ts`(`parseSlashCommand` 无需修改)
|
||||
- `packages/cli/src/ui/hooks/slashCommandProcessor.ts`(interactive 路径无需修改)
|
||||
- `packages/cli/src/ui/noninteractive/nonInteractiveUi.ts`(stub UI 无需修改)
|
||||
- 所有命令的 `action` 实现(Phase 1 不修改任何命令行为)
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ export type CommandSource =
|
|||
| ACP `argumentHint` | 已映射到 `availableCommands[].input.hint` | `acp-integration/session/Session.ts` |
|
||||
| ACP source/supportedModes/subcommands/modelInvocable | 未暴露 | `acp-integration/session/Session.ts` |
|
||||
| 冲突处理 | extension 命令冲突时已重命名为 `extensionName.commandName`,非 extension 同名为后加载覆盖前加载 | `services/CommandService.ts` |
|
||||
| `/doctor` | 已实现,支持 `interactive` / `non_interactive` / `acp` | `ui/commands/doctorCommand.ts`、`utils/doctorChecks.ts` |
|
||||
| `/doctor` | 已实现,支持 `interactive` / `non_interactive` / `acp` | `ui/commands/doctorCommand.ts`、`ui/commands/doctorChecks.ts` |
|
||||
|
||||
### 2.3 Claude Code 可借鉴点
|
||||
|
||||
|
|
@ -548,7 +548,7 @@ type AcpSubcommandMeta = {
|
|||
- 模式:`['interactive', 'non_interactive', 'acp']`
|
||||
- interactive:展示 `HistoryItemDoctor`
|
||||
- non_interactive/acp:返回 JSON `message`
|
||||
- 诊断逻辑:`packages/cli/src/utils/doctorChecks.ts`
|
||||
- 诊断逻辑:`packages/cli/src/ui/commands/doctorChecks.ts`
|
||||
|
||||
Phase 3 只需在 Help 和补全中为 `/doctor` 正确展示来源、mode;如需优化,可将 headless JSON 改为更适合人读的 Markdown,但这不是必需项。
|
||||
|
||||
|
|
|
|||
|
|
@ -766,7 +766,7 @@ For authentication-related variables (like `OPENAI_*`) and the recommended `.qwe
|
|||
| `QWEN_TELEMETRY_OUTFILE` | Sets the file path to write telemetry to. When set, overrides OTLP export. | Overrides the `telemetry.outfile` setting. |
|
||||
| `QWEN_SANDBOX` | Alternative to the `sandbox` setting in `settings.json`. | Accepts `true`, `false`, `docker`, `podman`, or a custom command string. |
|
||||
| `QWEN_SANDBOX_IMAGE` | Overrides sandbox image selection for Docker/Podman. | Takes precedence over `tools.sandboxImage`. |
|
||||
| `SEATBELT_PROFILE` | (macOS specific) Switches the Seatbelt (`sandbox-exec`) profile on macOS. | `permissive-open`: (Default) Restricts writes to the project folder (and a few other folders, see `packages/cli/src/utils/sandbox-macos-permissive-open.sb`) but allows other operations. `strict`: Uses a strict profile that declines operations by default. `<profile_name>`: Uses a custom profile. To define a custom profile, create a file named `sandbox-macos-<profile_name>.sb` in your project's `.qwen/` directory (e.g., `my-project/.qwen/sandbox-macos-custom.sb`). |
|
||||
| `SEATBELT_PROFILE` | (macOS specific) Switches the Seatbelt (`sandbox-exec`) profile on macOS. | `permissive-open`: (Default) Restricts writes to the project folder (and a few other folders, see `packages/cli/src/serve/sandbox-macos-permissive-open.sb`) but allows other operations. `strict`: Uses a strict profile that declines operations by default. `<profile_name>`: Uses a custom profile. To define a custom profile, create a file named `sandbox-macos-<profile_name>.sb` in your project's `.qwen/` directory (e.g., `my-project/.qwen/sandbox-macos-custom.sb`). |
|
||||
| `DEBUG` or `DEBUG_MODE` | (often used by underlying libraries or the CLI itself) Set to `true` or `1` to enable verbose debug logging, which can be helpful for troubleshooting. | **Note:** These variables are automatically excluded from project `.env` files by default to prevent interference with the CLI behavior. Use `.qwen/.env` files if you need to set these for Qwen Code specifically. |
|
||||
| `NO_COLOR` | Set to any value to disable all color output in the CLI. | |
|
||||
| `FORCE_HYPERLINK` | Override the OSC 8 clickable-link detection in the markdown renderer. Set to `1` (or any non-zero integer, or empty string) to force-enable; set to `0` or a non-numeric value such as `false` / `off` to force-disable. Honors `NO_COLOR` / `QWEN_DISABLE_HYPERLINKS` opt-outs above it. | Use this to opt into OSC 8 inside `tmux` / GNU `screen` (auto-detection refuses by default because the host terminal's capabilities are hidden behind the multiplexer). Requires `set -g allow-passthrough on` on tmux 3.3+. Also enables Hyper, which isn't auto-detected. |
|
||||
|
|
|
|||
192
eslint-rules/no-utils-upward-import.js
Normal file
192
eslint-rules/no-utils-upward-import.js
Normal file
|
|
@ -0,0 +1,192 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright 2026 Qwen Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import path from 'node:path';
|
||||
|
||||
/**
|
||||
* `packages/cli/src/utils/` is the leaf layer that every other directory
|
||||
* imports. It must not import back up into a domain directory (`config/`,
|
||||
* `ui/`, `i18n/`, `nonInteractive/`, `commands/`, `serve/`,
|
||||
* `acp-integration/`, ...): that is the dependency-direction invariant
|
||||
* tracked in #9146.
|
||||
*
|
||||
* The only permitted "upward" references are the type-only constructs that
|
||||
* are genuinely erased at compile time: statement-level `import type`,
|
||||
* `export type ... from`, and TS `import('...').T` type queries. Inline type
|
||||
* specifiers (`import { type X } from` / `export { type X } from`) are
|
||||
* reported instead: under this repo's `verbatimModuleSyntax`, tsc keeps the
|
||||
* declaration and emits `import {} from` / `export {} from`, a runtime edge
|
||||
* that still evaluates the target module. Everything else (value imports,
|
||||
* value re-exports, dynamic `import()`) is reported too: a literal or
|
||||
* single-segment template source is checked against its resolved path. CLI
|
||||
* baseUrl specifiers rooted at `src/` are resolved from `packages/cli/` and
|
||||
* checked the same way. A
|
||||
* computed source (a multi-segment template or a `+` concatenation) whose
|
||||
* statically known prefix is local is reported fail-closed, because
|
||||
* interpolation can contribute a `../` step no static check can rule out. A
|
||||
* computed source with no statically known local prefix is dropped, the same
|
||||
* boundary applied to package and builtin specifiers. The two remaining
|
||||
* instances (`Settings` in `modelConfigUtils.ts`, `CommandContext` in
|
||||
* `sessionPaths.ts`) are this irreducible type-level coupling.
|
||||
*/
|
||||
|
||||
const CLI_PACKAGE_MARKER = 'packages/cli/';
|
||||
const CLI_UTILS_MARKER = `${CLI_PACKAGE_MARKER}src/utils/`;
|
||||
const TEST_OR_FIXTURE_SEGMENTS = new Set(['__tests__', 'fixtures']);
|
||||
|
||||
function isCliUtilsProductionFile(filename) {
|
||||
if (!filename || filename === '<input>' || filename === '<text>') {
|
||||
return false;
|
||||
}
|
||||
const normalized = path.normalize(filename).replaceAll('\\', '/');
|
||||
const start = normalized.lastIndexOf(CLI_UTILS_MARKER);
|
||||
if (start < 0) {
|
||||
return false;
|
||||
}
|
||||
const relativePath = normalized.slice(start + CLI_UTILS_MARKER.length);
|
||||
if (/\.(test|spec)\.[cm]?[jt]sx?$/.test(relativePath)) {
|
||||
return false;
|
||||
}
|
||||
return !relativePath.split('/').some((s) => TEST_OR_FIXTURE_SEGMENTS.has(s));
|
||||
}
|
||||
|
||||
function escapesUtils(filename, importedPath) {
|
||||
const normalized = path.normalize(filename).replaceAll('\\', '/');
|
||||
const markerStart = normalized.lastIndexOf(CLI_UTILS_MARKER);
|
||||
const utilsRoot = normalized.slice(0, markerStart + CLI_UTILS_MARKER.length);
|
||||
const cliRoot = normalized.slice(0, markerStart + CLI_PACKAGE_MARKER.length);
|
||||
const resolved = path.resolve(
|
||||
importedPath.startsWith('src/') ? cliRoot : path.dirname(filename),
|
||||
importedPath,
|
||||
);
|
||||
return path
|
||||
.relative(utilsRoot, resolved)
|
||||
.replaceAll('\\', '/')
|
||||
.startsWith('..');
|
||||
}
|
||||
|
||||
/**
|
||||
* The statically known leading characters of a computed dynamic-import
|
||||
* source: the first quasi of a template literal, the string literal itself,
|
||||
* or the leftmost operand of a `+` concatenation. Anything else (a bare
|
||||
* identifier, a call, an empty first quasi) has no statically known prefix.
|
||||
*/
|
||||
function knownDynamicPrefix(node) {
|
||||
if (node.type === 'Literal') {
|
||||
return typeof node.value === 'string' ? node.value : null;
|
||||
}
|
||||
if (node.type === 'TemplateLiteral') {
|
||||
return node.quasis[0].value.cooked;
|
||||
}
|
||||
if (node.type === 'BinaryExpression' && node.operator === '+') {
|
||||
return knownDynamicPrefix(node.left);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Whether a known prefix spells a relative specifier (`./`, `../`, `.`, `..`). */
|
||||
function isRelativePrefix(prefix) {
|
||||
return (
|
||||
prefix === '.' ||
|
||||
prefix === '..' ||
|
||||
prefix.startsWith('./') ||
|
||||
prefix.startsWith('../')
|
||||
);
|
||||
}
|
||||
|
||||
function isCliBaseUrlPrefix(prefix) {
|
||||
return prefix.startsWith('src/');
|
||||
}
|
||||
|
||||
export default {
|
||||
meta: {
|
||||
type: 'problem',
|
||||
docs: {
|
||||
description:
|
||||
'packages/cli/src/utils must not import outside utils/ (leaf-layer dependency direction).',
|
||||
},
|
||||
messages: {
|
||||
noUtilsUpwardImport:
|
||||
'packages/cli/src/utils must not import outside utils/. ' +
|
||||
'Invert the dependency (pass the value in) or move the module to the ' +
|
||||
'domain directory that owns it (#9146).',
|
||||
noUtilsUnprovableDynamicImport:
|
||||
'packages/cli/src/utils cannot statically prove this computed ' +
|
||||
'dynamic import() stays inside utils/ — interpolation can ' +
|
||||
'contribute a `../` step. Resolve the target through a literal or ' +
|
||||
'single-segment template source, or pass the module in (#9146).',
|
||||
},
|
||||
},
|
||||
create(context) {
|
||||
const { filename } = context;
|
||||
if (!isCliUtilsProductionFile(filename)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const reportIfEscaping = (sourceNode, importedPath) => {
|
||||
if (
|
||||
typeof importedPath === 'string' &&
|
||||
(importedPath.startsWith('.') || isCliBaseUrlPrefix(importedPath)) &&
|
||||
escapesUtils(filename, importedPath)
|
||||
) {
|
||||
context.report({ node: sourceNode, messageId: 'noUtilsUpwardImport' });
|
||||
}
|
||||
};
|
||||
|
||||
const checkStatic = (node) => {
|
||||
// Statement-level type-only imports (`import type`, `export type ...
|
||||
// from`) are erased at compile time and cannot create a runtime cycle.
|
||||
// Inline type specifiers (`import { type X } from ...`) are NOT exempt:
|
||||
// under this repo's `verbatimModuleSyntax`, tsc keeps the declaration
|
||||
// and emits `import {} from ...` / `export {} from ...`, a runtime edge
|
||||
// that evaluates the target module — so they are reported like value
|
||||
// imports.
|
||||
if (node.importKind === 'type' || node.exportKind === 'type') {
|
||||
return;
|
||||
}
|
||||
reportIfEscaping(node.source, node.source?.value);
|
||||
};
|
||||
|
||||
const checkDynamic = (node) => {
|
||||
const { source } = node;
|
||||
if (source.type === 'Literal') {
|
||||
reportIfEscaping(source, source.value);
|
||||
return;
|
||||
}
|
||||
if (source.type === 'TemplateLiteral' && source.quasis.length === 1) {
|
||||
reportIfEscaping(source, source.quasis[0].value.cooked);
|
||||
return;
|
||||
}
|
||||
// Computed sources — multi-segment templates and `+` concatenations —
|
||||
// fail closed when their statically known prefix is relative:
|
||||
// interpolation can contribute a `../` step, so no static check can
|
||||
// prove the import stays inside utils/ (a leading `../` cannot be
|
||||
// undone by interpolation at all). A computed source with no known
|
||||
// local prefix — a bare identifier or a package-like prefix — is
|
||||
// dropped, the same boundary applied to package and builtin static
|
||||
// specifiers. CLI baseUrl sources rooted at `src/` are local too.
|
||||
const prefix = knownDynamicPrefix(source);
|
||||
if (
|
||||
typeof prefix === 'string' &&
|
||||
(isRelativePrefix(prefix) || isCliBaseUrlPrefix(prefix))
|
||||
) {
|
||||
context.report({
|
||||
node: source,
|
||||
messageId: 'noUtilsUnprovableDynamicImport',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
ImportDeclaration: checkStatic,
|
||||
ExportNamedDeclaration: checkStatic,
|
||||
ExportAllDeclaration: checkStatic,
|
||||
ImportExpression: checkDynamic,
|
||||
// TSImportType (`import('../config/x').T`) is type-only by definition, so
|
||||
// it is intentionally not reported.
|
||||
};
|
||||
},
|
||||
};
|
||||
|
|
@ -16,6 +16,7 @@ import globals from 'globals';
|
|||
import storybook from 'eslint-plugin-storybook';
|
||||
import checkFile from 'eslint-plugin-check-file';
|
||||
import noCoreRootBarrelImport from './eslint-rules/no-core-root-barrel-import.js';
|
||||
import noUtilsUpwardImport from './eslint-rules/no-utils-upward-import.js';
|
||||
import { legacyFilenames } from './eslint.legacy-filenames.mjs';
|
||||
|
||||
// General syntax restrictions applied to every TS/TSX source file. Hoisted so
|
||||
|
|
@ -109,24 +110,20 @@ export default tseslint.config(
|
|||
},
|
||||
},
|
||||
{
|
||||
// `utils/` is the layer every other directory imports, so it must not
|
||||
// import back into one. The daemon direction is clean and enforced here;
|
||||
// the remaining `ui/`, `config/`, `i18n/` and `nonInteractive/` edges are
|
||||
// tracked in #9146 and will be added to this group as they are resolved.
|
||||
// `utils/` is the leaf layer that every other directory imports, so it
|
||||
// must not import back up into a domain directory. Type-only imports are
|
||||
// exempt: they are erased at compile time and cannot create a runtime
|
||||
// cycle. See #9146.
|
||||
files: ['packages/cli/src/utils/**/*.{ts,tsx}'],
|
||||
rules: {
|
||||
'no-restricted-imports': [
|
||||
'error',
|
||||
{
|
||||
patterns: [
|
||||
{
|
||||
group: ['**/serve/*', '**/serve/**'],
|
||||
message:
|
||||
'packages/cli/src/utils must not import serve/. Move lifecycle-free logic down into utils/ instead (#9146).',
|
||||
},
|
||||
],
|
||||
plugins: {
|
||||
architecture: {
|
||||
rules: {
|
||||
'no-utils-upward-import': noUtilsUpwardImport,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
'architecture/no-utils-upward-import': 'error',
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ const WINDOWS_ABSOLUTE_PATH_RE = /^([A-Za-z]):[\\/](.*)$/;
|
|||
/**
|
||||
* Maps a Windows-shaped absolute path to the container mount produced by the
|
||||
* host-side sandbox launcher (`C:\work\proj` → `/c/work/proj`, mirroring
|
||||
* `getContainerPath` in `cli/src/utils/sandbox.ts`).
|
||||
* `getContainerPath` in `cli/src/serve/sandbox.ts`).
|
||||
*
|
||||
* A Windows host relaunching `qwen serve` into a Linux Docker/Podman sandbox
|
||||
* translates the bind mount and `--workdir`, but path-valued CLI arguments
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ const { mockMcpPoolDrainAll } = vi.hoisted(() => ({
|
|||
vi.mock('../utils/cleanup.js', () => ({
|
||||
runExitCleanup: mockRunExitCleanup,
|
||||
}));
|
||||
vi.mock('../utils/housekeeping/scheduler.js', () => ({
|
||||
vi.mock('../services/housekeeping/scheduler.js', () => ({
|
||||
startNonInteractiveOpenAILogHousekeeping:
|
||||
mockStartNonInteractiveOpenAILogHousekeeping,
|
||||
}));
|
||||
|
|
@ -886,7 +886,7 @@ vi.mock('./session/Session.js', () => {
|
|||
}),
|
||||
};
|
||||
});
|
||||
vi.mock('../utils/languageUtils.js', () => ({
|
||||
vi.mock('../i18n/languageUtils.js', () => ({
|
||||
updateOutputLanguageFile: vi.fn(),
|
||||
writeOutputLanguageAndRegisterPath: vi.fn(
|
||||
(
|
||||
|
|
@ -1011,7 +1011,7 @@ import {
|
|||
resolveOutputLanguageOrPreserveAuto,
|
||||
updateOutputLanguageFile,
|
||||
writeOutputLanguageAndRegisterPath,
|
||||
} from '../utils/languageUtils.js';
|
||||
} from '../i18n/languageUtils.js';
|
||||
import { buildAuthMethods } from './authMethods.js';
|
||||
import {
|
||||
ACTIVE_WORK_HEARTBEAT_META_KEY,
|
||||
|
|
|
|||
|
|
@ -187,7 +187,7 @@ import {
|
|||
ACP_EVENT_LOOP_STALL_RESTART_MS,
|
||||
CHANNEL_PROMPT_META_KEY,
|
||||
} from '@qwen-code/channel-base';
|
||||
import { observeAcpToolResultWire } from '../utils/tool-result-boundary-diagnostics.js';
|
||||
import { observeAcpToolResultWire } from '../nonInteractive/tool-result-boundary-diagnostics.js';
|
||||
import { Readable, Writable } from 'node:stream';
|
||||
import { normalizeDisabledToolList } from '../config/normalizeDisabledTools.js';
|
||||
import { pipeline } from 'node:stream/promises';
|
||||
|
|
@ -279,11 +279,11 @@ import {
|
|||
resolveOutputLanguageOrPreserveAuto,
|
||||
getOutputLanguageFilePath,
|
||||
writeOutputLanguageAndRegisterPath,
|
||||
} from '../utils/languageUtils.js';
|
||||
} from '../i18n/languageUtils.js';
|
||||
import { runWithAcpRuntimeOutputDir } from './runtimeOutputDirContext.js';
|
||||
import { ACP_ERROR_CODES } from './errorCodes.js';
|
||||
import { runExitCleanup } from '../utils/cleanup.js';
|
||||
import { startNonInteractiveOpenAILogHousekeeping } from '../utils/housekeeping/scheduler.js';
|
||||
import { startNonInteractiveOpenAILogHousekeeping } from '../services/housekeeping/scheduler.js';
|
||||
import { appEvents, AppEvent } from '../utils/events.js';
|
||||
import {
|
||||
setLanguageAsync,
|
||||
|
|
|
|||
|
|
@ -270,10 +270,8 @@ import type {
|
|||
AgentSideConnection,
|
||||
} from '@agentclientprotocol/sdk';
|
||||
import { SettingScope, type LoadedSettings } from '../../config/settings.js';
|
||||
import {
|
||||
insertAfterFunctionResponses,
|
||||
normalizePartList,
|
||||
} from '../../utils/nonInteractiveHelpers.js';
|
||||
import { insertAfterFunctionResponses } from '../../nonInteractive/nonInteractiveHelpers.js';
|
||||
import { normalizePartList } from '../../utils/normalize-part-list.js';
|
||||
import { prefixMidTurnUserMessageParts } from '../../utils/midTurnUserMessage.js';
|
||||
import {
|
||||
handleSlashCommand,
|
||||
|
|
@ -322,7 +320,7 @@ import type {
|
|||
} from './types.js';
|
||||
import { HistoryReplayer } from './history-replayer.js';
|
||||
import { projectAcpToolResultUpdate } from './acp-tool-result-text-projection.js';
|
||||
import { observeAcpToolResultProjection } from '../../utils/tool-result-boundary-diagnostics.js';
|
||||
import { observeAcpToolResultProjection } from '../../nonInteractive/tool-result-boundary-diagnostics.js';
|
||||
import { ToolCallEmitter } from './emitters/tool-call-emitter.js';
|
||||
import { ToolCallPreparationTracker } from './tool-call-preparation-tracker.js';
|
||||
import { PlanEmitter } from './emitters/PlanEmitter.js';
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ import {
|
|||
createTranscriptToolCallStartUpdate,
|
||||
} from '@qwen-code/acp-bridge/transcriptReplay';
|
||||
import { sanitizeTerminalText } from '../../../ui/utils/textUtils.js';
|
||||
import { associateAcpToolResultArtifact } from '../../../utils/tool-result-boundary-diagnostics.js';
|
||||
import { associateAcpToolResultArtifact } from '../../../nonInteractive/tool-result-boundary-diagnostics.js';
|
||||
|
||||
const KIND_MAP: Record<Kind, ToolKind> = {
|
||||
[Kind.Read]: 'read',
|
||||
|
|
|
|||
|
|
@ -28,10 +28,10 @@ import {
|
|||
|
||||
const observeAcpProjectionMock = vi.hoisted(() => vi.fn());
|
||||
vi.mock(
|
||||
'../../utils/tool-result-boundary-diagnostics.js',
|
||||
'../../nonInteractive/tool-result-boundary-diagnostics.js',
|
||||
async (original) => ({
|
||||
...(await original<
|
||||
typeof import('../../utils/tool-result-boundary-diagnostics.js')
|
||||
typeof import('../../nonInteractive/tool-result-boundary-diagnostics.js')
|
||||
>()),
|
||||
observeAcpToolResultProjection: observeAcpProjectionMock,
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import type { SessionUpdate } from '@agentclientprotocol/sdk';
|
|||
import type { TranscriptReplayStateV1 } from '@qwen-code/acp-bridge/transcriptReplay';
|
||||
import { Buffer } from 'node:buffer';
|
||||
import { projectAcpToolResultUpdate } from './acp-tool-result-text-projection.js';
|
||||
import { observeAcpToolResultProjection } from '../../utils/tool-result-boundary-diagnostics.js';
|
||||
import { observeAcpToolResultProjection } from '../../nonInteractive/tool-result-boundary-diagnostics.js';
|
||||
import { HistoryReplayer } from './history-replayer.js';
|
||||
import type { PendingReplayToolCall } from './history-replayer.js';
|
||||
import type { CumulativeUsage, SessionEmitterContext } from './types.js';
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import type { Argv, CommandModule } from 'yargs';
|
|||
import { parseArgsCommand } from './review/parse-args.js';
|
||||
import { matchRemoteCommand } from './review/match-remote.js';
|
||||
import { composeReviewCommand } from './review/compose-review.js';
|
||||
import { findingsCommand } from '../utils/findings.js';
|
||||
import { findingsCommand } from './review/findings.js';
|
||||
import { recoverFindingsCommand } from './review/recover-findings.js';
|
||||
import { fetchPrCommand } from './review/fetch-pr.js';
|
||||
import { captureLocalCommand } from './review/capture-local.js';
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ import {
|
|||
SOURCES,
|
||||
type Severity,
|
||||
type Source,
|
||||
} from '../../utils/findings.js';
|
||||
} from './findings.js';
|
||||
import { BRIEFS } from './lib/agent-briefs.js';
|
||||
import {
|
||||
budgetStopDisclosure,
|
||||
|
|
|
|||
|
|
@ -42,9 +42,9 @@ import {
|
|||
} from 'node:fs';
|
||||
import type { Stats } from 'node:fs';
|
||||
import { dirname, resolve, sep } from 'node:path';
|
||||
import { writeStdoutLine, writeStderrLine } from './stdioHelpers.js';
|
||||
import type { AnchorRequest } from '../commands/review/lib/anchors.js';
|
||||
import { isSameFile } from '../commands/review/lib/same-file.js';
|
||||
import { writeStdoutLine, writeStderrLine } from '../../utils/stdioHelpers.js';
|
||||
import type { AnchorRequest } from './lib/anchors.js';
|
||||
import { isSameFile } from './lib/same-file.js';
|
||||
|
||||
// These four lists have a second consumer: the Web Shell review renderer
|
||||
// (packages/web-shell/client/components/artifacts/CodeReviewArtifactDetail.tsx)
|
||||
|
|
@ -112,7 +112,7 @@ describe('openedBrief / readBrief', () => {
|
|||
const arg = `{"absolute_path":${needle}}`;
|
||||
|
||||
it('does not credit a shell command that merely MENTIONS the brief', () => {
|
||||
// The trap a prose matcher walks into: `utils/findings.ts` has a
|
||||
// The trap a prose matcher walks into: `findings.ts` has a
|
||||
// same-purpose-looking `namesPath` that matches on a name boundary, and
|
||||
// it credits this arg. Deleting a brief is not opening it — so this atom
|
||||
// matches the whole JSON string value instead, and keeps a different
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ export function declaresOwnUncoverable(
|
|||
* not one per half. This wrapper only spreads it over a record's call list;
|
||||
* every path atom below routes through it.
|
||||
*
|
||||
* The name is deliberately not `namesPath`: `utils/findings.ts` has a
|
||||
* The name is deliberately not `namesPath`: `findings.ts` has a
|
||||
* module-private `namesPath` that matches a path named in PROSE on a name
|
||||
* boundary — it credits `rm /plan/chunk-3.brief.md` for naming the brief.
|
||||
* Unifying these two would make `openedBrief` credit an agent for deleting a
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ vi.mock('../../../config/settings.js', async (importOriginal) => {
|
|||
return { ...actual, loadSettings: loadSettingsMock };
|
||||
});
|
||||
import { operatorReviewSettings } from './review-settings.js';
|
||||
import { getDialogSettingKeys } from '../../../utils/settingsUtils.js';
|
||||
import { getDialogSettingKeys } from '../../../config/settingsUtils.js';
|
||||
|
||||
function setReview(review: unknown): void {
|
||||
loadSettingsMock.mockReturnValue({ merged: { review } });
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
* plain `'…'` wrap traded that for breaking on the first embedded apostrophe
|
||||
* (`~/Documents/John's Projects/…` is an ordinary macOS workspace). The
|
||||
* `'\''` dance closes both: end the quote, emit a literal `'`, reopen.
|
||||
* Same pattern as `shellQuoteForSh` in utils/standalone-update.ts.
|
||||
* Same pattern as `shellQuoteForSh` in ui/standalone-update.ts.
|
||||
*/
|
||||
export function shellQuotePath(p: string): string {
|
||||
return `'${p.replace(/'/g, "'\\''")}'`;
|
||||
|
|
|
|||
|
|
@ -477,9 +477,12 @@ describe('the bundled skill stops on what this module prints', () => {
|
|||
const services = join(root, 'packages', 'cli', 'src', 'services');
|
||||
mkdirSync(services, { recursive: true });
|
||||
writeFileSync(join(services, 'review-worktree-lease.ts'), 'leases');
|
||||
// No `findings.ts` under `utils/`: it moved back under
|
||||
// `commands/review/` (#9146), which the directory root already covers
|
||||
// via `drive.ts`. No root digests `utils/` wholesale, so the materialized
|
||||
// tree mirrors `reviewSourceRoots` exactly.
|
||||
const utils = join(root, 'packages', 'cli', 'src', 'utils');
|
||||
mkdirSync(utils, { recursive: true });
|
||||
writeFileSync(join(utils, 'findings.ts'), 'validates');
|
||||
writeFileSync(join(utils, 'shell-args.ts'), 'tokenizes');
|
||||
writeFileSync(join(utils, 'paths.ts'), 'flattens');
|
||||
const skillDir = join(
|
||||
|
|
@ -586,12 +589,9 @@ describe('reviewSourceRoots', () => {
|
|||
),
|
||||
kind: 'code',
|
||||
},
|
||||
// The review helpers lifted out of `commands/review/`; the digest
|
||||
// covered them there before the lift.
|
||||
{
|
||||
path: join('/w', 'packages', 'cli', 'src', 'utils', 'findings.ts'),
|
||||
kind: 'code',
|
||||
},
|
||||
// The helpers of the findings validator live in `utils/`, outside the
|
||||
// `review/` directory root; the validator itself lives back under
|
||||
// `commands/review/` (#9146), which the directory root covers.
|
||||
{
|
||||
path: join('/w', 'packages', 'cli', 'src', 'utils', 'shell-args.ts'),
|
||||
kind: 'code',
|
||||
|
|
|
|||
|
|
@ -30,8 +30,9 @@
|
|||
//
|
||||
// SCOPE, so silence is not read as more than it is: the roots are the review
|
||||
// commands, the file that registers them, the review-only lease they import
|
||||
// from `services/`, the three review helpers lifted into `utils/`, and the
|
||||
// bundled skill — not the modules those import. Editing
|
||||
// from `services/`, the two review helpers left in `utils/`, and the bundled
|
||||
// skill — not the modules those import. The validator itself is back under
|
||||
// `commands/review/`, which the directory root covers. Editing
|
||||
// `utils/stdioHelpers.ts` or a core helper on a review path and skipping the
|
||||
// rebuild produces no warning. The line drawn here is the code
|
||||
// whose behaviour a review is about; a quiet run means that code matches the
|
||||
|
|
@ -361,14 +362,11 @@ export function reviewSourceRoots(repoRoot: string): ReviewSourceRoot[] {
|
|||
),
|
||||
kind: 'code',
|
||||
},
|
||||
// The findings validator and its two helpers were lifted out of
|
||||
// `commands/review/` into `utils/`; the digest covered them there, and a
|
||||
// root list that lost them would keep both digest copies equal while a
|
||||
// skipped rebuild silently runs the bundle's old validator.
|
||||
{
|
||||
path: join(repoRoot, 'packages', 'cli', 'src', 'utils', 'findings.ts'),
|
||||
kind: 'code',
|
||||
},
|
||||
// The two helpers of the findings validator live in `utils/`, outside
|
||||
// the `review/` directory root; a root list that lost them would keep
|
||||
// both digest copies equal while a skipped rebuild silently runs the
|
||||
// bundle's old validator. The validator itself moved back into
|
||||
// `commands/review/` (#9146), which the directory root covers.
|
||||
{
|
||||
path: join(repoRoot, 'packages', 'cli', 'src', 'utils', 'shell-args.ts'),
|
||||
kind: 'code',
|
||||
|
|
|
|||
|
|
@ -827,7 +827,7 @@ describe('serializedArgsNamePath — the one needle both halves use', () => {
|
|||
|
||||
it('does not credit a shell command that merely mentions the path', () => {
|
||||
// The divergence the review measured between this and the prose-boundary
|
||||
// `namesPath` in `utils/findings.ts`, which returns true here. Both the
|
||||
// `namesPath` in `findings.ts`, which returns true here. Both the
|
||||
// diff-read half and the brief atoms route through THIS one, so the
|
||||
// certification bar cannot credit `rm <file>` as opening it.
|
||||
expect(
|
||||
|
|
|
|||
|
|
@ -264,7 +264,7 @@ function rangeOf(args: Record<string, unknown>): [number, number] | null {
|
|||
* (normalisation, escaping, a stricter compare) reaches the whole bar at once
|
||||
* rather than half of it.
|
||||
*
|
||||
* NOT `namesPath` in `utils/findings.ts`: that one matches a path mentioned in
|
||||
* NOT `namesPath` in `findings.ts`: that one matches a path mentioned in
|
||||
* PROSE on a name boundary, so it credits `rm /plan/chunk-3.brief.md` for
|
||||
* naming the brief. Crediting an agent for deleting a file it never opened is
|
||||
* precisely what this predicate must not do, which is why the two keep
|
||||
|
|
|
|||
|
|
@ -51,11 +51,7 @@ import {
|
|||
type AssetsManifest,
|
||||
type PublishedAsset,
|
||||
} from './lib/assets.js';
|
||||
import {
|
||||
validateFindings,
|
||||
buildReport,
|
||||
type Finding,
|
||||
} from '../../utils/findings.js';
|
||||
import { validateFindings, buildReport, type Finding } from './findings.js';
|
||||
|
||||
interface PublishAssetsArgs {
|
||||
pr: number;
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ import { tmpdir } from 'node:os';
|
|||
import { join } from 'node:path';
|
||||
import yargs from 'yargs';
|
||||
import type { Argv } from 'yargs';
|
||||
import { buildReport, type Finding } from '../../utils/findings.js';
|
||||
import { buildReport, type Finding } from './findings.js';
|
||||
import { saveArtifactCommand, saveReviewArtifact } from './save-artifact.js';
|
||||
|
||||
// On a case-sensitive filesystem the alias below never exists, so that test
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ import {
|
|||
buildReport,
|
||||
type FindingsReport,
|
||||
validateFindings,
|
||||
} from '../../utils/findings.js';
|
||||
} from './findings.js';
|
||||
import { EFFORT_LEVELS, type ReviewEffort } from './parse-args.js';
|
||||
import { REVIEWS_DIR } from './lib/paths.js';
|
||||
import { isSameFile } from './lib/same-file.js';
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ vi.mock('../utils/installationInfo.js', () => ({
|
|||
getInstallationInfo,
|
||||
resolveUpdateCommand,
|
||||
}));
|
||||
vi.mock('../utils/standalone-update.js', () => ({ performStandaloneUpdate }));
|
||||
vi.mock('../ui/standalone-update.js', () => ({ performStandaloneUpdate }));
|
||||
vi.mock('../utils/package.js', () => ({ getPackageJson }));
|
||||
vi.mock('../utils/stdioHelpers.js', () => ({
|
||||
writeStdoutLine,
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ export const updateCommand: CommandModule = {
|
|||
import('../config/settings.js'),
|
||||
import('../ui/utils/updateCheck.js'),
|
||||
import('../utils/installationInfo.js'),
|
||||
import('../utils/standalone-update.js'),
|
||||
import('../ui/standalone-update.js'),
|
||||
import('../utils/stdioHelpers.js'),
|
||||
import('../utils/updateEventEmitter.js'),
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@
|
|||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import type { LoadedSettings } from '../config/settings.js';
|
||||
import { SettingScope } from '../config/settings.js';
|
||||
import type { LoadedSettings } from './settings.js';
|
||||
import { SettingScope } from './settings.js';
|
||||
import { settingExistsInScope } from './settingsUtils.js';
|
||||
|
||||
/**
|
||||
|
|
@ -10,9 +10,8 @@ import { SettingScope } from './settings.js';
|
|||
|
||||
// settingsUtils makes real fs calls in backup/restore — stub them out so the
|
||||
// tests can focus on adapter behavior without touching disk.
|
||||
vi.mock('../utils/settingsUtils.js', async (importOriginal) => {
|
||||
const actual =
|
||||
await importOriginal<typeof import('../utils/settingsUtils.js')>();
|
||||
vi.mock('./settingsUtils.js', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('./settingsUtils.js')>();
|
||||
return {
|
||||
...actual,
|
||||
backupSettingsFile: vi.fn(),
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ import {
|
|||
cleanupSettingsBackup,
|
||||
restoreSettingsFromBackup,
|
||||
getNestedProperty,
|
||||
} from '../utils/settingsUtils.js';
|
||||
} from './settingsUtils.js';
|
||||
|
||||
export function createLoadedSettingsAdapter(
|
||||
settings: LoadedSettings,
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import {
|
|||
V1_TO_V2_PRESERVE_DISABLE_MAP,
|
||||
V2_CONTAINER_KEYS,
|
||||
} from './v1-to-v2-shared.js';
|
||||
import { setNestedPropertySafe } from '../../../utils/settingsUtils.js';
|
||||
import { setNestedPropertySafe } from '../../settingsUtils.js';
|
||||
|
||||
/**
|
||||
* Heuristic indicators for deciding whether an object is "V1-like".
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import {
|
|||
deleteNestedPropertySafe,
|
||||
getNestedProperty,
|
||||
setNestedPropertySafe,
|
||||
} from '../../../utils/settingsUtils.js';
|
||||
} from '../../settingsUtils.js';
|
||||
|
||||
/**
|
||||
* Path mapping for boolean polarity migration (V2 disable* -> V3 enable*).
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import type { SettingsMigration } from '../types.js';
|
|||
import {
|
||||
getNestedProperty,
|
||||
setNestedPropertySafe,
|
||||
} from '../../../utils/settingsUtils.js';
|
||||
} from '../../settingsUtils.js';
|
||||
|
||||
const GIT_CO_AUTHOR_PATH = 'general.gitCoAuthor';
|
||||
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ import {
|
|||
import {
|
||||
WORKSPACE_RESTRICTED_SETTINGS,
|
||||
WORKSPACE_RESTRICTED_SETTING_KEYS,
|
||||
} from '../utils/settingsUtils.js';
|
||||
} from './settingsUtils.js';
|
||||
import { needsMigration } from './migration/index.js';
|
||||
import { QWEN_DIR } from '@qwen-code/qwen-code-core';
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@ import { hasOwnModelProviders } from './modelProvidersScope.js';
|
|||
import {
|
||||
type Settings,
|
||||
type MemoryImportFormat,
|
||||
type MergeStrategy,
|
||||
type SettingsSchema,
|
||||
type SettingDefinition,
|
||||
getSettingsSchema,
|
||||
|
|
@ -34,8 +33,8 @@ import { resolveEnvVarsInObject } from '../utils/envVarResolver.js';
|
|||
import {
|
||||
setNestedPropertySafe,
|
||||
WORKSPACE_RESTRICTED_SETTINGS,
|
||||
} from '../utils/settingsUtils.js';
|
||||
import { customDeepMerge } from '../utils/deepMerge.js';
|
||||
} from './settingsUtils.js';
|
||||
import { customDeepMerge, type MergeStrategy } from '../utils/deepMerge.js';
|
||||
import { updateSettingsFilePreservingFormat } from '../utils/jsonc-editor.js';
|
||||
import { runMigrations, needsMigration } from './migration/index.js';
|
||||
import {
|
||||
|
|
|
|||
|
|
@ -12,11 +12,11 @@ import {
|
|||
} from '@qwen-code/qwen-code-core';
|
||||
import {
|
||||
getSettingsSchema,
|
||||
MergeStrategy,
|
||||
type SettingDefinition,
|
||||
type Settings,
|
||||
type SettingsSchema,
|
||||
} from './settingsSchema.js';
|
||||
import { MergeStrategy } from '../utils/deepMerge.js';
|
||||
import {
|
||||
MAX_CONCURRENT_SUB_SESSIONS_PER_CALLER,
|
||||
MAX_CONCURRENT_SUB_SESSIONS_TOTAL,
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import {
|
|||
} from '@qwen-code/qwen-code-core';
|
||||
import type { CustomTheme } from '../ui/themes/theme.js';
|
||||
import { getLanguageSettingsOptions } from '../i18n/languages.js';
|
||||
import { MergeStrategy } from '../utils/deepMerge.js';
|
||||
|
||||
export const DEFAULT_OPENAI_LOG_RETENTION_DAYS = 7;
|
||||
|
||||
|
|
@ -64,17 +65,6 @@ export interface SettingEnumOption {
|
|||
label: string;
|
||||
}
|
||||
|
||||
export enum MergeStrategy {
|
||||
// Replace the old value with the new value. This is the default.
|
||||
REPLACE = 'replace',
|
||||
// Concatenate arrays.
|
||||
CONCAT = 'concat',
|
||||
// Merge arrays, ensuring unique values.
|
||||
UNION = 'union',
|
||||
// Shallow merge objects.
|
||||
SHALLOW_MERGE = 'shallow_merge',
|
||||
}
|
||||
|
||||
export interface SettingDefinition {
|
||||
type: SettingsType;
|
||||
label: string;
|
||||
|
|
|
|||
|
|
@ -32,11 +32,10 @@ import {
|
|||
type Settings,
|
||||
type SettingsSchema,
|
||||
type SettingsSchemaType,
|
||||
} from '../config/settingsSchema.js';
|
||||
} from './settingsSchema.js';
|
||||
|
||||
vi.mock('../config/settingsSchema.js', async (importOriginal) => {
|
||||
const original =
|
||||
await importOriginal<typeof import('../config/settingsSchema.js')>();
|
||||
vi.mock('./settingsSchema.js', async (importOriginal) => {
|
||||
const original = await importOriginal<typeof import('./settingsSchema.js')>();
|
||||
return {
|
||||
...original,
|
||||
getSettingsSchema: vi.fn(),
|
||||
|
|
@ -5,19 +5,15 @@
|
|||
*/
|
||||
|
||||
import * as fs from 'node:fs';
|
||||
import type {
|
||||
Settings,
|
||||
SettingScope,
|
||||
LoadedSettings,
|
||||
} from '../config/settings.js';
|
||||
import type { Settings, SettingScope, LoadedSettings } from './settings.js';
|
||||
import type {
|
||||
SettingDefinition,
|
||||
SettingsSchema,
|
||||
SettingsValue,
|
||||
} from '../config/settingsSchema.js';
|
||||
import { getSettingsSchema } from '../config/settingsSchema.js';
|
||||
} from './settingsSchema.js';
|
||||
import { getSettingsSchema } from './settingsSchema.js';
|
||||
import { t } from '../i18n/index.js';
|
||||
import { isAutoLanguage } from './languageUtils.js';
|
||||
import { isAutoLanguage } from '../i18n/languageUtils.js';
|
||||
|
||||
// The schema is now nested, but many parts of the UI and logic work better
|
||||
// with a flattened structure and dot-notation keys. This section flattens the
|
||||
|
|
@ -9,7 +9,7 @@ import * as path from 'node:path';
|
|||
import { watch as watchFs, type FSWatcher } from 'chokidar';
|
||||
import { createDebugLogger } from '@qwen-code/qwen-code-core';
|
||||
import { type LoadedSettings, SettingScope } from './settings.js';
|
||||
import { getFlattenedSchema } from '../utils/settingsUtils.js';
|
||||
import { getFlattenedSchema } from './settingsUtils.js';
|
||||
|
||||
const debugLogger = createDebugLogger('SETTINGS_WATCHER');
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import type { PermissionSuggestion } from '../nonInteractive/types.js';
|
|||
import { createDebugLogger } from '@qwen-code/qwen-code-core';
|
||||
import type { Part } from '@google/genai';
|
||||
import { StreamJsonOutputAdapter } from '../nonInteractive/io/index.js';
|
||||
import { reportChatRecordingFailureToAdapter } from '../utils/chat-recording-failure.js';
|
||||
import { reportChatRecordingFailureToAdapter } from '../nonInteractive/chat-recording-failure.js';
|
||||
|
||||
const debugLogger = createDebugLogger('DUAL_OUTPUT');
|
||||
|
||||
|
|
|
|||
|
|
@ -169,7 +169,7 @@ vi.mock('./utils/events.js', async (importOriginal) => {
|
|||
};
|
||||
});
|
||||
|
||||
vi.mock('./utils/sandbox.js', () => ({
|
||||
vi.mock('./serve/sandbox.js', () => ({
|
||||
sandbox_command: vi.fn(() => ''), // Default to no sandbox command
|
||||
start_sandbox: vi.fn(() => Promise.resolve()), // Mock as an async function that resolves
|
||||
}));
|
||||
|
|
@ -206,7 +206,7 @@ vi.mock('./startup/startup-prefetch.js', () => ({
|
|||
mockStartPostRenderPrefetches(...args),
|
||||
}));
|
||||
|
||||
vi.mock('./utils/update-relaunch.js', () => ({
|
||||
vi.mock('./ui/update-relaunch.js', () => ({
|
||||
updateBeforeRelaunch: (...args: unknown[]) =>
|
||||
mockUpdateBeforeRelaunch(...args),
|
||||
}));
|
||||
|
|
@ -219,9 +219,11 @@ vi.mock('./acp-integration/acpAgent.js', () => ({
|
|||
runAcpAgent: (...args: unknown[]) => mockRunAcpAgent(...args),
|
||||
}));
|
||||
|
||||
vi.mock('./utils/housekeeping/scheduler.js', async (importOriginal) => {
|
||||
vi.mock('./services/housekeeping/scheduler.js', async (importOriginal) => {
|
||||
const actual =
|
||||
await importOriginal<typeof import('./utils/housekeeping/scheduler.js')>();
|
||||
await importOriginal<
|
||||
typeof import('./services/housekeeping/scheduler.js')
|
||||
>();
|
||||
return {
|
||||
...actual,
|
||||
startNonInteractiveOpenAILogHousekeeping: (...args: unknown[]) =>
|
||||
|
|
@ -1232,7 +1234,7 @@ describe('gemini.tsx main function', () => {
|
|||
);
|
||||
const { loadSettings } = await import('./config/settings.js');
|
||||
const { loadSandboxConfig } = await import('./config/sandboxConfig.js');
|
||||
const { start_sandbox } = await import('./utils/sandbox.js');
|
||||
const { start_sandbox } = await import('./serve/sandbox.js');
|
||||
const { relaunchOnExitCode } = await import('./utils/relaunch.js');
|
||||
|
||||
vi.mocked(start_sandbox).mockClear();
|
||||
|
|
|
|||
|
|
@ -87,14 +87,14 @@ import {
|
|||
relaunchAppInChildProcess,
|
||||
relaunchOnExitCode,
|
||||
} from './utils/relaunch.js';
|
||||
import { start_sandbox } from './utils/sandbox.js';
|
||||
import { start_sandbox } from './serve/sandbox.js';
|
||||
import { getStartupWarnings } from './utils/startupWarnings.js';
|
||||
import { getUserStartupWarnings } from './utils/userStartupWarnings.js';
|
||||
import { initializeWarningHandler } from './utils/warningHandler.js';
|
||||
import { writeStderrLine, writeStderrLineSafe } from './utils/stdioHelpers.js';
|
||||
import { sanitizeTerminalText } from './ui/utils/textUtils.js';
|
||||
import { getHeadlessYoloSafetyWarning } from './utils/headlessSafetyWarnings.js';
|
||||
import { initializeLlmOutputLanguage } from './utils/languageUtils.js';
|
||||
import { initializeLlmOutputLanguage } from './i18n/languageUtils.js';
|
||||
import {
|
||||
CUSTOM_SANDBOX_IMAGE_ENV_VAR,
|
||||
HOST_UPDATE_RELAUNCH_ENV_VAR,
|
||||
|
|
@ -518,9 +518,7 @@ export async function main() {
|
|||
await initializeI18n(
|
||||
resolveLanguageSetting(settings.merged.general?.language as string),
|
||||
);
|
||||
const { updateBeforeRelaunch } = await import(
|
||||
'./utils/update-relaunch.js'
|
||||
);
|
||||
const { updateBeforeRelaunch } = await import('./ui/update-relaunch.js');
|
||||
const shouldRelaunch = await updateBeforeRelaunch(
|
||||
settings,
|
||||
updateProjectRoot,
|
||||
|
|
@ -873,7 +871,7 @@ export async function main() {
|
|||
|
||||
const nonInteractiveHousekeeping =
|
||||
!config.isInteractive() || config.getExperimentalZedIntegration()
|
||||
? await import('./utils/housekeeping/scheduler.js')
|
||||
? await import('./services/housekeeping/scheduler.js')
|
||||
: undefined;
|
||||
if (nonInteractiveHousekeeping) {
|
||||
registerCleanup(() =>
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ vi.mock('node:fs', () => ({
|
|||
}));
|
||||
|
||||
// Mock i18n module
|
||||
vi.mock('../i18n/index.js', () => ({
|
||||
vi.mock('./index.js', () => ({
|
||||
detectSystemLanguage: vi.fn(),
|
||||
getLanguageNameFromLocale: vi.fn((locale: string) => {
|
||||
const map: Record<string, string> = {
|
||||
|
|
@ -42,7 +42,7 @@ vi.mock('@qwen-code/qwen-code-core', () => ({
|
|||
},
|
||||
}));
|
||||
|
||||
import * as i18n from '../i18n/index.js';
|
||||
import * as i18n from './index.js';
|
||||
import {
|
||||
OUTPUT_LANGUAGE_AUTO,
|
||||
isAutoLanguage,
|
||||
|
|
@ -13,8 +13,8 @@
|
|||
import * as fs from 'node:fs';
|
||||
import * as path from 'node:path';
|
||||
import { Storage } from '@qwen-code/qwen-code-core';
|
||||
import { getLanguageNameFromLocale } from '../i18n/index.js';
|
||||
import { SUPPORTED_LANGUAGES } from '../i18n/languages.js';
|
||||
import { getLanguageNameFromLocale } from './index.js';
|
||||
import { SUPPORTED_LANGUAGES } from './languages.js';
|
||||
|
||||
const LLM_OUTPUT_LANGUAGE_RULE_FILENAME = 'output-language.md';
|
||||
const LLM_OUTPUT_LANGUAGE_MARKER_PREFIX = 'qwen-code:llm-output-language:';
|
||||
|
|
@ -11,7 +11,7 @@ import {
|
|||
type ChatRecordingFailureListener,
|
||||
type Config,
|
||||
} from '@qwen-code/qwen-code-core';
|
||||
import type { JsonOutputAdapterInterface } from '../nonInteractive/io/BaseJsonOutputAdapter.js';
|
||||
import type { JsonOutputAdapterInterface } from './io/BaseJsonOutputAdapter.js';
|
||||
import {
|
||||
createChatRecordingFailureSystemMessage,
|
||||
settleChatRecording,
|
||||
|
|
@ -22,7 +22,7 @@ const { mockWriteStderrLine } = vi.hoisted(() => ({
|
|||
mockWriteStderrLine: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('./stdioHelpers.js', () => ({
|
||||
vi.mock('../utils/stdioHelpers.js', () => ({
|
||||
writeStderrLine: mockWriteStderrLine,
|
||||
}));
|
||||
|
||||
|
|
@ -11,10 +11,10 @@ import {
|
|||
type ChatRecordingFailureEvent,
|
||||
type Config,
|
||||
} from '@qwen-code/qwen-code-core';
|
||||
import type { JsonOutputAdapterInterface } from '../nonInteractive/io/BaseJsonOutputAdapter.js';
|
||||
import type { CLISystemMessage } from '../nonInteractive/types.js';
|
||||
import type { JsonOutputAdapterInterface } from './io/BaseJsonOutputAdapter.js';
|
||||
import type { CLISystemMessage } from './types.js';
|
||||
import { t } from '../i18n/index.js';
|
||||
import { writeStderrLine } from './stdioHelpers.js';
|
||||
import { writeStderrLine } from '../utils/stdioHelpers.js';
|
||||
|
||||
export const CHAT_RECORDING_FAILURE_MESSAGE =
|
||||
'Session recording stopped after a write failure. New messages for the affected session will not be saved. Check disk space and permissions, then start a new session to resume recording. See the debug log for details.';
|
||||
|
|
@ -36,7 +36,7 @@ import type {
|
|||
PermissionSuggestion,
|
||||
} from '../../types.js';
|
||||
import { BaseController } from './baseController.js';
|
||||
import { buildPermissionSuggestions } from '../../../utils/permission-suggestions.js';
|
||||
import { buildPermissionSuggestions } from '../../permission-suggestions.js';
|
||||
|
||||
const DEFAULT_CAN_USE_TOOL_TIMEOUT_MS = 60_000;
|
||||
|
||||
|
|
|
|||
|
|
@ -42,9 +42,9 @@ import type {
|
|||
ToolUseBlock,
|
||||
Usage,
|
||||
} from '../types.js';
|
||||
import { functionResponsePartsToString } from '../../utils/nonInteractiveHelpers.js';
|
||||
import { functionResponsePartsToString } from '../nonInteractiveHelpers.js';
|
||||
import { projectHeadlessToolResultContent } from './headless-tool-result-text-projection.js';
|
||||
import { observeHeadlessToolResultProjection } from '../../utils/tool-result-boundary-diagnostics.js';
|
||||
import { observeHeadlessToolResultProjection } from '../tool-result-boundary-diagnostics.js';
|
||||
|
||||
/**
|
||||
* Internal state for managing a single message context (main agent or subagent).
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import {
|
|||
type JsonOutputAdapterInterface,
|
||||
type ResultOptions,
|
||||
} from './BaseJsonOutputAdapter.js';
|
||||
import { observeHeadlessJsonToolResultWire } from '../../utils/tool-result-boundary-diagnostics.js';
|
||||
import { observeHeadlessJsonToolResultWire } from '../tool-result-boundary-diagnostics.js';
|
||||
|
||||
/**
|
||||
* JSON output adapter that collects all messages and emits them
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ import {
|
|||
type ResultOptions,
|
||||
type JsonOutputAdapterInterface,
|
||||
} from './BaseJsonOutputAdapter.js';
|
||||
import { observeHeadlessToolResultWire } from '../../utils/tool-result-boundary-diagnostics.js';
|
||||
import { observeHeadlessToolResultWire } from '../tool-result-boundary-diagnostics.js';
|
||||
|
||||
/**
|
||||
* Stream JSON output adapter that emits messages immediately
|
||||
|
|
|
|||
|
|
@ -17,13 +17,9 @@ import {
|
|||
OutputFormat,
|
||||
} from '@qwen-code/qwen-code-core';
|
||||
import type { Part } from '@google/genai';
|
||||
import type {
|
||||
CLIUserMessage,
|
||||
PermissionMode,
|
||||
} from '../nonInteractive/types.js';
|
||||
import type { JsonOutputAdapterInterface } from '../nonInteractive/io/BaseJsonOutputAdapter.js';
|
||||
import type { CLIUserMessage, PermissionMode } from './types.js';
|
||||
import type { JsonOutputAdapterInterface } from './io/BaseJsonOutputAdapter.js';
|
||||
import {
|
||||
normalizePartList,
|
||||
extractPartsFromUserMessage,
|
||||
computeUsageFromMetrics,
|
||||
buildSystemMessage,
|
||||
|
|
@ -89,42 +85,6 @@ vi.mock('@qwen-code/qwen-code-core', async (importOriginal) => {
|
|||
};
|
||||
});
|
||||
|
||||
describe('normalizePartList', () => {
|
||||
it('should return empty array for null input', () => {
|
||||
expect(normalizePartList(null)).toEqual([]);
|
||||
});
|
||||
|
||||
it('should return empty array for undefined input', () => {
|
||||
expect(normalizePartList(undefined as unknown as null)).toEqual([]);
|
||||
});
|
||||
|
||||
it('should convert string to Part array', () => {
|
||||
const result = normalizePartList('test string');
|
||||
expect(result).toEqual([{ text: 'test string' }]);
|
||||
});
|
||||
|
||||
it('should convert array of strings to Part array', () => {
|
||||
const result = normalizePartList(['hello', 'world']);
|
||||
expect(result).toEqual([{ text: 'hello' }, { text: 'world' }]);
|
||||
});
|
||||
|
||||
it('should convert array of mixed strings and Parts to Part array', () => {
|
||||
const part: Part = { text: 'existing' };
|
||||
const result = normalizePartList(['new', part]);
|
||||
expect(result).toEqual([{ text: 'new' }, part]);
|
||||
});
|
||||
|
||||
it('should convert single Part object to array', () => {
|
||||
const part: Part = { text: 'single part' };
|
||||
const result = normalizePartList(part);
|
||||
expect(result).toEqual([part]);
|
||||
});
|
||||
|
||||
it('should handle empty array', () => {
|
||||
expect(normalizePartList([])).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('extractPartsFromUserMessage', () => {
|
||||
it('should return null for undefined message', () => {
|
||||
expect(extractPartsFromUserMessage(undefined)).toBeNull();
|
||||
|
|
@ -30,40 +30,16 @@ import type {
|
|||
Usage,
|
||||
PermissionMode,
|
||||
CLISystemMessage,
|
||||
} from '../nonInteractive/types.js';
|
||||
} from './types.js';
|
||||
import type {
|
||||
JsonOutputAdapterInterface,
|
||||
MessageEmitter,
|
||||
} from '../nonInteractive/io/BaseJsonOutputAdapter.js';
|
||||
} from './io/BaseJsonOutputAdapter.js';
|
||||
import { computeSessionStats } from '../ui/utils/computeStats.js';
|
||||
import { getAvailableCommands } from '../nonInteractiveCliCommands.js';
|
||||
|
||||
const debugLogger = createDebugLogger('NON_INTERACTIVE');
|
||||
|
||||
/**
|
||||
* Normalizes various part list formats into a consistent Part[] array.
|
||||
*
|
||||
* @param parts - Input parts in various formats (string, Part, Part[], or null)
|
||||
* @returns Normalized array of Part objects
|
||||
*/
|
||||
export function normalizePartList(parts: PartListUnion | null): Part[] {
|
||||
if (!parts) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (typeof parts === 'string') {
|
||||
return [{ text: parts }];
|
||||
}
|
||||
|
||||
if (Array.isArray(parts)) {
|
||||
return parts.map((part) =>
|
||||
typeof part === 'string' ? { text: part } : (part as Part),
|
||||
);
|
||||
}
|
||||
|
||||
return [parts as Part];
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts user message parts from a CLI protocol message.
|
||||
*
|
||||
|
|
@ -4,7 +4,7 @@
|
|||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import type { PermissionSuggestion } from '../nonInteractive/types.js';
|
||||
import type { PermissionSuggestion } from './types.js';
|
||||
|
||||
function withWarnings(
|
||||
description: string,
|
||||
|
|
@ -49,7 +49,7 @@ import {
|
|||
import {
|
||||
settleChatRecording,
|
||||
subscribeToHeadlessChatRecordingFailures,
|
||||
} from '../utils/chat-recording-failure.js';
|
||||
} from './chat-recording-failure.js';
|
||||
|
||||
const debugLogger = createDebugLogger('NON_INTERACTIVE_SESSION');
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|||
import type { SessionUpdate } from '@agentclientprotocol/sdk';
|
||||
import { LOAD_REPLAY_META_KEY } from '@qwen-code/acp-bridge/bridgeTypes';
|
||||
import type { ToolResultBoundaryObservation } from '@qwen-code/qwen-code-core';
|
||||
import type { CLIUserMessage } from '../nonInteractive/types.js';
|
||||
import type { CLIUserMessage } from './types.js';
|
||||
|
||||
const { mockObserveBoundary } = vi.hoisted(() => ({
|
||||
mockObserveBoundary: vi.fn(
|
||||
|
|
@ -11,7 +11,7 @@ import {
|
|||
type ToolResultBoundaryArtifact,
|
||||
type ToolResultBoundaryValue,
|
||||
} from '@qwen-code/qwen-code-core';
|
||||
import type { CLIMessage, ToolResultBlock } from '../nonInteractive/types.js';
|
||||
import type { CLIMessage, ToolResultBlock } from './types.js';
|
||||
|
||||
interface ProjectedToolResult {
|
||||
mutated: boolean;
|
||||
|
|
@ -94,7 +94,7 @@ import { RunBudgetEnforcer } from './utils/runBudget.js';
|
|||
import {
|
||||
settleChatRecording,
|
||||
subscribeToHeadlessChatRecordingFailures,
|
||||
} from './utils/chat-recording-failure.js';
|
||||
} from './nonInteractive/chat-recording-failure.js';
|
||||
import { registerCleanup } from './utils/cleanup.js';
|
||||
import { cleanupReviewWorktreeLeases } from './services/review-worktree-lease.js';
|
||||
|
||||
|
|
@ -151,8 +151,8 @@ function suppressedOutputBody(structuredCaptured: boolean): string {
|
|||
: SUPPRESSED_OUTPUT_RETRY;
|
||||
}
|
||||
|
||||
import { normalizePartList } from './utils/normalize-part-list.js';
|
||||
import {
|
||||
normalizePartList,
|
||||
extractPartsFromUserMessage,
|
||||
buildSystemMessage,
|
||||
createToolProgressHandler,
|
||||
|
|
@ -160,7 +160,7 @@ import {
|
|||
computeUsageFromMetrics,
|
||||
buildInitialSystemReminders,
|
||||
insertAfterFunctionResponses,
|
||||
} from './utils/nonInteractiveHelpers.js';
|
||||
} from './nonInteractive/nonInteractiveHelpers.js';
|
||||
|
||||
// Human-readable labels for the detectors that can fire mid-stream.
|
||||
// Surfaced to stderr in TEXT mode so a headless run that halts on a loop
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import type { PartListUnion } from '@google/genai';
|
|||
import {
|
||||
parseSlashCommand,
|
||||
parseStackedSlashCommands,
|
||||
} from './utils/commands.js';
|
||||
} from './ui/commands/commands.js';
|
||||
import {
|
||||
Logger,
|
||||
uiTelemetryService,
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ import {
|
|||
type ServePreflightKind,
|
||||
type ServeWorkspaceEnvStatus,
|
||||
} from '@qwen-code/acp-bridge';
|
||||
import { getGitVersion, getNpmVersion } from '../utils/systemInfo.js';
|
||||
import { getGitVersion, getNpmVersion } from '../ui/systemInfo.js';
|
||||
import { buildEnvStatusFromEnv, snapshotProcessEnv } from './env-snapshot.js';
|
||||
|
||||
const REQUIRED_NODE_MAJOR = 22;
|
||||
|
|
|
|||
|
|
@ -164,6 +164,60 @@ const allowedProcessEnvAccesses = normalizeAllowances([
|
|||
},
|
||||
},
|
||||
],
|
||||
[
|
||||
'packages/cli/src/serve/sandbox.ts',
|
||||
{
|
||||
reason:
|
||||
'The sandbox launcher assembles the sandboxed child environment: ' +
|
||||
'it passes through the process environment, forwards provider keys, ' +
|
||||
'proxy settings, and debug switches, and reads the SANDBOX_* control ' +
|
||||
'variables. It entered the scanned serve/ layer via the #9146 ' +
|
||||
'leaf-layer move; its access surface is unchanged.',
|
||||
accesses: {
|
||||
'computed:envVar': 2,
|
||||
'key:BUILD_SANDBOX': 2,
|
||||
'key:COLORTERM': 2,
|
||||
'key:DEBUG': 5,
|
||||
'key:DEBUG_MODE': 1,
|
||||
'key:DEBUG_PORT': 2,
|
||||
'key:GEMINI_API_KEY': 2,
|
||||
'key:GEMINI_MODEL': 2,
|
||||
'key:GOOGLE_API_KEY': 2,
|
||||
'key:GOOGLE_APPLICATION_CREDENTIALS': 2,
|
||||
'key:GOOGLE_CLOUD_LOCATION': 2,
|
||||
'key:GOOGLE_CLOUD_PROJECT': 2,
|
||||
'key:GOOGLE_GENAI_USE_GCA': 2,
|
||||
'key:GOOGLE_GENAI_USE_VERTEXAI': 2,
|
||||
'key:HTTP_PROXY': 2,
|
||||
'key:HTTPS_PROXY': 2,
|
||||
'key:NO_PROXY': 2,
|
||||
'key:NODE_ENV': 1,
|
||||
'key:NODE_OPTIONS': 1,
|
||||
'key:OPENAI_API_KEY': 2,
|
||||
'key:OPENAI_BASE_URL': 2,
|
||||
'key:OPENAI_MODEL': 2,
|
||||
'key:PATH': 2,
|
||||
'key:PYTHONPATH': 2,
|
||||
'key:QWEN_CODE_INTEGRATION_TEST': 1,
|
||||
'key:QWEN_CODE_MCP_APPROVALS_PATH': 2,
|
||||
'key:QWEN_CODE_SCRUB_ELECTRON_RUN_AS_NODE': 1,
|
||||
'key:QWEN_CODE_TEST_VAR': 2,
|
||||
'key:QWEN_SANDBOX_PROXY_COMMAND': 2,
|
||||
'key:SANDBOX_ENV': 2,
|
||||
'key:SANDBOX_FLAGS': 2,
|
||||
'key:SANDBOX_MOUNTS': 2,
|
||||
'key:SANDBOX_PORTS': 1,
|
||||
'key:SANDBOX_SET_UID_GID': 1,
|
||||
'key:SEATBELT_PROFILE': 1,
|
||||
'key:TERM': 2,
|
||||
'key:VIRTUAL_ENV': 1,
|
||||
'key:http_proxy': 2,
|
||||
'key:https_proxy': 2,
|
||||
'key:no_proxy': 2,
|
||||
whole: 6,
|
||||
},
|
||||
},
|
||||
],
|
||||
[
|
||||
'packages/cli/src/serve/server/fs-factory.ts',
|
||||
{
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import {
|
|||
getOwnKeyScope,
|
||||
getWritableScopes,
|
||||
} from '../../config/modelProvidersScope.js';
|
||||
import { getSettingDefinition } from '../../utils/settingsUtils.js';
|
||||
import { getSettingDefinition } from '../../config/settingsUtils.js';
|
||||
import { writeStderrLine } from '../../utils/stdioHelpers.js';
|
||||
import {
|
||||
isActiveModelSelection,
|
||||
|
|
|
|||
|
|
@ -85,10 +85,9 @@ function makeQualifiedApp() {
|
|||
safeBody: (req) =>
|
||||
req.body && typeof req.body === 'object' ? req.body : {},
|
||||
persistSetting,
|
||||
workspaceRegistry:
|
||||
registry as unknown as Parameters<
|
||||
typeof registerWorkspaceQualifiedSettingsRoutes
|
||||
>[1]['workspaceRegistry'],
|
||||
workspaceRegistry: registry as unknown as Parameters<
|
||||
typeof registerWorkspaceQualifiedSettingsRoutes
|
||||
>[1]['workspaceRegistry'],
|
||||
invalidateServeFeaturesCache: () => {},
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import {
|
|||
getSettingDefinition,
|
||||
validateSettingValue,
|
||||
WORKSPACE_RESTRICTED_SETTING_KEYS,
|
||||
} from '../../utils/settingsUtils.js';
|
||||
} from '../../config/settingsUtils.js';
|
||||
import { writeStderrLine } from '../../utils/stdioHelpers.js';
|
||||
import { parseAndValidateWorkspaceClientId } from '../server/request-helpers.js';
|
||||
import {
|
||||
|
|
|
|||
|
|
@ -32,14 +32,15 @@ vi.mock('node:child_process', async (importOriginal) => {
|
|||
};
|
||||
});
|
||||
|
||||
import { isContainerPathWithinWorkdir } from './sandbox-path.js';
|
||||
import { isContainerPathWithinWorkdir } from '../utils/sandbox-path.js';
|
||||
import {
|
||||
BUILTIN_SEATBELT_PROFILES,
|
||||
getSandboxPassthroughEnvArgs,
|
||||
resolveSeatbeltProfileFile,
|
||||
start_sandbox,
|
||||
} from './sandbox.js';
|
||||
import { parseSandboxImageName } from './sandboxImageName.js';
|
||||
import { parseSandboxMountSpec } from './sandboxMounts.js';
|
||||
import { parseSandboxImageName } from '../utils/sandboxImageName.js';
|
||||
import { parseSandboxMountSpec } from '../utils/sandboxMounts.js';
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
|
|
@ -157,23 +158,37 @@ describe('resolveSeatbeltProfileFile', () => {
|
|||
});
|
||||
|
||||
it('keeps source-mode seatbelt profile paths next to the module', () => {
|
||||
const utilsDir = path.resolve(
|
||||
const serveDir = path.resolve(
|
||||
path.sep,
|
||||
'repo',
|
||||
'packages',
|
||||
'cli',
|
||||
'src',
|
||||
'utils',
|
||||
'serve',
|
||||
);
|
||||
const sourceUrl = pathToFileURL(
|
||||
path.join(utilsDir, 'sandbox.ts'),
|
||||
path.join(serveDir, 'sandbox.ts'),
|
||||
).toString();
|
||||
|
||||
expect(resolveSeatbeltProfileFile('restrictive-closed', sourceUrl)).toBe(
|
||||
path.join(utilsDir, 'sandbox-macos-restrictive-closed.sb'),
|
||||
path.join(serveDir, 'sandbox-macos-restrictive-closed.sb'),
|
||||
);
|
||||
});
|
||||
|
||||
it('keeps every builtin seatbelt profile colocated with the real module', () => {
|
||||
// Uses the default `import.meta.url` (the real module location), so this
|
||||
// fails loudly if sandbox.ts or the .sb profiles move without the other.
|
||||
// Iterate the module's own list rather than a hand-copied snapshot, so a
|
||||
// profile added to `BUILTIN_SEATBELT_PROFILES` without its `.sb` file
|
||||
// fails here instead of on a `sandbox-exec` ENOENT at launch. The length
|
||||
// guard keeps an emptied list from passing the loop vacuously.
|
||||
expect(BUILTIN_SEATBELT_PROFILES.length).toBeGreaterThan(0);
|
||||
for (const profile of BUILTIN_SEATBELT_PROFILES) {
|
||||
const profileFile = resolveSeatbeltProfileFile(profile);
|
||||
expect(fs.existsSync(profileFile), `missing ${profileFile}`).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
it('keeps custom seatbelt profiles under project settings', () => {
|
||||
const bundleDir = path.resolve(path.sep, 'tmp', 'qwen', 'lib');
|
||||
const chunkUrl = pathToFileURL(
|
||||
|
|
@ -22,15 +22,15 @@ import {
|
|||
resolveBundleDir,
|
||||
} from '@qwen-code/qwen-code-core';
|
||||
import { randomBytes } from 'node:crypto';
|
||||
import { writeStderrLine } from './stdioHelpers.js';
|
||||
import { parseSandboxImageName } from './sandboxImageName.js';
|
||||
import { isContainerPathWithinWorkdir } from './sandbox-path.js';
|
||||
import { parseSandboxMountSpec } from './sandboxMounts.js';
|
||||
import { writeStderrLine } from '../utils/stdioHelpers.js';
|
||||
import { parseSandboxImageName } from '../utils/sandboxImageName.js';
|
||||
import { isContainerPathWithinWorkdir } from '../utils/sandbox-path.js';
|
||||
import { parseSandboxMountSpec } from '../utils/sandboxMounts.js';
|
||||
import {
|
||||
CUSTOM_SANDBOX_IMAGE_ENV_VAR,
|
||||
HOST_UPDATE_RELAUNCH_ENV_VAR,
|
||||
SKIP_UPDATE_CHECK_ENV_VAR,
|
||||
} from './processUtils.js';
|
||||
} from '../utils/processUtils.js';
|
||||
import {
|
||||
QWEN_CODE_DESKTOP_ENV,
|
||||
QWEN_CODE_SERVE_ENV,
|
||||
|
|
@ -61,7 +61,12 @@ function ensureDirectoryAndGetRealPath(dir: string): string {
|
|||
const LOCAL_DEV_SANDBOX_IMAGE_NAME = 'qwen-code-sandbox';
|
||||
const SANDBOX_NETWORK_NAME = 'qwen-code-sandbox';
|
||||
const SANDBOX_PROXY_NAME = 'qwen-code-sandbox-proxy';
|
||||
const BUILTIN_SEATBELT_PROFILES = [
|
||||
/**
|
||||
* Exported so the colocation tripwire in `sandbox.test.ts` can iterate every
|
||||
* builtin profile by construction instead of pinning a hand-copied snapshot
|
||||
* that silently stops at the list as written.
|
||||
*/
|
||||
export const BUILTIN_SEATBELT_PROFILES = [
|
||||
'permissive-open',
|
||||
'permissive-closed',
|
||||
'permissive-proxied',
|
||||
|
|
@ -16,15 +16,18 @@ const mocks = vi.hoisted(() => ({
|
|||
runThrottledOnce: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('./cleanup.js', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('./cleanup.js')>();
|
||||
vi.mock('../../utils/housekeeping/cleanup.js', async (importOriginal) => {
|
||||
const actual =
|
||||
await importOriginal<
|
||||
typeof import('../../utils/housekeeping/cleanup.js')
|
||||
>();
|
||||
return {
|
||||
...actual,
|
||||
cleanupOldOpenAILogs: mocks.cleanupOldOpenAILogs,
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock('./throttledOnce.js', () => ({
|
||||
vi.mock('../../utils/housekeeping/throttledOnce.js', () => ({
|
||||
runThrottledOnce: mocks.runThrottledOnce,
|
||||
}));
|
||||
|
||||
|
|
@ -25,7 +25,7 @@ import {
|
|||
noteInteraction,
|
||||
_resetForTesting as resetInteraction,
|
||||
_setLastInteractionForTesting,
|
||||
} from './lastInteractionAt.js';
|
||||
} from '../../utils/housekeeping/lastInteractionAt.js';
|
||||
|
||||
const MS_PER_HOUR = 60 * 60 * 1000;
|
||||
const MS_PER_DAY = 24 * MS_PER_HOUR;
|
||||
|
|
@ -21,9 +21,9 @@ import {
|
|||
cleanupOldOpenAILogs,
|
||||
cleanupOldSubagentTranscripts,
|
||||
getCutoffDate,
|
||||
} from './cleanup.js';
|
||||
import { runThrottledOnce } from './throttledOnce.js';
|
||||
import { msSinceLastInteraction } from './lastInteractionAt.js';
|
||||
} from '../../utils/housekeeping/cleanup.js';
|
||||
import { runThrottledOnce } from '../../utils/housekeeping/throttledOnce.js';
|
||||
import { msSinceLastInteraction } from '../../utils/housekeeping/lastInteractionAt.js';
|
||||
|
||||
const debugLogger = createDebugLogger('HOUSEKEEPING');
|
||||
|
||||
|
|
@ -64,7 +64,7 @@ vi.mock('../utils/processUtils.js', () => ({
|
|||
requestUpdateOnExit: (...args: unknown[]) => mockRequestUpdateOnExit(...args),
|
||||
}));
|
||||
|
||||
vi.mock('../utils/handleAutoUpdate.js', () => ({
|
||||
vi.mock('../ui/handleAutoUpdate.js', () => ({
|
||||
handleAutoUpdate: (...args: unknown[]) => mockHandleAutoUpdate(...args),
|
||||
}));
|
||||
|
||||
|
|
@ -92,7 +92,7 @@ vi.mock('../core/initializer.js', () => ({
|
|||
mockConnectIdeForStartup(...args),
|
||||
}));
|
||||
|
||||
vi.mock('../utils/housekeeping/scheduler.js', () => ({
|
||||
vi.mock('../services/housekeeping/scheduler.js', () => ({
|
||||
startBackgroundHousekeeping: (...args: unknown[]) =>
|
||||
mockStartBackgroundHousekeeping(...args),
|
||||
}));
|
||||
|
|
|
|||
|
|
@ -166,7 +166,7 @@ export function startPostRenderPrefetches(
|
|||
{ t },
|
||||
] = await Promise.all([
|
||||
import('../ui/utils/updateCheck.js'),
|
||||
import('../utils/handleAutoUpdate.js'),
|
||||
import('../ui/handleAutoUpdate.js'),
|
||||
import('../utils/installationInfo.js'),
|
||||
import('../utils/updateEventEmitter.js'),
|
||||
import('../i18n/index.js'),
|
||||
|
|
@ -273,7 +273,7 @@ export function startPostRenderPrefetches(
|
|||
if (config.isInteractive()) {
|
||||
runDeferredTask('background_housekeeping', async () => {
|
||||
const { startBackgroundHousekeeping } = await import(
|
||||
'../utils/housekeeping/scheduler.js'
|
||||
'../services/housekeeping/scheduler.js'
|
||||
);
|
||||
startBackgroundHousekeeping(config, settings);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -184,7 +184,7 @@ vi.mock('../services/prompt-stash.js');
|
|||
|
||||
// Mock external utilities
|
||||
vi.mock('../utils/events.js');
|
||||
vi.mock('../utils/handleAutoUpdate.js');
|
||||
vi.mock('./handleAutoUpdate.js');
|
||||
vi.mock('../utils/cleanup.js');
|
||||
|
||||
const mockLoadHierarchicalGeminiMemory = vi.hoisted(() => vi.fn());
|
||||
|
|
|
|||
|
|
@ -179,7 +179,7 @@ import {
|
|||
detectWorkflowKeyword,
|
||||
buildWorkflowSteeringNotice,
|
||||
} from './utils/workflow-keyword.js';
|
||||
import { parseSlashCommand } from '../utils/commands.js';
|
||||
import { parseSlashCommand } from './commands/commands.js';
|
||||
import { type LoadedSettings, SettingScope } from '../config/settings.js';
|
||||
import { type InitializationResult } from '../core/initializer.js';
|
||||
import { ExtensionRefreshState } from '../config/extension-refresh-state.js';
|
||||
|
|
@ -199,7 +199,7 @@ import { useCommandMigration } from './hooks/useCommandMigration.js';
|
|||
import { migrateTomlCommands } from '../services/command-migration-tool.js';
|
||||
import { sendNotification } from '../services/notificationService.js';
|
||||
import { type UpdateObject } from './utils/updateCheck.js';
|
||||
import { setUpdateHandler } from '../utils/handleAutoUpdate.js';
|
||||
import { setUpdateHandler } from './handleAutoUpdate.js';
|
||||
import { registerCleanup, runExitCleanup } from '../utils/cleanup.js';
|
||||
import {
|
||||
useMessageQueue,
|
||||
|
|
@ -231,8 +231,8 @@ import {
|
|||
} from './contexts/BackgroundTaskViewContext.js';
|
||||
import { getLiveAgentPanelLayoutKey } from './components/background-view/liveAgentPanelVisibility.js';
|
||||
import { t } from '../i18n/index.js';
|
||||
import { TUI_CHAT_RECORDING_FAILURE_MESSAGE } from '../utils/chat-recording-failure.js';
|
||||
import { buildPermissionSuggestions } from '../utils/permission-suggestions.js';
|
||||
import { TUI_CHAT_RECORDING_FAILURE_MESSAGE } from '../nonInteractive/chat-recording-failure.js';
|
||||
import { buildPermissionSuggestions } from '../nonInteractive/permission-suggestions.js';
|
||||
import { useWelcomeBack } from './hooks/useWelcomeBack.js';
|
||||
import { useDialogClose } from './hooks/useDialogClose.js';
|
||||
import { useInitializationAuthError } from './hooks/useInitializationAuthError.js';
|
||||
|
|
|
|||
|
|
@ -30,9 +30,9 @@ vi.mock('../hooks/useQwenAuth.js', () => ({
|
|||
})),
|
||||
}));
|
||||
|
||||
vi.mock('../../utils/settingsUtils.js', async (importOriginal) => {
|
||||
vi.mock('../../config/settingsUtils.js', async (importOriginal) => {
|
||||
const actual =
|
||||
await importOriginal<typeof import('../../utils/settingsUtils.js')>();
|
||||
await importOriginal<typeof import('../../config/settingsUtils.js')>();
|
||||
return {
|
||||
...actual,
|
||||
backupSettingsFile: vi.fn(),
|
||||
|
|
|
|||
|
|
@ -9,10 +9,10 @@ import { aboutCommand } from './aboutCommand.js';
|
|||
import { type CommandContext } from './types.js';
|
||||
import { createMockCommandContext } from '../../test-utils/mockCommandContext.js';
|
||||
import { MessageType } from '../types.js';
|
||||
import * as systemInfoUtils from '../../utils/systemInfo.js';
|
||||
import * as systemInfoUtils from '../systemInfo.js';
|
||||
import * as sessionPathsUtils from '../../utils/sessionPaths.js';
|
||||
|
||||
vi.mock('../../utils/systemInfo.js');
|
||||
vi.mock('../systemInfo.js');
|
||||
vi.mock('../../utils/sessionPaths.js');
|
||||
|
||||
describe('aboutCommand', () => {
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
import type { SlashCommand } from './types.js';
|
||||
import { CommandKind } from './types.js';
|
||||
import { MessageType, type HistoryItemAbout } from '../types.js';
|
||||
import { getExtendedSystemInfo } from '../../utils/systemInfo.js';
|
||||
import { getExtendedSystemInfo } from '../systemInfo.js';
|
||||
import { t } from '../../i18n/index.js';
|
||||
import {
|
||||
collectSessionPathInfo,
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import { bugCommand } from './bugCommand.js';
|
|||
import { createMockCommandContext } from '../../test-utils/mockCommandContext.js';
|
||||
import { GIT_COMMIT_INFO } from '../../generated/git-commit.js';
|
||||
import { AuthType } from '@qwen-code/qwen-code-core';
|
||||
import * as systemInfoUtils from '../../utils/systemInfo.js';
|
||||
import * as systemInfoUtils from '../systemInfo.js';
|
||||
|
||||
const mockOpenBrowserSecurely = vi.hoisted(() => vi.fn());
|
||||
|
||||
|
|
@ -22,7 +22,7 @@ vi.mock('@qwen-code/qwen-code-core', async (importOriginal) => {
|
|||
openBrowserSecurely: mockOpenBrowserSecurely,
|
||||
};
|
||||
});
|
||||
vi.mock('../../utils/systemInfo.js');
|
||||
vi.mock('../systemInfo.js');
|
||||
|
||||
describe('bugCommand', () => {
|
||||
beforeEach(() => {
|
||||
|
|
|
|||
|
|
@ -11,8 +11,8 @@ import {
|
|||
} from './types.js';
|
||||
import { openBrowserSecurely } from '@qwen-code/qwen-code-core';
|
||||
import { MessageType, type HistoryItem } from '../types.js';
|
||||
import { getExtendedSystemInfo } from '../../utils/systemInfo.js';
|
||||
import { getSystemInfoFields } from '../../utils/systemInfoFields.js';
|
||||
import { getExtendedSystemInfo } from '../systemInfo.js';
|
||||
import { getSystemInfoFields } from '../systemInfoFields.js';
|
||||
import { t } from '../../i18n/index.js';
|
||||
|
||||
export const bugCommand: SlashCommand = {
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import {
|
|||
parseSlashCommand,
|
||||
parseStackedSlashCommands,
|
||||
} from './commands.js';
|
||||
import { CommandKind, type SlashCommand } from '../ui/commands/types.js';
|
||||
import { CommandKind, type SlashCommand } from './types.js';
|
||||
|
||||
// Mock command structure for testing
|
||||
const mockCommands: readonly SlashCommand[] = [
|
||||
|
|
@ -4,7 +4,7 @@
|
|||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { CommandKind, type SlashCommand } from '../ui/commands/types.js';
|
||||
import { CommandKind, type SlashCommand } from './types.js';
|
||||
|
||||
/** Maximum number of stacked skill commands that can be loaded in one prompt. */
|
||||
export const MAX_STACKED_SKILLS = 5;
|
||||
|
|
@ -19,7 +19,7 @@ import {
|
|||
getNestedProperty,
|
||||
getSettingDefinition,
|
||||
validateSettingValue,
|
||||
} from '../../utils/settingsUtils.js';
|
||||
} from '../../config/settingsUtils.js';
|
||||
|
||||
const SETTABLE_TYPES = new Set(['boolean', 'string', 'number', 'enum']);
|
||||
|
||||
|
|
|
|||
|
|
@ -6,14 +6,14 @@
|
|||
|
||||
import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
|
||||
import { runDoctorChecks } from './doctorChecks.js';
|
||||
import { type CommandContext } from '../ui/commands/types.js';
|
||||
import { createMockCommandContext } from '../test-utils/mockCommandContext.js';
|
||||
import * as systemInfoUtils from './systemInfo.js';
|
||||
import * as authModule from '../config/auth.js';
|
||||
import { type CommandContext } from './types.js';
|
||||
import { createMockCommandContext } from '../../test-utils/mockCommandContext.js';
|
||||
import * as systemInfoUtils from '../systemInfo.js';
|
||||
import * as authModule from '../../config/auth.js';
|
||||
import * as allProviders from '@qwen-code/qwen-code-core';
|
||||
|
||||
vi.mock('./systemInfo.js');
|
||||
vi.mock('../config/auth.js');
|
||||
vi.mock('../systemInfo.js');
|
||||
vi.mock('../../config/auth.js');
|
||||
vi.mock('@qwen-code/qwen-code-core', async (importOriginal) => {
|
||||
const actual =
|
||||
(await importOriginal()) as typeof import('@qwen-code/qwen-code-core');
|
||||
|
|
@ -6,17 +6,17 @@
|
|||
|
||||
import process from 'node:process';
|
||||
import os from 'node:os';
|
||||
import { getNpmVersion, getGitVersion } from './systemInfo.js';
|
||||
import { validateAuthMethod } from '../config/auth.js';
|
||||
import { getNpmVersion, getGitVersion } from '../systemInfo.js';
|
||||
import { validateAuthMethod } from '../../config/auth.js';
|
||||
import {
|
||||
findProviderByCredentials,
|
||||
canUseRipgrep,
|
||||
getMCPServerStatus,
|
||||
MCPServerStatus,
|
||||
} from '@qwen-code/qwen-code-core';
|
||||
import type { CommandContext } from '../ui/commands/types.js';
|
||||
import type { DoctorCheckResult } from '../ui/types.js';
|
||||
import { t } from '../i18n/index.js';
|
||||
import type { CommandContext } from './types.js';
|
||||
import type { DoctorCheckResult } from '../types.js';
|
||||
import { t } from '../../i18n/index.js';
|
||||
|
||||
const MIN_NODE_MAJOR = 22;
|
||||
|
||||
|
|
@ -8,14 +8,14 @@ import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
|
|||
import { doctorCommand } from './doctorCommand.js';
|
||||
import { type CommandContext } from './types.js';
|
||||
import { createMockCommandContext } from '../../test-utils/mockCommandContext.js';
|
||||
import * as doctorChecksModule from '../../utils/doctorChecks.js';
|
||||
import * as doctorChecksModule from './doctorChecks.js';
|
||||
import * as memoryDiagnosticsModule from '../../utils/memoryDiagnostics.js';
|
||||
import * as cpuProfilerModule from '../../utils/cpuProfiler.js';
|
||||
import { collectMemoryDiagnostics } from '@qwen-code/qwen-code-core';
|
||||
import type { Content } from '@google/genai';
|
||||
import type { DoctorCheckResult } from '../types.js';
|
||||
|
||||
vi.mock('../../utils/doctorChecks.js');
|
||||
vi.mock('./doctorChecks.js');
|
||||
vi.mock('../../utils/memoryDiagnostics.js');
|
||||
vi.mock('../../utils/cpuProfiler.js');
|
||||
vi.mock('@qwen-code/qwen-code-core', async (importOriginal) => ({
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
import type { CommandContext, SlashCommand } from './types.js';
|
||||
import { CommandKind } from './types.js';
|
||||
import type { HistoryItemDoctor } from '../types.js';
|
||||
import { runDoctorChecks } from '../../utils/doctorChecks.js';
|
||||
import { runDoctorChecks } from './doctorChecks.js';
|
||||
import {
|
||||
collectMemoryPressureSamples,
|
||||
formatMemoryDiagnostics,
|
||||
|
|
@ -21,7 +21,7 @@ import {
|
|||
startCpuProfile,
|
||||
stopCpuProfile,
|
||||
} from '../../utils/cpuProfiler.js';
|
||||
import { rollbackStandaloneUpdate } from '../../utils/standalone-update.js';
|
||||
import { rollbackStandaloneUpdate } from '../standalone-update.js';
|
||||
import { getInstallationInfo } from '../../utils/installationInfo.js';
|
||||
import { t } from '../../i18n/index.js';
|
||||
import {
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ import { languageCommand } from './languageCommand.js';
|
|||
import {
|
||||
initializeLlmOutputLanguage,
|
||||
writeOutputLanguageFile,
|
||||
} from '../../utils/languageUtils.js';
|
||||
} from '../../i18n/languageUtils.js';
|
||||
|
||||
describe('languageCommand', () => {
|
||||
let mockContext: CommandContext;
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ import {
|
|||
isAutoLanguage,
|
||||
resolveOutputLanguageOrPreserveAuto,
|
||||
writeOutputLanguageAndRegisterPath,
|
||||
} from '../../utils/languageUtils.js';
|
||||
} from '../../i18n/languageUtils.js';
|
||||
import { createDebugLogger } from '@qwen-code/qwen-code-core';
|
||||
|
||||
const debugLogger = createDebugLogger('LANGUAGE_COMMAND');
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ vi.mock('../../utils/processUtils.js', () => ({
|
|||
HOST_UPDATE_RELAUNCH_ENV_VAR: 'QWEN_CODE_HOST_UPDATE_RELAUNCH',
|
||||
relaunchForUpdate,
|
||||
}));
|
||||
vi.mock('../../utils/standalone-update.js', () => ({
|
||||
vi.mock('../standalone-update.js', () => ({
|
||||
performStandaloneUpdate,
|
||||
}));
|
||||
vi.mock('../../utils/installationInfo.js', () => ({
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue