Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 83 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ on:
branches:
- main
workflow_dispatch:
inputs:
windows_prerelease_tag:
description: Optional non-v tag for a Windows Dev pre-release
required: false
type: string

permissions:
contents: write
Expand Down Expand Up @@ -268,15 +273,92 @@ jobs:
- run: npm ci
- run: npm test
- run: npm run typecheck
- run: npm run package:win
- name: Build Windows release package
if: startsWith(github.ref, 'refs/tags/v')
run: npm run package:win
- name: Build isolated Windows development package
if: github.event_name == 'workflow_dispatch'
run: npm run package:dev:win
- name: Smoke test packaged Windows Harness
if: github.event_name == 'workflow_dispatch'
shell: pwsh
run: |
$userData = Join-Path $env:APPDATA 'dsh-desktop-dev'
$logPath = Join-Path $userData 'logs\harness.log'
$executable = 'dist-dev\win-unpacked\DSH Desktop Dev.exe'
if (Test-Path $userData) { Remove-Item -Recurse -Force $userData }
$desktop = Start-Process -FilePath $executable -PassThru
try {
$deadline = (Get-Date).AddSeconds(75)
$ready = $false
while ((Get-Date) -lt $deadline) {
if ($desktop.HasExited) {
throw "DSH Desktop Dev exited before Harness was ready (exit code $($desktop.ExitCode))."
}
if (Test-Path $logPath) {
$log = Get-Content -Raw $logPath
$match = [regex]::Match($log, '\[desktop\] endpoint (http://127\.0\.0\.1:\d+)')
if ($match.Success) {
try {
$response = Invoke-WebRequest -UseBasicParsing -Uri $match.Groups[1].Value -TimeoutSec 2
if ($response.StatusCode -ge 200 -and $response.StatusCode -lt 500) {
Start-Sleep -Seconds 5
$stableLog = Get-Content -Raw $logPath
if ($stableLog -match '\[stderr\]') {
throw 'Harness reported stderr after HTTP became ready.'
}
if ($desktop.HasExited) {
throw "DSH Desktop Dev exited after Harness became ready (exit code $($desktop.ExitCode))."
}
$ready = $true
break
}
} catch { }
}
}
Start-Sleep -Milliseconds 500
}
if (-not $ready) { throw 'Packaged Harness did not become ready within 75 seconds.' }
Write-Host 'Packaged Windows Harness smoke test passed.'
} finally {
if (Test-Path $logPath) {
Write-Host '--- harness.log ---'
Get-Content $logPath
}
if (-not $desktop.HasExited) { Stop-Process -Id $desktop.Id -Force }
}
- name: Publish validated Windows development pre-release
if: github.event_name == 'workflow_dispatch' && inputs.windows_prerelease_tag != ''
shell: pwsh
env:
GH_TOKEN: ${{ github.token }}
PRERELEASE_TAG: ${{ inputs.windows_prerelease_tag }}
run: |
gh release create $env:PRERELEASE_TAG `
--repo $env:GITHUB_REPOSITORY `
--target $env:GITHUB_SHA `
--prerelease `
--title 'DSH Desktop Windows Harness Test' `
--notes "Windows x64 development build for validating the Harness startup fix from PR #39. This build uses an isolated app identity and user data directory, and has passed the packaged Windows Harness smoke test." `
'dist-dev/dsh-desktop-dev-windows-x64-setup.exe#DSH Desktop Dev Windows x64 Setup'
- uses: actions/upload-artifact@v4
if: startsWith(github.ref, 'refs/tags/v')
with:
name: windows-x64
path: |
dist/dsh-desktop-windows-x64-setup.exe
dist/dsh-desktop-windows-x64-setup.exe.blockmap
dist/latest.yml
if-no-files-found: error
- uses: actions/upload-artifact@v4
if: github.event_name == 'workflow_dispatch'
with:
name: windows-x64-dev
path: |
dist-dev/dsh-desktop-dev-windows-x64-setup.exe
dist-dev/dsh-desktop-dev-windows-x64-setup.exe.blockmap
dist-dev/latest.yml
if-no-files-found: error

publish:
name: Publish GitHub Release
Expand Down
15 changes: 15 additions & 0 deletions build/dsh-desktop.patch.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# DSH Desktop always runs the Host on the same machine as its operator.
# Pin the dual-face native interaction instead of the web/remote browse fallback.
- id: directory-picker
disabled: true

- insert:
- id: directory-picker-native-desktop
name: '@deepseek-ai/dsh-host-directory-picker-native'

# The native interaction has a second, renderless client row. Without it
# ui-workspace has no directory-flow occupant, so every workspace picker
# entry point becomes inert or disappears even though the Host capability
# itself is running.
- id: directory-picker-native-desktop-surface
name: '@deepseek-ai/dsh-client-ui-directory-picker-native'
32 changes: 32 additions & 0 deletions build/harness-node-entry.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { pathToFileURL } from 'node:url'

const [dshEntryPath, ...dshArguments] = process.argv.slice(2)

function report(label, value) {
process.stderr.write(`[harness-node] ${label}: ${value}\n`)
}

process.on('uncaughtException', (error) => report('uncaught exception', error?.stack ?? error))
process.on('unhandledRejection', (error) => report('unhandled rejection', error?.stack ?? error))

process.stdout.write(
`[harness-node] runtime node=${process.version} platform=${process.platform} arch=${process.arch}\n`
)
process.stdout.write(`[harness-node] execPath=${process.execPath}\n`)
process.stdout.write(`[harness-node] cwd=${process.cwd()}\n`)
process.stdout.write(`[harness-node] DSH_HOME=${process.env.DSH_HOME ?? ''}\n`)

if (!dshEntryPath) {
report('startup error', 'missing DSH entry path')
process.exitCode = 1
} else {
process.stdout.write(`[harness-node] loading=${dshEntryPath}\n`)
process.argv = [process.execPath, dshEntryPath, ...dshArguments]
try {
await import(pathToFileURL(dshEntryPath).href)
process.stdout.write('[harness-node] DSH entry loaded\n')
} catch (error) {
report('DSH entry failed', error?.stack ?? error)
process.exitCode = 1
}
}
42 changes: 42 additions & 0 deletions build/splash.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title></title>
<style>
:root { color-scheme: light dark; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }
* { box-sizing: border-box; }
html, body { width: 100%; height: 100%; margin: 0; overflow: hidden; }
body { display: grid; place-items: center; color: #17181a; background: #f8f8f6; }
main { display: grid; justify-items: center; gap: 18px; padding: 32px; text-align: center; }
.mark { width: 72px; height: 72px; border-radius: 18px; animation: breathe 1.9s ease-in-out infinite; }
.title { margin: 0; font-size: 23px; font-weight: 650; letter-spacing: -0.02em; }
.status { margin: -7px 0 0; color: #777a80; font-size: 13px; }
.track { width: 168px; height: 3px; overflow: hidden; border-radius: 99px; background: rgba(20, 21, 24, 0.09); }
.bar { width: 42%; height: 100%; border-radius: inherit; background: #4d6bfe; animation: travel 1.25s ease-in-out infinite; }
@keyframes breathe { 0%, 100% { transform: scale(0.97); opacity: 0.86; } 50% { transform: scale(1); opacity: 1; } }
@keyframes travel { 0% { transform: translateX(-105%); } 100% { transform: translateX(345%); } }
@media (prefers-color-scheme: dark) {
body { color: #f3f4f6; background: #141416; }
.status { color: #9ca0a8; }
.track { background: rgba(255, 255, 255, 0.11); }
}
@media (prefers-reduced-motion: reduce) { .mark, .bar { animation: none; } .bar { width: 100%; opacity: 0.6; } }
</style>
</head>
<body>
<main>
<img class="mark" src="icon.png" alt="" />
<h1 class="title" id="title">Starting DSH Desktop</h1>
<p class="status" id="status">First launch may take a moment.</p>
<div class="track" aria-hidden="true"><div class="bar"></div></div>
</main>
<script>
if (navigator.language.toLowerCase().startsWith('zh')) {
document.getElementById('title').textContent = '正在启动 DSH Desktop'
document.getElementById('status').textContent = '首次启动可能需要一点时间。'
}
</script>
</body>
</html>
4 changes: 4 additions & 0 deletions electron-builder.dev.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,9 @@ module.exports = {
productName: 'DSH Desktop Dev',
dshDesktopChannel: 'development'
},
nsis: {
...packageJson.build.nsis,
artifactName: 'dsh-desktop-dev-windows-${arch}-setup.${ext}'
},
publish: null
}
103 changes: 24 additions & 79 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 15 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"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: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",
"package:mac:x64": "node scripts/verify-target.mjs darwin x64 && npm run build && electron-builder --mac --x64 --publish never",
Expand All @@ -59,7 +60,8 @@
"@deepseek-ai/dsh-subprocess": "0.1.0-rc.6",
"@deepseek-ai/dsh-timeout": "0.1.0-rc.6",
"@deepseek-ai/dsh-workflow": "0.1.0-rc.6",
"electron-updater": "^6.8.9"
"electron-updater": "^6.8.9",
"node": "24.9.0"
},
"devDependencies": {
"@types/node": "24.10.1",
Expand Down Expand Up @@ -89,6 +91,18 @@
{
"from": "build/app-icon.png",
"to": "icon.png"
},
{
"from": "build/harness-node-entry.mjs",
"to": "harness-node-entry.mjs"
},
{
"from": "build/dsh-desktop.patch.yml",
"to": "dsh-desktop.patch.yml"
},
{
"from": "build/splash.html",
"to": "splash.html"
}
],
"publish": [
Expand Down
Loading
Loading