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
45 changes: 38 additions & 7 deletions .github/workflows/desktop-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@
# Required repository secrets:
# TAURI_SIGNING_PRIVATE_KEY
# TAURI_SIGNING_PRIVATE_KEY_PASSWORD
# macOS additionally requires:
# macOS optionally uses these secrets for Developer ID signing and notarization:
# APPLE_CERTIFICATE, APPLE_CERTIFICATE_PASSWORD, APPLE_SIGNING_IDENTITY
# APPLE_ID, APPLE_PASSWORD, APPLE_TEAM_ID
# When they are absent, both native macOS packages use ad-hoc signing. Users
# must approve those builds through System Settings > Privacy & Security.
#
# To cut a release:
# 1. Run `npm --prefix desktop run version:desktop -- X.Y.Z` and commit the
Expand Down Expand Up @@ -147,18 +149,47 @@ jobs:
- name: Verify dependency installation kept the checkout clean
run: git diff --exit-code -- desktop/package-lock.json

# Do not pass empty APPLE_CERTIFICATE variables to tauri-action: their
# presence makes the action try to import an empty keychain certificate.
# Export the complete Developer ID environment only when every signing
# and notarization secret is available; otherwise request ad-hoc signing.
- name: Configure macOS signing
if: startsWith(matrix.platform, 'macos-')
shell: bash
env:
APPLE_CERTIFICATE_SECRET: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE_PASSWORD_SECRET: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
APPLE_SIGNING_IDENTITY_SECRET: ${{ secrets.APPLE_SIGNING_IDENTITY }}
APPLE_ID_SECRET: ${{ secrets.APPLE_ID }}
APPLE_PASSWORD_SECRET: ${{ secrets.APPLE_PASSWORD }}
APPLE_TEAM_ID_SECRET: ${{ secrets.APPLE_TEAM_ID }}
run: |
if [[ -n "$APPLE_CERTIFICATE_SECRET" \
&& -n "$APPLE_CERTIFICATE_PASSWORD_SECRET" \
&& -n "$APPLE_SIGNING_IDENTITY_SECRET" \
&& -n "$APPLE_ID_SECRET" \
&& -n "$APPLE_PASSWORD_SECRET" \
&& -n "$APPLE_TEAM_ID_SECRET" ]]; then
{
printf 'APPLE_CERTIFICATE=%s\n' "$APPLE_CERTIFICATE_SECRET"
printf 'APPLE_CERTIFICATE_PASSWORD=%s\n' "$APPLE_CERTIFICATE_PASSWORD_SECRET"
printf 'APPLE_SIGNING_IDENTITY=%s\n' "$APPLE_SIGNING_IDENTITY_SECRET"
printf 'APPLE_ID=%s\n' "$APPLE_ID_SECRET"
printf 'APPLE_PASSWORD=%s\n' "$APPLE_PASSWORD_SECRET"
printf 'APPLE_TEAM_ID=%s\n' "$APPLE_TEAM_ID_SECRET"
} >> "$GITHUB_ENV"
echo "Using Developer ID signing and notarization."
else
echo 'APPLE_SIGNING_IDENTITY=-' >> "$GITHUB_ENV"
echo "Apple credentials are incomplete; using ad-hoc signing."
fi

- name: Build and publish release
uses: tauri-apps/tauri-action@v0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
HUGAGENT_RELEASE_BUILD: "1"
# tsc + Vite peaks near Node's default heap limit on hosted runners.
NODE_OPTIONS: "--max-old-space-size=4096"
Expand Down
8 changes: 7 additions & 1 deletion desktop/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,13 @@ npm --prefix desktop run lock:desktop
`prepare-bundle.mjs` 会在耗时构建开始前校验三者一致。公开 CE 的 Desktop Release workflow 会在
启动 Windows x86_64、Linux x86_64、macOS arm64、macOS x86_64 四个原生目标前校验 release tag
必须精确等于 `desktop-v<上述版本号>`;版本或 tag 不一致时不会创建任何平台产物。工作流固定使用
`uv 0.11.33`;macOS 正式发布还需配置 Apple 证书、签名身份与 notarization 所需 secrets。
`uv 0.11.33`;macOS 正式发布建议配置 Apple 证书、签名身份与 notarization 所需 secrets。

Apple 凭据不是生成测试安装包的硬前置。公开 CE 的 Release workflow 在未配置 Apple secrets 时会
自动使用 ad-hoc 身份(`-`)签名 App 及内置 Python runtime 的 Mach-O 文件,并分别生成 Apple
Silicon 与 Intel DMG;用户首次打开时需要在“系统设置 → 隐私与安全性”中选择“仍要打开”。配置
Developer ID 与 notarization secrets 后,workflow 会自动改用正式签名,避免这一步人工放行。
Tauri updater 的 `TAURI_SIGNING_PRIVATE_KEY` 是独立的更新包验签机制,仍为必需项。

## 关键文件

Expand Down
4 changes: 2 additions & 2 deletions desktop/package-lock.json

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

2 changes: 1 addition & 1 deletion desktop/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "hugagent-desktop",
"version": "0.2.7",
"version": "0.2.8",
"private": true,
"description": "HugAgentOS 桌面客户端(远程连接 + Windows/macOS/Linux 离线本机服务)",
"scripts": {
Expand Down
8 changes: 5 additions & 3 deletions desktop/scripts/build-runtime.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -204,9 +204,11 @@ function directorySize(root) {

function signMacRuntime(root) {
if (process.platform !== "darwin") return;
const identity = process.env.APPLE_SIGNING_IDENTITY || "-";
if (process.env.HUGAGENT_RELEASE_BUILD === "1" && identity === "-") {
throw new Error("APPLE_SIGNING_IDENTITY is required for a macOS release runtime.");
const identity = process.env.APPLE_SIGNING_IDENTITY?.trim() || "-";
if (identity === "-") {
console.warn(
"[desktop] Apple signing identity unavailable; using ad-hoc signing for the macOS runtime.",
);
}
const files = [];
const visit = (directory) => {
Expand Down
82 changes: 81 additions & 1 deletion desktop/scripts/mac-installer.test.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { existsSync, readFileSync } from "node:fs";
import { dirname, join, resolve } from "node:path";
import test from "node:test";
import { fileURLToPath } from "node:url";
Expand All @@ -10,6 +10,7 @@ import {
} from "./desktop-build-target.mjs";

const desktopDir = resolve(dirname(fileURLToPath(import.meta.url)), "..");
const repoDir = resolve(desktopDir, "..");
const rustDir = join(desktopDir, "src-tauri", "src");

test("desktop local install is offline and shared by all three operating systems", () => {
Expand Down Expand Up @@ -47,6 +48,51 @@ test("Windows macOS and Linux packages embed both source and runtime payloads",
}
});

test("minimize-to-tray destroys the close confirmation window", () => {
const source = readFileSync(join(rustDir, "lib.rs"), "utf8");
const decisionStart = source.indexOf(
'if let Some(cw) = app2.get_webview_window("close-confirm")',
);
assert.notEqual(decisionStart, -1);
const decision = source.slice(decisionStart, source.indexOf("if exit", decisionStart));
assert.match(decision, /cw\.hide\(\)/);
assert.match(decision, /cw\.close\(\)/);
assert.ok(decision.indexOf("cw.hide()") < decision.indexOf("cw.close()"));
});

test("CE project creation follows the provisioned desktop mode", () => {
const deploymentStore = readFileSync(
join(repoDir, "src", "frontend", "src", "stores", "deploymentModeStore.ts"),
"utf8",
);
const projectComponent = (name) => {
const overlayPath = join(
repoDir,
"ce",
"overlay",
"src",
"frontend",
"src",
"components",
"projects",
name,
);
return existsSync(overlayPath)
? overlayPath
: join(repoDir, "src", "frontend", "src", "components", "projects", name);
};
const projectsPanel = readFileSync(projectComponent("ProjectsPanel.tsx"), "utf8");
const createModal = readFileSync(projectComponent("CreateProjectModal.tsx"), "utf8");

assert.match(deploymentStore, /local_only'.*cloud: false, local: true/);
assert.match(deploymentStore, /dual'.*cloud: true, local: true/);
assert.match(projectsPanel, /projectCreationTargets\(isDesktop, provisionMode\)/);
assert.match(projectsPanel, /canCreateCloudProject && canCreateLocalProject/);
assert.match(projectsPanel, /\/__desktop\/pick-local-folder/);
assert.match(createModal, /if \(!canCreateCloudProject\)/);
assert.match(createModal, /if \(!canCreateCloudProject\) return null/);
});

test("release builder validates dependencies and relocatable runtime before archiving", () => {
const builder = readFileSync(join(desktopDir, "scripts", "build-runtime.mjs"), "utf8");
const smoke = readFileSync(join(desktopDir, "scripts", "runtime-smoke.py"), "utf8");
Expand All @@ -58,6 +104,40 @@ test("release builder validates dependencies and relocatable runtime before arch
assert.match(smoke, /import_module\("cli"\)/);
});

test("macOS release falls back to ad-hoc signing without Apple credentials", () => {
const builder = readFileSync(join(desktopDir, "scripts", "build-runtime.mjs"), "utf8");
const overlayWorkflow = join(
repoDir,
"ce",
"overlay",
".github",
"workflows",
"desktop-release.yml",
);
const workflow = readFileSync(
existsSync(overlayWorkflow)
? overlayWorkflow
: join(repoDir, ".github", "workflows", "desktop-release.yml"),
"utf8",
);

assert.match(builder, /APPLE_SIGNING_IDENTITY\?\.trim\(\) \|\| "-"/);
assert.doesNotMatch(builder, /APPLE_SIGNING_IDENTITY is required/);
assert.match(builder, /\["--force", "--sign", identity\]/);

const configureStepStart = workflow.indexOf("- name: Configure macOS signing");
const buildStepStart = workflow.indexOf("- name: Build and publish release");
assert.notEqual(configureStepStart, -1);
assert.ok(buildStepStart > configureStepStart);

const configureStep = workflow.slice(configureStepStart, buildStepStart);
const buildStep = workflow.slice(buildStepStart);
assert.match(configureStep, /APPLE_CERTIFICATE_SECRET/);
assert.match(configureStep, /echo 'APPLE_SIGNING_IDENTITY=-'/);
assert.doesNotMatch(buildStep, /APPLE_CERTIFICATE:/);
assert.doesNotMatch(buildStep, /APPLE_CERTIFICATE_PASSWORD:/);
});

test("CE generation uses an isolated pinned release-builder dependency", () => {
const prepare = readFileSync(join(desktopDir, "scripts", "prepare-bundle.mjs"), "utf8");
const requirements = readFileSync(
Expand Down
2 changes: 1 addition & 1 deletion desktop/src-tauri/Cargo.lock

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

2 changes: 1 addition & 1 deletion desktop/src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "hugagent-desktop"
version = "0.2.7"
version = "0.2.8"
description = "HugAgentOS桌面客户端"
edition = "2021"
rust-version = "1.77"
Expand Down
6 changes: 4 additions & 2 deletions desktop/src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -672,9 +672,11 @@ fn open_close_confirm(app: &tauri::AppHandle) {
);
}
if let Some(cw) = app2.get_webview_window("close-confirm") {
// Reuse the fully-loaded WebView next time. Rebuilding it is
// both slower and the main source of close-dialog flicker.
// Destroy the confirmation window after making it invisible.
// Keeping the loaded WebView around races its page-load handler:
// it can show itself again after the main window was hidden.
let _ = cw.hide();
let _ = cw.close();
}
if exit {
app2.exit(0);
Expand Down
2 changes: 1 addition & 1 deletion desktop/src-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "HugAgentOS",
"version": "0.2.7",
"version": "0.2.8",
"identifier": "com.hugagent.desktop",
"build": {
"frontendDist": "../../src/frontend/dist",
Expand Down
6 changes: 4 additions & 2 deletions document/en/deployment/windows-deployment.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Windows installation and deployment

> Last updated: July 23, 2026 | [简体中文](../../zh-CN/deployment/windows-deployment.md) | Back to [Deployment Guide](README.md)
> Last updated: July 31, 2026 | [简体中文](../../zh-CN/deployment/windows-deployment.md) | Back to [Deployment Guide](README.md)

Windows users can run a Docker-free, desktop-managed local service or deploy the
team service through Docker Desktop and WSL2. The local option is for personal use;
Expand Down Expand Up @@ -47,7 +47,9 @@ don't need to reinstall the desktop client.

The local service listens only on `http://127.0.0.1:32101`; it isn't exposed to the
LAN. Exiting the desktop app stops the service process. Minimizing to the tray keeps
it running for background automations.
it running for background automations. After **Minimize to tray** is selected, the
close-confirmation window is destroyed immediately and does not reappear when the
main window is restored from the tray.

The runtime and data live under this directory:

Expand Down
16 changes: 15 additions & 1 deletion document/en/modules/projects-myspace.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Projects & MySpace

> Last updated: 2026-07-30
> Last updated: 2026-07-31

HugAgentOS provides two tiers of personal workspace:

Expand Down Expand Up @@ -74,6 +74,20 @@ shortcuts on the project row:
deletes the project, and existing conversations return to the regular chat
history.

### Desktop project-creation entry points

Desktop creation controls strictly follow the provision mode selected during
installation, with the same behavior on the Projects page and in the chat
composer's project menu:

- **Local only (`local_only`)** shows only **New Local Project** and uses the
system folder picker; the cloud-project modal is unavailable.
- **Cloud only (`cloud_only`)** shows only **New Cloud Project** and offers no
local-folder project action.
- **Dual mode (`dual`)** offers both cloud and local project actions.
- The regular Web application has no desktop folder picker and keeps the cloud
personal/team project flow.

## MySpace

### File assets and personal folders
Expand Down
5 changes: 3 additions & 2 deletions document/zh-CN/deployment/windows-deployment.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Windows 安装与部署

> 最后更新:2026-07-23 | [English](../../en/deployment/windows-deployment.md) | 返回 [部署指南](README.md)
> 最后更新:2026-07-31 | [English](../../en/deployment/windows-deployment.md) | 返回 [部署指南](README.md)

Windows 用户可以选择桌面端托管的无 Docker 单机服务,也可以通过 Docker Desktop + WSL2
运行团队版 Compose 服务。前者适合个人使用,后者适合多人协作与生产。
Expand Down Expand Up @@ -40,7 +40,8 @@ Windows x86_64 个人用户可以直接使用 NSIS 桌面安装包。安装包
### 运行与数据

本机服务只监听 `http://127.0.0.1:32101`,不会向局域网暴露。客户端退出时会回收服务进程;
最小化到托盘时服务继续运行,以支持后台自动化任务。
最小化到托盘时服务继续运行,以支持后台自动化任务。选择“最小化到托盘”后,退出确认窗口会立即
关闭并销毁;从托盘恢复主窗口时不会残留上一次的退出提示。

数据与运行环境位于:

Expand Down
11 changes: 10 additions & 1 deletion document/zh-CN/modules/projects-myspace.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# 项目空间与我的空间

> 最后更新:2026-07-30
> 最后更新:2026-07-31

HugAgentOS 提供两级个人化工作区:

Expand Down Expand Up @@ -70,6 +70,15 @@ teams ─────────┬── team_members(role: owner/admin/memb
- 打开更多菜单,可将项目置顶、在项目面板中打开,或移除项目。置顶与移除仅对
项目管理员可用;移除会软删除项目,已有对话会回落到普通历史对话列表。

### 桌面端新建项目入口

桌面端的新建入口严格跟随安装时选定的运行形态,项目列表页与聊天输入区的项目下拉保持一致:

- **纯本机(`local_only`)**:只显示“新建本地项目”,通过系统文件夹选择器把本机目录建立为项目;不显示、也不能打开云端项目创建弹窗;
- **纯云端(`cloud_only`)**:只显示“新建云端项目”,不提供本机文件夹项目入口;
- **双模式(`dual`)**:同时提供云端项目和本地项目,由用户在新建时选择;
- **普通 Web**:没有桌面文件夹选择器,因此保持云端个人/团队项目创建流程。

## 我的空间(MySpace)

### 文件资产与个人文件夹
Expand Down
Loading
Loading