release: v0.1.3

This commit is contained in:
2026-08-17 21:01:02 +08:00
parent 67f684d988
commit 9d9fbd3184
8 changed files with 114 additions and 12 deletions

4
Cargo.lock generated
View File

@@ -949,7 +949,7 @@ dependencies = [
[[package]] [[package]]
name = "dsh-core" name = "dsh-core"
version = "0.1.2" version = "0.1.3"
dependencies = [ dependencies = [
"base64 0.22.1", "base64 0.22.1",
"dirs", "dirs",
@@ -966,7 +966,7 @@ dependencies = [
[[package]] [[package]]
name = "dsh-desktop" name = "dsh-desktop"
version = "0.1.2" version = "0.1.3"
dependencies = [ dependencies = [
"arboard", "arboard",
"dsh-core", "dsh-core",

View File

@@ -3,7 +3,7 @@ members = ["crates/dsh-core", "src-tauri"]
resolver = "2" resolver = "2"
[workspace.package] [workspace.package]
version = "0.1.2" version = "0.1.3"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
authors = ["TommyFang2077"] authors = ["TommyFang2077"]

View File

@@ -68,18 +68,19 @@ dsh 原本运行在浏览器标签页中;本项目用 Tauri 2 和系统 WebVie
| 平台 | 产物 | 运行时要求 | | 平台 | 产物 | 运行时要求 |
| --- | --- | --- | | --- | --- | --- |
| Windows | NSIS `.exe` / `.msi` | [WebView2](https://developer.microsoft.com/microsoft-edge/webview2/)(安装器可引导下载)+ 本机 `dsh` | | Windows | NSIS `.exe` / `.msi` | [WebView2](https://developer.microsoft.com/microsoft-edge/webview2/)(安装器可引导下载)+ Node.js/npm首次启动自动安装 `dsh` 到内置目录) |
| macOS | Apple Silicon / Intel `.dmg` | 未公证,首次打开需在「隐私与安全性」允许 + 本机 `dsh` | | macOS | Apple Silicon / Intel `.dmg` | 未公证,首次打开需在「隐私与安全性」允许 + Node.js/npm首次启动自动安装 `dsh` 到内置目录) |
| Linux | `.deb` / `.rpm` | WebKitGTK 4.1 + 本机 `dsh` | | Linux | `.deb` / `.rpm` | WebKitGTK 4.1 + Node.js/npm首次启动自动安装 `dsh` 到内置目录) |
| Linux Flatpak | `.flatpak` | **零依赖**:自带 Node.js 24 与 `@deepseek-ai/dsh` | | Linux Flatpak | `.flatpak` | **零依赖**:自带 Node.js 24 与 `@deepseek-ai/dsh` |
除 Flatpak 外,需要先安装 `dsh` 除 Flatpak 外,需要本机有 Node.js/npm用于首次启动自动下载并安装 `dsh`。也可以提前安装或指定路径
```bash ```bash
npm install -g @deepseek-ai/dsh npm install -g @deepseek-ai/dsh
# 或设置 DSH_DESKTOP_DSH_BIN 指向 dsh 可执行文件
``` ```
安装后直接启动,等待启动页完成即可进入官方 WebUI。 未检测到 `dsh` 时,第一次启动会显示「正在下载并安装内置 dsh…」完成后自动进入官方 WebUI。
## 功能一览 ## 功能一览

View File

@@ -26,6 +26,15 @@ impl DshNotFound {
fn new(msg: impl Into<String>) -> Self { fn new(msg: impl Into<String>) -> Self {
Self::Message(msg.into()) Self::Message(msg.into())
} }
/// True when resolution successfully analyzed every candidate and found no
/// usable dsh at all (as opposed to an invalid user-supplied override).
pub fn is_missing(&self) -> bool {
matches!(
self,
DshNotFound::Message(message) if message.contains("找不到")
)
}
} }
pub fn strip_ansi(text: &str) -> String { pub fn strip_ansi(text: &str) -> String {
@@ -59,6 +68,18 @@ pub fn find_npx_dsh_bins() -> Vec<PathBuf> {
if let Some(globbed) = pattern.to_str() { if let Some(globbed) = pattern.to_str() {
if let Ok(paths) = glob_simple(globbed) { if let Ok(paths) = glob_simple(globbed) {
for path in paths { for path in paths {
// npm also drops an extensionless POSIX shim (`dsh`) next to
// `dsh.cmd`; Windows cannot CreateProcess that file, so prefer
// the cmd wrapper when present.
#[cfg(windows)]
let path = {
let cmd = path.with_extension("cmd");
if cmd.is_file() {
cmd
} else {
path
}
};
if is_executable(&path) { if is_executable(&path) {
bins.push(path); bins.push(path);
} }
@@ -105,7 +126,7 @@ impl DshLauncher {
} }
} }
pub fn resolve(&self) -> Result<Vec<String>, DshNotFound> { fn collect_candidates(&self) -> Result<Vec<Vec<String>>, DshNotFound> {
let mut candidates: Vec<Vec<String>> = Vec::new(); let mut candidates: Vec<Vec<String>> = Vec::new();
if let Ok(env_override) = std::env::var(ENV_BIN_OVERRIDE) { if let Ok(env_override) = std::env::var(ENV_BIN_OVERRIDE) {
@@ -163,6 +184,11 @@ impl DshLauncher {
} }
} }
Ok(candidates)
}
pub fn resolve(&self) -> Result<Vec<String>, DshNotFound> {
let candidates = self.collect_candidates()?;
let chosen = candidates.into_iter().next().ok_or_else(|| { let chosen = candidates.into_iter().next().ok_or_else(|| {
if self.in_flatpak { if self.in_flatpak {
DshNotFound::new( DshNotFound::new(
@@ -170,13 +196,22 @@ impl DshLauncher {
) )
} else { } else {
DshNotFound::new( DshNotFound::new(
"找不到 DeepSeek Harness (dsh),也没有可用的 npx\n安装npm install -g @deepseek-ai/dsh\n或用 DSH_DESKTOP_DSH_BIN 指定 dsh 的完整路径。", "找不到 DeepSeek Harness (dsh)。\n请安装 Node.js/npm首次启动会自动安装 dsh或用 DSH_DESKTOP_DSH_BIN 指定 dsh 的完整路径。",
) )
} }
})?; })?;
Ok(chosen) Ok(chosen)
} }
/// True when a real dsh binary is available without relying on the
/// transient `npx --yes @deepseek-ai/dsh` fallback.
pub fn has_installed_dsh(&self) -> Result<bool, DshNotFound> {
let candidates = self.collect_candidates()?;
Ok(candidates
.iter()
.any(|candidate| candidate.first().map(String::as_str) != Some("npx")))
}
pub fn web_argv(&self) -> Result<Vec<String>, DshNotFound> { pub fn web_argv(&self) -> Result<Vec<String>, DshNotFound> {
let mut argv = self.resolve()?; let mut argv = self.resolve()?;
argv.extend([ argv.extend([
@@ -406,6 +441,15 @@ mod tests {
assert_eq!(argv, vec!["echo"]); assert_eq!(argv, vec!["echo"]);
} }
#[test]
fn has_installed_dsh_with_override() {
let _guard = ENV_LOCK.lock().unwrap();
std::env::set_var(ENV_BIN_OVERRIDE, "echo");
let launcher = DshLauncher::new(None, None);
assert!(launcher.has_installed_dsh().unwrap());
std::env::remove_var(ENV_BIN_OVERRIDE);
}
#[test] #[test]
fn invalid_env_override_raises() { fn invalid_env_override_raises() {
let _guard = ENV_LOCK.lock().unwrap(); let _guard = ENV_LOCK.lock().unwrap();

View File

@@ -41,6 +41,15 @@
<content_rating type="oars-1.1" /> <content_rating type="oars-1.1" />
<releases> <releases>
<release version="0.1.3" date="2026-08-17">
<description>
<ul>
<li>Non-Flatpak installs auto-install dsh into the app's built-in prefix on first launch when no dsh is found</li>
<li>Fixed Gitea mirror publish failing on a first-version 404 (release lookup no longer retries 404)</li>
<li>Windows prefers the dsh.cmd shim over the extensionless POSIX script in the npx cache</li>
</ul>
</description>
</release>
<release version="0.1.2" date="2026-08-17"> <release version="0.1.2" date="2026-08-17">
<description> <description>
<ul> <ul>

View File

@@ -0,0 +1,21 @@
# DeepSeek Harness Desktop v0.1.3
**摘要 / Summary:** 首次启动自动安装内置 dsh非 Flatpak 不再要求手动安装Gitea 镜像发布修复首个版本的 404 卡死Windows 上优先使用 `dsh.cmd` shim。
## 新功能 / Features
- **未检测到 dsh 时自动安装**:非 Flatpak 安装包首次启动如果找不到可用的 dsh会自动下载并安装到应用内置目录Windows 为 `%APPDATA%\dsh-desktop\dsh-prefix`Linux/macOS 为 `~/.local/share/dsh-desktop/dsh-prefix`),完成后直接进入官方 WebUI不再要求先手动 `npm install -g @deepseek-ai/dsh`
- 需要本机有 Node.js/npm没有 npm 时仍会给出明确提示
- `DSH_DESKTOP_DSH_BIN` / `--dsh``--no-update` / `DSH_DESKTOP_NO_UPDATE` 的优先级与原有行为不变
## 修复 / Fixes
- **Gitea 镜像发布在首个版本上失败**Release 查询把 404tag 已同步但 Release 尚未创建)误当瞬时故障重试 4 次后退出;现在 404 会直接走「创建 Release」分支
- **Windows 上 npx 缓存里的 dsh 无法启动**npm 在 `.npm/_npx/*/node_modules/.bin` 同时生成无扩展名的 POSIX 脚本 `dsh``dsh.cmd`,此前可能选中无扩展名脚本导致启动失败;现在 Windows 优先使用 `dsh.cmd`
## 变更 / Changes
- 非 Flatpak 安装包的运行时要求从「本机已安装 dsh」改为「本机有 Node.js/npm 用于首次自动安装」README 同步更新)
## 安装与更新 / Install & Update
- 安装包Windows NSIS/MSI、macOS DMGApple Silicon 与 Intel、Linux deb/rpm、Flatpak
- 非 Flatpak首次启动会自动安装 dsh 到内置目录;也可设置 `DSH_DESKTOP_DSH_BIN` 指定 dsh 路径
- macOS 包未公证:首次打开需在「系统设置 → 隐私与安全性」中允许
- 更新提示出现在启动页:点击「下载并安装」即可(走 Gitea 镜像,签名校验后生效)

View File

@@ -57,7 +57,10 @@ else
release_body="大陆镜像安装包;文件与 GitHub Release 同源。应用内更新包由 Tauri 签名校验。" release_body="大陆镜像安装包;文件与 GitHub Release 同源。应用内更新包由 Tauri 签名校验。"
fi fi
release_json=$(curl_retry curl --fail --silent --show-error -H "$AUTH" "$API/releases/tags/$RELEASE_TAG") # 404 is expected when the mirror has not yet created a release for the tag;
# the create branch below handles it. Only retry transient failures, so the
# lookup itself must not go through curl_retry.
release_json=$(curl --fail --silent --show-error -H "$AUTH" "$API/releases/tags/$RELEASE_TAG" || true)
release_id=$(printf '%s' "$release_json" | jq -r '.id // empty') release_id=$(printf '%s' "$release_json" | jq -r '.id // empty')
if [[ -z "$release_id" ]]; then if [[ -z "$release_id" ]]; then
release_payload=$(jq -n \ release_payload=$(jq -n \

View File

@@ -357,14 +357,38 @@ fn boot(app: AppHandle) {
); );
let launcher = DshLauncher::new(state.args.dsh.clone(), state.args.cwd.clone()); let launcher = DshLauncher::new(state.args.dsh.clone(), state.args.cwd.clone());
let missing = match launcher.resolve() {
Ok(_) => !launcher.has_installed_dsh().unwrap_or(true),
Err(error) => error.is_missing(),
};
let mut bootstrap_message: Option<String> = None;
if missing && !state.args.no_update {
let _ = app.emit(
"status",
StatusPayload {
message: "未检测到 dsh正在下载并安装内置 dsh…".into(),
},
);
let result = update_dsh(true);
mark_update_checked();
log::info!(
"dsh not found; bootstrap install: {} ({})",
result.status,
result.message
);
bootstrap_message = Some(result.message);
}
let process = match launcher.start() { let process = match launcher.start() {
Ok(p) => Arc::new(p), Ok(p) => Arc::new(p),
Err(err) => { Err(err) => {
let detail = bootstrap_message
.map(|message| format!("{message}\n{err}"))
.unwrap_or_default();
let _ = app.emit( let _ = app.emit(
"error", "error",
ErrorPayload { ErrorPayload {
message: err.to_string(), message: err.to_string(),
detail: String::new(), detail,
}, },
); );
return; return;