diff --git a/packages/cli/src/commands/channel/start.test.ts b/packages/cli/src/commands/channel/start.test.ts index 8ba3b341779..5eeeb41a70b 100644 --- a/packages/cli/src/commands/channel/start.test.ts +++ b/packages/cli/src/commands/channel/start.test.ts @@ -1,4 +1,6 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { join } from 'node:path'; +import { pathToFileURL } from 'node:url'; const mockSetGlobalDispatcher = vi.hoisted(() => vi.fn()); const mockProxyAgent = vi.hoisted(() => @@ -84,7 +86,11 @@ vi.mock('@qwen-code/channel-base', () => ({ SessionRouter: mockSessionRouter, })); -import { resolveProxy, startCommand } from './start.js'; +import { + resolveExtensionChannelEntrySpecifier, + resolveProxy, + startCommand, +} from './start.js'; type StartCommandArgs = Parameters>[0]; @@ -168,6 +174,17 @@ describe('resolveProxy', () => { }); }); +describe('resolveExtensionChannelEntrySpecifier', () => { + it('returns a file URL for extension channel entry paths', () => { + const extensionPath = join('/tmp', 'qwen extension'); + const entry = join('dist', 'channel.js'); + + expect(resolveExtensionChannelEntrySpecifier(extensionPath, entry)).toBe( + pathToFileURL(join(extensionPath, entry)).href, + ); + }); +}); + describe('startCommand.handler', () => { it('loads settings.merged.proxy when no CLI proxy is provided', async () => { const settingsProxy = 'http://settings.example.com:8080'; diff --git a/packages/cli/src/commands/channel/start.ts b/packages/cli/src/commands/channel/start.ts index b4cf8c940ed..dc2c9355918 100644 --- a/packages/cli/src/commands/channel/start.ts +++ b/packages/cli/src/commands/channel/start.ts @@ -1,4 +1,5 @@ import * as path from 'node:path'; +import { pathToFileURL } from 'node:url'; import type { CommandModule } from 'yargs'; import { ProxyAgent, setGlobalDispatcher } from 'undici'; import { normalizeProxyUrl, Storage } from '@qwen-code/qwen-code-core'; @@ -64,6 +65,13 @@ function loadChannelsConfig(): Record { return channels || {}; } +export function resolveExtensionChannelEntrySpecifier( + extensionPath: string, + entry: string, +): string { + return pathToFileURL(path.join(extensionPath, entry)).href; +} + /** * Load channel plugins from active extensions. * Extensions declare channels in their qwen-extension.json manifest. @@ -85,9 +93,12 @@ async function loadChannelsFromExtensions(): Promise { continue; } - const entryPath = path.join(ext.path, channelDef.entry); + const entrySpecifier = resolveExtensionChannelEntrySpecifier( + ext.path, + channelDef.entry, + ); try { - const module = (await import(entryPath)) as { + const module = (await import(entrySpecifier)) as { plugin?: ChannelPlugin; }; const plugin = module.plugin;