From 8027a6bb85881ead565d1f5242b8d24ad7ce89d9 Mon Sep 17 00:00:00 2001 From: yaojin Date: Thu, 20 Aug 2026 02:35:24 -0700 Subject: [PATCH 1/4] fix: isolate macOS harness TCC responsibility --- src/main/index.ts | 7 +- .../runtime/disclaimed-utility-process.ts | 102 ++++++++++++++++++ src/main/runtime/harness-runtime.ts | 18 +++- test/runtime.test.ts | 53 +++++++++ 4 files changed, 174 insertions(+), 6 deletions(-) create mode 100644 src/main/runtime/disclaimed-utility-process.ts diff --git a/src/main/index.ts b/src/main/index.ts index 2ca78e92..2874d3bb 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -10,10 +10,12 @@ import { Menu, nativeTheme, shell, + utilityProcess, type IpcMainInvokeEvent, type MessageBoxOptions } from 'electron' import { extractFailureCause, HarnessRuntime } from './runtime/harness-runtime' +import { launchDisclaimedUtilityProcess } from './runtime/disclaimed-utility-process' import { removeProfilePluginWithDsh } from './runtime/profile-plugin-command' import { LanMobileBridge } from './mobile/lan-mobile-bridge' import { @@ -936,7 +938,10 @@ async function bootstrap(): Promise { dshPatchPath: desktopResourcePath('dsh-desktop.patch.yml'), dshHome: join(app.getPath('userData'), 'harness'), logPath: join(app.getPath('logs'), 'harness.log'), - launchProcess: (executablePath, args, options) => spawn(executablePath, args, options), + launchProcess: (executablePath, args, options) => + process.platform === 'darwin' + ? launchDisclaimedUtilityProcess(utilityProcess, args, options) + : spawn(executablePath, args, options), onChanged: (snapshot) => { if (snapshot.phase === 'ready' && snapshot.url) { void openHarness(snapshot.url).catch(showUnexpectedError) diff --git a/src/main/runtime/disclaimed-utility-process.ts b/src/main/runtime/disclaimed-utility-process.ts new file mode 100644 index 00000000..c72d0856 --- /dev/null +++ b/src/main/runtime/disclaimed-utility-process.ts @@ -0,0 +1,102 @@ +import { EventEmitter } from 'node:events' +import type { SpawnOptionsWithoutStdio } from 'node:child_process' +import { fileURLToPath } from 'node:url' +import type { UtilityProcess } from 'electron' +import type { HarnessChildProcess } from './harness-runtime' + +interface UtilityProcessLauncher { + fork(modulePath: string, args?: string[], options?: Electron.ForkOptions): UtilityProcess +} + +export interface DisclaimedUtilityProcessSpec { + modulePath: string + args: string[] + options: Electron.ForkOptions +} + +export function buildDisclaimedUtilityProcessSpec( + nodeArguments: readonly string[], + spawnOptions: SpawnOptionsWithoutStdio +): DisclaimedUtilityProcessSpec { + const [internalLoaderFlag, modulePath, ...args] = nodeArguments + if (internalLoaderFlag !== '--expose-internals' || !modulePath) { + throw new Error('Unexpected Harness Node arguments for the macOS utility process.') + } + + return { + modulePath, + args, + options: { + cwd: + typeof spawnOptions.cwd === 'string' + ? spawnOptions.cwd + : spawnOptions.cwd + ? fileURLToPath(spawnOptions.cwd) + : undefined, + env: definedEnvironment(spawnOptions.env), + execArgv: [internalLoaderFlag], + stdio: 'pipe', + serviceName: 'DSH Harness', + // Harness loads user-installed plugins and can launch third-party tools. + // Keep their TCC requests out of DSH Desktop's responsibility chain. + disclaim: true + } + } +} + +export function launchDisclaimedUtilityProcess( + launcher: UtilityProcessLauncher, + nodeArguments: readonly string[], + spawnOptions: SpawnOptionsWithoutStdio +): HarnessChildProcess { + const spec = buildDisclaimedUtilityProcessSpec(nodeArguments, spawnOptions) + return new UtilityProcessAdapter( + launcher.fork(spec.modulePath, spec.args, spec.options) + ) +} + +function definedEnvironment(environment: NodeJS.ProcessEnv | undefined): Record { + return Object.fromEntries( + Object.entries(environment ?? {}).filter( + (entry): entry is [string, string] => entry[1] !== undefined + ) + ) +} + +class UtilityProcessAdapter extends EventEmitter implements HarnessChildProcess { + readonly stdout: NodeJS.ReadableStream + readonly stderr: NodeJS.ReadableStream + exitCode: number | null = null + + constructor(private readonly child: UtilityProcess) { + super() + if (!child.stdout || !child.stderr) { + child.kill() + throw new Error('The DSH Harness utility process did not expose piped output.') + } + this.stdout = child.stdout + this.stderr = child.stderr + + child.once('spawn', () => this.emit('spawn')) + child.once('error', (type, location, report) => { + const detail = [type, location, report].filter(Boolean).join(': ') + this.emit('error', new Error(`Harness utility process failed: ${detail}`)) + }) + child.once('exit', (code) => { + this.exitCode = code + this.emit('exit', code, null) + }) + } + + kill(signal?: NodeJS.Signals): boolean { + if (signal === 'SIGKILL' && this.child.pid !== undefined) { + try { + process.kill(this.child.pid, signal) + return true + } catch { + return false + } + } + return this.child.kill() + } +} diff --git a/src/main/runtime/harness-runtime.ts b/src/main/runtime/harness-runtime.ts index 9e4b7375..ab97ea41 100644 --- a/src/main/runtime/harness-runtime.ts +++ b/src/main/runtime/harness-runtime.ts @@ -1,4 +1,5 @@ -import type { ChildProcessWithoutNullStreams, SpawnOptionsWithoutStdio } from 'node:child_process' +import type { SpawnOptionsWithoutStdio } from 'node:child_process' +import type { EventEmitter } from 'node:events' import { createWriteStream, existsSync, type WriteStream } from 'node:fs' import { mkdir } from 'node:fs/promises' import { createServer } from 'node:net' @@ -16,11 +17,18 @@ export interface HarnessRuntimeOptions { executablePath: string, args: string[], options: SpawnOptionsWithoutStdio - ): ChildProcessWithoutNullStreams + ): HarnessChildProcess startupTimeoutMs?: number onChanged(snapshot: RuntimeSnapshot): void } +export interface HarnessChildProcess extends EventEmitter { + readonly stdout: NodeJS.ReadableStream + readonly stderr: NodeJS.ReadableStream + readonly exitCode: number | null + kill(signal?: NodeJS.Signals): boolean +} + export function buildHarnessArguments(port: number, patchPath?: string): string[] { return [ 'web', @@ -86,7 +94,7 @@ export function updateReadyStability( } export class HarnessRuntime { - private child?: ChildProcessWithoutNullStreams + private child?: HarnessChildProcess private logStream?: WriteStream private phase: RuntimePhase = 'idle' private message = 'Harness is not running.' @@ -148,7 +156,7 @@ export class HarnessRuntime { this.writeLog(`[desktop] endpoint ${url}`) this.setState('starting', 'Starting DeepSeek Harness…') - let child: ChildProcessWithoutNullStreams + let child: HarnessChildProcess try { child = this.options.launchProcess( this.options.nodeExecutablePath, @@ -228,7 +236,7 @@ ${cause}` this.setState('idle', 'Harness is not running.') } - private async stopChild(child: ChildProcessWithoutNullStreams): Promise { + private async stopChild(child: HarnessChildProcess): Promise { if (child.exitCode !== null) return const exitPromise = new Promise((resolve) => child.once('exit', () => resolve(true)) diff --git a/test/runtime.test.ts b/test/runtime.test.ts index 15606c13..20effe78 100644 --- a/test/runtime.test.ts +++ b/test/runtime.test.ts @@ -13,6 +13,7 @@ import { updateReadyStability } from '../src/main/runtime/harness-runtime' import { canGrantWindowPermission, isTrustedAppUrl } from '../src/main/security-policy' +import { buildDisclaimedUtilityProcessSpec } from '../src/main/runtime/disclaimed-utility-process' import { desktopHarnessUrl, isAbortedNavigationError, @@ -108,6 +109,58 @@ describe('Harness launch contract', () => { ]) }) + it('disclaims macOS TCC responsibility when Harness runs as a utility process', () => { + const spawnOptions = buildHarnessSpawnOptions( + '/Users/tester/Library/Application Support/dsh-desktop/launch-root', + '/Users/tester/Library/Application Support/dsh-desktop/harness', + 'darwin', + { PATH: '/usr/bin', ELECTRON_RUN_AS_NODE: '1' } + ) + const nodeArguments = buildNodeArguments( + '/Applications/DSH Desktop.app/Contents/Resources/harness-node-entry.mjs', + '/Applications/DSH Desktop.app/Contents/Resources/app/node_modules/@deepseek-ai/dsh/lib/bin.js', + 43127, + '/Applications/DSH Desktop.app/Contents/Resources/dsh-desktop.patch.yml' + ) + + expect(buildDisclaimedUtilityProcessSpec(nodeArguments, spawnOptions)).toEqual({ + modulePath: '/Applications/DSH Desktop.app/Contents/Resources/harness-node-entry.mjs', + args: [ + '/Applications/DSH Desktop.app/Contents/Resources/app/node_modules/@deepseek-ai/dsh/lib/bin.js', + 'web', + '--patch', + '/Applications/DSH Desktop.app/Contents/Resources/dsh-desktop.patch.yml', + '--no-open', + '--host', + '127.0.0.1', + '--port', + '43127' + ], + options: { + cwd: '/Users/tester/Library/Application Support/dsh-desktop/launch-root', + env: { + PATH: '/usr/bin', + DSH_HOME: '/Users/tester/Library/Application Support/dsh-desktop/harness', + NO_COLOR: '1' + }, + execArgv: ['--expose-internals'], + stdio: 'pipe', + serviceName: 'DSH Harness', + disclaim: true + } + }) + }) + + it('rejects an unexpected macOS Harness argument layout', () => { + expect(() => + buildDisclaimedUtilityProcessSpec(['entry.mjs'], { + cwd: '/tmp/dsh', + env: {}, + stdio: ['pipe', 'pipe', 'pipe'] + }) + ).toThrow('Unexpected Harness Node arguments') + }) + it('makes native Windows termination codes diagnosable', () => { expect(formatExitCode(4294930435)).toContain( '0xFFFF7003, Crashpad handler unavailable' From b76be70c66d9ee89c0b906d7737824d8298530a4 Mon Sep 17 00:00:00 2001 From: yaojin3616 Date: Fri, 21 Aug 2026 13:16:03 +0800 Subject: [PATCH 2/4] fix(ci): package macOS development channel on non-tag builds and resolve test suite errors --- .github/scripts/feishu_release_notes.py | 32 +++++++++++------ .github/workflows/release.yml | 46 ++++++++++++++++++++++--- electron-builder.dev.cjs | 1 + package.json | 2 ++ test/feishu-release-notes.test.ts | 14 +++++--- test/release.test.ts | 12 ++++++- 6 files changed, 87 insertions(+), 20 deletions(-) diff --git a/.github/scripts/feishu_release_notes.py b/.github/scripts/feishu_release_notes.py index a3d1f159..0ecf9be9 100644 --- a/.github/scripts/feishu_release_notes.py +++ b/.github/scripts/feishu_release_notes.py @@ -14,6 +14,17 @@ from dataclasses import dataclass from pathlib import Path +if sys.stdout.encoding and sys.stdout.encoding.lower() != "utf-8": + try: + sys.stdout.reconfigure(encoding="utf-8", errors="replace") + except Exception: + pass +if sys.stderr.encoding and sys.stderr.encoding.lower() != "utf-8": + try: + sys.stderr.reconfigure(encoding="utf-8", errors="replace") + except Exception: + pass + STABLE_TAG_PATTERN = re.compile(r"^v\d+\.\d+\.\d+$") TOPIC_PATTERN = re.compile(r"^\*\*.+? (\d+)\. .+\*\*$", re.MULTILINE) LINK_PATTERN = re.compile(r"https?://|\[[^\]]+\]\([^)]+\)") @@ -125,20 +136,19 @@ class ReleaseEvidence: code_diff: str -def git_output(*args: str) -> str: - return subprocess.check_output( - ["git", *args], - text=True, - stderr=subprocess.DEVNULL, - ).strip() +def git_output(*args: str, default: str = "") -> str: + try: + return subprocess.check_output( + ["git", *args], + text=True, + stderr=subprocess.DEVNULL, + ).strip() + except Exception: + return default def read_annotated_tag_note(release_tag: str) -> str: - try: - object_type = git_output("cat-file", "-t", f"refs/tags/{release_tag}") - except subprocess.CalledProcessError: - return f"Release {release_tag}" - + object_type = git_output("cat-file", "-t", f"refs/tags/{release_tag}") if object_type != "tag": return f"Release {release_tag}" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4b098058..ed00a494 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -27,6 +27,8 @@ jobs: runs-on: macos-15 steps: - uses: actions/checkout@v4 + with: + fetch-depth: 0 - uses: actions/setup-node@v4 with: node-version: 22 @@ -78,13 +80,17 @@ jobs: env: CSC_KEYCHAIN: ${{ steps.signing_keychain.outputs.keychain }} run: npm run package:mac:arm64 - - name: Build unsigned Apple Silicon verification package + - name: Build isolated Apple Silicon development package if: ${{ !startsWith(github.ref, 'refs/tags/v') }} env: CSC_IDENTITY_AUTO_DISCOVERY: 'false' - run: npm run package:mac:arm64 + run: npm run package:dev:mac:arm64 - name: Preserve Apple Silicon update metadata + if: startsWith(github.ref, 'refs/tags/v') run: mv dist/latest-mac.yml dist/latest-mac-arm64.yml + - name: Preserve Apple Silicon development update metadata + if: ${{ !startsWith(github.ref, 'refs/tags/v') }} + run: mv dist-dev/latest-mac.yml dist-dev/latest-mac-arm64.yml - name: Sign, notarize, and staple Apple Silicon DMG if: startsWith(github.ref, 'refs/tags/v') env: @@ -130,6 +136,7 @@ jobs: spctl --assess --type open --context context:primary-signature --verbose=4 dist/dsh-desktop-mac-arm64.dmg xcrun stapler validate dist/dsh-desktop-mac-arm64.dmg - uses: actions/upload-artifact@v4 + if: startsWith(github.ref, 'refs/tags/v') with: name: macos-apple-silicon path: | @@ -138,6 +145,16 @@ jobs: dist/dsh-desktop-mac-arm64.zip.blockmap dist/latest-mac-arm64.yml if-no-files-found: error + - uses: actions/upload-artifact@v4 + if: ${{ !startsWith(github.ref, 'refs/tags/v') }} + with: + name: macos-apple-silicon-dev + path: | + dist-dev/dsh-desktop-dev-mac-arm64.dmg + dist-dev/dsh-desktop-dev-mac-arm64.zip + dist-dev/dsh-desktop-dev-mac-arm64.zip.blockmap + dist-dev/latest-mac-arm64.yml + if-no-files-found: error macos-intel: name: macOS Intel @@ -145,6 +162,8 @@ jobs: runs-on: macos-15-intel steps: - uses: actions/checkout@v4 + with: + fetch-depth: 0 - uses: actions/setup-node@v4 with: node-version: 22 @@ -196,13 +215,17 @@ jobs: env: CSC_KEYCHAIN: ${{ steps.signing_keychain.outputs.keychain }} run: npm run package:mac:x64 - - name: Build unsigned Intel verification package + - name: Build isolated Intel development package if: ${{ !startsWith(github.ref, 'refs/tags/v') }} env: CSC_IDENTITY_AUTO_DISCOVERY: 'false' - run: npm run package:mac:x64 + run: npm run package:dev:mac:x64 - name: Preserve Intel update metadata + if: startsWith(github.ref, 'refs/tags/v') run: mv dist/latest-mac.yml dist/latest-mac-x64.yml + - name: Preserve Intel development update metadata + if: ${{ !startsWith(github.ref, 'refs/tags/v') }} + run: mv dist-dev/latest-mac.yml dist-dev/latest-mac-x64.yml - name: Sign, notarize, and staple Intel DMG if: startsWith(github.ref, 'refs/tags/v') env: @@ -248,6 +271,7 @@ jobs: spctl --assess --type open --context context:primary-signature --verbose=4 dist/dsh-desktop-mac-x64.dmg xcrun stapler validate dist/dsh-desktop-mac-x64.dmg - uses: actions/upload-artifact@v4 + if: startsWith(github.ref, 'refs/tags/v') with: name: macos-intel path: | @@ -256,6 +280,16 @@ jobs: dist/dsh-desktop-mac-x64.zip.blockmap dist/latest-mac-x64.yml if-no-files-found: error + - uses: actions/upload-artifact@v4 + if: ${{ !startsWith(github.ref, 'refs/tags/v') }} + with: + name: macos-intel-dev + path: | + dist-dev/dsh-desktop-dev-mac-x64.dmg + dist-dev/dsh-desktop-dev-mac-x64.zip + dist-dev/dsh-desktop-dev-mac-x64.zip.blockmap + dist-dev/latest-mac-x64.yml + if-no-files-found: error windows-x64: name: Windows x64 @@ -263,6 +297,8 @@ jobs: runs-on: windows-2022 steps: - uses: actions/checkout@v4 + with: + fetch-depth: 0 - uses: actions/setup-node@v4 with: node-version: 22 @@ -410,6 +446,8 @@ jobs: runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v4 + with: + fetch-depth: 0 - uses: actions/setup-node@v4 with: node-version: 22 diff --git a/electron-builder.dev.cjs b/electron-builder.dev.cjs index 4ec5151f..8c8e09ca 100644 --- a/electron-builder.dev.cjs +++ b/electron-builder.dev.cjs @@ -13,6 +13,7 @@ module.exports = { productName: 'DSH Desktop Dev', dshDesktopChannel: 'development' }, + artifactName: 'dsh-desktop-dev-${os}-${arch}.${ext}', nsis: { ...packageJson.build.nsis, artifactName: 'dsh-desktop-dev-windows-${arch}-setup.${ext}' diff --git a/package.json b/package.json index 7aa33592..93079de3 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,8 @@ "test:watch": "vitest", "package:dir": "npm run build && electron-builder --dir", "package:dev:dir": "npm run build && electron-builder --dir --config electron-builder.dev.cjs", + "package:dev:mac:arm64": "node scripts/verify-target.mjs darwin arm64 && npm run build && electron-builder --mac --arm64 --publish never --config electron-builder.dev.cjs", + "package:dev:mac:x64": "node scripts/verify-target.mjs darwin x64 && npm run build && electron-builder --mac --x64 --publish never --config electron-builder.dev.cjs", "package:dev:win": "node scripts/verify-target.mjs win32 x64 && npm run build && electron-builder --win --x64 --publish never --config electron-builder.dev.cjs", "package:mac": "npm run build && electron-builder --mac --publish never", "package:mac:arm64": "node scripts/verify-target.mjs darwin arm64 && npm run build && electron-builder --mac --arm64 --publish never", diff --git a/test/feishu-release-notes.test.ts b/test/feishu-release-notes.test.ts index 4aac6cf8..0d47c11a 100644 --- a/test/feishu-release-notes.test.ts +++ b/test/feishu-release-notes.test.ts @@ -7,9 +7,12 @@ describe('Feishu release notes pipeline', () => { const scriptPath = join(process.cwd(), '.github', 'scripts', 'feishu_release_notes.py') const workflowPath = join(process.cwd(), '.github', 'workflows', 'release.yml') + const pythonEnv = { ...process.env, PYTHONIOENCODING: 'utf-8' } + it('builds a prompt with valid metadata and evidence blocks', () => { const output = execFileSync('python3', [scriptPath, 'build-prompt', '--tag', 'v0.4.0'], { - encoding: 'utf8' + encoding: 'utf8', + env: pythonEnv }) expect(output).toContain("You are DSH Desktop's Release Bot.") @@ -26,7 +29,8 @@ describe('Feishu release notes pipeline', () => { const tempFile = join(process.cwd(), '.temp-feishu-test-notes.md') try { execFileSync('python3', [scriptPath, 'generate-fallback', '--tag', 'v0.4.0', '--output', tempFile], { - encoding: 'utf8' + encoding: 'utf8', + env: pythonEnv }) const content = readFileSync(tempFile, 'utf8') @@ -37,7 +41,8 @@ describe('Feishu release notes pipeline', () => { // Validate passes without error const validateOutput = execFileSync('python3', [scriptPath, 'validate', '--tag', 'v0.4.0', '--input', tempFile], { - encoding: 'utf8' + encoding: 'utf8', + env: pythonEnv }) expect(validateOutput).toContain('validated successfully') } finally { @@ -74,7 +79,8 @@ Description here. expect(() => { execFileSync('python3', [scriptPath, 'validate', '--tag', 'v0.4.0', '--input', tempFile], { encoding: 'utf8', - stdio: 'pipe' + stdio: 'pipe', + env: pythonEnv }) }).toThrow() } finally { diff --git a/test/release.test.ts b/test/release.test.ts index feb37b80..cd19c949 100644 --- a/test/release.test.ts +++ b/test/release.test.ts @@ -183,7 +183,10 @@ describe('GitHub release contract', () => { 'package:mac', 'package:mac:arm64', 'package:mac:x64', - 'package:win' + 'package:win', + 'package:dev:mac:arm64', + 'package:dev:mac:x64', + 'package:dev:win' ]) { expect(packageJson.scripts[script]).toContain('--publish never') } @@ -201,6 +204,10 @@ describe('GitHub release contract', () => { expect(packageJson.scripts['package:dev:dir']).toContain('npm run build') expect(packageJson.scripts['package:dev:dir']).toContain('electron-builder.dev.cjs') + expect(packageJson.scripts['package:dev:mac:arm64']).toContain('verify-target.mjs darwin arm64') + expect(packageJson.scripts['package:dev:mac:arm64']).toContain('electron-builder.dev.cjs') + expect(packageJson.scripts['package:dev:mac:x64']).toContain('verify-target.mjs darwin x64') + expect(packageJson.scripts['package:dev:mac:x64']).toContain('electron-builder.dev.cjs') expect(packageJson.scripts['package:dev:win']).toContain('verify-target.mjs win32 x64') expect(packageJson.scripts['package:dev:win']).toContain('electron-builder.dev.cjs') expect(packageJson.scripts['package:dev:win']).toContain('--publish never') @@ -208,6 +215,9 @@ describe('GitHub release contract', () => { expect(developmentConfig).toContain("productName: 'DSH Desktop Dev'") expect(developmentConfig).toContain("output: 'dist-dev'") expect(developmentConfig).toContain("dshDesktopChannel: 'development'") + expect(developmentConfig).toContain( + "artifactName: 'dsh-desktop-dev-${os}-${arch}.${ext}'" + ) expect(developmentConfig).toContain( "artifactName: 'dsh-desktop-dev-windows-${arch}-setup.${ext}'" ) From b502eb59e23da8b0d25cca1e05b7d7ccfe623ebc Mon Sep 17 00:00:00 2001 From: yaojin3616 Date: Fri, 21 Aug 2026 13:21:43 +0800 Subject: [PATCH 3/4] fix(ci): avoid moving non-existent update metadata on mac dev builds --- .github/workflows/release.yml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ed00a494..de544dc9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -88,9 +88,6 @@ jobs: - name: Preserve Apple Silicon update metadata if: startsWith(github.ref, 'refs/tags/v') run: mv dist/latest-mac.yml dist/latest-mac-arm64.yml - - name: Preserve Apple Silicon development update metadata - if: ${{ !startsWith(github.ref, 'refs/tags/v') }} - run: mv dist-dev/latest-mac.yml dist-dev/latest-mac-arm64.yml - name: Sign, notarize, and staple Apple Silicon DMG if: startsWith(github.ref, 'refs/tags/v') env: @@ -153,7 +150,6 @@ jobs: dist-dev/dsh-desktop-dev-mac-arm64.dmg dist-dev/dsh-desktop-dev-mac-arm64.zip dist-dev/dsh-desktop-dev-mac-arm64.zip.blockmap - dist-dev/latest-mac-arm64.yml if-no-files-found: error macos-intel: @@ -223,9 +219,6 @@ jobs: - name: Preserve Intel update metadata if: startsWith(github.ref, 'refs/tags/v') run: mv dist/latest-mac.yml dist/latest-mac-x64.yml - - name: Preserve Intel development update metadata - if: ${{ !startsWith(github.ref, 'refs/tags/v') }} - run: mv dist-dev/latest-mac.yml dist-dev/latest-mac-x64.yml - name: Sign, notarize, and staple Intel DMG if: startsWith(github.ref, 'refs/tags/v') env: @@ -288,7 +281,6 @@ jobs: dist-dev/dsh-desktop-dev-mac-x64.dmg dist-dev/dsh-desktop-dev-mac-x64.zip dist-dev/dsh-desktop-dev-mac-x64.zip.blockmap - dist-dev/latest-mac-x64.yml if-no-files-found: error windows-x64: From 979af3d9bd1bfda70c00ed29aba4185533e58996 Mon Sep 17 00:00:00 2001 From: yaojin3616 Date: Fri, 21 Aug 2026 13:41:47 +0800 Subject: [PATCH 4/4] fix(loader): resolve user-installed community plugins relative to profile baseUrl --- ...epseek-ai+cordis-plugin-loader+1.0.2.patch | 34 +++++++++++++++++++ test/cordis-plugin-loader-patch.test.ts | 22 ++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 patches/@deepseek-ai+cordis-plugin-loader+1.0.2.patch create mode 100644 test/cordis-plugin-loader-patch.test.ts diff --git a/patches/@deepseek-ai+cordis-plugin-loader+1.0.2.patch b/patches/@deepseek-ai+cordis-plugin-loader+1.0.2.patch new file mode 100644 index 00000000..995f5d15 --- /dev/null +++ b/patches/@deepseek-ai+cordis-plugin-loader+1.0.2.patch @@ -0,0 +1,34 @@ +diff --git a/node_modules/@deepseek-ai/cordis-plugin-loader/lib/index.js b/node_modules/@deepseek-ai/cordis-plugin-loader/lib/index.js +index 6f4c713..dc48f9d 100644 +--- a/node_modules/@deepseek-ai/cordis-plugin-loader/lib/index.js ++++ b/node_modules/@deepseek-ai/cordis-plugin-loader/lib/index.js +@@ -266,10 +266,25 @@ var EntryTree = class EntryTree { + /* @vite-ignore */ + new URL(name, this.ctx.baseUrl).href + )); +- else return await import(__rewriteRelativeImportExtension( +- /* @vite-ignore */ +- name +- )); ++ else { ++ try { ++ return await import(__rewriteRelativeImportExtension( ++ /* @vite-ignore */ ++ name ++ )); ++ } catch (error) { ++ if (this.ctx.baseUrl) { ++ try { ++ const { createRequire } = await import("node:module"); ++ const { pathToFileURL } = await import("node:url"); ++ const req = createRequire(new URL("package.json", this.ctx.baseUrl).href); ++ const resolved = req.resolve(name); ++ return await import(pathToFileURL(resolved).href); ++ } catch {} ++ } ++ throw error; ++ } ++ } + }, getOuterStack); + } + }; diff --git a/test/cordis-plugin-loader-patch.test.ts b/test/cordis-plugin-loader-patch.test.ts new file mode 100644 index 00000000..43cd9830 --- /dev/null +++ b/test/cordis-plugin-loader-patch.test.ts @@ -0,0 +1,22 @@ +import { readFile } from 'node:fs/promises' +import path from 'node:path' +import { describe, expect, it } from 'vitest' + +const projectRoot = path.resolve(import.meta.dirname, '..') + +describe('cordis-plugin-loader resolution patch', () => { + it('falls back to resolving bare plugins relative to ctx.baseUrl', async () => { + const patch = await readFile( + path.join( + projectRoot, + 'patches', + '@deepseek-ai+cordis-plugin-loader+1.0.2.patch' + ), + 'utf8' + ) + + expect(patch).toContain('const req = createRequire(new URL("package.json", this.ctx.baseUrl).href)') + expect(patch).toContain('const resolved = req.resolve(name)') + expect(patch).toContain('return await import(pathToFileURL(resolved).href)') + }) +})