diff --git a/.github/workflows/octop-desktop.yml b/.github/workflows/octop-desktop.yml index b9688796..65d5c623 100644 --- a/.github/workflows/octop-desktop.yml +++ b/.github/workflows/octop-desktop.yml @@ -153,6 +153,15 @@ jobs: run: | set -euo pipefail VER=$(sed -nE 's/^version[[:space:]]*=[[:space:]]*"([^"]+)".*/\1/p' pyproject.toml | head -1) + requested_tag="${{ github.event.inputs.release_tag }}" + if [[ "${GITHUB_REF}" == refs/tags/v* && "${GITHUB_REF_NAME#v}" != "$VER" ]]; then + echo "Refusing desktop release: tag ${GITHUB_REF_NAME} does not match pyproject.toml $VER" >&2 + exit 1 + fi + if [[ -n "$requested_tag" && "${requested_tag#v}" != "$VER" ]]; then + echo "Refusing desktop release: requested tag $requested_tag does not match pyproject.toml $VER" >&2 + exit 1 + fi echo "OCTOP_VERSION=${VER}" >> "$GITHUB_ENV" echo "version=${VER}" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 563a6c19..673f37ca 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -33,6 +33,16 @@ jobs: with: fetch-depth: 0 + - name: Verify release tag matches package version + run: | + set -euo pipefail + package_version=$(sed -nE 's/^version[[:space:]]*=[[:space:]]*"([^"]+)".*/\1/p' pyproject.toml | head -1) + tag_version="${GITHUB_REF_NAME#v}" + if [[ -z "$package_version" || "$package_version" != "$tag_version" ]]; then + echo "Refusing release: tag $GITHUB_REF_NAME does not match pyproject.toml $package_version" >&2 + exit 1 + fi + - uses: astral-sh/setup-uv@v4 with: enable-cache: true diff --git a/CHANGELOG.md b/CHANGELOG.md index 0224afc9..c88b2b72 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ ## [Unreleased] +## [0.0.6] - 2026-09-22 + ### 安全 - 桌面安装包与配置模板不再允许嵌入云厂商 API Key(含 DeepSeek)。首次运行不会预填真实密钥:优先本机 Ollama,云调用在密钥为空时直接拒绝并提示用户自行填写(即使环境里有 `DEEPSEEK_API_KEY` / `OPENAI_API_KEY` / `LLM_API_KEY` 也不写入 `providers`)。打包排除 `.env`、`octop.db`、`.freeos`,打 zip 前扫描 staging。生产/air-gap sidecar 不再回退 `LLM_API_KEY`。已发布的 **0.0.1–0.0.4** 安装包须下架(已从 Release 删除),并轮换可能泄露的 DeepSeek 密钥。 @@ -18,6 +20,10 @@ ### 修复 +- Windows 最小化到托盘后,从托盘菜单「显示 FreeOS」或再次启动应用可稳定恢复主窗口;宿主会消费隐藏窗口产生的延迟关闭事件,避免窗口显示后立刻再次隐藏。 +- 组织页自动启动不再因 React effect 清理而取消;桌面宿主与 Python 组织启动器不再争抢同一 openXYOS 进程。组织数据库改为原子落盘,模块开关可即时更新导航并在重启后保持。 +- Ollama 模型明确返回“不支持 tools”时,同一轮请求会自动去掉工具定义后重试。`qwen2.5vl:3b` 等视觉/对话模型不再因工具能力缺失连续触发 `stream_error`。 +- 撤回错误标记为 0.0.5、内部实际仍为 0.0.4 的 Windows 构建。桌面更新现在拒绝版本元数据与 portable 包内部版本不一致的下载,也拒绝重复安装当前或更旧版本,防止同一包循环下载、启动时反复解压约 1.15 GB / 6.8 万文件并拖垮系统。Windows WebView2 默认使用软件渲染,降低显卡驱动黑屏风险;发布工作流在标签与 `pyproject.toml` 版本不一致时直接失败。 - 组织嵌入仍走 openXYOS 自己的登录(`localStorage token` / sidecar JWT),不把 FreeOS 桌面访客(`auth_token` / `POST /api/auth/local-session`)写进组织房间。重启/恢复 sidecar 始终带 `FREEOS_ORG_LOCAL_TEST=1`,保留 `demo@demo.com` / `user@demo.com`。本机会话跳过组织映射行,不删除、不占用 openXYOS 用户。代理剥离宿主 Cookie 与 `X-FreeOS-*`。 / Org embed keeps openXYOS login; FreeOS local-session must not replace org users. Sidecar restart still seeds test accounts. Proxy drops host cookies and identity headers. - Windows 升级刷新 `~/.freeos/portable` 时,若目录节点被占用(无法改名为 `portable.previous`,报 “being used by another process”),先尝试结束残留的 portable / `launch.py` 进程,再把新运行时**原地覆盖**进现有文件夹,避免留下空的锁定 `portable` 桩,也不要求重启。 / If renaming `portable` → `portable.previous` fails because the directory is in use, stop leftover host processes and overlay the new runtime in place. - 桌面 `POST /api/auth/local-session` 对已有 `~/.freeos`(多用户 / 组织映射行)或 WebView 非 `127.0.0.1` Host 返回 403,前端重试后掉进注册登录。本机会话在 loopback / `*.localhost` / Origin 为本机时签发 JWT 并选用已有工作室账号;SPA 在 `/` 跳到 `/projects` 丢掉 `?desktop=1` 之前记住桌面壳。403 修复后的路径是:可选模型配置(云 Key / 本机,可跳过)→ 第一个智能体 `/chat/main`,不经过登录墙,也不停在工作台列表。`/setup` 在 guest 已创建后不再打回登录页。 diff --git a/README.md b/README.md index fa609aa5..55df7923 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@

Python 3.12+ - Version + Version License: MIT + Apache-2.0 Upstream: Octop Organization module: openXYOS @@ -136,7 +136,7 @@ Operator detail: [docs/asset-loop.md](docs/asset-loop.md). `FreeOS-desktop-windows-arm64-.exe`(ARM 电脑)。 2. 双击安装包。安装程序会放到「程序文件」并创建开始菜单和桌面快捷方式。 3. 打开 **FreeOS**。第一次启动会解压内置运行环境(可能要一两分钟),然后直接进入可用会话,无需先登录。 -4. 保存、导出或发布到账号时再在本机完成注册或登录(更广的服务或授权不挡起步)。侧栏 **Organization** 当前默认走宿主内组织能力;桌面 0.0.4 托管 Node + iframe(延续 0.0.3 过渡桥)、以及可选的完整 openXYOS Node 栈(`127.0.0.1:3780`,导出/同步/高级部署)都是 **过渡桥**,终态是把 openXYOS 迁入宿主。组织能力像工作室里另一间可独立布置的房间,与日常对话、助手协作同在一个 FreeOS,两种进入方式不捏成一种。 +4. 保存、导出或发布到账号时再在本机完成注册或登录(更广的服务或授权不挡起步)。侧栏 **Organization** 当前默认走宿主内组织能力;桌面 0.0.6 托管 Node + iframe(延续 0.0.3 过渡桥)、以及可选的完整 openXYOS Node 栈(`127.0.0.1:3780`,导出/同步/高级部署)都是 **过渡桥**,终态是把 openXYOS 迁入宿主。组织能力像工作室里另一间可独立布置的房间,与日常对话、助手协作同在一个 FreeOS,两种进入方式不捏成一种。 数据目录默认是 `%USERPROFILE%\.freeos`(可用环境变量 `FREEOS_HOME` 改)。旧版 Octop 的 `~/.octop` 仍会被识别。卸载安装包会清空安装目录(默认为 `Program Files\FreeOS`)并删除快捷方式,但**不会**删除该用户数据目录;详见 [desktop/README.md](desktop/README.md#windows-uninstall)。 diff --git a/README_CN.md b/README_CN.md index ae0ca174..fd7b3dcc 100644 --- a/README_CN.md +++ b/README_CN.md @@ -11,7 +11,7 @@

Python 3.12+ License: MIT - Version + Version 上游:Octop 组织模块:openXYOS PyPI diff --git a/dashboard/package.json b/dashboard/package.json index 7aded21b..4d62cdec 100644 --- a/dashboard/package.json +++ b/dashboard/package.json @@ -5,7 +5,7 @@ "type": "module", "scripts": { "dev": "vite --host", - "build": "tsc -b && NODE_ENV=production vite build", + "build": "tsc -b && vite build --mode production", "build:docker": "vite build", "build:prod": "tsc -b && vite build --mode production", "build:test": "tsc -b && vite build --mode test", diff --git a/dashboard/src/pages/Organization/OrgRestartOverlay.tsx b/dashboard/src/pages/Organization/OrgRestartOverlay.tsx index 58a57b80..0d9cb2db 100644 --- a/dashboard/src/pages/Organization/OrgRestartOverlay.tsx +++ b/dashboard/src/pages/Organization/OrgRestartOverlay.tsx @@ -14,12 +14,14 @@ import styles from "./Organization.module.less"; type OrgRestartOverlayProps = { mode: "restarting" | "needsRestart"; finished?: boolean; + error?: string; onRestart: () => void; }; export default function OrgRestartOverlay({ mode, finished = false, + error = "", onRestart, }: OrgRestartOverlayProps) { const { t } = useTranslation(); @@ -112,7 +114,14 @@ export default function OrgRestartOverlay({ {t("organization.previewNeedsRestartTitle")}

- {t("organization.previewNeedsRestart")} + {error ? ( + <> + {t("organization.restartSidecarFailed", { detail: "" })}{" "} + {error} + + ) : ( + t("organization.previewNeedsRestart") + )}