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
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.patch whitespace=-space-before-tab
175 changes: 153 additions & 22 deletions .github/workflows/installers.yml
Original file line number Diff line number Diff line change
@@ -1,57 +1,188 @@
name: Desktop Installers
name: Desktop Release

on:
push:
tags:
- "v*"
workflow_dispatch:

permissions:
contents: read
contents: write

env:
NODE_VERSION: 22.19.0
RELEASE_REPOSITORY: Modole/laobos-agent-studio

jobs:
verify:
runs-on: ubuntu-latest
timeout-minutes: 30
outputs:
version: ${{ steps.version.outputs.version }}
tag: ${{ steps.version.outputs.tag }}
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: ${{ env.NODE_VERSION }}
cache: npm
- run: npm ci
- id: version
shell: bash
run: |
version="$(node -p "require('./package.json').version")"
tag="v${version}"
if [[ "${GITHUB_REF_TYPE}" == "tag" && "${GITHUB_REF_NAME}" != "${tag}" ]]; then
echo "Tag ${GITHUB_REF_NAME} does not match package version ${version}." >&2
exit 1
fi
echo "version=${version}" >> "${GITHUB_OUTPUT}"
echo "tag=${tag}" >> "${GITHUB_OUTPUT}"
- run: npm run audit:public
- run: npm run lint
- run: npm test

macos-arm64:
needs: verify
runs-on: macos-15
timeout-minutes: 45
timeout-minutes: 60
env:
HAS_MAC_SIGNING: ${{ secrets.MAC_CSC_LINK != '' }}
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: 22.19.0
node-version: ${{ env.NODE_VERSION }}
cache: npm
- run: npm ci
- run: npm run audit:public
- run: npm run desktop:installer -- --mac dmg --arm64
- name: Build signed and notarized macOS release
if: env.HAS_MAC_SIGNING == 'true'
run: npm run desktop:installer -- --mac dmg zip --arm64
env:
CSC_LINK: ${{ secrets.MAC_CSC_LINK }}
CSC_KEY_PASSWORD: ${{ secrets.MAC_CSC_KEY_PASSWORD }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
- name: Build reproducibly signed community macOS release
if: env.HAS_MAC_SIGNING != 'true'
run: npm run desktop:installer:test-update -- --mac dmg zip --arm64
- run: node scripts/verify-packaged-runtime.mjs "out/installers/mac-arm64/劳博士.app/Contents/Resources/app.asar"
- run: test "$(find out/installers -maxdepth 1 -name 'laobos-studio-*-macos-arm64.dmg' | wc -l | tr -d ' ')" = "1"
- run: node scripts/verify-desktop-resources.mjs "out/installers/mac-arm64/劳博士.app/Contents/Resources"
- name: Verify macOS signature and update artifacts
shell: bash
run: |
codesign --verify --deep --strict --verbose=2 "out/installers/mac-arm64/劳博士.app"
if [[ "${HAS_MAC_SIGNING}" == "true" ]]; then
xcrun stapler validate "out/installers/mac-arm64/劳博士.app"
spctl --assess --type execute --verbose=2 "out/installers/mac-arm64/劳博士.app"
else
echo "No Developer ID secret configured; publishing the stable ad-hoc community update chain."
fi
test "$(find out/installers -maxdepth 1 -name 'laobos-studio-*-macos-arm64.dmg' | wc -l | tr -d ' ')" = "1"
test "$(find out/installers -maxdepth 1 -name 'laobos-studio-*-macos-arm64.zip' | wc -l | tr -d ' ')" = "1"
test -f out/installers/latest-mac.yml
- uses: actions/upload-artifact@v7
with:
name: laobos-studio-macos-arm64
path: out/installers/laobos-studio-*-macos-arm64.dmg
path: |
out/installers/laobos-studio-*-macos-arm64.dmg
out/installers/laobos-studio-*-macos-arm64.zip
out/installers/*.blockmap
out/installers/latest-mac.yml
if-no-files-found: error
compression-level: 0
retention-days: 7
retention-days: 3

windows-x64:
needs: verify
runs-on: windows-latest
timeout-minutes: 45
timeout-minutes: 60
env:
HAS_WINDOWS_SIGNING: ${{ secrets.WIN_CSC_LINK != '' }}
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: 22.19.0
node-version: ${{ env.NODE_VERSION }}
cache: npm
- run: npm ci
- run: npm run audit:public
- run: npm run desktop:installer -- --win nsis --x64
- name: Build signed Windows release
if: env.HAS_WINDOWS_SIGNING == 'true'
run: npm run desktop:installer -- --win nsis --x64
env:
CSC_LINK: ${{ secrets.WIN_CSC_LINK }}
CSC_KEY_PASSWORD: ${{ secrets.WIN_CSC_KEY_PASSWORD }}
- name: Build unsigned community Windows release
if: env.HAS_WINDOWS_SIGNING != 'true'
run: npm run desktop:installer -- --win nsis --x64
env:
CSC_IDENTITY_AUTO_DISCOVERY: false
- run: node scripts/verify-packaged-runtime.mjs "out/installers/win-unpacked/resources/app.asar"
- shell: pwsh
- run: node scripts/verify-desktop-resources.mjs "out/installers/win-unpacked/resources"
- run: node scripts/verify-windows-packaged-runtime.mjs "out/installers/win-unpacked/resources"
- name: Verify Windows signature and update artifacts
shell: pwsh
run: |
$installers = @(Get-ChildItem "out/installers/laobos-studio-*-windows-x64-setup.exe")
if ($installers.Count -ne 1) {
throw "Windows installer was not generated."
if ($installers.Count -ne 1) { throw "Windows installer was not generated." }
if ($env:HAS_WINDOWS_SIGNING -eq "true") {
$signedFiles = @($installers[0].FullName, "out/installers/win-unpacked/laobos-studio.exe")
foreach ($file in $signedFiles) {
$signature = Get-AuthenticodeSignature $file
if ($signature.Status -ne "Valid") { throw "Invalid Windows signature for ${file}: $($signature.Status)" }
}
} else {
Write-Host "No Authenticode secret configured; publishing the unsigned community installer."
}
if (-not (Test-Path "out/installers/latest.yml")) { throw "latest.yml was not generated." }
- uses: actions/upload-artifact@v7
with:
name: laobos-studio-windows-x64
path: out/installers/laobos-studio-*-windows-x64-setup.exe
path: |
out/installers/laobos-studio-*-windows-x64-setup.exe
out/installers/*.blockmap
out/installers/latest.yml
if-no-files-found: error
compression-level: 0
retention-days: 7
retention-days: 3

publish:
if: github.ref_type == 'tag'
needs: [verify, macos-arm64, windows-x64]
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v7
- uses: actions/download-artifact@v8
with:
pattern: laobos-studio-*
path: release-assets
merge-multiple: true
- name: Validate update metadata and create checksums
run: |
node scripts/verify-update-release.mjs release-assets "${{ needs.verify.outputs.version }}"
cd release-assets
sha256sum * > SHA256SUMS
- name: Publish release atomically
env:
GH_TOKEN: ${{ github.token }}
RELEASE_TAG: ${{ needs.verify.outputs.tag }}
RELEASE_VERSION: ${{ needs.verify.outputs.version }}
shell: bash
run: |
test -n "${GH_TOKEN}"
existing_draft="$(gh release view "${RELEASE_TAG}" --repo "${RELEASE_REPOSITORY}" --json isDraft --jq .isDraft 2>/dev/null || true)"
if [[ "${existing_draft}" == "false" ]]; then
echo "Published release ${RELEASE_TAG} already exists in ${RELEASE_REPOSITORY}." >&2
exit 1
fi
if [[ "${existing_draft}" != "true" ]]; then
gh release create "${RELEASE_TAG}" \
--repo "${RELEASE_REPOSITORY}" \
--draft \
--title "劳博士开源客户端 ${RELEASE_TAG}" \
--notes "劳博士开源客户端 ${RELEASE_VERSION}。首次安装请下载 DMG 或 Windows Setup;ZIP、blockmap 和 latest 元数据用于应用内自动更新。"
fi
gh release upload "${RELEASE_TAG}" release-assets/* --repo "${RELEASE_REPOSITORY}" --clobber
gh release edit "${RELEASE_TAG}" --repo "${RELEASE_REPOSITORY}" --draft=false --latest
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
/.next/
/.vinext/
/out/
/.cache/
/resources/mingit-win32-x64/

# misc
.DS_Store
Expand Down
10 changes: 10 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,16 @@ npm run audit:public

Pull Request 应说明改动目的、用户影响、验证方式,以及是否涉及数据迁移、权限、网络访问或依赖许可。

## 桌面与发布改动

涉及 Electron、DSH 内置插件、安装器、平台原生依赖或更新器时,请先阅读[桌面打包与应用内更新手册](docs/desktop-release-playbook.md),并满足以下要求:

- 插件运行时资源必须放在插件自身目录,不能跨目录引用根 `public/` 或开发机路径。
- Windows 原生依赖必须在 `app.asar.unpacked` 中验证真实的 x64 PE 文件,不能只检查 package.json。
- macOS 更新必须验证基线版与新版的完整签名和 designated requirement。
- 修改功能、架构或发布流程时同步更新 `README.md`、`docs/` 中对应文档及必要的回归测试。
- Release 先使用草稿聚合资产,安装器和 blockmap 上传成功后再发布 `latest*.yml`。

## 贡献许可

提交代码、文档或资源即表示:
Expand Down
37 changes: 25 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@
DSH 官方 Web UI
│ loopback HTTP
DeepSeek Harness 0.1.0-rc.6
DeepSeek Harness 0.1.0-rc.7
├─ Agent presets(标准 / PTC / 极简 / 创造 / 性能实验)
├─ Sandbox + Approval + Permission presets
├─ Plan 模式安全联动
└─ 劳博士项目资源插件(知识库 / 工作流 / Skills / MCP)
└─ 劳博士项目资源插件(知识库 / Skills / MCP)
```

DSH 只监听本机回环地址。Electron 使用随机端口启动运行时,渲染进程保持 `sandbox: true`、`nodeIntegration: false`,并拒绝网页权限请求。
Expand Down Expand Up @@ -55,6 +55,20 @@ npm run desktop:installer -- --win nsis --x64

当前公开构建未配置 Apple Developer ID 或 Windows Authenticode 证书,因此首次安装会出现系统的“未知开发者/未知发布者”安全提示;发布页同时提供 SHA-256 校验值用于核对文件完整性。签名证书和密码只允许通过 CI Secret 注入,禁止提交到仓库。

公开仓库的自动更新与源码 Release 使用同一个 GitHub 仓库。版本号以 `package.json` 为唯一来源,发布标签必须严格等于 `v<version>`。推送版本标签后,GitHub Actions 会分别在 macOS 和 Windows 原生 Runner 生成:

- macOS:DMG、自动更新 ZIP、两类 blockmap 与 `latest-mac.yml`
- Windows:NSIS 安装器、blockmap 与 `latest.yml`
- 汇总资产:`SHA256SUMS`

没有配置签名 Secret 时,流水线仍会生成可测试的社区构建:macOS 使用固定要求的稳定 ad-hoc 签名,保证相邻社区版本可以验证更新包;Windows 生成未签名安装器。配置 `MAC_CSC_LINK`、`MAC_CSC_KEY_PASSWORD`、Apple 公证凭据以及 `WIN_CSC_LINK`、`WIN_CSC_KEY_PASSWORD` 后,同一流水线会自动切换到正式签名。手动运行工作流只生成构建产物,不会发布 Release;只有推送匹配版本号的 `v*` 标签才会发布并成为应用内更新源。

本机生成 macOS 测试安装包与更新包:

```bash
npm run release:mac:test
```

常用诊断:

```bash
Expand Down Expand Up @@ -121,22 +135,18 @@ macOS 默认数据目录:

## 项目侧栏与资源管理

`@laobos/dsh-system-tools` 作为 DSH 插件运行。官方会话栏保持在左侧;项目导航固定在最右侧,与中间工作区组成三列布局。右栏折叠时保留 56px 图标轨道,展开宽度默认为 224px,并可在 200–300px 之间拖动调整。点击“对话、工作流、知识库、Skills、MCP、设置”都会复用中间工作区进行独立页面切换;设置页包含系统提示词编辑,以及可直达模型、Agent 预设、安全审批、插件分区的 DSH 系统设置入口;原左侧设置入口已迁移到右栏底部。
`@laobos/dsh-system-tools` 作为 DSH 插件运行。官方会话栏保持在左侧;项目导航固定在最右侧,与中间工作区组成三列布局。右栏折叠时保留 56px 图标轨道,展开宽度默认为 224px,并可在 200–300px 之间拖动调整。点击“对话、知识库、Skills、MCP、设置”都会复用中间工作区进行独立页面切换;设置页包含系统提示词编辑,以及可直达模型、Agent 预设、安全审批、插件分区的 DSH 系统设置入口;原左侧设置入口已迁移到右栏底部。

- 本地 SQLite + FTS 全文检索,支持自然问句拆词、标题加权和相邻片段扩展
- Agent 可按需自动创建、读取和更新知识;删除经过 DSH 审批,暂停的知识库不会被全局搜索访问
- Agent 默认把知识写入当前工作区;只有明确的跨项目知识才进入全局作用域
- 知识库页面保持轻量插件形态,支持召回测试、资料编辑、重新索引、启停和删除
- DAG 工作流由 Agent 通过 `workflow_manager` 创建、读取、更新、测试、发布和启停,删除经 `workflow_delete` 审批
- 工作流页面使用纯列表和 SVG 快捷操作;查看、编辑会打开弹窗,用户可在弹窗内查看流程图、微调节点、试运行、发布版本、启停和删除,不提供手动新建入口
- 首次发布会自动启用并注册为 DSH Agent 工具;后续发布保留用户设置的启停状态
- 已发布工作流同时投影到“设置 > Plugins > Agent 自动化”虚拟插件页,任一侧启停或删除都会同步
- Skills 以名称、简介、范围、状态和实际文件位置的管理表展示;默认管理 DSH 原生目录,可按需查看 `.agents/skills` 兼容内容,并支持一键复制位置、启停、删除和 DSH 热更新
- MCP 支持 stdio 与 streamable HTTP,以 Server、连接方式、连接目标、工具数量和连接状态的管理表展示;可启停、重连和删除,密钥只以遮罩返回界面
- 插件市场源码内置于 `packages/laobos-market`,可在“设置 > 插件市场”搜索、安装和管理带有 `dsh-plugin` topic 的插件;代理地址和上传账号默认留空,由使用者自行配置
- 管理页支持右栏折叠按钮、再次点击当前菜单项、页面“收起”按钮和 Esc 快速返回对话;未保存的 Skills/MCP 修改会先确认
- 设置中的“系统提示词”默认展示可编辑的“劳博士”身份,以 `order: 39` 注册在 preset persona 与旧迁移指令之后、工具说明之前;用户修改姓名或角色后会覆盖冲突设定,不会保留旧名称作为别名
- 实体插件清单与虚拟工作流插件统一使用 DSH 官方“设置 > Plugins”页面,不占用项目主侧栏
- 实体插件清单统一使用 DSH 官方“设置 > Plugins”页面,不占用项目主侧栏
- HTTP 管理接口只接受本机请求并校验 Origin

数据位于 DSH Home 的 `data/system-tools.db`。
Expand All @@ -145,17 +155,20 @@ macOS 默认数据目录:

桌面版把右侧项目栏分成“对话 / 工作台 / 集成管理”三组,并把高权限能力收敛到 Electron 主进程。网页侧只能调用经过校验的最小化 preload API,文件路径会做工作区边界、真实路径和符号链接检查。

- 会话:编辑上一轮并从稳定边界创建分支、重试上一轮、完整分页导出 PDF、重命名和可恢复删除
- 会话:编辑上一轮并从稳定边界创建分支、重试上一轮、导出带内嵌资源的离线 HTML、重命名和可恢复删除
- 右键菜单:工作区可打开文件管理器、Git 审查和终端;会话可打开、编辑、重试、导出、重命名和删除
- 文件工作台:目录浏览、文本/图片/PDF/音视频预览和 Finder 定位;敏感文件名、二进制内容和超大文件会被限制
- 版本中心:状态与 staged/unstaged diff、未跟踪文件预览、提交历史、初始化、暂存、提交、分支管理,以及需确认的恢复和远端同步;桌面 UI 与 Agent 共用结构化 Git 服务
- 文件上传:图片继续使用 DSH 原生图片附件;普通文件通过输入框回形针选择,复制到受管目录后以文件 Chip 显示,并把受管副本的绝对路径封装进提示词
- 文件工作台:目录浏览、创建、编辑、重命名、删除、文本/图片/PDF/音视频预览和系统文件管理器定位;首次访问启动目录外的工作区会请求授权并记住用户选择
- 版本中心:状态与 staged/unstaged diff、未跟踪文件预览、提交历史、初始化、暂存、提交、分支管理,以及需确认的恢复和远端同步;Windows 安装包内置固定校验的 MinGit,不依赖图形进程的 `PATH`
- 文件上传:支持选择、粘贴和桌面拖放;支持视觉的模型使用原生图片通道,不支持视觉时图片会作为普通受管文件交给 Agent
- 上传路径:在“设置 > 文件上传”中选择“默认”或“工作区内”;默认写入 DSH Home 的 `uploads/v1`,后者使用当前工作区的 `update` 文件夹
- 剪贴板:桌面端通过受约束的主进程 IPC 写入,浏览器 API 仍作为回退,并在失败时提供明确反馈
- 终端插件:基于 `node-pty` 与 xterm;每个标签使用独立的稳定命名 tmux 会话,打开或重连时自动回到当前工作区,未安装 tmux 时明确提示并回退到登录 Shell
- 浏览器插件:使用隔离的 `WebContentsView` 预览 HTTP(S) 地址;BrowserOps daemon 仅在用户点击后启动,并可随时停止
- SSH 插件:密码和私钥使用系统 `safeStorage` 加密,主机密钥采用 TOFU 校验,发生变更时阻止连接
- 应用管理插件:登记、探测、启动、停止和查看日志;进程始终以 `shell: false` 启动,“移出管理”不会删除项目文件
- Shell 与 WSL:自动检测 Windows 的 WSL、Linux 发行版、Git Bash 与 PowerShell;设置页提供需 UAC 确认的 WSL/Ubuntu 安装、分阶段进度、重启恢复和可编辑的标准模式诊断对话
- 软件更新:设置页可检查、下载并安装公开 GitHub Release;下载状态可恢复,应用退出时会等待本地运行时安全关闭
- 插件恢复:市场安装前校验 Host/Client 入口和模块 ID;用户插件导致启动失败时会自动隔离并以安全模式重启

删除的会话会先归档,再移动到 DSH Home 下的劳博士回收站;不会直接永久擦除。Terminal、BrowserOps、SSH 和应用管理均为独立 DSH 客户端插件,配置位于 `config/laobos.cordis.patch.yml`。修改终端或 SSH 的 JSX 源码后,可单独重建浏览器插件:

Expand Down
Loading
Loading