diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f98f81133d1..b646bd5d8b1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1202,6 +1202,19 @@ jobs: if: "${{ needs.classify_pr.outputs.skip_ci != 'true' && steps.ci_profile.outputs.ci_profile == 'full' }}" run: 'npm run check:serve-fast-path-bundle' + # cli sources name core modules by path (`.../config/storage.js`). In the + # repo those resolve through tsconfig paths or vitest aliases; in the + # published package neither exists and Node resolves them against core's + # `exports`, where one `./*` entry carries all of them. Nothing else + # exercises that entry, so without this a change to it — or to the + # dist layout — leaves every suite green and breaks `qwen` at startup. + # Runs after the step above has already built the workspace. + - name: 'Check core subpath exports resolve' + if: "${{ needs.classify_pr.outputs.skip_ci != 'true' && steps.ci_profile.outputs.ci_profile == 'full' }}" + run: |- + npm run build --workspace=packages/core + npm run check:core-subpath-exports + # The `github_ci_only` profile runs the .github/scripts helper tests, but a # `full` PR that touches those scripts skips that path and `npm run # test:ci` (vitest) does not collect `node:test` files — so run them here diff --git a/eslint-rules/no-core-root-barrel-import.js b/eslint-rules/no-core-root-barrel-import.js index cc2fe98292c..54795e401fc 100644 --- a/eslint-rules/no-core-root-barrel-import.js +++ b/eslint-rules/no-core-root-barrel-import.js @@ -28,10 +28,11 @@ function isCoreProductionFile(filename) { } const CORE_PACKAGE_SPECIFIER = '@qwen-code/qwen-code-core'; -// The package exports map ("./src/*", "./dist/*") also exposes the root +// The package exports map ("./*", "./src/*", "./dist/*") also exposes the root // barrel through these self-reference subpaths. const CORE_BARREL_SPECIFIERS = new Set([ CORE_PACKAGE_SPECIFIER, + `${CORE_PACKAGE_SPECIFIER}/index.js`, `${CORE_PACKAGE_SPECIFIER}/src/index.js`, `${CORE_PACKAGE_SPECIFIER}/src/index.ts`, `${CORE_PACKAGE_SPECIFIER}/dist/index.js`, diff --git a/eslint-rules/no-core-utils-upward-import.js b/eslint-rules/no-core-utils-upward-import.js index ee5c3691cc3..3f1799fdbd0 100644 --- a/eslint-rules/no-core-utils-upward-import.js +++ b/eslint-rules/no-core-utils-upward-import.js @@ -66,6 +66,55 @@ function stripExtension(rel) { return rel.replace(/\.(js|ts|tsx|mjs|cjs)$/, ''); } +function exportTargetOf(entry) { + return typeof entry === 'string' ? entry : entry?.import; +} + +// Node resolves a subpath against `exports` by exact key first, then by +// pattern: among keys containing a single `*`, the one with the longest +// literal prefix wins; ties prefer the longer full key. What `*` captured is +// substituted into the target. +// The rule has to follow the same order, because `packages/core/package.json` +// carries a `./*` catch-all — without pattern matching every deep specifier +// misses the exact lookup, resolves fine at runtime, and is reported by +// nothing. +export function resolveExportTarget( + exportKey, + packageExports = CORE_PACKAGE_EXPORTS, +) { + const exact = exportTargetOf(packageExports[exportKey]); + if (typeof exact === 'string') return exact; + + let best = null; + for (const [pattern, entry] of Object.entries(packageExports)) { + const star = pattern.indexOf('*'); + if (star < 0 || pattern.indexOf('*', star + 1) >= 0) continue; + const prefix = pattern.slice(0, star); + const suffix = pattern.slice(star + 1); + if (!exportKey.startsWith(prefix)) continue; + if (!exportKey.endsWith(suffix)) continue; + if (exportKey.length < prefix.length + suffix.length) continue; + if ( + best && + (best.prefix.length > prefix.length || + (best.prefix.length === prefix.length && + best.pattern.length >= pattern.length)) + ) { + continue; + } + best = { pattern, prefix, suffix, entry }; + } + if (!best) return null; + + const target = exportTargetOf(best.entry); + if (typeof target !== 'string' || !target.includes('*')) return null; + const captured = exportKey.slice( + best.prefix.length, + exportKey.length - best.suffix.length, + ); + return target.replace('*', captured); +} + function corePackageSourcePath(importedPath) { if (importedPath.startsWith(CORE_PACKAGE_SRC_PREFIX)) { return importedPath.slice(CORE_PACKAGE_SRC_PREFIX.length); @@ -83,9 +132,7 @@ function corePackageSourcePath(importedPath) { } const exportKey = `./${importedPath.slice(CORE_PACKAGE_SUBPATH_PREFIX.length)}`; - const exportEntry = CORE_PACKAGE_EXPORTS[exportKey]; - const exportTarget = - typeof exportEntry === 'string' ? exportEntry : exportEntry?.import; + const exportTarget = resolveExportTarget(exportKey); if (typeof exportTarget !== 'string') { return null; } diff --git a/integration-tests/terminal-capture/skill-review-harness/text-capture.tsx b/integration-tests/terminal-capture/skill-review-harness/text-capture.tsx index bb4aa5a63ce..a56c6ca13ab 100644 --- a/integration-tests/terminal-capture/skill-review-harness/text-capture.tsx +++ b/integration-tests/terminal-capture/skill-review-harness/text-capture.tsx @@ -8,8 +8,8 @@ * `@qwen-code/qwen-code-core`, which normally resolves through the package's * built `dist` (absent on a fresh clone, and stale whenever core src moves * ahead of the last build). To avoid both, this registers an ESM loader hook - * (same idea as scripts/dev.js) that redirects that specifier to - * `packages/core/index.ts`, then imports the core-dependent modules + * (same idea as scripts/dev.js) that redirects root and subpath specifiers to + * `packages/core` source, then imports the core-dependent modules * DYNAMICALLY so they resolve through the hook. Type-only imports below are * erased at runtime and never trigger core resolution. */ @@ -223,6 +223,12 @@ async function main() { // Redirect @qwen-code/qwen-code-core to its TypeScript source so the harness // runs without a build and can never pick up a stale dist. Registered before // the dynamic imports below, which is what routes them through the hook. + // + // No CI test executes this loader: the harness is exercised manually only, + // via `npm run test:terminal-bench` (vitest.terminal-bench.config.ts). Keep + // the `named` map in sync with the named entries in the exports map of + // packages/core/package.json; scripts/check-core-subpath-exports.mjs gates + // the real (compiled) resolution path in CI. const repoRoot = path.resolve( path.dirname(fileURLToPath(import.meta.url)), '../../..', @@ -230,11 +236,35 @@ async function main() { const coreSrcUrl = pathToFileURL( path.join(repoRoot, 'packages', 'core', 'index.ts'), ).href; + const coreModulesUrl = pathToFileURL( + path.join(repoRoot, 'packages', 'core', 'src') + '/', + ).href; const loader = ` + import { existsSync } from 'node:fs'; + const prefix = '@qwen-code/qwen-code-core/'; + const named = new Map([ + ['transcriptRecords', 'utils/transcript-records.ts'], ['envVarResolver', 'utils/envVarResolver.ts'], + ['goalWire', 'goals/goal-wire.ts'], ['memoryScopes', 'memory/scopes.ts'], + ['subSessionConstants', 'tools/sub-session-constants.ts'], ['toolWriteOrigin', 'services/tool-write-origin.ts'], + ['userPromptSubmitContext', 'hooks/user-prompt-submit-context.ts'], ['noFollowOpen', 'utils/no-follow-open.ts'], + ['conversationsRuntimeMarker', 'utils/conversations-runtime-marker.ts'], + ['storage', 'config/storage.ts'], ['atomicFileWrite', 'utils/atomicFileWrite.ts'], + ['debugLogger', 'utils/debugLogger.ts'], + ]); export function resolve(specifier, context, nextResolve) { if (specifier === '@qwen-code/qwen-code-core') { return { shortCircuit: true, url: '${coreSrcUrl}', format: 'module' }; } + if (specifier.startsWith(prefix)) { + const subpath = specifier.slice(prefix.length); + const namedPath = named.get(subpath); + if (namedPath) return { shortCircuit: true, url: new URL(namedPath, '${coreModulesUrl}').href, format: 'module' }; + const stem = subpath.replace(/\\.js$/, ''); + for (const ext of ['.ts', '.tsx']) { + const candidate = new URL(stem + ext, '${coreModulesUrl}'); + if (existsSync(candidate)) return { shortCircuit: true, url: candidate.href, format: 'module' }; + } + } return nextResolve(specifier, context); } `; diff --git a/integration-tests/tsconfig.json b/integration-tests/tsconfig.json index 3836b355d5f..45dfa216bda 100644 --- a/integration-tests/tsconfig.json +++ b/integration-tests/tsconfig.json @@ -23,10 +23,16 @@ // on whether someone had built them recently — a missing dist used to fail // resolution outright and a stale one silently typechecked against old // declarations. nodenext does no extension or index probing on - // substituted paths, so every subpath the program imports needs an - // explicit entry naming its source file; a bare wildcard falls through to - // the package exports map, i.e. back to dist. Keep these in sync with the - // packages' exports maps. The runtime vitest aliases + // substituted paths, but it does substitute `.js` for `.ts`, so the + // `@qwen-code/qwen-code-core/*` wildcard below covers every specifier + // whose source file sits at the matching path under `packages/core/src/`. + // Explicit entries remain necessary only where the specifier does not name + // its file — `noFollowOpen` lives at `utils/no-follow-open.ts`, and the + // wildcard would map it to a file that does not exist. Do not delete those + // entries: since core's exports map gained a `./*` catch-all they would + // still resolve, silently, against dist declarations rather than failing. + // Keep these in sync with the packages' exports maps. The runtime vitest + // aliases // (`integration-tests/vitest.config.ts`) still point at the built SDK // bundle to exercise the published-bundle shape; these entries only affect // type resolution. @@ -48,6 +54,25 @@ "@qwen-code/qwen-code-core/noFollowOpen": [ "../packages/core/src/utils/no-follow-open.ts" ], + "@qwen-code/qwen-code-core/toolWriteOrigin": [ + "../packages/core/src/services/tool-write-origin.ts" + ], + "@qwen-code/qwen-code-core/envVarResolver": [ + "../packages/core/src/utils/envVarResolver.ts" + ], + "@qwen-code/qwen-code-core/conversationsRuntimeMarker": [ + "../packages/core/src/utils/conversations-runtime-marker.ts" + ], + "@qwen-code/qwen-code-core/storage": [ + "../packages/core/src/config/storage.ts" + ], + "@qwen-code/qwen-code-core/atomicFileWrite": [ + "../packages/core/src/utils/atomicFileWrite.ts" + ], + "@qwen-code/qwen-code-core/debugLogger": [ + "../packages/core/src/utils/debugLogger.ts" + ], + "@qwen-code/qwen-code-core/*": ["../packages/core/src/*"], "@qwen-code/qwen-code-core/goalWire": [ "../packages/core/src/goals/goal-wire.ts" ], diff --git a/package.json b/package.json index e4e664fa232..d7ba06a2180 100644 --- a/package.json +++ b/package.json @@ -96,6 +96,7 @@ "check:lockfile": "node scripts/check-lockfile.js", "check:desktop-isolation": "node scripts/check-desktop-isolation.js", "check:tui-dep-direction": "node scripts/check-tui-dep-direction.mjs", + "check:core-subpath-exports": "node scripts/check-core-subpath-exports.mjs", "check:serve-fast-path-bundle": "node scripts/clean-package-build-artifacts.js && npm run build -- --cli-only && cross-env DEV=true npm run bundle && node scripts/check-serve-fast-path-bundle.js", "clean": "node scripts/clean.js", "pre-commit": "node scripts/pre-commit.js" diff --git a/packages/cli/src/agent-view/supervisor-store.ts b/packages/cli/src/agent-view/supervisor-store.ts index efad606f305..cb790628e15 100644 --- a/packages/cli/src/agent-view/supervisor-store.ts +++ b/packages/cli/src/agent-view/supervisor-store.ts @@ -7,7 +7,8 @@ import * as fs from 'node:fs/promises'; import * as path from 'node:path'; import { createHash } from 'node:crypto'; -import { atomicWriteFile, Storage } from '@qwen-code/qwen-code-core'; +import { Storage } from '@qwen-code/qwen-code-core/config/storage.js'; +import { atomicWriteFile } from '@qwen-code/qwen-code-core/utils/atomicFileWrite.js'; import type { AgentViewActivityFile, AgentViewLaunchFile, diff --git a/packages/cli/src/config/extension-file-watcher.ts b/packages/cli/src/config/extension-file-watcher.ts index 93429f02fe5..761712ad738 100644 --- a/packages/cli/src/config/extension-file-watcher.ts +++ b/packages/cli/src/config/extension-file-watcher.ts @@ -7,13 +7,11 @@ import * as fs from 'node:fs'; import * as path from 'node:path'; import { watch as watchFs, type FSWatcher } from 'chokidar'; -import { - createDebugLogger, - isSubpath, - Storage, - type Config, - type ExtensionMutationEvent, -} from '@qwen-code/qwen-code-core'; +import type { Config } from '@qwen-code/qwen-code-core/config/config.js'; +import { Storage } from '@qwen-code/qwen-code-core/config/storage.js'; +import type { ExtensionMutationEvent } from '@qwen-code/qwen-code-core/extension/extensionManager.js'; +import { createDebugLogger } from '@qwen-code/qwen-code-core/utils/debugLogger.js'; +import { isSubpath } from '@qwen-code/qwen-code-core/utils/paths.js'; import { ExtensionRefreshState } from './extension-refresh-state.js'; const debugLogger = createDebugLogger('EXTENSION_FILE_WATCHER'); diff --git a/packages/cli/src/nonInteractive/control/ControlDispatcher.ts b/packages/cli/src/nonInteractive/control/ControlDispatcher.ts index b45e66b7517..e0ee7cf89a5 100644 --- a/packages/cli/src/nonInteractive/control/ControlDispatcher.ts +++ b/packages/cli/src/nonInteractive/control/ControlDispatcher.ts @@ -36,7 +36,7 @@ import type { ControlResponse, ControlRequestPayload, } from '../types.js'; -import { createDebugLogger } from '@qwen-code/qwen-code-core'; +import { createDebugLogger } from '@qwen-code/qwen-code-core/utils/debugLogger.js'; const debugLogger = createDebugLogger('CONTROL_DISPATCHER'); diff --git a/packages/cli/src/nonInteractive/control/controllers/permissionController.ts b/packages/cli/src/nonInteractive/control/controllers/permissionController.ts index de61ca87a28..07a8de0265f 100644 --- a/packages/cli/src/nonInteractive/control/controllers/permissionController.ts +++ b/packages/cli/src/nonInteractive/control/controllers/permissionController.ts @@ -14,21 +14,21 @@ * Abstracts all permission logic from the session manager to keep it clean. */ +import type { TeammateApprovalRequestEvent } from '@qwen-code/qwen-code-core/agents/team/team-events.js'; +import type { WorkflowApproval } from '@qwen-code/qwen-code-core/agents/workflow-run-registry.js'; +import type { WaitingToolCall } from '@qwen-code/qwen-code-core/core/coreToolScheduler.js'; import type { - WaitingToolCall, ToolExecuteConfirmationDetails, ToolMcpConfirmationDetails, - TeammateApprovalRequestEvent, ToolConfirmationPayload, - WorkflowApproval, -} from '@qwen-code/qwen-code-core'; +} from '@qwen-code/qwen-code-core/tools/tools.js'; import { ApprovalMode, APPROVAL_MODES, - InputFormat, - ToolConfirmationOutcome, - ToolNames, -} from '@qwen-code/qwen-code-core'; +} from '@qwen-code/qwen-code-core/config/approval-mode.js'; +import { InputFormat } from '@qwen-code/qwen-code-core/output/types.js'; +import { ToolNames } from '@qwen-code/qwen-code-core/tools/tool-names.js'; +import { ToolConfirmationOutcome } from '@qwen-code/qwen-code-core/tools/tools.js'; import type { CLIControlPermissionRequest, CLIControlSetPermissionModeRequest, diff --git a/packages/cli/src/nonInteractive/session.ts b/packages/cli/src/nonInteractive/session.ts index a61977d85fb..852237649a6 100644 --- a/packages/cli/src/nonInteractive/session.ts +++ b/packages/cli/src/nonInteractive/session.ts @@ -7,13 +7,11 @@ import type { Config, ConfigInitializeOptions, -} from '@qwen-code/qwen-code-core'; -import { - createDebugLogger, - buildSessionRecoveryPlanFromApiHistory, - SendMessageType, - TURN_INTERRUPTION_HISTORY_TAIL_COUNT, -} from '@qwen-code/qwen-code-core'; +} from '@qwen-code/qwen-code-core/config/config.js'; +import { SendMessageType } from '@qwen-code/qwen-code-core/core/client.js'; +import { buildSessionRecoveryPlanFromApiHistory } from '@qwen-code/qwen-code-core/core/session-recovery.js'; +import { TURN_INTERRUPTION_HISTORY_TAIL_COUNT } from '@qwen-code/qwen-code-core/core/turn-interruption.js'; +import { createDebugLogger } from '@qwen-code/qwen-code-core/utils/debugLogger.js'; import { StreamJsonInputReader } from './io/StreamJsonInputReader.js'; import { StreamJsonOutputAdapter } from './io/StreamJsonOutputAdapter.js'; import { ControlContext } from './control/ControlContext.js'; diff --git a/packages/cli/src/serve/sandbox.ts b/packages/cli/src/serve/sandbox.ts index e8cfb738e1f..78abf58e36c 100644 --- a/packages/cli/src/serve/sandbox.ts +++ b/packages/cli/src/serve/sandbox.ts @@ -14,13 +14,14 @@ import { SETTINGS_DIRECTORY_NAME, } from '../config/settings.js'; import { promisify } from 'node:util'; -import type { Config, SandboxConfig } from '@qwen-code/qwen-code-core'; -import { - FatalSandboxError, - Storage, - isSubpath, - resolveBundleDir, -} from '@qwen-code/qwen-code-core'; +import type { + Config, + SandboxConfig, +} from '@qwen-code/qwen-code-core/config/config.js'; +import { Storage } from '@qwen-code/qwen-code-core/config/storage.js'; +import { resolveBundleDir } from '@qwen-code/qwen-code-core/utils/bundlePaths.js'; +import { FatalSandboxError } from '@qwen-code/qwen-code-core/utils/errors.js'; +import { isSubpath } from '@qwen-code/qwen-code-core/utils/paths.js'; import { randomBytes } from 'node:crypto'; import { writeStderrLine } from '../utils/stdioHelpers.js'; import { parseSandboxImageName } from '../utils/sandboxImageName.js'; diff --git a/packages/cli/src/services/command-migration-tool.ts b/packages/cli/src/services/command-migration-tool.ts index 4627a450e34..57a7961ca7e 100644 --- a/packages/cli/src/services/command-migration-tool.ts +++ b/packages/cli/src/services/command-migration-tool.ts @@ -11,7 +11,7 @@ import { promises as fs } from 'node:fs'; import path from 'node:path'; import { glob } from 'glob'; -import { convertTomlToMarkdown } from '@qwen-code/qwen-code-core'; +import { convertTomlToMarkdown } from '@qwen-code/qwen-code-core/utils/toml-to-markdown-converter.js'; import { t } from '../i18n/index.js'; export interface MigrationResult { diff --git a/packages/cli/src/services/notificationService.ts b/packages/cli/src/services/notificationService.ts index 160b0dd54cb..817925474a5 100644 --- a/packages/cli/src/services/notificationService.ts +++ b/packages/cli/src/services/notificationService.ts @@ -18,7 +18,7 @@ * When disabled, no notification is sent. */ -import { createDebugLogger } from '@qwen-code/qwen-code-core'; +import { createDebugLogger } from '@qwen-code/qwen-code-core/utils/debugLogger.js'; import type { TerminalNotification } from '../ui/hooks/useTerminalNotification.js'; import { detectTerminal, generateKittyId } from '../utils/osc.js'; diff --git a/packages/cli/src/services/prompt-stash.ts b/packages/cli/src/services/prompt-stash.ts index 3aab97dcc1a..4b4a0c9e170 100644 --- a/packages/cli/src/services/prompt-stash.ts +++ b/packages/cli/src/services/prompt-stash.ts @@ -6,7 +6,8 @@ import * as fs from 'node:fs'; import * as path from 'node:path'; -import { atomicWriteFileSync, Storage } from '@qwen-code/qwen-code-core'; +import { Storage } from '@qwen-code/qwen-code-core/config/storage.js'; +import { atomicWriteFileSync } from '@qwen-code/qwen-code-core/utils/atomicFileWrite.js'; const PROMPT_STASH_FILE = 'prompt-stash.json'; diff --git a/packages/cli/src/startup/worktreeStartup.ts b/packages/cli/src/startup/worktreeStartup.ts index 3a4d55ae778..79c67991dae 100644 --- a/packages/cli/src/startup/worktreeStartup.ts +++ b/packages/cli/src/startup/worktreeStartup.ts @@ -23,16 +23,19 @@ import * as path from 'node:path'; import { - createDebugLogger, GitWorktreeService, - readWorktreeSession, readWorktreeSessionMarker, - isSessionRuntimeActive, worktreeBranchForSlug, - writeWorktreeSession, writeWorktreeSessionMarker, -} from '@qwen-code/qwen-code-core'; -import type { Config, WorktreeSession } from '@qwen-code/qwen-code-core'; +} from '@qwen-code/qwen-code-core/services/gitWorktreeService.js'; +import { + readWorktreeSession, + isSessionRuntimeActive, + writeWorktreeSession, +} from '@qwen-code/qwen-code-core/services/worktreeSessionService.js'; +import { createDebugLogger } from '@qwen-code/qwen-code-core/utils/debugLogger.js'; +import type { Config } from '@qwen-code/qwen-code-core/config/config.js'; +import type { WorktreeSession } from '@qwen-code/qwen-code-core/services/worktreeSessionService.js'; const debugLogger = createDebugLogger('WORKTREE_STARTUP'); diff --git a/packages/cli/src/ui/auth/AuthDialog.tsx b/packages/cli/src/ui/auth/AuthDialog.tsx index f5543b22793..db64356b5d7 100644 --- a/packages/cli/src/ui/auth/AuthDialog.tsx +++ b/packages/cli/src/ui/auth/AuthDialog.tsx @@ -19,13 +19,15 @@ import { t } from '../../i18n/index.js'; import { findProviderById, findProviderByCredentials, - findExistingProviderModels, - getDefaultModelIds, - customProvider, ALIBABA_PROVIDERS, THIRD_PARTY_PROVIDERS, - type ProviderConfig, -} from '@qwen-code/qwen-code-core'; +} from '@qwen-code/qwen-code-core/providers/all-providers.js'; +import { customProvider } from '@qwen-code/qwen-code-core/providers/presets/custom-provider.js'; +import { + findExistingProviderModels, + getDefaultModelIds, +} from '@qwen-code/qwen-code-core/providers/provider-config.js'; +import type { ProviderConfig } from '@qwen-code/qwen-code-core/providers/types.js'; import { useProviderSetupFlow } from './useProviderSetupFlow.js'; import { ProviderSetupSteps } from './ProviderSetupSteps.js'; diff --git a/packages/cli/src/ui/components/ApprovalModeDialog.tsx b/packages/cli/src/ui/components/ApprovalModeDialog.tsx index f85d2919872..ea9f324b1ad 100644 --- a/packages/cli/src/ui/components/ApprovalModeDialog.tsx +++ b/packages/cli/src/ui/components/ApprovalModeDialog.tsx @@ -8,7 +8,10 @@ import type React from 'react'; import { useCallback, useState } from 'react'; import { Box, Text } from 'ink'; import { theme } from '../semantic-colors.js'; -import { ApprovalMode, APPROVAL_MODES } from '@qwen-code/qwen-code-core'; +import { + ApprovalMode, + APPROVAL_MODES, +} from '@qwen-code/qwen-code-core/config/approval-mode.js'; import { RadioButtonSelect } from './shared/RadioButtonSelect.js'; import type { LoadedSettings } from '../../config/settings.js'; import { SettingScope } from '../../config/settings.js'; diff --git a/packages/cli/src/ui/components/AutoAcceptIndicator.tsx b/packages/cli/src/ui/components/AutoAcceptIndicator.tsx index 6b1a851c59d..a02d0f50e52 100644 --- a/packages/cli/src/ui/components/AutoAcceptIndicator.tsx +++ b/packages/cli/src/ui/components/AutoAcceptIndicator.tsx @@ -7,7 +7,7 @@ import type React from 'react'; import { Text } from 'ink'; import { theme } from '../semantic-colors.js'; -import { ApprovalMode } from '@qwen-code/qwen-code-core'; +import { ApprovalMode } from '@qwen-code/qwen-code-core/config/approval-mode.js'; import { t } from '../../i18n/index.js'; import { getApprovalModeIndicatorColor } from './approvalModeVisuals.js'; diff --git a/packages/cli/src/ui/components/EditorSettingsDialog.tsx b/packages/cli/src/ui/components/EditorSettingsDialog.tsx index f2df5edaa33..26943bb66d6 100644 --- a/packages/cli/src/ui/components/EditorSettingsDialog.tsx +++ b/packages/cli/src/ui/components/EditorSettingsDialog.tsx @@ -17,11 +17,9 @@ import { RadioButtonSelect } from './shared/RadioButtonSelect.js'; import { ScopeSelector } from './shared/ScopeSelector.js'; import type { LoadedSettings } from '../../config/settings.js'; import { SettingScope } from '../../config/settings.js'; -import type { EditorType } from '@qwen-code/qwen-code-core'; -import { - createDebugLogger, - isEditorAvailable, -} from '@qwen-code/qwen-code-core'; +import type { EditorType } from '@qwen-code/qwen-code-core/utils/editor.js'; +import { createDebugLogger } from '@qwen-code/qwen-code-core/utils/debugLogger.js'; +import { isEditorAvailable } from '@qwen-code/qwen-code-core/utils/editor.js'; import { useKeypress } from '../hooks/useKeypress.js'; import { t } from '../../i18n/index.js'; diff --git a/packages/cli/src/ui/components/EffortDialog.tsx b/packages/cli/src/ui/components/EffortDialog.tsx index 9d57d971902..ad8ad2b1481 100644 --- a/packages/cli/src/ui/components/EffortDialog.tsx +++ b/packages/cli/src/ui/components/EffortDialog.tsx @@ -8,10 +8,8 @@ import type React from 'react'; import { useCallback } from 'react'; import { Box, Text } from 'ink'; import { theme } from '../semantic-colors.js'; -import { - REASONING_EFFORT_TIERS, - type ReasoningEffort, -} from '@qwen-code/qwen-code-core'; +import { REASONING_EFFORT_TIERS } from '@qwen-code/qwen-code-core/core/reasoning-effort.js'; +import type { ReasoningEffort } from '@qwen-code/qwen-code-core/core/reasoning-effort.js'; import { RadioButtonSelect } from './shared/RadioButtonSelect.js'; import { useKeypress } from '../hooks/useKeypress.js'; import { t } from '../../i18n/index.js'; diff --git a/packages/cli/src/ui/components/GoalPill.tsx b/packages/cli/src/ui/components/GoalPill.tsx index 36326d14373..6056e3b4044 100644 --- a/packages/cli/src/ui/components/GoalPill.tsx +++ b/packages/cli/src/ui/components/GoalPill.tsx @@ -7,12 +7,10 @@ import type React from 'react'; import { useEffect, useState } from 'react'; import { Text } from 'ink'; -import { elapsedActiveTime } from '@qwen-code/qwen-code-core'; -import type { - Config, - GoalRuntime, - GoalSnapshotV2, -} from '@qwen-code/qwen-code-core'; +import { elapsedActiveTime } from '@qwen-code/qwen-code-core/goals/goal-reducer.js'; +import type { Config } from '@qwen-code/qwen-code-core/config/config.js'; +import type { GoalSnapshotV2 } from '@qwen-code/qwen-code-core/goals/goal-protocol.js'; +import type { GoalRuntime } from '@qwen-code/qwen-code-core/goals/goal-runtime.js'; import { useConfig } from '../contexts/ConfigContext.js'; import { theme } from '../semantic-colors.js'; import { ICON } from '../constants.js'; diff --git a/packages/cli/src/ui/components/Header.tsx b/packages/cli/src/ui/components/Header.tsx index e59d49f882b..1bdcff96b38 100644 --- a/packages/cli/src/ui/components/Header.tsx +++ b/packages/cli/src/ui/components/Header.tsx @@ -7,7 +7,10 @@ import type React from 'react'; import { Box, Text } from 'ink'; import Gradient from 'ink-gradient'; -import { shortenPath, tildeifyPath } from '@qwen-code/qwen-code-core'; +import { + shortenPath, + tildeifyPath, +} from '@qwen-code/qwen-code-core/utils/paths.js'; import { theme } from '../semantic-colors.js'; import { shortAsciiLogo } from './AsciiArt.js'; import { getAsciiArtWidth, getCachedStringWidth } from '../utils/textUtils.js'; diff --git a/packages/cli/src/ui/components/HistoryItemDisplay.tsx b/packages/cli/src/ui/components/HistoryItemDisplay.tsx index ec2c86651b1..92c2ef7c497 100644 --- a/packages/cli/src/ui/components/HistoryItemDisplay.tsx +++ b/packages/cli/src/ui/components/HistoryItemDisplay.tsx @@ -47,7 +47,7 @@ import { SessionSummaryDisplay } from './SessionSummaryDisplay.js'; import { Help } from './Help.js'; import type { SlashCommand } from '../commands/types.js'; import { ExtensionsList } from './views/ExtensionsList.js'; -import { getMCPServerStatus } from '@qwen-code/qwen-code-core'; +import { getMCPServerStatus } from '@qwen-code/qwen-code-core/tools/mcp-status.js'; import { SkillsList } from './views/SkillsList.js'; import { ToolsList } from './views/ToolsList.js'; import { McpStatus } from './views/McpStatus.js'; diff --git a/packages/cli/src/ui/components/IdeTrustChangeDialog.tsx b/packages/cli/src/ui/components/IdeTrustChangeDialog.tsx index ed4128e81f6..501cd47d316 100644 --- a/packages/cli/src/ui/components/IdeTrustChangeDialog.tsx +++ b/packages/cli/src/ui/components/IdeTrustChangeDialog.tsx @@ -9,7 +9,7 @@ import { theme } from '../semantic-colors.js'; import { useKeypress } from '../hooks/useKeypress.js'; import { relaunchApp } from '../../utils/processUtils.js'; import { type RestartReason } from '../hooks/useIdeTrustListener.js'; -import { createDebugLogger } from '@qwen-code/qwen-code-core'; +import { createDebugLogger } from '@qwen-code/qwen-code-core/utils/debugLogger.js'; interface IdeTrustChangeDialogProps { reason: RestartReason; diff --git a/packages/cli/src/ui/components/InputPrompt.tsx b/packages/cli/src/ui/components/InputPrompt.tsx index d2776f94bd4..8820ce73875 100644 --- a/packages/cli/src/ui/components/InputPrompt.tsx +++ b/packages/cli/src/ui/components/InputPrompt.tsx @@ -32,12 +32,10 @@ import { keyMatchers, Command } from '../keyMatchers.js'; import type { CommandContext, SlashCommand } from '../commands/types.js'; import { parseSlashCommand } from '../commands/commands.js'; import { StreamingState } from '../types.js'; -import { - ApprovalMode, - type Config, - Storage, - createDebugLogger, -} from '@qwen-code/qwen-code-core'; +import { ApprovalMode } from '@qwen-code/qwen-code-core/config/approval-mode.js'; +import type { Config } from '@qwen-code/qwen-code-core/config/config.js'; +import { Storage } from '@qwen-code/qwen-code-core/config/storage.js'; +import { createDebugLogger } from '@qwen-code/qwen-code-core/utils/debugLogger.js'; import { parseInputForHighlighting, buildSegmentsForVisualSlice, diff --git a/packages/cli/src/ui/components/MemoryDialog.tsx b/packages/cli/src/ui/components/MemoryDialog.tsx index a8c08e11657..2beb3cb7827 100644 --- a/packages/cli/src/ui/components/MemoryDialog.tsx +++ b/packages/cli/src/ui/components/MemoryDialog.tsx @@ -10,14 +10,14 @@ import fs from 'node:fs/promises'; import os from 'node:os'; import path from 'node:path'; import { spawn } from 'node:child_process'; +import { Storage } from '@qwen-code/qwen-code-core/config/storage.js'; import { - getAllMemoryFilenames, - Storage, getAutoMemoryRoot, getAutoMemoryProjectStateDir, getUserAutoMemoryRoot, AUTO_MEMORY_INDEX_FILENAME, -} from '@qwen-code/qwen-code-core'; +} from '@qwen-code/qwen-code-core/memory/paths.js'; +import { getAllMemoryFilenames } from '@qwen-code/qwen-code-core/utils/memory-constants.js'; import { useConfig } from '../contexts/ConfigContext.js'; import { useSettings } from '../contexts/SettingsContext.js'; import { SettingScope } from '../../config/settings.js'; diff --git a/packages/cli/src/ui/components/OutputStyleDialog.tsx b/packages/cli/src/ui/components/OutputStyleDialog.tsx index ef7b3889f2c..50325bcff7f 100644 --- a/packages/cli/src/ui/components/OutputStyleDialog.tsx +++ b/packages/cli/src/ui/components/OutputStyleDialog.tsx @@ -7,8 +7,8 @@ import type React from 'react'; import { Box, Text } from 'ink'; import { theme } from '../semantic-colors.js'; -import type { OutputStyleDefinition } from '@qwen-code/qwen-code-core'; -import { BUILT_IN_OUTPUT_STYLES } from '@qwen-code/qwen-code-core'; +import { BUILT_IN_OUTPUT_STYLES } from '@qwen-code/qwen-code-core/core/output-styles.js'; +import type { OutputStyleDefinition } from '@qwen-code/qwen-code-core/core/output-styles.js'; import { RadioButtonSelect } from './shared/RadioButtonSelect.js'; import { useKeypress } from '../hooks/useKeypress.js'; import { t } from '../../i18n/index.js'; diff --git a/packages/cli/src/ui/components/PermissionsDialog.tsx b/packages/cli/src/ui/components/PermissionsDialog.tsx index 853f4fafa0c..614874aea17 100644 --- a/packages/cli/src/ui/components/PermissionsDialog.tsx +++ b/packages/cli/src/ui/components/PermissionsDialog.tsx @@ -19,12 +19,13 @@ import { SettingScope } from '../../config/settings.js'; import { TextInput } from './shared/TextInput.js'; import { Colors } from '../colors.js'; import { t } from '../../i18n/index.js'; +import type { PermissionManager } from '@qwen-code/qwen-code-core/permissions/permission-manager.js'; import type { - PermissionManager, RuleWithSource, RuleType, -} from '@qwen-code/qwen-code-core'; -import { isPathWithinRoot, parseRule } from '@qwen-code/qwen-code-core'; +} from '@qwen-code/qwen-code-core/permissions/types.js'; +import { parseRule } from '@qwen-code/qwen-code-core/permissions/rule-parser.js'; +import { isPathWithinRoot } from '@qwen-code/qwen-code-core/utils/workspaceContext.js'; // --------------------------------------------------------------------------- // Types diff --git a/packages/cli/src/ui/components/SettingsDialog.tsx b/packages/cli/src/ui/components/SettingsDialog.tsx index c5d27179b93..6fd9037cea8 100644 --- a/packages/cli/src/ui/components/SettingsDialog.tsx +++ b/packages/cli/src/ui/components/SettingsDialog.tsx @@ -37,7 +37,8 @@ import { useVimModeState, useVimModeActions, } from '../contexts/VimModeContext.js'; -import { createDebugLogger, type Config } from '@qwen-code/qwen-code-core'; +import type { Config } from '@qwen-code/qwen-code-core/config/config.js'; +import { createDebugLogger } from '@qwen-code/qwen-code-core/utils/debugLogger.js'; import { useKeypress } from '../hooks/useKeypress.js'; import { isDeletionKey, diff --git a/packages/cli/src/ui/components/ShellConfirmationDialog.tsx b/packages/cli/src/ui/components/ShellConfirmationDialog.tsx index f9163fa5a63..9d638b2aaf5 100644 --- a/packages/cli/src/ui/components/ShellConfirmationDialog.tsx +++ b/packages/cli/src/ui/components/ShellConfirmationDialog.tsx @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { ToolConfirmationOutcome } from '@qwen-code/qwen-code-core'; +import { ToolConfirmationOutcome } from '@qwen-code/qwen-code-core/tools/tools.js'; import { Box, Text } from 'ink'; import type React from 'react'; import { theme } from '../semantic-colors.js'; diff --git a/packages/cli/src/ui/components/ShellInputPrompt.tsx b/packages/cli/src/ui/components/ShellInputPrompt.tsx index a22869f78fa..07477379a8e 100644 --- a/packages/cli/src/ui/components/ShellInputPrompt.tsx +++ b/packages/cli/src/ui/components/ShellInputPrompt.tsx @@ -7,7 +7,7 @@ import { useCallback } from 'react'; import type React from 'react'; import { useKeypress } from '../hooks/useKeypress.js'; -import { ShellExecutionService } from '@qwen-code/qwen-code-core'; +import { ShellExecutionService } from '@qwen-code/qwen-code-core/services/shellExecutionService.js'; import { keyToAnsi, type Key } from '../hooks/keyToAnsi.js'; import { keyMatchers, Command } from '../keyMatchers.js'; diff --git a/packages/cli/src/ui/components/SkillReviewDialog.tsx b/packages/cli/src/ui/components/SkillReviewDialog.tsx index ef3140562d1..5564cff44e2 100644 --- a/packages/cli/src/ui/components/SkillReviewDialog.tsx +++ b/packages/cli/src/ui/components/SkillReviewDialog.tsx @@ -24,7 +24,7 @@ import { import { theme } from '../semantic-colors.js'; import { t } from '../../i18n/index.js'; import type { PendingSkillView } from '../contexts/UIStateContext.js'; -import { createDebugLogger } from '@qwen-code/qwen-code-core'; +import { createDebugLogger } from '@qwen-code/qwen-code-core/utils/debugLogger.js'; type Choice = 'keep' | 'discard' | 'keepAll' | 'discardAll' | 'turnOff'; diff --git a/packages/cli/src/ui/components/StandaloneSessionPicker.test.tsx b/packages/cli/src/ui/components/StandaloneSessionPicker.test.tsx index c36d38f1b62..b5f9362db66 100644 --- a/packages/cli/src/ui/components/StandaloneSessionPicker.test.tsx +++ b/packages/cli/src/ui/components/StandaloneSessionPicker.test.tsx @@ -17,9 +17,18 @@ import type { SessionListItem, ListSessionsResult, } from '@qwen-code/qwen-code-core'; - -vi.mock('@qwen-code/qwen-code-core', async () => { - const actual = await vi.importActual('@qwen-code/qwen-code-core'); +import { getGitBranch } from '@qwen-code/qwen-code-core/utils/gitUtils.js'; + +// This suite imports getGitBranch from the subpath module itself, so the mock +// has to name that same specifier: mocking the package root would not +// intercept it and would drag core's whole index into this suite's module +// graph. Scope note — the wrapper `StandaloneSessionPicker.tsx` is not in this +// graph (the tests render `SessionPicker`, which takes `currentBranch` as a +// prop), so what this mock intercepts is the suite's own import. +vi.mock('@qwen-code/qwen-code-core/utils/gitUtils.js', async () => { + const actual = await vi.importActual( + '@qwen-code/qwen-code-core/utils/gitUtils.js', + ); return { ...actual, getGitBranch: vi.fn().mockReturnValue('main'), @@ -82,6 +91,23 @@ function createMockSessionService( }; } +describe('mock wiring', () => { + it('stubs getGitBranch on the core subpath specifier this suite imports', () => { + // Pins two things about this file's own import: the vitest alias resolves + // `core/utils/gitUtils.js` to core's TypeScript source, and the vi.mock + // above intercepts that specifier (moving the mock back to the package + // root makes both assertions fail, and the branch fixture 'main' stops + // applying). It does not cover `StandaloneSessionPicker.tsx` — the only + // production caller of the stub — because the suite renders `SessionPicker` + // and never imports the wrapper; a specifier change there is caught by + // scripts/check-core-subpath-exports.mjs, which resolves every + // `@qwen-code/qwen-code-core/*` specifier in packages/cli/src against the + // built exports map. + expect(vi.isMockFunction(getGitBranch)).toBe(true); + expect(getGitBranch('/does/not/matter')).toBe('main'); + }); +}); + describe('SessionPicker', () => { const flush = async () => { await act(async () => { diff --git a/packages/cli/src/ui/components/StandaloneSessionPicker.tsx b/packages/cli/src/ui/components/StandaloneSessionPicker.tsx index aecbbdb7b4b..abc9f390588 100644 --- a/packages/cli/src/ui/components/StandaloneSessionPicker.tsx +++ b/packages/cli/src/ui/components/StandaloneSessionPicker.tsx @@ -6,12 +6,10 @@ import { useState } from 'react'; import { render, Box, useApp } from 'ink'; -import { - getGitBranch, - SessionService, - type Config, - type SessionListItem, -} from '@qwen-code/qwen-code-core'; +import type { Config } from '@qwen-code/qwen-code-core/config/config.js'; +import { SessionService } from '@qwen-code/qwen-code-core/services/sessionService.js'; +import type { SessionListItem } from '@qwen-code/qwen-code-core/services/sessionService.js'; +import { getGitBranch } from '@qwen-code/qwen-code-core/utils/gitUtils.js'; import { KeypressProvider } from '../contexts/KeypressContext.js'; import { ConfigContext } from '../contexts/ConfigContext.js'; import { SettingsContext } from '../contexts/SettingsContext.js'; diff --git a/packages/cli/src/ui/components/StatsDialog.tsx b/packages/cli/src/ui/components/StatsDialog.tsx index 0669fd4f8cd..b071eab2694 100644 --- a/packages/cli/src/ui/components/StatsDialog.tsx +++ b/packages/cli/src/ui/components/StatsDialog.tsx @@ -10,10 +10,8 @@ import { useState, useEffect, useCallback } from 'react'; import { theme } from '../semantic-colors.js'; import { useKeypress } from '../hooks/useKeypress.js'; import { loadStatsData, type StatsData } from '../utils/statsDataService.js'; -import { - metricsToUsageRecord, - type TimeRange, -} from '@qwen-code/qwen-code-core'; +import { metricsToUsageRecord } from '@qwen-code/qwen-code-core/services/usageHistoryService.js'; +import type { TimeRange } from '@qwen-code/qwen-code-core/services/usageHistoryService.js'; import { useSessionStats } from '../contexts/SessionContext.js'; import { useConfig } from '../contexts/ConfigContext.js'; import { t } from '../../i18n/index.js'; diff --git a/packages/cli/src/ui/components/agent-view/AgentChatContent.tsx b/packages/cli/src/ui/components/agent-view/AgentChatContent.tsx index 7723436b9f7..26f9674926c 100644 --- a/packages/cli/src/ui/components/agent-view/AgentChatContent.tsx +++ b/packages/cli/src/ui/components/agent-view/AgentChatContent.tsx @@ -12,14 +12,12 @@ import { Box, Text, Static } from 'ink'; import { useMemo, useState, useEffect, useCallback, useRef } from 'react'; -import { - AgentStatus, - AgentEventType, - getGitBranch, - type AgentCore, - type AgentInteractive, - type AgentStatusChangeEvent, -} from '@qwen-code/qwen-code-core'; +import type { AgentCore } from '@qwen-code/qwen-code-core/agents/runtime/agent-core.js'; +import { AgentEventType } from '@qwen-code/qwen-code-core/agents/runtime/agent-events.js'; +import type { AgentStatusChangeEvent } from '@qwen-code/qwen-code-core/agents/runtime/agent-events.js'; +import type { AgentInteractive } from '@qwen-code/qwen-code-core/agents/runtime/agent-interactive.js'; +import { AgentStatus } from '@qwen-code/qwen-code-core/agents/runtime/agent-types.js'; +import { getGitBranch } from '@qwen-code/qwen-code-core/utils/gitUtils.js'; import { useUIState } from '../../contexts/UIStateContext.js'; import { useTerminalSize } from '../../hooks/useTerminalSize.js'; import { useKeypress } from '../../hooks/useKeypress.js'; diff --git a/packages/cli/src/ui/components/agent-view/AgentChatView.tsx b/packages/cli/src/ui/components/agent-view/AgentChatView.tsx index 0a17f04ab95..2e2e8cb8e70 100644 --- a/packages/cli/src/ui/components/agent-view/AgentChatView.tsx +++ b/packages/cli/src/ui/components/agent-view/AgentChatView.tsx @@ -11,7 +11,7 @@ */ import { Box, Text } from 'ink'; -import { createDebugLogger } from '@qwen-code/qwen-code-core'; +import { createDebugLogger } from '@qwen-code/qwen-code-core/utils/debugLogger.js'; import { useAgentViewState, useAgentViewActions, diff --git a/packages/cli/src/ui/components/agent-view/AgentComposer.tsx b/packages/cli/src/ui/components/agent-view/AgentComposer.tsx index 6193267befe..fe72a059b88 100644 --- a/packages/cli/src/ui/components/agent-view/AgentComposer.tsx +++ b/packages/cli/src/ui/components/agent-view/AgentComposer.tsx @@ -19,11 +19,11 @@ import { Box, Text, useStdin } from 'ink'; import { useCallback, useEffect, useMemo } from 'react'; +import { AgentStatus } from '@qwen-code/qwen-code-core/agents/runtime/agent-types.js'; import { - AgentStatus, ApprovalMode, APPROVAL_MODES, -} from '@qwen-code/qwen-code-core'; +} from '@qwen-code/qwen-code-core/config/approval-mode.js'; import { useAgentViewState, useAgentViewActions, diff --git a/packages/cli/src/ui/components/agent-view/AgentHeader.tsx b/packages/cli/src/ui/components/agent-view/AgentHeader.tsx index 1bf9d4c34bd..cf5aa8b9989 100644 --- a/packages/cli/src/ui/components/agent-view/AgentHeader.tsx +++ b/packages/cli/src/ui/components/agent-view/AgentHeader.tsx @@ -12,7 +12,10 @@ import type React from 'react'; import { Box, Text } from 'ink'; -import { shortenPath, tildeifyPath } from '@qwen-code/qwen-code-core'; +import { + shortenPath, + tildeifyPath, +} from '@qwen-code/qwen-code-core/utils/paths.js'; import { theme } from '../../semantic-colors.js'; import { useTerminalSize } from '../../hooks/useTerminalSize.js'; diff --git a/packages/cli/src/ui/components/agent-view/AgentTabBar.tsx b/packages/cli/src/ui/components/agent-view/AgentTabBar.tsx index 371966acb0e..ae54bba8520 100644 --- a/packages/cli/src/ui/components/agent-view/AgentTabBar.tsx +++ b/packages/cli/src/ui/components/agent-view/AgentTabBar.tsx @@ -24,7 +24,8 @@ import { Box, Text } from 'ink'; import { useState, useEffect, useCallback } from 'react'; -import { AgentStatus, AgentEventType } from '@qwen-code/qwen-code-core'; +import { AgentEventType } from '@qwen-code/qwen-code-core/agents/runtime/agent-events.js'; +import { AgentStatus } from '@qwen-code/qwen-code-core/agents/runtime/agent-types.js'; import { useAgentViewState, useAgentViewActions, diff --git a/packages/cli/src/ui/components/approvalModeVisuals.ts b/packages/cli/src/ui/components/approvalModeVisuals.ts index 46d8ad4d7d0..e62b6ed1e2a 100644 --- a/packages/cli/src/ui/components/approvalModeVisuals.ts +++ b/packages/cli/src/ui/components/approvalModeVisuals.ts @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { ApprovalMode } from '@qwen-code/qwen-code-core'; +import { ApprovalMode } from '@qwen-code/qwen-code-core/config/approval-mode.js'; import { theme } from '../semantic-colors.js'; export function getApprovalModeIndicatorColor( diff --git a/packages/cli/src/ui/components/arena/ArenaSelectDialog.tsx b/packages/cli/src/ui/components/arena/ArenaSelectDialog.tsx index e6e6df39f1e..ce52530f6bc 100644 --- a/packages/cli/src/ui/components/arena/ArenaSelectDialog.tsx +++ b/packages/cli/src/ui/components/arena/ArenaSelectDialog.tsx @@ -7,12 +7,10 @@ import type React from 'react'; import { useCallback, useMemo, useState } from 'react'; import { Box, Text } from 'ink'; -import { - type ArenaManager, - isSuccessStatus, - type Config, - type ArenaAgentResult, -} from '@qwen-code/qwen-code-core'; +import type { ArenaManager } from '@qwen-code/qwen-code-core/agents/arena/ArenaManager.js'; +import type { ArenaAgentResult } from '@qwen-code/qwen-code-core/agents/arena/types.js'; +import { isSuccessStatus } from '@qwen-code/qwen-code-core/agents/runtime/agent-types.js'; +import type { Config } from '@qwen-code/qwen-code-core/config/config.js'; import { theme } from '../../semantic-colors.js'; import { useKeypress } from '../../hooks/useKeypress.js'; import { MessageType, type HistoryItemWithoutId } from '../../types.js'; diff --git a/packages/cli/src/ui/components/arena/ArenaStatusDialog.tsx b/packages/cli/src/ui/components/arena/ArenaStatusDialog.tsx index 84e9629cfc3..7623e384aec 100644 --- a/packages/cli/src/ui/components/arena/ArenaStatusDialog.tsx +++ b/packages/cli/src/ui/components/arena/ArenaStatusDialog.tsx @@ -7,15 +7,13 @@ import type React from 'react'; import { useEffect, useMemo, useState } from 'react'; import { Box, Text } from 'ink'; -import { - type ArenaManager, - type ArenaAgentState, - type InProcessBackend, - type AgentStatsSummary, - isSettledStatus, - ArenaSessionStatus, - DISPLAY_MODE, -} from '@qwen-code/qwen-code-core'; +import type { ArenaManager } from '@qwen-code/qwen-code-core/agents/arena/ArenaManager.js'; +import { ArenaSessionStatus } from '@qwen-code/qwen-code-core/agents/arena/types.js'; +import type { ArenaAgentState } from '@qwen-code/qwen-code-core/agents/arena/types.js'; +import type { InProcessBackend } from '@qwen-code/qwen-code-core/agents/backends/InProcessBackend.js'; +import { DISPLAY_MODE } from '@qwen-code/qwen-code-core/agents/backends/types.js'; +import type { AgentStatsSummary } from '@qwen-code/qwen-code-core/agents/runtime/agent-statistics.js'; +import { isSettledStatus } from '@qwen-code/qwen-code-core/agents/runtime/agent-types.js'; import { theme } from '../../semantic-colors.js'; import { useKeypress } from '../../hooks/useKeypress.js'; import { formatDuration } from '../../utils/formatters.js'; diff --git a/packages/cli/src/ui/components/arena/ArenaStopDialog.tsx b/packages/cli/src/ui/components/arena/ArenaStopDialog.tsx index 65f363793a1..43dbe185348 100644 --- a/packages/cli/src/ui/components/arena/ArenaStopDialog.tsx +++ b/packages/cli/src/ui/components/arena/ArenaStopDialog.tsx @@ -7,11 +7,9 @@ import type React from 'react'; import { useCallback, useMemo, useState } from 'react'; import { Box, Text } from 'ink'; -import { - ArenaSessionStatus, - createDebugLogger, - type Config, -} from '@qwen-code/qwen-code-core'; +import { ArenaSessionStatus } from '@qwen-code/qwen-code-core/agents/arena/types.js'; +import type { Config } from '@qwen-code/qwen-code-core/config/config.js'; +import { createDebugLogger } from '@qwen-code/qwen-code-core/utils/debugLogger.js'; import { theme } from '../../semantic-colors.js'; import { useKeypress } from '../../hooks/useKeypress.js'; import { MessageType, type HistoryItemWithoutId } from '../../types.js'; diff --git a/packages/cli/src/ui/components/background-view/BackgroundTasksDialog.tsx b/packages/cli/src/ui/components/background-view/BackgroundTasksDialog.tsx index b4309cd6be1..7ad20a87267 100644 --- a/packages/cli/src/ui/components/background-view/BackgroundTasksDialog.tsx +++ b/packages/cli/src/ui/components/background-view/BackgroundTasksDialog.tsx @@ -32,16 +32,22 @@ import { theme } from '../../semantic-colors.js'; import { useConfig } from '../../contexts/ConfigContext.js'; import { buildBackgroundEntryLabel, + MAX_RECENT_ACTIVITIES, +} from '@qwen-code/qwen-code-core/agents/background-tasks.js'; +import type { + AgentTask, + BackgroundApproval, +} from '@qwen-code/qwen-code-core/agents/background-tasks.js'; +import { isActiveWorkflowStatus, isTerminalWorkflowStatus, - MAX_RECENT_ACTIVITIES, - type AgentTask, - type BackgroundApproval, - type MonitorTask, - type ToolCallConfirmationDetails, - type WorkflowApproval, - type WorkflowTask, -} from '@qwen-code/qwen-code-core'; +} from '@qwen-code/qwen-code-core/agents/workflow-run-registry.js'; +import type { + WorkflowApproval, + WorkflowTask, +} from '@qwen-code/qwen-code-core/agents/workflow-run-registry.js'; +import type { MonitorTask } from '@qwen-code/qwen-code-core/services/monitorRegistry.js'; +import type { ToolCallConfirmationDetails } from '@qwen-code/qwen-code-core/tools/tools.js'; import { ToolConfirmationMessage } from '../messages/ToolConfirmationMessage.js'; import { WorkflowSaveOverlay } from './workflow-save-overlay.js'; import { formatDuration, formatTokenCount } from '../../utils/formatters.js'; diff --git a/packages/cli/src/ui/components/background-view/LiveAgentPanel.tsx b/packages/cli/src/ui/components/background-view/LiveAgentPanel.tsx index 6e832af0770..f460332082a 100644 --- a/packages/cli/src/ui/components/background-view/LiveAgentPanel.tsx +++ b/packages/cli/src/ui/components/background-view/LiveAgentPanel.tsx @@ -29,7 +29,7 @@ import type React from 'react'; import { useContext, useEffect, useMemo, useRef, useState } from 'react'; import { Box, Text } from 'ink'; -import { DEFAULT_BUILTIN_SUBAGENT_TYPE as CORE_DEFAULT_SUBAGENT_TYPE } from '@qwen-code/qwen-code-core'; +import { DEFAULT_BUILTIN_SUBAGENT_TYPE as CORE_DEFAULT_SUBAGENT_TYPE } from '@qwen-code/qwen-code-core/subagents/builtin-agents.js'; import { localizeToolDisplayName } from '../../../i18n/index.js'; import { useBackgroundTaskViewActions, diff --git a/packages/cli/src/ui/components/extensions/steps/ExtensionDetailStep.tsx b/packages/cli/src/ui/components/extensions/steps/ExtensionDetailStep.tsx index af2e9854c5f..cc700aaaa70 100644 --- a/packages/cli/src/ui/components/extensions/steps/ExtensionDetailStep.tsx +++ b/packages/cli/src/ui/components/extensions/steps/ExtensionDetailStep.tsx @@ -6,12 +6,12 @@ import { Box, Text } from 'ink'; import { theme } from '../../../semantic-colors.js'; +import type { Extension } from '@qwen-code/qwen-code-core/extension/extensionManager.js'; import { - redactUrlCredentials, getExtensionDisplayName, getExtensionDescription, - type Extension, -} from '@qwen-code/qwen-code-core'; +} from '@qwen-code/qwen-code-core/extension/i18n.js'; +import { redactUrlCredentials } from '@qwen-code/qwen-code-core/extension/redaction.js'; import { t, getCurrentLanguage } from '../../../../i18n/index.js'; interface ExtensionDetailStepProps { diff --git a/packages/cli/src/ui/components/extensions/steps/ExtensionListStep.tsx b/packages/cli/src/ui/components/extensions/steps/ExtensionListStep.tsx index 8d64b9b3911..e11920f7345 100644 --- a/packages/cli/src/ui/components/extensions/steps/ExtensionListStep.tsx +++ b/packages/cli/src/ui/components/extensions/steps/ExtensionListStep.tsx @@ -9,11 +9,11 @@ import { Box, Text } from 'ink'; import { theme } from '../../../semantic-colors.js'; import { useKeypress } from '../../../hooks/useKeypress.js'; import { keyMatchers, Command } from '../../../keyMatchers.js'; +import type { Extension } from '@qwen-code/qwen-code-core/extension/extensionManager.js'; import { - type Extension, getExtensionDisplayName, getExtensionDescription, -} from '@qwen-code/qwen-code-core'; +} from '@qwen-code/qwen-code-core/extension/i18n.js'; import { useTerminalSize } from '../../../hooks/useTerminalSize.js'; import { t, getCurrentLanguage } from '../../../../i18n/index.js'; import { ExtensionUpdateState } from '../../../state/extensions.js'; diff --git a/packages/cli/src/ui/components/extensions/steps/ScopeSelectStep.tsx b/packages/cli/src/ui/components/extensions/steps/ScopeSelectStep.tsx index bb4b3450edc..07e24bc2eec 100644 --- a/packages/cli/src/ui/components/extensions/steps/ScopeSelectStep.tsx +++ b/packages/cli/src/ui/components/extensions/steps/ScopeSelectStep.tsx @@ -6,10 +6,8 @@ import { Box, Text } from 'ink'; import { RadioButtonSelect } from '../../shared/RadioButtonSelect.js'; -import { - type Extension, - getExtensionDisplayName, -} from '@qwen-code/qwen-code-core'; +import type { Extension } from '@qwen-code/qwen-code-core/extension/extensionManager.js'; +import { getExtensionDisplayName } from '@qwen-code/qwen-code-core/extension/i18n.js'; import { theme } from '../../../semantic-colors.js'; import { t, getCurrentLanguage } from '../../../../i18n/index.js'; diff --git a/packages/cli/src/ui/components/hooks/HandlerListBody.tsx b/packages/cli/src/ui/components/hooks/HandlerListBody.tsx index 4c405077922..ba48d0eca76 100644 --- a/packages/cli/src/ui/components/hooks/HandlerListBody.tsx +++ b/packages/cli/src/ui/components/hooks/HandlerListBody.tsx @@ -7,7 +7,7 @@ import { Box, Text } from 'ink'; import { theme } from '../../semantic-colors.js'; import { useTerminalSize } from '../../hooks/useTerminalSize.js'; -import { HookType } from '@qwen-code/qwen-code-core'; +import { HookType } from '@qwen-code/qwen-code-core/hooks/types.js'; import type { HookConfigDisplayInfo } from './types.js'; import { getConfigSourceDisplay } from './sourceLabels.js'; import { t } from '../../../i18n/index.js'; diff --git a/packages/cli/src/ui/components/hooks/HookConfigDetailStep.tsx b/packages/cli/src/ui/components/hooks/HookConfigDetailStep.tsx index ef5db0c4c40..34e61889586 100644 --- a/packages/cli/src/ui/components/hooks/HookConfigDetailStep.tsx +++ b/packages/cli/src/ui/components/hooks/HookConfigDetailStep.tsx @@ -8,7 +8,7 @@ import { Box, Text } from 'ink'; import { theme } from '../../semantic-colors.js'; import { useTerminalSize } from '../../hooks/useTerminalSize.js'; import type { HookConfigDisplayInfo, HookEventDisplayInfo } from './types.js'; -import { HooksConfigSource } from '@qwen-code/qwen-code-core'; +import { HooksConfigSource } from '@qwen-code/qwen-code-core/hooks/types.js'; import { t } from '../../../i18n/index.js'; import { getTranslatedSourceDisplayMap, diff --git a/packages/cli/src/ui/components/hooks/sourceLabels.ts b/packages/cli/src/ui/components/hooks/sourceLabels.ts index 64c3dd219f5..9cc9393ee71 100644 --- a/packages/cli/src/ui/components/hooks/sourceLabels.ts +++ b/packages/cli/src/ui/components/hooks/sourceLabels.ts @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { HooksConfigSource } from '@qwen-code/qwen-code-core'; +import { HooksConfigSource } from '@qwen-code/qwen-code-core/hooks/types.js'; import type { HookConfigDisplayInfo } from './types.js'; import { getTranslatedSourceDisplayMap } from './constants.js'; import { t } from '../../../i18n/index.js'; diff --git a/packages/cli/src/ui/components/mcp/MCPManagementDialog.tsx b/packages/cli/src/ui/components/mcp/MCPManagementDialog.tsx index a1b7b9353b2..ec1e8382149 100644 --- a/packages/cli/src/ui/components/mcp/MCPManagementDialog.tsx +++ b/packages/cli/src/ui/components/mcp/MCPManagementDialog.tsx @@ -25,22 +25,24 @@ import { ResourceDetailStep } from './steps/ResourceDetailStep.js'; import { DisableScopeSelectStep } from './steps/DisableScopeSelectStep.js'; import { AuthenticateStep } from './steps/AuthenticateStep.js'; import { useConfig } from '../../contexts/ConfigContext.js'; +import { + isGatedMcpScope, + matchesAnyServerPattern, +} from '@qwen-code/qwen-code-core/config/mcp-server-config.js'; +import type { MCPServerConfig } from '@qwen-code/qwen-code-core/config/mcp-server-config.js'; +import { MCPOAuthTokenStorage } from '@qwen-code/qwen-code-core/mcp/oauth-token-storage.js'; +import { mcpServerRequiresOAuth } from '@qwen-code/qwen-code-core/tools/mcp-client.js'; +import type { DiscoveredMCPPrompt } from '@qwen-code/qwen-code-core/tools/mcp-client.js'; import { getMCPServerStatus, removeMCPServerStatus, addMCPStatusChangeListener, removeMCPStatusChangeListener, - mcpServerRequiresOAuth, MCPServerStatus, - DiscoveredMCPTool, - MCPOAuthTokenStorage, - isGatedMcpScope, - type MCPServerConfig, - type AnyDeclarativeTool, - type DiscoveredMCPPrompt, - createDebugLogger, - matchesAnyServerPattern, -} from '@qwen-code/qwen-code-core'; +} from '@qwen-code/qwen-code-core/tools/mcp-status.js'; +import { DiscoveredMCPTool } from '@qwen-code/qwen-code-core/tools/mcp-tool.js'; +import type { AnyDeclarativeTool } from '@qwen-code/qwen-code-core/tools/tools.js'; +import { createDebugLogger } from '@qwen-code/qwen-code-core/utils/debugLogger.js'; import { loadSettings, SettingScope } from '../../../config/settings.js'; import { loadMcpApprovals } from '../../../config/mcpApprovals.js'; import { isToolValid, getToolInvalidReasons } from './utils.js'; diff --git a/packages/cli/src/ui/components/mcp/steps/ServerListStep.tsx b/packages/cli/src/ui/components/mcp/steps/ServerListStep.tsx index 3378690f22e..4388bf6917a 100644 --- a/packages/cli/src/ui/components/mcp/steps/ServerListStep.tsx +++ b/packages/cli/src/ui/components/mcp/steps/ServerListStep.tsx @@ -11,7 +11,7 @@ import { ICON } from '../../../constants.js'; import { useKeypress } from '../../../hooks/useKeypress.js'; import { keyMatchers, Command } from '../../../keyMatchers.js'; import { t } from '../../../../i18n/index.js'; -import { MCPServerStatus } from '@qwen-code/qwen-code-core'; +import { MCPServerStatus } from '@qwen-code/qwen-code-core/tools/mcp-status.js'; import type { ServerListStepProps, MCPServerDisplayInfo } from '../types.js'; import { groupServersBySource, diff --git a/packages/cli/src/ui/components/messages/AskUserQuestionDialog.tsx b/packages/cli/src/ui/components/messages/AskUserQuestionDialog.tsx index 551539a1261..d15ea39fb73 100644 --- a/packages/cli/src/ui/components/messages/AskUserQuestionDialog.tsx +++ b/packages/cli/src/ui/components/messages/AskUserQuestionDialog.tsx @@ -7,11 +7,11 @@ import type React from 'react'; import { useRef, useState } from 'react'; import { Box, Text } from 'ink'; -import { - type ToolAskUserQuestionConfirmationDetails, - ToolConfirmationOutcome, - type ToolConfirmationPayload, -} from '@qwen-code/qwen-code-core'; +import { ToolConfirmationOutcome } from '@qwen-code/qwen-code-core/tools/tools.js'; +import type { + ToolAskUserQuestionConfirmationDetails, + ToolConfirmationPayload, +} from '@qwen-code/qwen-code-core/tools/tools.js'; import { theme } from '../../semantic-colors.js'; import { useKeypress } from '../../hooks/useKeypress.js'; import { keyMatchers, Command } from '../../keyMatchers.js'; diff --git a/packages/cli/src/ui/components/messages/ConversationMessages.tsx b/packages/cli/src/ui/components/messages/ConversationMessages.tsx index 39c794cbb3d..6923a4286c8 100644 --- a/packages/cli/src/ui/components/messages/ConversationMessages.tsx +++ b/packages/cli/src/ui/components/messages/ConversationMessages.tsx @@ -17,7 +17,7 @@ import { SCREEN_READER_USER_PREFIX, } from '../../textConstants.js'; import { t } from '../../../i18n/index.js'; -import { createDebugLogger } from '@qwen-code/qwen-code-core'; +import { createDebugLogger } from '@qwen-code/qwen-code-core/utils/debugLogger.js'; import { ErrorBoundary } from '../shared/ErrorBoundary.js'; import { ICON } from '../../constants.js'; import { sanitizeTerminalText } from '../../utils/textUtils.js'; diff --git a/packages/cli/src/ui/components/messages/ToolConfirmationMessage.tsx b/packages/cli/src/ui/components/messages/ToolConfirmationMessage.tsx index 064e6f3be2e..ae9d64add58 100644 --- a/packages/cli/src/ui/components/messages/ToolConfirmationMessage.tsx +++ b/packages/cli/src/ui/components/messages/ToolConfirmationMessage.tsx @@ -14,18 +14,16 @@ import wrapAnsi from 'wrap-ansi'; import { DiffRenderer } from './DiffRenderer.js'; import { RenderInline } from '../../utils/InlineMarkdownRenderer.js'; import { MarkdownDisplay } from '../../utils/MarkdownDisplay.js'; +import type { Config } from '@qwen-code/qwen-code-core/config/config.js'; import type { ToolCallConfirmationDetails, ToolExecuteConfirmationDetails, ToolMcpConfirmationDetails, - Config, - EditorType, -} from '@qwen-code/qwen-code-core'; -import { - IdeClient, - ToolConfirmationOutcome, - buildHumanReadableRuleLabel, -} from '@qwen-code/qwen-code-core'; +} from '@qwen-code/qwen-code-core/tools/tools.js'; +import type { EditorType } from '@qwen-code/qwen-code-core/utils/editor.js'; +import { IdeClient } from '@qwen-code/qwen-code-core/ide/ide-client.js'; +import { buildHumanReadableRuleLabel } from '@qwen-code/qwen-code-core/permissions/rule-parser.js'; +import { ToolConfirmationOutcome } from '@qwen-code/qwen-code-core/tools/tools.js'; import type { RadioSelectItem } from '../shared/RadioButtonSelect.js'; import { RadioButtonSelect } from '../shared/RadioButtonSelect.js'; import { MaxSizedBox, MINIMUM_MAX_HEIGHT } from '../shared/MaxSizedBox.js'; diff --git a/packages/cli/src/ui/components/messages/ToolMessage.tsx b/packages/cli/src/ui/components/messages/ToolMessage.tsx index 39881b58761..9a98839b27b 100644 --- a/packages/cli/src/ui/components/messages/ToolMessage.tsx +++ b/packages/cli/src/ui/components/messages/ToolMessage.tsx @@ -15,25 +15,27 @@ import type { ShellStatsBarProps } from '../AnsiOutput.js'; import { MaxSizedBox, MINIMUM_MAX_HEIGHT } from '../shared/MaxSizedBox.js'; import { TodoDisplay } from '../TodoDisplay.js'; import { FindingsDisplay } from '../FindingsDisplay.js'; +import type { Config } from '@qwen-code/qwen-code-core/config/config.js'; import type { TodoResultDisplay, FindingsResultDisplay, AgentResultDisplay, PlanResultDisplay, - AnsiOutput, AnsiOutputDisplay, - Config, McpToolProgressData, FileDiff, TerminalImageDisplay, -} from '@qwen-code/qwen-code-core'; +} from '@qwen-code/qwen-code-core/tools/tools.js'; +import type { AnsiOutput } from '@qwen-code/qwen-code-core/utils/terminalSerializer.js'; import { formatVisionBridgeNoticeDisplay, - isTerminalImageDisplay, isVisionBridgeNoticeDisplay, +} from '@qwen-code/qwen-code-core/services/visionBridge/vision-bridge-service.js'; +import { ToolNames, ToolNamesMigration, -} from '@qwen-code/qwen-code-core'; +} from '@qwen-code/qwen-code-core/tools/tool-names.js'; +import { isTerminalImageDisplay } from '@qwen-code/qwen-code-core/tools/tools.js'; import { ToolConfirmationMessage } from './ToolConfirmationMessage.js'; import { PlanSummaryDisplay } from '../PlanSummaryDisplay.js'; import { ShellInputPrompt } from '../ShellInputPrompt.js'; diff --git a/packages/cli/src/ui/components/shared/VirtualizedList.tsx b/packages/cli/src/ui/components/shared/VirtualizedList.tsx index 84425c56ba3..0d8bb95ba88 100644 --- a/packages/cli/src/ui/components/shared/VirtualizedList.tsx +++ b/packages/cli/src/ui/components/shared/VirtualizedList.tsx @@ -19,7 +19,7 @@ import { useBatchedScroll } from '../../hooks/useBatchedScroll.js'; import { useAnimatedScrollbar } from '../../hooks/useAnimatedScrollbar.js'; import { StaticRender } from './StaticRender.js'; import { type DOMElement, Box, Text, useBoxMetrics } from 'ink'; -import { createDebugLogger } from '@qwen-code/qwen-code-core'; +import { createDebugLogger } from '@qwen-code/qwen-code-core/utils/debugLogger.js'; import { measureElementPosition } from '../../utils/measure-element-position.js'; const debugLogger = createDebugLogger('VIRTUALIZED_LIST'); diff --git a/packages/cli/src/ui/components/subagents/create/CreationSummary.tsx b/packages/cli/src/ui/components/subagents/create/CreationSummary.tsx index 75ce74803e3..79712026795 100644 --- a/packages/cli/src/ui/components/subagents/create/CreationSummary.tsx +++ b/packages/cli/src/ui/components/subagents/create/CreationSummary.tsx @@ -7,11 +7,9 @@ import { useCallback, useState, useEffect } from 'react'; import { Box, Text } from 'ink'; import type { WizardStepProps } from '../types.js'; -import type { - SubagentManager, - SubagentConfig, -} from '@qwen-code/qwen-code-core'; -import { createDebugLogger } from '@qwen-code/qwen-code-core'; +import type { SubagentManager } from '@qwen-code/qwen-code-core/subagents/subagent-manager.js'; +import type { SubagentConfig } from '@qwen-code/qwen-code-core/subagents/types.js'; +import { createDebugLogger } from '@qwen-code/qwen-code-core/utils/debugLogger.js'; import { theme } from '../../../semantic-colors.js'; import { shouldShowColor, getColorForDisplay } from '../utils.js'; import { useLaunchEditor } from '../../../hooks/useLaunchEditor.js'; diff --git a/packages/cli/src/ui/components/subagents/create/ToolSelector.tsx b/packages/cli/src/ui/components/subagents/create/ToolSelector.tsx index 547e14edd19..60d8d822af0 100644 --- a/packages/cli/src/ui/components/subagents/create/ToolSelector.tsx +++ b/packages/cli/src/ui/components/subagents/create/ToolSelector.tsx @@ -8,7 +8,8 @@ import { useState, useMemo, useEffect } from 'react'; import { Box, Text } from 'ink'; import { RadioButtonSelect } from '../../shared/RadioButtonSelect.js'; import type { ToolCategory } from '../types.js'; -import { Kind, type Config } from '@qwen-code/qwen-code-core'; +import type { Config } from '@qwen-code/qwen-code-core/config/config.js'; +import { Kind } from '@qwen-code/qwen-code-core/tools/tools.js'; import { theme } from '../../../semantic-colors.js'; import { t } from '../../../../i18n/index.js'; diff --git a/packages/cli/src/ui/components/subagents/manage/AgentDeleteStep.tsx b/packages/cli/src/ui/components/subagents/manage/AgentDeleteStep.tsx index 215fe4bb32b..ad492d2495b 100644 --- a/packages/cli/src/ui/components/subagents/manage/AgentDeleteStep.tsx +++ b/packages/cli/src/ui/components/subagents/manage/AgentDeleteStep.tsx @@ -5,8 +5,8 @@ */ import { Box, Text } from 'ink'; -import { type SubagentConfig } from '@qwen-code/qwen-code-core'; -import { createDebugLogger } from '@qwen-code/qwen-code-core'; +import type { SubagentConfig } from '@qwen-code/qwen-code-core/subagents/types.js'; +import { createDebugLogger } from '@qwen-code/qwen-code-core/utils/debugLogger.js'; import type { StepNavigationProps } from '../types.js'; import { theme } from '../../../semantic-colors.js'; import { useKeypress } from '../../../hooks/useKeypress.js'; diff --git a/packages/cli/src/ui/components/subagents/manage/AgentsManagerDialog.tsx b/packages/cli/src/ui/components/subagents/manage/AgentsManagerDialog.tsx index 79b8597079e..4c800f6043a 100644 --- a/packages/cli/src/ui/components/subagents/manage/AgentsManagerDialog.tsx +++ b/packages/cli/src/ui/components/subagents/manage/AgentsManagerDialog.tsx @@ -16,8 +16,9 @@ import { ColorSelector } from '../create/ColorSelector.js'; import { MANAGEMENT_STEPS } from '../types.js'; import { theme } from '../../../semantic-colors.js'; import { getColorForDisplay, shouldShowColor } from '../utils.js'; -import type { SubagentConfig, Config } from '@qwen-code/qwen-code-core'; -import { createDebugLogger } from '@qwen-code/qwen-code-core'; +import type { Config } from '@qwen-code/qwen-code-core/config/config.js'; +import type { SubagentConfig } from '@qwen-code/qwen-code-core/subagents/types.js'; +import { createDebugLogger } from '@qwen-code/qwen-code-core/utils/debugLogger.js'; import { useKeypress } from '../../../hooks/useKeypress.js'; import { t } from '../../../../i18n/index.js'; diff --git a/packages/cli/src/ui/components/views/ExtensionsList.tsx b/packages/cli/src/ui/components/views/ExtensionsList.tsx index c6a211eec67..a6161a57500 100644 --- a/packages/cli/src/ui/components/views/ExtensionsList.tsx +++ b/packages/cli/src/ui/components/views/ExtensionsList.tsx @@ -7,10 +7,8 @@ import { Box, Text } from 'ink'; import { useUIState } from '../../contexts/UIStateContext.js'; import { ExtensionUpdateState } from '../../state/extensions.js'; -import { - createDebugLogger, - getExtensionDisplayName, -} from '@qwen-code/qwen-code-core'; +import { getExtensionDisplayName } from '@qwen-code/qwen-code-core/extension/i18n.js'; +import { createDebugLogger } from '@qwen-code/qwen-code-core/utils/debugLogger.js'; import { getCurrentLanguage } from '../../../i18n/index.js'; const debugLogger = createDebugLogger('EXTENSIONS_LIST'); diff --git a/packages/cli/src/ui/components/views/McpStatus.tsx b/packages/cli/src/ui/components/views/McpStatus.tsx index 87a27256d44..f6e8378b0ea 100644 --- a/packages/cli/src/ui/components/views/McpStatus.tsx +++ b/packages/cli/src/ui/components/views/McpStatus.tsx @@ -4,8 +4,8 @@ * SPDX-License-Identifier: Apache-2.0 */ -import type { MCPServerConfig } from '@qwen-code/qwen-code-core'; -import { MCPServerStatus } from '@qwen-code/qwen-code-core'; +import type { MCPServerConfig } from '@qwen-code/qwen-code-core/config/mcp-server-config.js'; +import { MCPServerStatus } from '@qwen-code/qwen-code-core/tools/mcp-status.js'; import { Box, Text } from 'ink'; import type React from 'react'; import { theme } from '../../semantic-colors.js'; diff --git a/packages/cli/src/ui/contexts/BackgroundTaskViewContext.tsx b/packages/cli/src/ui/contexts/BackgroundTaskViewContext.tsx index 89cb88fa574..355da31c6e5 100644 --- a/packages/cli/src/ui/contexts/BackgroundTaskViewContext.tsx +++ b/packages/cli/src/ui/contexts/BackgroundTaskViewContext.tsx @@ -19,7 +19,8 @@ import { useMemo, useState, } from 'react'; -import { type Config, createDebugLogger } from '@qwen-code/qwen-code-core'; +import type { Config } from '@qwen-code/qwen-code-core/config/config.js'; +import { createDebugLogger } from '@qwen-code/qwen-code-core/utils/debugLogger.js'; import { type DialogEntry, useBackgroundTaskView, diff --git a/packages/cli/src/ui/daemon/daemon-tui-adapter.ts b/packages/cli/src/ui/daemon/daemon-tui-adapter.ts index beffc86cc00..0aa2cf16669 100644 --- a/packages/cli/src/ui/daemon/daemon-tui-adapter.ts +++ b/packages/cli/src/ui/daemon/daemon-tui-adapter.ts @@ -9,15 +9,15 @@ import type { RequestPermissionRequest, RequestPermissionResponse, } from '@agentclientprotocol/sdk'; +import { isVisionBridgeNoticeDisplay } from '@qwen-code/qwen-code-core/services/visionBridge/vision-bridge-service.js'; import { - createDebugLogger, FINDING_CONFIDENCES, FINDING_OUTCOMES, FINDING_SEVERITIES, FINDING_SOURCES, - isVisionBridgeNoticeDisplay, REPORT_FINDINGS_LEVELS, -} from '@qwen-code/qwen-code-core'; +} from '@qwen-code/qwen-code-core/tools/report-findings.js'; +import { createDebugLogger } from '@qwen-code/qwen-code-core/utils/debugLogger.js'; import { ToolCallStatus, type HistoryItemToolGroup, diff --git a/packages/cli/src/ui/editors/editorSettingsManager.ts b/packages/cli/src/ui/editors/editorSettingsManager.ts index 612e4ff5694..56f6bd858d2 100644 --- a/packages/cli/src/ui/editors/editorSettingsManager.ts +++ b/packages/cli/src/ui/editors/editorSettingsManager.ts @@ -7,8 +7,8 @@ import { allowEditorTypeInSandbox, checkHasEditorType, - type EditorType, -} from '@qwen-code/qwen-code-core'; +} from '@qwen-code/qwen-code-core/utils/editor.js'; +import type { EditorType } from '@qwen-code/qwen-code-core/utils/editor.js'; export interface EditorDisplay { name: string; diff --git a/packages/cli/src/ui/hooks/use-effort-command.ts b/packages/cli/src/ui/hooks/use-effort-command.ts index d2ea5fad596..717f5966911 100644 --- a/packages/cli/src/ui/hooks/use-effort-command.ts +++ b/packages/cli/src/ui/hooks/use-effort-command.ts @@ -5,8 +5,9 @@ */ import { useState, useCallback } from 'react'; -import type { Config, ReasoningEffort } from '@qwen-code/qwen-code-core'; -import { applyReasoningEffort } from '@qwen-code/qwen-code-core'; +import type { Config } from '@qwen-code/qwen-code-core/config/config.js'; +import type { ReasoningEffort } from '@qwen-code/qwen-code-core/core/reasoning-effort.js'; +import { applyReasoningEffort } from '@qwen-code/qwen-code-core/core/reasoning-effort.js'; import type { LoadedSettings } from '../../config/settings.js'; import { getPersistScopeForModelSelection } from '../../config/modelProvidersScope.js'; import { MessageType, type HistoryItemWithoutId } from '../types.js'; diff --git a/packages/cli/src/ui/hooks/use-output-style-command.ts b/packages/cli/src/ui/hooks/use-output-style-command.ts index 7f1fc0bb027..42e9f31a882 100644 --- a/packages/cli/src/ui/hooks/use-output-style-command.ts +++ b/packages/cli/src/ui/hooks/use-output-style-command.ts @@ -5,11 +5,10 @@ */ import { useState, useCallback, useRef } from 'react'; -import type { Config, OutputStyleDefinition } from '@qwen-code/qwen-code-core'; -import { - BUILT_IN_OUTPUT_STYLES, - createDebugLogger, -} from '@qwen-code/qwen-code-core'; +import type { Config } from '@qwen-code/qwen-code-core/config/config.js'; +import type { OutputStyleDefinition } from '@qwen-code/qwen-code-core/core/output-styles.js'; +import { BUILT_IN_OUTPUT_STYLES } from '@qwen-code/qwen-code-core/core/output-styles.js'; +import { createDebugLogger } from '@qwen-code/qwen-code-core/utils/debugLogger.js'; import type { LoadedSettings } from '../../config/settings.js'; import { t } from '../../i18n/index.js'; import { MessageType, type HistoryItemWithoutId } from '../types.js'; diff --git a/packages/cli/src/ui/hooks/useAgentStreamingState.ts b/packages/cli/src/ui/hooks/useAgentStreamingState.ts index 8094a0fa24a..8c26a4928e2 100644 --- a/packages/cli/src/ui/hooks/useAgentStreamingState.ts +++ b/packages/cli/src/ui/hooks/useAgentStreamingState.ts @@ -14,13 +14,13 @@ */ import { useState, useEffect, useCallback, useMemo, useRef } from 'react'; +import { AgentEventType } from '@qwen-code/qwen-code-core/agents/runtime/agent-events.js'; +import type { AgentEventEmitter } from '@qwen-code/qwen-code-core/agents/runtime/agent-events.js'; +import type { AgentInteractive } from '@qwen-code/qwen-code-core/agents/runtime/agent-interactive.js'; import { AgentStatus, - AgentEventType, isTerminalStatus, - type AgentInteractive, - type AgentEventEmitter, -} from '@qwen-code/qwen-code-core'; +} from '@qwen-code/qwen-code-core/agents/runtime/agent-types.js'; import { StreamingState } from '../types.js'; import { useTimer } from './useTimer.js'; diff --git a/packages/cli/src/ui/hooks/useArenaInProcess.ts b/packages/cli/src/ui/hooks/useArenaInProcess.ts index c75634a2a98..9a0b34cabf8 100644 --- a/packages/cli/src/ui/hooks/useArenaInProcess.ts +++ b/packages/cli/src/ui/hooks/useArenaInProcess.ts @@ -16,16 +16,16 @@ */ import { useEffect, useRef } from 'react'; -import { - ArenaEventType, - ArenaSessionStatus, - DISPLAY_MODE, - type ArenaAgentStartEvent, - type ArenaManager, - type ArenaSessionCompleteEvent, - type Config, - type InProcessBackend, -} from '@qwen-code/qwen-code-core'; +import type { ArenaManager } from '@qwen-code/qwen-code-core/agents/arena/ArenaManager.js'; +import { ArenaEventType } from '@qwen-code/qwen-code-core/agents/arena/arena-events.js'; +import type { + ArenaAgentStartEvent, + ArenaSessionCompleteEvent, +} from '@qwen-code/qwen-code-core/agents/arena/arena-events.js'; +import { ArenaSessionStatus } from '@qwen-code/qwen-code-core/agents/arena/types.js'; +import type { InProcessBackend } from '@qwen-code/qwen-code-core/agents/backends/InProcessBackend.js'; +import { DISPLAY_MODE } from '@qwen-code/qwen-code-core/agents/backends/types.js'; +import type { Config } from '@qwen-code/qwen-code-core/config/config.js'; import type { AgentViewActions } from '../contexts/AgentViewContext.js'; import { theme } from '../semantic-colors.js'; diff --git a/packages/cli/src/ui/hooks/useAtCompletion.ts b/packages/cli/src/ui/hooks/useAtCompletion.ts index f84a7612243..32fd3c120b8 100644 --- a/packages/cli/src/ui/hooks/useAtCompletion.ts +++ b/packages/cli/src/ui/hooks/useAtCompletion.ts @@ -5,8 +5,10 @@ */ import { useEffect, useReducer, useRef } from 'react'; -import type { Config, FileSearch } from '@qwen-code/qwen-code-core'; -import { FileSearchFactory, escapePath } from '@qwen-code/qwen-code-core'; +import type { Config } from '@qwen-code/qwen-code-core/config/config.js'; +import type { FileSearch } from '@qwen-code/qwen-code-core/utils/filesearch/fileSearch.js'; +import { FileSearchFactory } from '@qwen-code/qwen-code-core/utils/filesearch/fileSearch.js'; +import { escapePath } from '@qwen-code/qwen-code-core/utils/paths.js'; import type { Suggestion } from '../components/SuggestionsDisplay.js'; import { MAX_SUGGESTIONS_TO_SHOW } from '../components/SuggestionsDisplay.js'; import { matchMcpServerPrefix, buildMcpResourceRef } from './mcpResourceRef.js'; diff --git a/packages/cli/src/ui/hooks/useAttentionNotifications.ts b/packages/cli/src/ui/hooks/useAttentionNotifications.ts index 842af64b8e6..d256e30288b 100644 --- a/packages/cli/src/ui/hooks/useAttentionNotifications.ts +++ b/packages/cli/src/ui/hooks/useAttentionNotifications.ts @@ -7,11 +7,9 @@ import { useEffect, useMemo, useRef } from 'react'; import { StreamingState } from '../types.js'; import type { LoadedSettings } from '../../config/settings.js'; -import type { Config } from '@qwen-code/qwen-code-core'; -import { - fireNotificationHook, - NotificationType, -} from '@qwen-code/qwen-code-core'; +import type { Config } from '@qwen-code/qwen-code-core/config/config.js'; +import { fireNotificationHook } from '@qwen-code/qwen-code-core/core/toolHookTriggers.js'; +import { NotificationType } from '@qwen-code/qwen-code-core/hooks/types.js'; import type { TerminalNotification } from './useTerminalNotification.js'; import type { TrackedToolCall } from './useReactToolScheduler.js'; import { sendNotification } from '../../services/notificationService.js'; diff --git a/packages/cli/src/ui/hooks/useCommandMigration.ts b/packages/cli/src/ui/hooks/useCommandMigration.ts index 9de6894ab6d..0b523e9f775 100644 --- a/packages/cli/src/ui/hooks/useCommandMigration.ts +++ b/packages/cli/src/ui/hooks/useCommandMigration.ts @@ -5,7 +5,7 @@ */ import { useEffect, useState } from 'react'; -import { Storage } from '@qwen-code/qwen-code-core'; +import { Storage } from '@qwen-code/qwen-code-core/config/storage.js'; import { detectTomlCommands } from '../../services/command-migration-tool.js'; import type { LoadedSettings } from '../../config/settings.js'; diff --git a/packages/cli/src/ui/hooks/useConfigInitMessage.ts b/packages/cli/src/ui/hooks/useConfigInitMessage.ts index ffc12d24485..df0196e4886 100644 --- a/packages/cli/src/ui/hooks/useConfigInitMessage.ts +++ b/packages/cli/src/ui/hooks/useConfigInitMessage.ts @@ -6,7 +6,8 @@ import { useEffect, useState } from 'react'; import { appEvents } from '../../utils/events.js'; -import { type McpClient, MCPServerStatus } from '@qwen-code/qwen-code-core'; +import type { McpClient } from '@qwen-code/qwen-code-core/tools/mcp-client.js'; +import { MCPServerStatus } from '@qwen-code/qwen-code-core/tools/mcp-status.js'; import { t } from '../../i18n/index.js'; // Tracks MCP connection progress. Returns the current status string while diff --git a/packages/cli/src/ui/hooks/useContextualTips.ts b/packages/cli/src/ui/hooks/useContextualTips.ts index 663f24dbda2..49aac68dc2e 100644 --- a/packages/cli/src/ui/hooks/useContextualTips.ts +++ b/packages/cli/src/ui/hooks/useContextualTips.ts @@ -10,11 +10,9 @@ */ import { useEffect, useRef } from 'react'; -import { - type Config, - DEFAULT_TOKEN_LIMIT, - computeThresholds, -} from '@qwen-code/qwen-code-core'; +import type { Config } from '@qwen-code/qwen-code-core/config/config.js'; +import { DEFAULT_TOKEN_LIMIT } from '@qwen-code/qwen-code-core/core/tokenLimits.js'; +import { computeThresholds } from '@qwen-code/qwen-code-core/services/chatCompressionService.js'; import { StreamingState, MessageType, diff --git a/packages/cli/src/ui/hooks/useDiffData.ts b/packages/cli/src/ui/hooks/useDiffData.ts index f2cfd5a1a35..60687fc2c91 100644 --- a/packages/cli/src/ui/hooks/useDiffData.ts +++ b/packages/cli/src/ui/hooks/useDiffData.ts @@ -6,12 +6,12 @@ import { useEffect, useState } from 'react'; import type { Hunk } from 'diff'; +import { createDebugLogger } from '@qwen-code/qwen-code-core/utils/debugLogger.js'; import { - createDebugLogger, fetchGitDiff, fetchGitDiffHunks, - type GitDiffResult, -} from '@qwen-code/qwen-code-core'; +} from '@qwen-code/qwen-code-core/utils/gitDiff.js'; +import type { GitDiffResult } from '@qwen-code/qwen-code-core/utils/gitDiff.js'; const debugLogger = createDebugLogger('DiffDialog'); diff --git a/packages/cli/src/ui/hooks/useExtensionUpdates.ts b/packages/cli/src/ui/hooks/useExtensionUpdates.ts index 17a3a22ac7a..d4c86afec64 100644 --- a/packages/cli/src/ui/hooks/useExtensionUpdates.ts +++ b/packages/cli/src/ui/hooks/useExtensionUpdates.ts @@ -4,10 +4,8 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { - type ExtensionManager, - getExtensionDisplayName, -} from '@qwen-code/qwen-code-core'; +import type { ExtensionManager } from '@qwen-code/qwen-code-core/extension/extensionManager.js'; +import { getExtensionDisplayName } from '@qwen-code/qwen-code-core/extension/i18n.js'; import { getCurrentLanguage } from '../../i18n/index.js'; import { getErrorMessage } from '../../utils/errors.js'; import { diff --git a/packages/cli/src/ui/hooks/useFeedbackDialog.ts b/packages/cli/src/ui/hooks/useFeedbackDialog.ts index 44448577396..6e875e887dc 100644 --- a/packages/cli/src/ui/hooks/useFeedbackDialog.ts +++ b/packages/cli/src/ui/hooks/useFeedbackDialog.ts @@ -1,14 +1,12 @@ import { useState, useCallback, useEffect } from 'react'; import * as fs from 'node:fs'; -import { - type Config, - createDebugLogger, - logUserFeedback, - UserFeedbackEvent, - type UserFeedbackRating, - isNodeError, - Storage, -} from '@qwen-code/qwen-code-core'; +import type { Config } from '@qwen-code/qwen-code-core/config/config.js'; +import { Storage } from '@qwen-code/qwen-code-core/config/storage.js'; +import { logUserFeedback } from '@qwen-code/qwen-code-core/telemetry/loggers.js'; +import { UserFeedbackEvent } from '@qwen-code/qwen-code-core/telemetry/types.js'; +import type { UserFeedbackRating } from '@qwen-code/qwen-code-core/telemetry/types.js'; +import { createDebugLogger } from '@qwen-code/qwen-code-core/utils/debugLogger.js'; +import { isNodeError } from '@qwen-code/qwen-code-core/utils/errors.js'; import { StreamingState, MessageType, type HistoryItem } from '../types.js'; import { SettingScope, type LoadedSettings } from '../../config/settings.js'; import type { SessionStatsState } from '../contexts/SessionContext.js'; diff --git a/packages/cli/src/ui/hooks/useGitBranchName.test.ts b/packages/cli/src/ui/hooks/useGitBranchName.test.ts index 1f2c69f8640..b4097226ac9 100644 --- a/packages/cli/src/ui/hooks/useGitBranchName.test.ts +++ b/packages/cli/src/ui/hooks/useGitBranchName.test.ts @@ -16,7 +16,10 @@ import { } from 'vitest'; import { act } from 'react'; import { renderHook } from '@testing-library/react'; -import { resolveBranchName, watchRepoBranch } from '@qwen-code/qwen-code-core'; +import { + resolveBranchName, + watchRepoBranch, +} from '@qwen-code/qwen-code-core/utils/gitDirect.js'; import { useGitBranchName, BRANCH_POLL_INTERVAL_MS, @@ -25,7 +28,7 @@ import { // The hook is a thin wrapper over core's gitDirect helpers; the direct-read // logic itself is covered by core's gitDirect.test.ts. Here we mock those two // functions and exercise the hook's wiring and lifecycle. -vi.mock('@qwen-code/qwen-code-core', () => ({ +vi.mock('@qwen-code/qwen-code-core/utils/gitDirect.js', () => ({ resolveBranchName: vi.fn(), watchRepoBranch: vi.fn(), })); diff --git a/packages/cli/src/ui/hooks/useGitBranchName.ts b/packages/cli/src/ui/hooks/useGitBranchName.ts index d6371d82891..c9598f9717c 100644 --- a/packages/cli/src/ui/hooks/useGitBranchName.ts +++ b/packages/cli/src/ui/hooks/useGitBranchName.ts @@ -5,7 +5,10 @@ */ import { useState, useEffect, useRef } from 'react'; -import { resolveBranchName, watchRepoBranch } from '@qwen-code/qwen-code-core'; +import { + resolveBranchName, + watchRepoBranch, +} from '@qwen-code/qwen-code-core/utils/gitDirect.js'; /** * Polling interval (ms) for the branch-name fallback. `fs.watch` on the diff --git a/packages/cli/src/ui/hooks/useLaunchEditor.ts b/packages/cli/src/ui/hooks/useLaunchEditor.ts index 839a0ff056b..71819cf0e41 100644 --- a/packages/cli/src/ui/hooks/useLaunchEditor.ts +++ b/packages/cli/src/ui/hooks/useLaunchEditor.ts @@ -1,7 +1,7 @@ import { useCallback } from 'react'; import { useStdin } from 'ink'; -import type { EditorType } from '@qwen-code/qwen-code-core'; -import { getEditorExecutable } from '@qwen-code/qwen-code-core'; +import type { EditorType } from '@qwen-code/qwen-code-core/utils/editor.js'; +import { getEditorExecutable } from '@qwen-code/qwen-code-core/utils/editor.js'; import { spawnSync } from 'child_process'; import { useSettings } from '../contexts/SettingsContext.js'; diff --git a/packages/cli/src/ui/hooks/useLogger.ts b/packages/cli/src/ui/hooks/useLogger.ts index a388f836983..2a54d3f7719 100644 --- a/packages/cli/src/ui/hooks/useLogger.ts +++ b/packages/cli/src/ui/hooks/useLogger.ts @@ -5,8 +5,8 @@ */ import { useState, useEffect } from 'react'; -import type { Storage } from '@qwen-code/qwen-code-core'; -import { Logger } from '@qwen-code/qwen-code-core'; +import type { Storage } from '@qwen-code/qwen-code-core/config/storage.js'; +import { Logger } from '@qwen-code/qwen-code-core/core/logger.js'; /** * Hook to manage the logger instance. diff --git a/packages/cli/src/ui/hooks/useMCPHealth.ts b/packages/cli/src/ui/hooks/useMCPHealth.ts index 9a18387e47a..4161bb99102 100644 --- a/packages/cli/src/ui/hooks/useMCPHealth.ts +++ b/packages/cli/src/ui/hooks/useMCPHealth.ts @@ -19,7 +19,7 @@ import { addMCPStatusChangeListener, removeMCPStatusChangeListener, getAllMCPServerStatuses, -} from '@qwen-code/qwen-code-core'; +} from '@qwen-code/qwen-code-core/tools/mcp-status.js'; export interface MCPHealthSnapshot { /** Total servers tracked by the registry (configured + discovered). */ diff --git a/packages/cli/src/ui/hooks/useMemoryMonitor.test.ts b/packages/cli/src/ui/hooks/useMemoryMonitor.test.ts index c5edb8c1f78..db90239eed8 100644 --- a/packages/cli/src/ui/hooks/useMemoryMonitor.test.ts +++ b/packages/cli/src/ui/hooks/useMemoryMonitor.test.ts @@ -17,7 +17,7 @@ const { mockDebugLogger } = vi.hoisted(() => ({ error: vi.fn(), }, })); -vi.mock('@qwen-code/qwen-code-core', () => ({ +vi.mock('@qwen-code/qwen-code-core/utils/debugLogger.js', () => ({ createDebugLogger: () => mockDebugLogger, })); diff --git a/packages/cli/src/ui/hooks/useMemoryMonitor.ts b/packages/cli/src/ui/hooks/useMemoryMonitor.ts index ade61c84c7d..3087e15adda 100644 --- a/packages/cli/src/ui/hooks/useMemoryMonitor.ts +++ b/packages/cli/src/ui/hooks/useMemoryMonitor.ts @@ -8,7 +8,7 @@ import { useEffect, useRef } from 'react'; import process from 'node:process'; import os from 'node:os'; import v8 from 'v8'; -import { createDebugLogger } from '@qwen-code/qwen-code-core'; +import { createDebugLogger } from '@qwen-code/qwen-code-core/utils/debugLogger.js'; import { type HistoryItemWithoutId, MessageType } from '../types.js'; const debugLogger = createDebugLogger('MEMORY_MONITOR'); diff --git a/packages/cli/src/ui/hooks/useTeamInProcess.ts b/packages/cli/src/ui/hooks/useTeamInProcess.ts index ce794a7e25d..e1990d40283 100644 --- a/packages/cli/src/ui/hooks/useTeamInProcess.ts +++ b/packages/cli/src/ui/hooks/useTeamInProcess.ts @@ -17,15 +17,15 @@ */ import { useEffect, useRef } from 'react'; -import { - DISPLAY_MODE, - TeamEventType, - type Config, - type InProcessBackend, - type TeamManager, - type TeammateJoinedEvent, - type TeammateExitedEvent, -} from '@qwen-code/qwen-code-core'; +import type { InProcessBackend } from '@qwen-code/qwen-code-core/agents/backends/InProcessBackend.js'; +import { DISPLAY_MODE } from '@qwen-code/qwen-code-core/agents/backends/types.js'; +import type { TeamManager } from '@qwen-code/qwen-code-core/agents/team/TeamManager.js'; +import { TeamEventType } from '@qwen-code/qwen-code-core/agents/team/team-events.js'; +import type { + TeammateJoinedEvent, + TeammateExitedEvent, +} from '@qwen-code/qwen-code-core/agents/team/team-events.js'; +import type { Config } from '@qwen-code/qwen-code-core/config/config.js'; import type { AgentViewActions } from '../contexts/AgentViewContext.js'; import { theme } from '../semantic-colors.js'; diff --git a/packages/cli/src/ui/hooks/useWorktreeSession.ts b/packages/cli/src/ui/hooks/useWorktreeSession.ts index 88b919c690c..6173ce591c2 100644 --- a/packages/cli/src/ui/hooks/useWorktreeSession.ts +++ b/packages/cli/src/ui/hooks/useWorktreeSession.ts @@ -8,8 +8,9 @@ import { useState, useEffect } from 'react'; import fs from 'node:fs'; import fsPromises from 'node:fs/promises'; import path from 'node:path'; -import type { Config, WorktreeSession } from '@qwen-code/qwen-code-core'; -import { readWorktreeSession } from '@qwen-code/qwen-code-core'; +import type { Config } from '@qwen-code/qwen-code-core/config/config.js'; +import type { WorktreeSession } from '@qwen-code/qwen-code-core/services/worktreeSessionService.js'; +import { readWorktreeSession } from '@qwen-code/qwen-code-core/services/worktreeSessionService.js'; /** * Watches the active session's WorktreeSession sidecar file and returns diff --git a/packages/cli/src/ui/hooks/vim.ts b/packages/cli/src/ui/hooks/vim.ts index e112f749884..3d1cc593e83 100644 --- a/packages/cli/src/ui/hooks/vim.ts +++ b/packages/cli/src/ui/hooks/vim.ts @@ -5,7 +5,7 @@ */ import { useCallback, useReducer, useEffect, useRef } from 'react'; -import { createDebugLogger } from '@qwen-code/qwen-code-core'; +import { createDebugLogger } from '@qwen-code/qwen-code-core/utils/debugLogger.js'; import type { Key } from './useKeypress.js'; import type { TextBuffer } from '../components/shared/text-buffer.js'; import { diff --git a/packages/cli/src/ui/opentui/dialogs-confirm.tsx b/packages/cli/src/ui/opentui/dialogs-confirm.tsx index f4c512fdb45..7d47335110a 100644 --- a/packages/cli/src/ui/opentui/dialogs-confirm.tsx +++ b/packages/cli/src/ui/opentui/dialogs-confirm.tsx @@ -37,11 +37,11 @@ import { useState, type ReactNode, } from 'react'; -import { - ToolConfirmationOutcome, - type ToolCallConfirmationDetails, - type ToolConfirmationPayload, -} from '@qwen-code/qwen-code-core'; +import { ToolConfirmationOutcome } from '@qwen-code/qwen-code-core/tools/tools.js'; +import type { + ToolCallConfirmationDetails, + ToolConfirmationPayload, +} from '@qwen-code/qwen-code-core/tools/tools.js'; import { useKeyboard } from '@opentui/react'; import { C } from './theme.js'; import { toOriginalKey } from './key-map.js'; diff --git a/packages/cli/src/ui/opentui/dialogs-mcp.tsx b/packages/cli/src/ui/opentui/dialogs-mcp.tsx index 097aa0bfe21..de04ba6d597 100644 --- a/packages/cli/src/ui/opentui/dialogs-mcp.tsx +++ b/packages/cli/src/ui/opentui/dialogs-mcp.tsx @@ -19,7 +19,7 @@ import { useState } from 'react'; import { useKeyboard } from '@opentui/react'; import { C } from './theme.js'; import { t } from '../../i18n/index.js'; -import { MCPServerStatus } from '@qwen-code/qwen-code-core'; +import { MCPServerStatus } from '@qwen-code/qwen-code-core/tools/mcp-status.js'; import { ICON } from '../constants.js'; import { toOriginalKey } from './key-map.js'; import { keyMatchers, Command } from '../keyMatchers.js'; diff --git a/packages/cli/src/ui/opentui/dialogs-memory-status.tsx b/packages/cli/src/ui/opentui/dialogs-memory-status.tsx index 5db4fb40fc2..93cf9ff2966 100644 --- a/packages/cli/src/ui/opentui/dialogs-memory-status.tsx +++ b/packages/cli/src/ui/opentui/dialogs-memory-status.tsx @@ -16,8 +16,9 @@ import { useLayoutEffect } from 'react'; import { useRenderer } from '@opentui/react'; import fs from 'node:fs'; import path from 'node:path'; -import type { Config } from '@qwen-code/qwen-code-core'; -import { getAllGeminiMdFilenames, Storage } from '@qwen-code/qwen-code-core'; +import type { Config } from '@qwen-code/qwen-code-core/config/config.js'; +import { Storage } from '@qwen-code/qwen-code-core/config/storage.js'; +import { getAllGeminiMdFilenames } from '@qwen-code/qwen-code-core/utils/memory-constants.js'; import type { LoadedSettings } from '../../config/settings.js'; import { STATUS_LINE_PRESET_ITEMS } from '../statusLinePresets.js'; import { C } from './theme.js'; diff --git a/packages/cli/src/ui/opentui/dialogs-misc.tsx b/packages/cli/src/ui/opentui/dialogs-misc.tsx index ce6dcd4b958..7e4068dcc57 100644 --- a/packages/cli/src/ui/opentui/dialogs-misc.tsx +++ b/packages/cli/src/ui/opentui/dialogs-misc.tsx @@ -22,16 +22,14 @@ import { type ReactNode, } from 'react'; import { useRenderer, useKeyboard } from '@opentui/react'; -import type { - Config, - EditorType, - SessionListItem, -} from '@qwen-code/qwen-code-core'; +import type { Config } from '@qwen-code/qwen-code-core/config/config.js'; +import type { SessionListItem } from '@qwen-code/qwen-code-core/services/sessionService.js'; +import type { EditorType } from '@qwen-code/qwen-code-core/utils/editor.js'; import { allowEditorTypeInSandbox, checkHasEditorType, isEditorAvailable, -} from '@qwen-code/qwen-code-core'; +} from '@qwen-code/qwen-code-core/utils/editor.js'; import { SettingScope, type LoadedSettings } from '../../config/settings.js'; import { EDITOR_DISPLAY_NAMES, diff --git a/packages/cli/src/ui/opentui/dialogs-modes.tsx b/packages/cli/src/ui/opentui/dialogs-modes.tsx index 03b9945865d..8f5891c3434 100644 --- a/packages/cli/src/ui/opentui/dialogs-modes.tsx +++ b/packages/cli/src/ui/opentui/dialogs-modes.tsx @@ -20,15 +20,15 @@ import { type ReactNode, } from 'react'; import { useRenderer, useKeyboard } from '@opentui/react'; +import { APPROVAL_MODES } from '@qwen-code/qwen-code-core/config/approval-mode.js'; +import type { ApprovalMode } from '@qwen-code/qwen-code-core/config/approval-mode.js'; +import type { Config } from '@qwen-code/qwen-code-core/config/config.js'; +import type { OutputStyleDefinition } from '@qwen-code/qwen-code-core/core/output-styles.js'; import { applyReasoningEffort, - APPROVAL_MODES, REASONING_EFFORT_TIERS, - type ApprovalMode, - type OutputStyleDefinition, - type ReasoningEffort, - type Config, -} from '@qwen-code/qwen-code-core'; +} from '@qwen-code/qwen-code-core/core/reasoning-effort.js'; +import type { ReasoningEffort } from '@qwen-code/qwen-code-core/core/reasoning-effort.js'; import { SettingScope, type LoadedSettings } from '../../config/settings.js'; import { getPersistScopeForModelSelection } from '../../config/modelProvidersScope.js'; import { diff --git a/packages/cli/src/ui/opentui/dialogs-permissions.tsx b/packages/cli/src/ui/opentui/dialogs-permissions.tsx index 49713ce4766..4f73c256915 100644 --- a/packages/cli/src/ui/opentui/dialogs-permissions.tsx +++ b/packages/cli/src/ui/opentui/dialogs-permissions.tsx @@ -25,7 +25,8 @@ import { useKeyboard } from '@opentui/react'; import { C } from './theme.js'; import { t } from '../../i18n/index.js'; import { SettingScope } from '../../config/settings.js'; -import { isPathWithinRoot, parseRule } from '@qwen-code/qwen-code-core'; +import { parseRule } from '@qwen-code/qwen-code-core/permissions/rule-parser.js'; +import { isPathWithinRoot } from '@qwen-code/qwen-code-core/utils/workspaceContext.js'; import { toOriginalKey } from './key-map.js'; import { matchesSearchQuery } from './dialogs-core.js'; import { diff --git a/packages/cli/src/ui/opentui/dialogs-stats-skills.tsx b/packages/cli/src/ui/opentui/dialogs-stats-skills.tsx index 471b1a9ecde..363159460e6 100644 --- a/packages/cli/src/ui/opentui/dialogs-stats-skills.tsx +++ b/packages/cli/src/ui/opentui/dialogs-stats-skills.tsx @@ -16,8 +16,8 @@ import { useEffect, useLayoutEffect, useState, type ReactNode } from 'react'; import { useRenderer, useKeyboard } from '@opentui/react'; -import type { Config } from '@qwen-code/qwen-code-core'; -import { uiTelemetryService } from '@qwen-code/qwen-code-core'; +import type { Config } from '@qwen-code/qwen-code-core/config/config.js'; +import { uiTelemetryService } from '@qwen-code/qwen-code-core/telemetry/uiTelemetry.js'; import { computeSessionStats } from '../utils/computeStats.js'; import { formatDuration } from '../utils/formatters.js'; import { diff --git a/packages/cli/src/ui/opentui/input-prompt-model.ts b/packages/cli/src/ui/opentui/input-prompt-model.ts index 0b1692e2927..78aa79b502f 100644 --- a/packages/cli/src/ui/opentui/input-prompt-model.ts +++ b/packages/cli/src/ui/opentui/input-prompt-model.ts @@ -28,7 +28,7 @@ * and the keyboard; it delegates every decision here. */ -import { escapePath } from '@qwen-code/qwen-code-core'; +import { escapePath } from '@qwen-code/qwen-code-core/utils/paths.js'; import { Fzf, type FzfResultItem } from 'fzf'; import type { Suggestion } from '../utils/suggestions.js'; import { MAX_SUGGESTIONS_TO_SHOW } from '../utils/suggestions.js'; diff --git a/packages/cli/src/ui/opentui/live-turn.ts b/packages/cli/src/ui/opentui/live-turn.ts index b8f8396672c..eb0a11e5d0f 100644 --- a/packages/cli/src/ui/opentui/live-turn.ts +++ b/packages/cli/src/ui/opentui/live-turn.ts @@ -26,12 +26,12 @@ import { useCallback, useEffect, useRef, useState } from 'react'; import { readFileSync } from 'node:fs'; -import type { Config } from '@qwen-code/qwen-code-core'; +import type { Config } from '@qwen-code/qwen-code-core/config/config.js'; import { collectText, normalizeParts, - ToolConfirmationOutcome, -} from '@qwen-code/qwen-code-core'; +} from '@qwen-code/qwen-code-core/services/visionBridge/image-part-utils.js'; +import { ToolConfirmationOutcome } from '@qwen-code/qwen-code-core/tools/tools.js'; import type { Part, PartListUnion } from '@google/genai'; import { foldLiveEvent, diff --git a/packages/cli/src/ui/opentui/opentui-assets.ts b/packages/cli/src/ui/opentui/opentui-assets.ts index fd0741ba639..2dc6ab2e61a 100644 --- a/packages/cli/src/ui/opentui/opentui-assets.ts +++ b/packages/cli/src/ui/opentui/opentui-assets.ts @@ -33,7 +33,7 @@ import { statSync } from 'node:fs'; import { join } from 'node:path'; -import { resolveBundleDir } from '@qwen-code/qwen-code-core'; +import { resolveBundleDir } from '@qwen-code/qwen-code-core/utils/bundlePaths.js'; /** Directory name the bundle step writes the relocated assets into. */ export const OPENTUI_ASSETS_DIRNAME = 'opentui-assets'; diff --git a/packages/cli/src/ui/opentui/opentui-host.ts b/packages/cli/src/ui/opentui/opentui-host.ts index 897ff81fbf9..dd0086db8ad 100644 --- a/packages/cli/src/ui/opentui/opentui-host.ts +++ b/packages/cli/src/ui/opentui/opentui-host.ts @@ -25,11 +25,9 @@ */ import type { ReactNode } from 'react'; -import { - createDebugLogger, - type Config, - type Logger, -} from '@qwen-code/qwen-code-core'; +import type { Config } from '@qwen-code/qwen-code-core/config/config.js'; +import type { Logger } from '@qwen-code/qwen-code-core/core/logger.js'; +import { createDebugLogger } from '@qwen-code/qwen-code-core/utils/debugLogger.js'; import { type ConfirmationRequest, type HistoryItem, diff --git a/packages/cli/src/ui/opentui/session-compaction.tsx b/packages/cli/src/ui/opentui/session-compaction.tsx index 1f2282a0e29..598d8120575 100644 --- a/packages/cli/src/ui/opentui/session-compaction.tsx +++ b/packages/cli/src/ui/opentui/session-compaction.tsx @@ -15,7 +15,7 @@ */ import { useEffect, useState } from 'react'; -import { CompressionStatus } from '@qwen-code/qwen-code-core'; +import { CompressionStatus } from '@qwen-code/qwen-code-core/core/turn.js'; import { t } from '../../i18n/index.js'; import { ICON } from '../constants.js'; import { C } from './theme.js'; diff --git a/packages/cli/src/ui/opentui/session-rewind-model.ts b/packages/cli/src/ui/opentui/session-rewind-model.ts index addeb31dc8c..e7c8f8157bf 100644 --- a/packages/cli/src/ui/opentui/session-rewind-model.ts +++ b/packages/cli/src/ui/opentui/session-rewind-model.ts @@ -14,7 +14,7 @@ import { t } from '../../i18n/index.js'; import { isSlashCommand } from '../utils/commandUtils.js'; import { isUserTextContent } from '../utils/historyMapping.js'; -import { getStartupContextLength } from '@qwen-code/qwen-code-core'; +import { getStartupContextLength } from '@qwen-code/qwen-code-core/core/environmentContext.js'; import type { Content } from '@google/genai'; export const REWIND_MAX_VISIBLE_ITEMS = 7; diff --git a/packages/cli/src/ui/opentui/session-switch.ts b/packages/cli/src/ui/opentui/session-switch.ts index cc5ba64169a..ac46642f7b0 100644 --- a/packages/cli/src/ui/opentui/session-switch.ts +++ b/packages/cli/src/ui/opentui/session-switch.ts @@ -22,15 +22,15 @@ */ import { randomUUID } from 'node:crypto'; +import type { Config } from '@qwen-code/qwen-code-core/config/config.js'; +import { buildSessionRecoveryPlan } from '@qwen-code/qwen-code-core/core/session-recovery.js'; +import { SessionStartSource } from '@qwen-code/qwen-code-core/hooks/types.js'; +import type { ChatRecord } from '@qwen-code/qwen-code-core/services/chatRecordingService.js'; import { SessionService, - buildSessionRecoveryPlan, computeUniqueBranchTitle, - SessionStartSource, - type ChatRecord, - type Config, - type ResumedSessionData, -} from '@qwen-code/qwen-code-core'; +} from '@qwen-code/qwen-code-core/services/sessionService.js'; +import type { ResumedSessionData } from '@qwen-code/qwen-code-core/services/sessionService.js'; import type { HistoryItem, HistoryItemWithoutId } from '../types.js'; import { MessageType } from '../types.js'; import type { LoadedSettings } from '../../config/settings.js'; diff --git a/packages/cli/src/ui/utils/customBanner.ts b/packages/cli/src/ui/utils/customBanner.ts index 163439a3438..f0f074380af 100644 --- a/packages/cli/src/ui/utils/customBanner.ts +++ b/packages/cli/src/ui/utils/customBanner.ts @@ -6,13 +6,13 @@ import * as fs from 'node:fs'; import * as path from 'node:path'; +import { createDebugLogger } from '@qwen-code/qwen-code-core/utils/debugLogger.js'; import { - createDebugLogger, stripTerminalControlSequences, TERMINAL_OSC_REGEX, TERMINAL_CSI_REGEX, TERMINAL_SHIFT_DCS_REGEX, -} from '@qwen-code/qwen-code-core'; +} from '@qwen-code/qwen-code-core/utils/terminalSafe.js'; import type { LoadedSettings, SettingsFile } from '../../config/settings.js'; import type { AsciiArtSource, diff --git a/packages/cli/src/ui/utils/displayUtils.ts b/packages/cli/src/ui/utils/displayUtils.ts index 7e7ff1cf73e..46db34d3552 100644 --- a/packages/cli/src/ui/utils/displayUtils.ts +++ b/packages/cli/src/ui/utils/displayUtils.ts @@ -6,7 +6,7 @@ import { theme } from '../semantic-colors.js'; import { ICON } from '../constants.js'; -import { AgentStatus } from '@qwen-code/qwen-code-core'; +import { AgentStatus } from '@qwen-code/qwen-code-core/agents/runtime/agent-types.js'; // --- Status Labels --- diff --git a/packages/cli/src/ui/utils/modelsBySource.ts b/packages/cli/src/ui/utils/modelsBySource.ts index 2937024e859..4aefb74030b 100644 --- a/packages/cli/src/ui/utils/modelsBySource.ts +++ b/packages/cli/src/ui/utils/modelsBySource.ts @@ -4,11 +4,11 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { - MAIN_SOURCE, - type ModelMetrics, - type ModelMetricsCore, -} from '@qwen-code/qwen-code-core'; +import type { + ModelMetrics, + ModelMetricsCore, +} from '@qwen-code/qwen-code-core/telemetry/uiTelemetry.js'; +import { MAIN_SOURCE } from '@qwen-code/qwen-code-core/utils/subagentNameContext.js'; /** * One entry in the flattened view of the `models` metric map. Each entry diff --git a/packages/cli/src/ui/utils/terminal-resize-reflow.ts b/packages/cli/src/ui/utils/terminal-resize-reflow.ts index 26176b688db..b3f7fd4dde8 100644 --- a/packages/cli/src/ui/utils/terminal-resize-reflow.ts +++ b/packages/cli/src/ui/utils/terminal-resize-reflow.ts @@ -5,7 +5,7 @@ */ import ansiEscapes from 'ansi-escapes'; -import { createDebugLogger } from '@qwen-code/qwen-code-core'; +import { createDebugLogger } from '@qwen-code/qwen-code-core/utils/debugLogger.js'; import stringWidth from 'string-width'; import stripAnsi from 'strip-ansi'; import { diff --git a/packages/cli/src/ui/utils/terminalRedrawOptimizer.ts b/packages/cli/src/ui/utils/terminalRedrawOptimizer.ts index 90aad2b7f64..b99167ca93d 100644 --- a/packages/cli/src/ui/utils/terminalRedrawOptimizer.ts +++ b/packages/cli/src/ui/utils/terminalRedrawOptimizer.ts @@ -6,7 +6,7 @@ import ansiEscapes from 'ansi-escapes'; -import { isWsl } from '@qwen-code/qwen-code-core'; +import { isWsl } from '@qwen-code/qwen-code-core/utils/terminal-env.js'; const ESC = '\u001B['; export const ERASE_LINE = `${ESC}2K`; diff --git a/packages/cli/src/ui/utils/tool-display-map.ts b/packages/cli/src/ui/utils/tool-display-map.ts index 22ab2198b0a..cae5a4c415e 100644 --- a/packages/cli/src/ui/utils/tool-display-map.ts +++ b/packages/cli/src/ui/utils/tool-display-map.ts @@ -4,7 +4,10 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { ToolDisplayNames, ToolNames } from '@qwen-code/qwen-code-core'; +import { + ToolDisplayNames, + ToolNames, +} from '@qwen-code/qwen-code-core/tools/tool-names.js'; /** * Internal-tool-name → user-facing display-name lookup diff --git a/packages/cli/src/utils/earlyInputCapture.ts b/packages/cli/src/utils/earlyInputCapture.ts index 6bd8aa6136a..1c9d87b8086 100644 --- a/packages/cli/src/utils/earlyInputCapture.ts +++ b/packages/cli/src/utils/earlyInputCapture.ts @@ -12,7 +12,7 @@ * user input being lost during startup. */ -import { createDebugLogger } from '@qwen-code/qwen-code-core'; +import { createDebugLogger } from '@qwen-code/qwen-code-core/utils/debugLogger.js'; const debugLogger = createDebugLogger('EARLY_INPUT'); diff --git a/packages/cli/src/utils/managed-npm-update.ts b/packages/cli/src/utils/managed-npm-update.ts index 52443c76a24..26f1091c5ed 100644 --- a/packages/cli/src/utils/managed-npm-update.ts +++ b/packages/cli/src/utils/managed-npm-update.ts @@ -4,7 +4,8 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { Storage, createDebugLogger } from '@qwen-code/qwen-code-core'; +import { Storage } from '@qwen-code/qwen-code-core/config/storage.js'; +import { createDebugLogger } from '@qwen-code/qwen-code-core/utils/debugLogger.js'; import { execFile, execFileSync, spawn } from 'node:child_process'; import { createHash } from 'node:crypto'; import * as fs from 'node:fs'; diff --git a/packages/cli/src/utils/userPromptExpansionHook.ts b/packages/cli/src/utils/userPromptExpansionHook.ts index 35f33eeddd3..6b9deac65f0 100644 --- a/packages/cli/src/utils/userPromptExpansionHook.ts +++ b/packages/cli/src/utils/userPromptExpansionHook.ts @@ -5,10 +5,8 @@ */ import type { PartListUnion } from '@google/genai'; -import { - partToString, - sanitizeUserPromptExpansionAdditionalContext, -} from '@qwen-code/qwen-code-core'; +import { sanitizeUserPromptExpansionAdditionalContext } from '@qwen-code/qwen-code-core/hooks/types.js'; +import { partToString } from '@qwen-code/qwen-code-core/utils/partUtils.js'; export function appendUserPromptExpansionAdditionalContext( content: PartListUnion, diff --git a/packages/cli/src/utils/userStartupWarnings.ts b/packages/cli/src/utils/userStartupWarnings.ts index ca0d91656b5..a204b93ab6f 100644 --- a/packages/cli/src/utils/userStartupWarnings.ts +++ b/packages/cli/src/utils/userStartupWarnings.ts @@ -6,7 +6,7 @@ import fs from 'node:fs/promises'; import path from 'node:path'; -import { canUseRipgrep } from '@qwen-code/qwen-code-core'; +import { canUseRipgrep } from '@qwen-code/qwen-code-core/utils/ripgrepUtils.js'; type WarningCheckOptions = { workspaceRoot: string; diff --git a/packages/cli/src/validateNonInterActiveAuth.ts b/packages/cli/src/validateNonInterActiveAuth.ts index 0a7080aea67..97d606db9cd 100644 --- a/packages/cli/src/validateNonInterActiveAuth.ts +++ b/packages/cli/src/validateNonInterActiveAuth.ts @@ -4,8 +4,8 @@ * SPDX-License-Identifier: Apache-2.0 */ -import type { Config } from '@qwen-code/qwen-code-core'; -import { OutputFormat } from '@qwen-code/qwen-code-core'; +import type { Config } from '@qwen-code/qwen-code-core/config/config.js'; +import { OutputFormat } from '@qwen-code/qwen-code-core/output/types.js'; import { validateAuthMethod } from './config/auth.js'; import { type LoadedSettings } from './config/settings.js'; import { JsonOutputAdapter } from './nonInteractive/io/JsonOutputAdapter.js'; diff --git a/packages/core/package.json b/packages/core/package.json index 0b658ca05ad..ad79cf1f54c 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -62,6 +62,7 @@ "import": "./dist/src/utils/conversations-runtime-marker.js" }, "./package.json": "./package.json", + "./*": "./dist/src/*", "./dist/*": "./dist/*", "./src/*": "./src/*" }, diff --git a/scripts/check-core-subpath-exports.mjs b/scripts/check-core-subpath-exports.mjs new file mode 100644 index 00000000000..e4a5a1c9e0b --- /dev/null +++ b/scripts/check-core-subpath-exports.mjs @@ -0,0 +1,124 @@ +/** + * @license + * Copyright 2026 Qwen Team + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @fileoverview Checks that a compiled cli can still reach core by module path. + * + * The cli's own sources — and the workspace packages its runtime graph pulls in + * (acp-bridge, sdk-typescript) — say + * `@qwen-code/qwen-code-core/config/storage.js` and the like. Inside the repo + * those resolve through tsconfig `paths` (for tsc and esbuild) or through + * vitest aliases (for the suites) — three mechanisms, none of which the + * published package has. There, the emitted JS keeps the specifier verbatim + * and Node resolves it against core's `exports` map. + * + * Nothing else exercises that entry. Remove it, rename the `dist/src` root, or + * add a pattern that shadows it, and every suite stays green while `qwen` dies + * on its first core subpath import. This runs Node's real resolver against the + * built package so that failure lands in CI instead. It also rejects targets + * outside core's published `dist/`, so a specifier the `./src/*` exports entry + * routes to an in-repo file that never ships cannot pass the gate either. + */ + +import { existsSync, globSync, readFileSync } from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath, pathToFileURL } from 'node:url'; + +const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); +const EXPORT_PROBES = new Map([ + ['@qwen-code/qwen-code-core/config/storage.js', 'Storage'], + ['@qwen-code/qwen-code-core/utils/debugLogger.js', 'createDebugLogger'], + ['@qwen-code/qwen-code-core/utils/errors.js', 'getErrorMessage'], + ['@qwen-code/qwen-code-core/noFollowOpen', 'openSyncNoFollow'], +]); +const specifiers = new Set(EXPORT_PROBES.keys()); +// acp-bridge and sdk-typescript are runtime dependencies of the cli, and their +// compiled dist keeps core subpath specifiers verbatim just like the cli's own +// dist. Scanning them too gates entries that only a dependency names (e.g. +// ./goalWire, ./transcriptRecords, ./subSessionConstants); without this, those +// exports entries could be deleted while every gate stays green. +const scannedSources = [ + path.join(root, 'packages', 'cli', 'src'), + path.join(root, 'packages', 'acp-bridge', 'src'), + path.join(root, 'packages', 'sdk-typescript', 'src'), +]; +const coreSpecifier = /['"](@qwen-code\/qwen-code-core\/[^'"]+)['"]/g; +for (const sourceDir of scannedSources) { + for (const relativePath of globSync('**/*.{ts,tsx}', { cwd: sourceDir })) { + const source = readFileSync(path.join(sourceDir, relativePath), 'utf8'); + for (const match of source.matchAll(coreSpecifier)) { + specifiers.add(match[1]); + } + } +} + +const coreDist = path.join(root, 'packages', 'core', 'dist', 'src'); +if (!existsSync(coreDist)) { + console.error( + `core is not built (${path.relative(root, coreDist)} is missing) — run "npm run build" first`, + ); + process.exit(1); +} + +// core's package.json publishes only dist, vendor and scripts/postinstall.js +// ("files"), but its exports map also carries "./src/*": "./src/*". A +// specifier routed through that entry resolves to a real in-repo +// packages/core/src/... file, so a bare existsSync passes while the published +// artifact ships nothing and the installed CLI dies with ERR_MODULE_NOT_FOUND. +// Legitimate runtime specifiers all resolve under dist/, so require exactly +// that instead of trusting the repo tree. The one exception is core's own +// package.json, which the exports map deliberately publishes +// ("./package.json": "./package.json") and npm ships regardless of "files". +const publishedDist = path.join(root, 'packages', 'core', 'dist') + path.sep; +const corePackageJson = path.join(root, 'packages', 'core', 'package.json'); + +let failed = 0; +for (const specifier of specifiers) { + const exportName = EXPORT_PROBES.get(specifier); + let resolved; + try { + resolved = fileURLToPath(import.meta.resolve(specifier)); + } catch (error) { + console.error(`✗ ${specifier}\n ${error.code ?? ''} ${error.message}`); + failed++; + continue; + } + if (!existsSync(resolved)) { + console.error( + `✗ ${specifier}\n resolved target does not exist: ${resolved}`, + ); + failed++; + continue; + } + if (!resolved.startsWith(publishedDist) && resolved !== corePackageJson) { + console.error( + `✗ ${specifier}\n resolved target is not published: ${resolved} lies outside ${path.relative(root, publishedDist)} (core ships "files": dist, vendor, scripts/postinstall.js)`, + ); + failed++; + continue; + } + const mod = exportName && (await import(pathToFileURL(resolved).href)); + if (exportName && typeof mod[exportName] !== 'function') { + console.error( + `✗ ${specifier}\n resolved to ${path.relative(root, resolved)} but ${exportName} is ${typeof mod[exportName]}`, + ); + failed++; + continue; + } + console.log(`✓ ${specifier} → ${path.relative(root, resolved)}`); +} + +if (failed) { + console.error( + `\n${failed} of ${specifiers.size} core subpath specifiers do not resolve through the package exports map.\n` + + 'The published CLI resolves them this way and nothing else does, so this breaks `qwen` at startup\n' + + 'while every in-repo suite stays green. Check the exports map in packages/core/package.json.', + ); + process.exit(1); +} +console.log( + `\nAll ${specifiers.size} core subpath specifiers resolve through the package exports map.`, +); diff --git a/scripts/dev.js b/scripts/dev.js index 072cc6533a2..70eae53595b 100755 --- a/scripts/dev.js +++ b/scripts/dev.js @@ -68,14 +68,8 @@ const loaderPath = join(tmpDir, 'loader.mjs'); const coreDir = join(root, 'packages', 'core'); const coreSpecifier = '@qwen-code/qwen-code-core'; const coreSourceUrl = pathToFileURL(join(coreDir, 'index.ts')).href; +const coreSrcUrl = pathToFileURL(join(coreDir, 'src') + '/').href; -// Dev runs the CLI from TypeScript source, so every core specifier has to land -// on packages/core/src. Intercepting only the package root is not enough: a -// named subpath (`…/debugLogger`) resolves through the package's `exports` map -// into packages/core/dist, while the root loads src — one process holding two -// instances of the same module, with split static state and a debug logger -// bound to a session the other copy never set. Deriving the map from `exports` -// keeps the interception complete as subpaths are added. const coreExports = JSON.parse( readFileSync(join(coreDir, 'package.json'), 'utf-8'), ).exports; @@ -98,8 +92,13 @@ for (const [subpath, conditions] of Object.entries(coreExports ?? {})) { } const loaderCode = ` +import { existsSync } from 'node:fs'; +import { fileURLToPath } from 'node:url'; + const coreSourceUrl = '${coreSourceUrl}'; const coreSubpathSourceUrls = ${JSON.stringify(coreSubpathSourceUrls, null, 2)}; +const coreSrcUrl = '${coreSrcUrl}'; +const corePrefix = '${coreSpecifier}/'; export function resolve(specifier, context, nextResolve) { const url = @@ -113,6 +112,16 @@ export function resolve(specifier, context, nextResolve) { format: 'module', }; } + if (specifier.startsWith(corePrefix)) { + // Deep paths mirror packages/core/src; leave missing files to Node. + const sub = specifier.slice(corePrefix.length).replace(/\\.js$/, ''); + for (const ext of ['.ts', '.tsx']) { + const candidate = new URL(sub + ext, coreSrcUrl); + if (existsSync(fileURLToPath(candidate))) { + return { shortCircuit: true, url: candidate.href, format: 'module' }; + } + } + } return nextResolve(specifier, context); } `; diff --git a/scripts/tests/check-core-subpath-exports.test.js b/scripts/tests/check-core-subpath-exports.test.js new file mode 100644 index 00000000000..e36a4a2f364 --- /dev/null +++ b/scripts/tests/check-core-subpath-exports.test.js @@ -0,0 +1,245 @@ +/** + * @license + * Copyright 2026 Qwen Team + * SPDX-License-Identifier: Apache-2.0 + */ + +import { describe, expect, it, afterEach } from 'vitest'; +import { execFile } from 'node:child_process'; +import { + mkdirSync, + mkdtempSync, + readFileSync, + rmSync, + symlinkSync, + writeFileSync, +} from 'node:fs'; +import { tmpdir } from 'node:os'; +import { dirname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const root = join(dirname(fileURLToPath(import.meta.url)), '..', '..'); +const scriptPath = join(root, 'scripts', 'check-core-subpath-exports.mjs'); + +// The intact exports map mirrors packages/core/package.json: a named entry +// only a runtime dependency names (./goalWire, compare ./transcriptRecords), +// the ./* catch-all that carries the cli's path-style specifiers, and the +// ./src/* entry that routes to files the published package does not ship. +const INTACT_EXPORTS = { + './noFollowOpen': { + types: './dist/src/utils/no-follow-open.d.ts', + import: './dist/src/utils/no-follow-open.js', + }, + './goalWire': { + types: './dist/src/goals/goal-wire.d.ts', + import: './dist/src/goals/goal-wire.js', + }, + './package.json': './package.json', + './*': './dist/src/*', + './src/*': './src/*', +}; + +describe('scripts/check-core-subpath-exports.mjs', () => { + let cwd; + + afterEach(() => { + if (cwd) { + rmSync(cwd, { recursive: true, force: true }); + cwd = undefined; + } + }); + + function createFixture({ + coreExports = INTACT_EXPORTS, + cliSpecifiers = ['@qwen-code/qwen-code-core/config/storage.js'], + acpBridgeSpecifiers = ['@qwen-code/qwen-code-core/goalWire'], + sdkSpecifiers = ['@qwen-code/qwen-code-core/version.js'], + distOverrides = {}, + withUnpublishedSrcTarget = false, + } = {}) { + cwd = mkdtempSync(join(tmpdir(), 'qwen-core-subpath-check-')); + + // The check derives its repo root from its own location and resolves + // specifiers with import.meta.resolve against that root, so run a copy of + // the real script inside the fixture tree. + mkdirSync(join(cwd, 'scripts'), { recursive: true }); + writeFileSync( + join(cwd, 'scripts', 'check-core-subpath-exports.mjs'), + readFileSync(scriptPath, 'utf8'), + ); + + const coreDir = join(cwd, 'packages', 'core'); + const distFiles = { + // The four EXPORT_PROBES targets the script always resolves and imports. + 'dist/src/config/storage.js': 'export function Storage() {}', + 'dist/src/utils/debugLogger.js': 'export function createDebugLogger() {}', + 'dist/src/utils/errors.js': 'export function getErrorMessage() {}', + 'dist/src/utils/no-follow-open.js': + 'export function openSyncNoFollow() {}', + // Named-entry and catch-all targets named only by fixture sources. + 'dist/src/goals/goal-wire.js': 'export const goalWire = {};', + 'dist/src/version.js': "export const version = 'fixture';", + // Entries here replace the defaults above (e.g. a probe target that + // exists but lacks the export the script probes for). + ...distOverrides, + }; + for (const [relativePath, source] of Object.entries(distFiles)) { + const filePath = join(coreDir, relativePath); + mkdirSync(dirname(filePath), { recursive: true }); + writeFileSync(filePath, `${source}\n`); + } + if (withUnpublishedSrcTarget) { + // Exists in the repo tree, absent from the published artifact. + const srcFile = join(coreDir, 'src', 'config', 'storage.ts'); + mkdirSync(dirname(srcFile), { recursive: true }); + writeFileSync(srcFile, 'export type Storage = unknown;\n'); + } + writeFileSync( + join(coreDir, 'package.json'), + `${JSON.stringify( + { + name: '@qwen-code/qwen-code-core', + version: '0.0.0-fixture', + type: 'module', + files: ['dist', 'vendor', 'scripts/postinstall.js'], + exports: coreExports, + }, + null, + 2, + )}\n`, + ); + + const sources = { + cli: cliSpecifiers, + 'acp-bridge': acpBridgeSpecifiers, + 'sdk-typescript': sdkSpecifiers, + }; + for (const [packageName, specifiers] of Object.entries(sources)) { + const sourceDir = join(cwd, 'packages', packageName, 'src'); + mkdirSync(sourceDir, { recursive: true }); + const source = specifiers + .map((specifier, index) => `import { v${index} } from '${specifier}';`) + .join('\n'); + writeFileSync(join(sourceDir, 'entry.ts'), `${source}\n`); + } + + // Node finds core through the workspace symlink, just like the repo. + const linkParent = join(cwd, 'node_modules', '@qwen-code'); + mkdirSync(linkParent, { recursive: true }); + symlinkSync( + join(coreDir), + join(linkParent, 'qwen-code-core'), + process.platform === 'win32' ? 'junction' : 'dir', + ); + return cwd; + } + + function runCheck() { + return new Promise((resolve, reject) => { + execFile( + process.execPath, + [join(cwd, 'scripts', 'check-core-subpath-exports.mjs')], + { cwd }, + (err, stdout, stderr) => { + // A non-zero exit is the verdict under test; only a spawn failure + // (string code) must reject. + if (err && typeof err.code === 'string') reject(err); + else resolve({ status: err ? err.code : 0, stdout, stderr }); + }, + ); + }); + } + + it('resolves specifiers named only by the scanned runtime dependencies', async () => { + // Pins the acp-bridge/sdk-typescript source scan: goalWire is named by no + // cli source, exactly like ./transcriptRecords and ./subSessionConstants + // in the repo. Reverting the scan to cli/src alone drops these specifiers + // from the collected set and this assertion fails. + createFixture(); + const { status, stdout } = await runCheck(); + expect(status).toBe(0); + expect(stdout).toContain('✓ @qwen-code/qwen-code-core/goalWire'); + expect(stdout).toContain('✓ @qwen-code/qwen-code-core/version.js'); + expect(stdout).toContain( + 'core subpath specifiers resolve through the package exports map.', + ); + }); + + it('fails when a dep-only exports entry is removed', async () => { + // Mutation witness for the finding above: with the ./goalWire entry gone, + // only the dep scan keeps this red — nothing in cli/src names it. + const mutatedExports = { ...INTACT_EXPORTS }; + delete mutatedExports['./goalWire']; + createFixture({ coreExports: mutatedExports }); + const { status, stderr } = await runCheck(); + expect(status).toBe(1); + expect(stderr).toContain('@qwen-code/qwen-code-core/goalWire'); + expect(stderr).toContain( + 'core subpath specifiers do not resolve through the package exports map.', + ); + }); + + it('fails when a specifier routes through the unpublished ./src/* entry', async () => { + // The ./src/* exports entry resolves to a real in-repo file, so a bare + // existsSync passes; the dist-containment guard is what rejects it, and + // removing that guard turns this green and the test red. + createFixture({ + cliSpecifiers: [ + '@qwen-code/qwen-code-core/config/storage.js', + '@qwen-code/qwen-code-core/src/config/storage.ts', + ], + withUnpublishedSrcTarget: true, + }); + const { status, stderr } = await runCheck(); + expect(status).toBe(1); + expect(stderr).toContain('@qwen-code/qwen-code-core/src/config/storage.ts'); + expect(stderr).toContain('resolved target is not published'); + }); + + it('passes for the deliberately published ./package.json specifier', async () => { + // core's exports map carries "./package.json": "./package.json" and npm + // ships package.json regardless of "files", so the dist-containment + // guard must allow it. Removing the allow-clause from the script turns + // this red while the unpublished ./src/* test above stays red. + createFixture({ + cliSpecifiers: [ + '@qwen-code/qwen-code-core/config/storage.js', + '@qwen-code/qwen-code-core/package.json', + ], + }); + const { status, stdout } = await runCheck(); + expect(status).toBe(0); + expect(stdout).toContain('✓ @qwen-code/qwen-code-core/package.json'); + }); + + it('fails when a probe target exists but lacks the probed export', async () => { + // Witnesses the named-export probe stage: an exports edit retargeting a + // probed specifier at an existing dist module without the export must + // fail. Removing the probe import and the export-name check from the + // script turns this green. + createFixture({ + distOverrides: { + 'dist/src/config/storage.js': 'export const notStorage = 1;', + }, + }); + const { status, stderr } = await runCheck(); + expect(status).toBe(1); + expect(stderr).toContain('@qwen-code/qwen-code-core/config/storage.js'); + expect(stderr).toContain('Storage is undefined'); + }); + + it('fails when the ./* catch-all is missing from the exports map', async () => { + // Drives path-style specifiers into the resolve-failure branch: without + // the ./* catch-all, import.meta.resolve throws ERR_PACKAGE_PATH_NOT_EXPORTED. + // Removing failed++ from the script's resolve catch turns this green. + const mutatedExports = { ...INTACT_EXPORTS }; + delete mutatedExports['./*']; + createFixture({ coreExports: mutatedExports }); + const { status, stderr } = await runCheck(); + expect(status).toBe(1); + expect(stderr).toContain('@qwen-code/qwen-code-core/config/storage.js'); + expect(stderr).toContain( + 'core subpath specifiers do not resolve through the package exports map.', + ); + }); +}); diff --git a/scripts/tests/ci-platform-lanes.test.js b/scripts/tests/ci-platform-lanes.test.js index 794222498e6..b567b8fa17c 100644 --- a/scripts/tests/ci-platform-lanes.test.js +++ b/scripts/tests/ci-platform-lanes.test.js @@ -471,6 +471,7 @@ describe('post-merge push lane', () => { 'Generate VS Code companion notices', 'Check VS Code companion notices are up-to-date', 'Check serve fast-path bundle closure', + 'Check core subpath exports resolve', 'Run .github/scripts helper tests', ]; const steps = ci.jobs.lint_and_static.steps ?? []; diff --git a/scripts/tests/dev.test.js b/scripts/tests/dev.test.js index 447cfa3a85f..894336112c9 100644 --- a/scripts/tests/dev.test.js +++ b/scripts/tests/dev.test.js @@ -149,6 +149,16 @@ describe('scripts/dev.js launcher', () => { } }); + it.skipIf(process.platform === 'win32')( + 'keeps the dev entry executable for QWEN_CODE_CLI subprocesses', + async () => { + const fs = await vi.importActual('node:fs'); + expect(() => + fs.accessSync(new URL('../dev.js', import.meta.url), fs.constants.X_OK), + ).not.toThrow(); + }, + ); + it('resolves core subpaths to packages/core/src, not the exports map dist', async () => { // Intercepting only the package root leaves a named subpath to Node's // `exports` map, which resolves into packages/core/dist while the root @@ -180,6 +190,8 @@ describe('scripts/dev.js launcher', () => { 'packages/core/src/config/storage.ts', '@qwen-code/qwen-code-core/atomicFileWrite': 'packages/core/src/utils/atomicFileWrite.ts', + '@qwen-code/qwen-code-core/utils/debugLogger.js': + 'packages/core/src/utils/debugLogger.ts', }; // Every named subpath the exports map publishes, so the interception diff --git a/scripts/tests/integration-tsconfig-core-paths-sync.test.js b/scripts/tests/integration-tsconfig-core-paths-sync.test.js new file mode 100644 index 00000000000..6b93468bd95 --- /dev/null +++ b/scripts/tests/integration-tsconfig-core-paths-sync.test.js @@ -0,0 +1,77 @@ +/** + * @license + * Copyright 2026 Qwen Team + * SPDX-License-Identifier: Apache-2.0 + */ + +import { describe, expect, it } from 'vitest'; +import { existsSync, readFileSync } from 'node:fs'; +import { dirname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const root = join(dirname(fileURLToPath(import.meta.url)), '..', '..'); +const tsconfigPath = join(root, 'integration-tests', 'tsconfig.json'); +const coreDir = join(root, 'packages', 'core'); + +// integration-tests/tsconfig.json resolves workspace packages from source so +// `tsc -p integration-tests/tsconfig.json` never typechecks against dist +// declarations. Its `@qwen-code/qwen-code-core/*` wildcard substitutes +// ../packages/core/src/, which covers every specifier whose source +// file mirrors its name; a named exports key whose target stem differs from +// the key needs an explicit `paths` entry, or resolution silently falls back +// to packages/core/dist (a hard TS2307 on an unbuilt tree). Gate the named +// keys of core's exports map against that paths block the same way +// text-capture-core-loader-sync.test.js gates the harness loader's map. +describe('integration-tests core paths sync', () => { + // The tsconfig carries `//` comments; drop whole-line comments before + // parsing (its strings never contain a line comment). + const tsconfig = JSON.parse( + readFileSync(tsconfigPath, 'utf8').replace(/^\s*\/\/.*$/gm, ''), + ); + const paths = tsconfig.compilerOptions.paths; + const exportsMap = JSON.parse( + readFileSync(join(coreDir, 'package.json'), 'utf8'), + ).exports; + const namedKeys = Object.keys(exportsMap).filter( + (key) => + key.startsWith('./') && !key.includes('*') && key !== './package.json', + ); + + it('has a paths entry for every named exports key whose file it does not mirror', () => { + expect(namedKeys.length).toBeGreaterThan(0); + for (const key of namedKeys) { + const name = key.slice('./'.length); + const entry = exportsMap[key]; + const importTarget = typeof entry === 'string' ? entry : entry?.import; + const stem = importTarget + .replace(/^\.\/dist\/src\//, '') + .replace(/\.js$/, ''); + // The wildcard covers specifiers that mirror their source file. + if (stem === name) continue; + const candidates = [ + `../packages/core/src/${stem}.ts`, + `../packages/core/src/${stem}.tsx`, + ]; + const mapped = paths[`@qwen-code/qwen-code-core/${name}`]; + expect( + mapped, + `integration-tests/tsconfig.json paths is missing ` + + `@qwen-code/qwen-code-core/${name} (exports ${key} -> ${importTarget}); ` + + `the specifier falls through the wildcard to dist or fails outright`, + ).toBeDefined(); + // Every paths value must stay an array (a string value makes tsc + // abort before it checks a single file). + expect(Array.isArray(mapped)).toBe(true); + expect(mapped).toHaveLength(1); + expect( + candidates, + `paths entry for ${name} must point at the exports target's ` + + `source file (${importTarget})`, + ).toContain(mapped[0]); + expect( + existsSync(join(root, 'integration-tests', mapped[0])), + `paths entry for ${name} points at a file that does not exist`, + ).toBe(true); + } + }); +}); diff --git a/scripts/tests/no-core-root-barrel-import.test.js b/scripts/tests/no-core-root-barrel-import.test.js index 1f8e05d8caa..a257616e43e 100644 --- a/scripts/tests/no-core-root-barrel-import.test.js +++ b/scripts/tests/no-core-root-barrel-import.test.js @@ -40,6 +40,7 @@ describe('no-core-root-barrel-import', () => { 'packages/core/src/core/client.ts', '@qwen-code/qwen-code-core/dist/index.js', ], + ['packages/core/src/core/client.ts', '@qwen-code/qwen-code-core/index.js'], // .ts spelling of the exports-map src subpath [ 'packages/core/src/core/client.ts', diff --git a/scripts/tests/no-core-utils-upward-import.test.js b/scripts/tests/no-core-utils-upward-import.test.js index b53682fbcde..3a3a6681eef 100644 --- a/scripts/tests/no-core-utils-upward-import.test.js +++ b/scripts/tests/no-core-utils-upward-import.test.js @@ -1,7 +1,9 @@ import { describe, expect, it } from 'vitest'; import { Linter } from 'eslint'; import tsParser from '@typescript-eslint/parser'; -import rule from '../../eslint-rules/no-core-utils-upward-import.js'; +import rule, { + resolveExportTarget, +} from '../../eslint-rules/no-core-utils-upward-import.js'; function runRule(code, filename) { const linter = new Linter({ configType: 'flat', cwd: '/' }); @@ -27,6 +29,15 @@ function runRule(code, filename) { } describe('no-core-utils-upward-import', () => { + it('uses Node pattern ordering when literal prefixes tie', () => { + expect( + resolveExportTarget('./tools/x.js', { + './tools/*': './dist/src/utils/*', + './tools/*.js': './dist/src/tools/*.js', + }), + ).toBe('./dist/src/tools/x.js'); + }); + it('rejects value imports that leave utils/', () => { expect( runRule( @@ -217,4 +228,50 @@ describe('no-core-utils-upward-import', () => { ), ).toHaveLength(0); }); + it('resolves self-reference subpaths through the exports wildcard', () => { + // `packages/core/package.json` carries a `./*` catch-all, so a deep + // specifier resolves at runtime without matching any named export key. + // Without pattern matching in the rule these read as unresolvable and go + // unreported, which would let the utils layer regain upward runtime + // dependencies with lint, typecheck and CI all green. + expect( + runRule( + "import { Storage } from '@qwen-code/qwen-code-core/config/storage.js';", + 'packages/core/src/utils/foo.ts', + ), + ).toHaveLength(1); + expect( + runRule( + "import { X } from '@qwen-code/qwen-code-core/tools/tools.js';", + 'packages/core/src/utils/foo.ts', + ), + ).toHaveLength(1); + }); + + it('keeps exact export keys ahead of the wildcard', () => { + // The fixture must be a specifier whose two resolutions disagree, or this + // test cannot see the regression it names: the rule verdicts by directory + // layer and never checks file existence. `transcriptRecords` maps exactly + // to utils/transcript-records.js (inside utils/ — allowed), while the + // `./*` wildcard would resolve it to a `transcriptRecords` path outside + // utils/ (a violation). Dropping the exact-key branch of + // resolveExportTarget flips the expectation from 0 to 1; a fixture like + // `goalWire` — whose exact and wildcard resolutions both land outside + // utils/ — reports 1 either way and would stay green on the mutant. + expect( + runRule( + "import { X } from '@qwen-code/qwen-code-core/transcriptRecords';", + 'packages/core/src/utils/foo.ts', + ), + ).toHaveLength(0); + }); + + it('allows a sibling utils module reached through the wildcard', () => { + expect( + runRule( + "import { X } from '@qwen-code/qwen-code-core/utils/paths.js';", + 'packages/core/src/utils/foo.ts', + ), + ).toHaveLength(0); + }); }); diff --git a/scripts/tests/text-capture-core-loader-sync.test.js b/scripts/tests/text-capture-core-loader-sync.test.js new file mode 100644 index 00000000000..8ca16c54687 --- /dev/null +++ b/scripts/tests/text-capture-core-loader-sync.test.js @@ -0,0 +1,92 @@ +/** + * @license + * Copyright 2026 Qwen Team + * SPDX-License-Identifier: Apache-2.0 + */ + +import { describe, expect, it } from 'vitest'; +import { existsSync, readFileSync } from 'node:fs'; +import { dirname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const root = join(dirname(fileURLToPath(import.meta.url)), '..', '..'); +const capturePath = join( + root, + 'integration-tests', + 'terminal-capture', + 'skill-review-harness', + 'text-capture.tsx', +); +const coreDir = join(root, 'packages', 'core'); + +// The skill-review-harness loader redirects core subpath specifiers to their +// TypeScript sources so a capture runs without a build and can never mix in a +// stale dist. Its inline `named` map must carry one entry per named key of +// core's exports map: a missing entry whose specifier has no mirrored +// packages/core/src/.ts file falls through to the exports map — on a +// fresh clone the capture dies mid-import with ERR_MODULE_NOT_FOUND, and with +// a stale dist present that one module silently loads from compiled output, +// the exact failure mode the loader exists to prevent. +describe('skill-review-harness core loader sync', () => { + const source = readFileSync(capturePath, 'utf8'); + const namedBlock = source.match(/const named = new Map\(\[([\s\S]*?)\]\);/); + const named = new Map( + [...(namedBlock?.[1] ?? '').matchAll(/\['([^']+)',\s*'([^']+)'\]/g)].map( + (match) => [match[1], match[2]], + ), + ); + const exportsMap = JSON.parse( + readFileSync(join(coreDir, 'package.json'), 'utf8'), + ).exports; + const namedKeys = Object.keys(exportsMap).filter( + (key) => + key.startsWith('./') && !key.includes('*') && key !== './package.json', + ); + + it('covers every named entry of the core exports map', () => { + expect(namedKeys.length).toBeGreaterThan(0); + for (const key of namedKeys) { + expect( + named.has(key.slice('./'.length)), + `loader named map is missing exports entry ${key}`, + ).toBe(true); + } + }); + + it('points every entry at the exports target in source space', () => { + expect(named.size).toBeGreaterThan(0); + for (const [name, target] of named) { + expect( + existsSync(join(coreDir, 'src', target)), + `loader entry ${name} -> packages/core/src/${target} does not exist`, + ).toBe(true); + // Key coverage alone is not enough: a retargeted exports entry would + // leave the loader serving the old module (the named map short-circuits + // ahead of the loader's stem probe) while a built CLI runs the new one. + // All named entries target ./dist/src/.js; an entry targeting + // elsewhere needs an explicit exemption, not a blanket rewrite. + const entry = exportsMap[`./${name}`]; + const importTarget = typeof entry === 'string' ? entry : entry?.import; + const stem = importTarget + .replace(/^\.\/dist\/src\//, '') + .replace(/\.js$/, ''); + expect( + [`${stem}.ts`, `${stem}.tsx`], + `loader entry ${name} -> ${target} disagrees with exports target ${importTarget}`, + ).toContain(target); + } + }); + + it('has no loader entries without a matching exports key', () => { + // The reverse direction of the key-set identity: a stale loader-only + // entry short-circuits in a capture to a module the shipped package can + // no longer resolve (ERR_PACKAGE_PATH_NOT_EXPORTED), so the capture runs + // green on a module graph production rejects. + for (const [name] of named) { + expect( + namedKeys.includes(`./${name}`), + `loader entry ${name} has no matching exports key ./${name}`, + ).toBe(true); + } + }); +});