From 6a95f48f9fbc1e242dfe8b3d6b7c7b147d4469cb Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sat, 19 Sep 2026 05:35:55 +0000 Subject: [PATCH 1/2] chore: Phase 5 default installer stays zero-Node Keep the default Windows/macOS/Linux package and Docker Compose as a single FreeOS process with in-host Organization. Sidecar remains opt-in (FREEOS_ORG_SIDECAR / SHIP_OPENXYOS_RUNTIME=1). Align docs and lock the defaults with tests. Co-authored-by: XYAI Labs --- CHANGELOG.md | 3 +- README.md | 1 + README_CN.md | 4 +- .../src/pages/Organization/OrgMiniBrowser.tsx | 5 + desktop/README.md | 21 ++-- desktop/src/build/stage_openxyos_runtime.py | 6 +- .../src/build/windows/nsis/wails_tools.nsh | 9 +- docker/README.md | 11 ++- docker/README_CN.md | 10 +- docker/docker-compose.yml | 5 +- docker/docker-entrypoint.sh | 2 + docs/adr/001-single-process-model.md | 8 ++ .../003-org-ui-single-source-dual-delivery.md | 2 +- docs/architecture-integration.md | 3 +- docs/org-export.md | 12 +-- docs/org-merge-plan.md | 38 +++++-- scripts/org-export/README.md | 9 +- src/octop/cli/commands/org.py | 6 +- src/octop/modules/org_os/export_standalone.py | 4 +- tests/unit/desktop/test_nsis_uninstall.py | 5 + tests/unit/desktop/test_phase5_defaults.py | 98 +++++++++++++++++++ 21 files changed, 218 insertions(+), 44 deletions(-) create mode 100644 tests/unit/desktop/test_phase5_defaults.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ffa743e..906b0c6d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,8 @@ ### 新增 -- Organization Phase 4(独立站导出起步):`freeos org export-standalone --out ` 从 `dashboard/src/org-ui` 生成可运行的 Vite 包(OpenApp 风格路由、本地 JWT IdentityBridge `openxyos.standalone.jwt`、登录页、Docker / docker-compose、`server/proxy.mjs` 把 `/api` 反代到 FreeOS `FREEOS_UPSTREAM`)。Chat 不导出。默认安装器与 sidecar 不变(Phase 5)。说明见 `docs/org-export.md`。 +- Organization Phase 5(默认安装器瘦身):Windows/macOS/Linux 默认包装与 Docker Compose 保持 **单进程 FreeOS + 宿主内 Organization**,不捆绑、不解压、不自动拉起 openXYOS Node。`FREEOS_ORG_SIDECAR=1` 与 `SHIP_OPENXYOS_RUNTIME=1` 仍是可选高级路径。文档对齐 #55 / ADR 001 / ADR 003 / `docs/org-export.md`。`modules/openxyos` 保留给导出与开发,不是默认运行时。 +- Organization Phase 4(独立站导出起步):`freeos org export-standalone --out ` 从 `dashboard/src/org-ui` 生成可运行的 Vite 包(OpenApp 风格路由、本地 JWT IdentityBridge `openxyos.standalone.jwt`、登录页、Docker / docker-compose、`server/proxy.mjs` 把 `/api` 反代到 FreeOS `FREEOS_UPSTREAM`)。Chat 不导出。默认安装器零 Node(Phase 5)。说明见 `docs/org-export.md`。 - Organization Phase 3(工作台总览切片,Open-12 收口):宿主内薄 Workspace / OpenDashboard。Dashboard `/organization/workspace` 使用共享 `dashboard/src/org-ui` 的 `WorkspacePage`。数据复用已有 `GET /api/org-module/overview`,链到已迁的公告 / 架构 / 员工 / 技能 / 智能体 / 任务 / 知识 / 反思 / 治理 / 设置。**不是**第二套控制面(assemble / pack / loop 仍在 `/organization`)。**Chat 永久不迁。** `freeos org export-standalone` 同时列出 Workspace。Phase 3 Open-12 宿主 UI 完成,下一步 Phase 4 导出。 - Organization Phase 3(智能体切片):宿主内 Agent Studio / 智能体定制。Dashboard `/organization/agents` 使用共享 `dashboard/src/org-ui` 的 `AgentsPage`。列表走 `GET /api/org-module/agents`;编译 / 生命周期 / 注册复用已有 `POST /api/org-module/blueprints/compile`、`/employees/transition`、`/employees/spawn`(`{FREEOS_HOME}/tenants//employees/`)。**不是** FreeOS 个性化智能体编辑器,也不是 Chat,也不依赖未挂载的 sidecar `/api/agent-studio/*`。注册后的同事出现在 Experts。`freeos org export-standalone` 同时列出 Agents。未迁:Chat、资料上传、人才市场。 - Organization Phase 3(设置切片):宿主内 Organization Settings。Dashboard `/organization/settings` 使用共享 `dashboard/src/org-ui` 的 `SettingsPage`。本页只编辑 `org_os` 目录模块开关(复用 `GET/PUT /api/org-module/modules`)与组织本地偏好(`GET /api/org-module/settings` · `PUT /api/org-module/prefs`,`{FREEOS_HOME}/org-os/prefs.json`)。**不是** FreeOS 系统设置:大模型密钥、用户、时区仍链到 `/system-settings`。没有边车,也不复制 sidecar 的 AI/用户/数据库页。`freeos org export-standalone` 同时列出 Settings。未迁:Chat、边车系统设置。 diff --git a/README.md b/README.md index 2d11d728..d83080cb 100644 --- a/README.md +++ b/README.md @@ -122,6 +122,7 @@ The TypeScript sidecar under `modules/openxyos/` is optional. | CLI | `uv run freeos org enable` · `uv run freeos org status` · `uv run freeos org loop run` | | Plugin | Admin → Plugins → **Organization OS** (`org-os`) | | API | `PATCH /api/org-module` with `{ "enabled": true }` | +| Standalone commercial site | `uv run freeos org export-standalone --out dist/openxyos-web` — see [docs/org-export.md](docs/org-export.md) | Optional Node sidecar (export/sync/advanced deploy only): diff --git a/README_CN.md b/README_CN.md index 2dc6ed31..d56ee0ca 100644 --- a/README_CN.md +++ b/README_CN.md @@ -208,12 +208,12 @@ curl -fsSL https://finnie-1258344699.cos.ap-guangzhou.myqcloud.com/octop/install | 平台 | 制品 | |------|------| -| Windows | `FreeOS-desktop-windows-amd64-.exe`(64 位)/ `FreeOS-desktop-windows-arm64-.exe`(ARM64)— NSIS 安装程序,内置 openXYOS,打开即可聊天+组织 | +| Windows | `FreeOS-desktop-windows-amd64-.exe`(64 位)/ `FreeOS-desktop-windows-arm64-.exe`(ARM64)— NSIS 安装程序,单进程 FreeOS + 宿主内 Organization;不捆绑也不自动拉起 openXYOS Node | | macOS | `FreeOS-desktop-darwin-arm64-.dmg`(Apple 芯片)/ `FreeOS-desktop-darwin-amd64-.dmg`(Intel) | | Linux | `FreeOS-desktop-linux-amd64-.tar.gz` / `FreeOS-desktop-linux-arm64-.tar.gz` | | 飞牛 NAS(FnOS) | `Octop-fnos-docker-.fpk`(依赖 Docker)/ `Octop-fnos-native-.fpk`(无需 Docker)— 通过应用中心安装 | -桌面客户端说明见 [desktop/README.md](desktop/README.md),飞牛打包指南见 [fnos/README.md](fnos/README.md)。 +桌面客户端说明见 [desktop/README.md](desktop/README.md),飞牛打包指南见 [fnos/README.md](fnos/README.md)。独立商业组织站见 [docs/org-export.md](docs/org-export.md)(`freeos org export-standalone`),不是默认安装器里的第二套 Node。 **备选 — PyPI**(若你已自行管理 Python 环境): diff --git a/dashboard/src/pages/Organization/OrgMiniBrowser.tsx b/dashboard/src/pages/Organization/OrgMiniBrowser.tsx index 621eaa1d..7600cd4c 100644 --- a/dashboard/src/pages/Organization/OrgMiniBrowser.tsx +++ b/dashboard/src/pages/Organization/OrgMiniBrowser.tsx @@ -1,3 +1,8 @@ +/** + * Leftover iframe chrome for the optional Node sidecar (`FREEOS_ORG_SIDECAR=1`). + * The default Organization workbench (`index.tsx`) does not mount this. + * Core org pages live in `dashboard/src/org-ui` and talk to `/api/org-module/*`. + */ import { useMemo, type MutableRefObject } from "react"; import { Button, Input, Tooltip } from "antd"; import { ArrowLeft, ArrowRight, RotateCcw } from "lucide-react"; diff --git a/desktop/README.md b/desktop/README.md index 4828c9ba..8e4b9ec2 100644 --- a/desktop/README.md +++ b/desktop/README.md @@ -6,8 +6,8 @@ Windows end-user product: **download the NSIS `.exe` → install → open FreeOS | Path | Role | |------|------| -| [`portable/`](portable/) | Green zip packaging (CPython + host wheel + Node/openXYOS sidecar) | -| [`src/`](src/) | Wails v3 shell: extract zip, start host + sidecar, tray/settings | +| [`portable/`](portable/) | Green zip packaging (CPython + host wheel; Node/openXYOS sidecar opt-in) | +| [`src/`](src/) | Wails v3 shell: extract zip, start host (sidecar only if `FREEOS_ORG_SIDECAR=1`), tray/settings | | [`package-release.sh`](package-release.sh) | Native end-to-end portable + Wails release build | ## Data directory @@ -109,21 +109,23 @@ On first open the shell: ## Build green zip -From repo root (needs Node 20+ so openXYOS can be built into the zip): +From repo root (Node 20+ is only required to rebuild the dashboard or to +opt into `SHIP_OPENXYOS_RUNTIME=1`): ```bash make -f desktop/portable/Makefile green ``` -The Node sidecar is optional. Package it with the green zip when you want -advanced export/sync; skip it with `SKIP_ORG_SIDECAR=1`. Desktop/runtime -never requires it for Organization. +The Node sidecar is optional. Default green zips skip it (`SKIP_ORG_SIDECAR=1`). +Package it only with `SHIP_OPENXYOS_RUNTIME=1` (or `SKIP_ORG_SIDECAR=0`) when +you want advanced export/sync. Desktop/runtime never requires it for +Organization. CI: `.github/workflows/octop-desktop.yml` (**name:** FreeOS Desktop Package) builds native platform/arch variants. `v*` tags and `workflow_dispatch` (platforms=`all`) run all six; pull requests that touch `desktop/` or `modules/openxyos/` build `darwin-*` and `windows-*` (amd64 + arm64). Each job -creates the green zip (including the sidecar) and packages the Wails app. +creates the slim green zip (Python host only) and packages the Wails app. This cloud/Linux VM cannot emit a Windows `.exe`. The job that does is **FreeOS Desktop Package** → matrix `windows-amd64` / `windows-arm64` @@ -132,8 +134,9 @@ This cloud/Linux VM cannot emit a Windows `.exe`. The job that does is ## Build a complete desktop release Run the end-to-end script on the matching native host. It builds the Dashboard, -creates and verifies the portable runtime (with openXYOS), embeds it into Wails, -and produces the final native package: +creates and verifies the portable Python host (Node/openXYOS only if +`SHIP_OPENXYOS_RUNTIME=1`), embeds it into Wails, and produces the final +native package: ```bash desktop/package-release.sh diff --git a/desktop/src/build/stage_openxyos_runtime.py b/desktop/src/build/stage_openxyos_runtime.py index 63149ca8..82a8dbc4 100644 --- a/desktop/src/build/stage_openxyos_runtime.py +++ b/desktop/src/build/stage_openxyos_runtime.py @@ -1,8 +1,10 @@ #!/usr/bin/env python3 """Build openxyos-runtime.zip from a green portable zip or staging tree. -The Windows NSIS installer copies this zip into $INSTDIR and expands it to -$INSTDIR\\openxyos so the finish log shows FE+BE, not only FreeOS.exe. +Opt-in only. Default Windows/macOS/Linux packages do not call this script +and do not ship the zip. Pass SHIP_OPENXYOS_RUNTIME=1 to stage it; NSIS +then copies the zip into $INSTDIR but still does not extract or start Node +unless FREEOS_ORG_SIDECAR=1 later. """ from __future__ import annotations diff --git a/desktop/src/build/windows/nsis/wails_tools.nsh b/desktop/src/build/windows/nsis/wails_tools.nsh index 14485bf9..e5a7c7e0 100644 --- a/desktop/src/build/windows/nsis/wails_tools.nsh +++ b/desktop/src/build/windows/nsis/wails_tools.nsh @@ -142,15 +142,15 @@ RequestExecutionLevel "${REQUEST_EXECUTION_LEVEL}" ${EndIf} !macroend -# Organization is in-host. Default Setup copies no openxyos-runtime.zip. -# Opt-in builds (-DOPENXYOS_RUNTIME_ZIP_PRESENT) copy the zip + scripts -# but still never extract, start Node, wait for livez, or Abort. +# Organization is in-host. Default Setup copies no openxyos-runtime.zip +# and does not create $INSTDIR\openxyos. Opt-in builds +# (-DOPENXYOS_RUNTIME_ZIP_PRESENT) copy the zip + scripts but still +# never extract, start Node, wait for livez, or Abort. # Do not FileWrite goto-label .cmd scripts here. Do not register logon autostart. !macro wails.provisionOpenXYOS !insertmacro wails.userLocalAppData SetDetailsPrint both DetailPrint "$(OPENXYOS_WORKDIR)" - CreateDirectory "$INSTDIR\openxyos" !ifdef OPENXYOS_RUNTIME_ZIP_PRESENT !if /FileExists "${OPENXYOS_RUNTIME_ZIP}" DetailPrint "$(OPENXYOS_COPY_ZIP)" @@ -159,6 +159,7 @@ RequestExecutionLevel "${REQUEST_EXECUTION_LEVEL}" File "provision-openxyos.cmd" File "start-sidecar.ps1" File "start-sidecar.cmd" + CreateDirectory "$INSTDIR\openxyos" DetailPrint "$(OPENXYOS_OPTIONAL_SKIP)" !else !error "OPENXYOS_RUNTIME_ZIP_PRESENT is set but ${OPENXYOS_RUNTIME_ZIP} is missing" diff --git a/docker/README.md b/docker/README.md index e765a376..8537e6e1 100644 --- a/docker/README.md +++ b/docker/README.md @@ -2,7 +2,14 @@ --- -This directory contains the Docker build and deployment assets for Octop. +This directory contains the Docker build and deployment assets for Octop / +FreeOS. Compose stays **one Python process**. There is no Node/openXYOS +sidecar service. Organization runs in-host (`/api/org-module/*`). + +**Home directory names:** the product name is `FREEOS_HOME`. Compose and +this image still use `OCTOP_*` and `~/.octop` as compatibility aliases +(`HOME=/data` → `/data/.octop`). Set `FREEOS_HOME` (or `OCTOP_HOME`) if +you want an explicit path; the app honors `FREEOS_HOME` first. ### Files @@ -57,6 +64,8 @@ bash docker/docker_build.sh | Variable | Default | Description | |----------|---------|-------------| | `HOME` | `/data` | Must be `/data` so `~/.octop` maps to the data volume | +| `FREEOS_HOME` | _(unset)_ | Product home. When set, wins over `OCTOP_HOME` / `~/.octop` | +| `OCTOP_HOME` | _(unset)_ | Legacy alias for the same home directory | | `OCTOP_PORT` | `8088` | HTTP listen port | | `OCTOP_DEFAULT_PASSWORD` | _(unset)_ | First-run admin password (≥8 chars, letters + digits). When unset, a random password is generated and written to `credential.txt` | | `OCTOP_ADMIN_USERNAME` | `admin` | Initial admin username | diff --git a/docker/README_CN.md b/docker/README_CN.md index 4be162cf..28da4428 100644 --- a/docker/README_CN.md +++ b/docker/README_CN.md @@ -2,7 +2,13 @@ --- -本目录包含 Octop 的 Docker 构建与部署相关文件。 +本目录包含 Octop / FreeOS 的 Docker 构建与部署相关文件。Compose **只有一个 +Python 进程**,没有 Node / openXYOS 边车。Organization 在宿主内运行 +(`/api/org-module/*`)。 + +**目录命名:** 产品名是 `FREEOS_HOME`。本镜像与 Compose 仍用 `OCTOP_*` 和 +`~/.octop` 作为兼容别名(`HOME=/data` → `/data/.octop`)。需要显式路径时 +设置 `FREEOS_HOME`(或遗留的 `OCTOP_HOME`);应用优先读 `FREEOS_HOME`。 ### 文件说明 @@ -57,6 +63,8 @@ bash docker/docker_build.sh | 变量 | 默认值 | 说明 | |------|--------|------| | `HOME` | `/data` | 必须为 `/data`,数据目录映射到 `~/.octop` | +| `FREEOS_HOME` | _(未设置)_ | 产品家目录。设置后优先于 `OCTOP_HOME` / `~/.octop` | +| `OCTOP_HOME` | _(未设置)_ | 同一家目录的遗留别名 | | `OCTOP_PORT` | `8088` | HTTP 服务端口 | | `OCTOP_DEFAULT_PASSWORD` | _(未设置)_ | 首次管理员密码(≥8 位,字母+数字)。未设置时自动生成随机密码并写入 `credential.txt` | | `OCTOP_ADMIN_USERNAME` | `admin` | 首次管理员用户名 | diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 8cb5d3d0..82a4144e 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -1,5 +1,8 @@ # ============================================================================= -# Octop — Docker Compose +# FreeOS / Octop — Docker Compose (single Python process) +# +# One service. No Node / openXYOS sidecar. Organization is in-host. +# Product home name is FREEOS_HOME; OCTOP_* and ~/.octop remain aliases. # # 用法(在仓库根目录执行): # docker compose -f docker/docker-compose.yml up -d diff --git a/docker/docker-entrypoint.sh b/docker/docker-entrypoint.sh index 42b8b32b..b212c4bf 100644 --- a/docker/docker-entrypoint.sh +++ b/docker/docker-entrypoint.sh @@ -4,6 +4,8 @@ # # 环境变量: # HOME — 必须为 /data,使 ~/.octop 映射到数据卷 +# FREEOS_HOME — 产品家目录(可选;应用优先于 OCTOP_HOME) +# OCTOP_HOME — 遗留别名。入口脚本仍用 ~/.octop 做首次 init # OCTOP_DEFAULT_PASSWORD — 首次管理员密码(须 ≥8 位且含字母和数字; # 不设置则自动生成随机密码,凭据写入 # /data/.octop/credential.txt) diff --git a/docs/adr/001-single-process-model.md b/docs/adr/001-single-process-model.md index 480f0f26..242f067e 100644 --- a/docs/adr/001-single-process-model.md +++ b/docs/adr/001-single-process-model.md @@ -33,3 +33,11 @@ Everything runs in a single Python process served by uvicorn. There is no extern - All async; blocking calls must use `run_in_executor`. - `SharedServices` (DI container) is process-global — safe because the process owns all state. - Future scale-out would require extracting the worker into a separate process and adding a queue; that seam is already partially visible in `infra/gateway/processor.py`. + +## Later confirmation (FreeOS Organization, 2026-09) + +Organization control-plane UI and BFF stay in this same Python process +(`/api/org-module/*`, Dashboard `/organization`). A Node openXYOS sidecar +is **not** part of the default installer (Phase 5). Opt in only with +`FREEOS_ORG_SIDECAR=1` / `SHIP_OPENXYOS_RUNTIME=1`. This does not change +the original decision: one process, one port, no required extra runtime. diff --git a/docs/adr/003-org-ui-single-source-dual-delivery.md b/docs/adr/003-org-ui-single-source-dual-delivery.md index 0455305d..7f02fdc0 100644 --- a/docs/adr/003-org-ui-single-source-dual-delivery.md +++ b/docs/adr/003-org-ui-single-source-dual-delivery.md @@ -94,4 +94,4 @@ Phase 2 proves the contract on **Announcements** (see [org-merge-plan.md](../org ## Follow-through -Implementation sequencing, overlap matrix, package layout, API ownership, and Phase 2 acceptance criteria live in [org-merge-plan.md](../org-merge-plan.md). This ADR does not move pages or change the installer. +Implementation sequencing, overlap matrix, package layout, API ownership, and Phase 2–5 status live in [org-merge-plan.md](../org-merge-plan.md). Phase 5 default installer is **zero-Node**; standalone commercial deploy is [org-export.md](../org-export.md), not a second always-on runtime. diff --git a/docs/architecture-integration.md b/docs/architecture-integration.md index 6463add7..caab3ac9 100644 --- a/docs/architecture-integration.md +++ b/docs/architecture-integration.md @@ -13,7 +13,8 @@ FreeOS/Octop remains the execution runtime. - [ADR 001](adr/001-single-process-model.md) — single process, no external queue - [ADR 003](adr/003-org-ui-single-source-dual-delivery.md) — org-ui single source, dual delivery (Accepted) -- [org-merge-plan.md](org-merge-plan.md) — Phases 0–5; Phase 3 in progress (org chart / employees / skills / governance / knowledge / tasks / reflections / settings / agents) +- [org-merge-plan.md](org-merge-plan.md) — Phases 0–5; Phase 5 default installer is zero-Node (sidecar opt-in) +- [org-export.md](org-export.md) — standalone commercial Organization site (`freeos org export-standalone`) - [asset-loop.md](asset-loop.md) — operator self-growth loop - [ADR index](adr/) diff --git a/docs/org-export.md b/docs/org-export.md index a41fe9b2..02533d8d 100644 --- a/docs/org-export.md +++ b/docs/org-export.md @@ -1,8 +1,8 @@ -# 独立组织站导出(Phase 4) +# 独立组织站导出(Phase 4 包 + Phase 5 默认安装器) `freeos org export-standalone --out ` 从 **同一套** `dashboard/src/org-ui` 生成可部署的 openXYOS 风格独立站。这是商业加售的交付物,不是第二份手维护前端。 -默认 FreeOS 安装器 **不** 因此改成捆绑 Node。Sidecar 仍是可选兼容(`FREEOS_ORG_SIDECAR` / `SHIP_OPENXYOS_RUNTIME`)。安装器瘦身是 **Phase 5**。 +默认 FreeOS 安装器 **不** 捆绑 Node,也 **不** 自动拉起 openXYOS。Sidecar 仅作可选兼容(`FREEOS_ORG_SIDECAR=1` / `SHIP_OPENXYOS_RUNTIME=1`)。这是 **Phase 5** 已落地的默认拓扑:一个 Python 进程 + 宿主内 Organization。 ## 生成 @@ -51,7 +51,7 @@ FREEOS_UPSTREAM=http://127.0.0.1:8088 docker compose up --build | UI | 本包 SPA(org-ui) | 同一套 org-ui | | API | 反向代理到 FreeOS `/api/org-module/*` | 本包自包含服务实现同一合同 | | 身份 | 本地 JWT,登录打 FreeOS `/api/auth/login` | 客户自己的账号体系发本地 JWT | -| 安装器 | 不变;sidecar 仍可选 | Phase 5:默认安装器零 Node | +| 安装器 | **已落地(Phase 5)**:默认零 Node,sidecar 仅 opt-in | 保持单进程;不把独立站 Node 打进默认安装包 | `VITE_API_BASE` 默认 `/api`(同源)。不要在浏览器里直连另一个源的 FreeOS,除非那个宿主已配好 CORS。推荐始终走代理。 @@ -63,9 +63,9 @@ FREEOS_UPSTREAM=http://127.0.0.1:8088 docker compose up --build | `FREEOS_UPSTREAM` | 运行 | 拥有 `/api/org-module` 与 `/api/auth` 的 FreeOS 源 | | `PORT` | `npm start` / Vite | 监听端口,默认 `3780` | -## 非目标(本波) +## 非目标 / 残留 -- 从默认安装器里摘掉 sidecar(Phase 5) -- 重写整套 Express → 独立 Node API +- 重写整套 Express → 独立 Node API(导出仍代理到宿主 `/api/org-module`) - 迁 openXYOS Chat - 抽出 `packages/org-ui` monorepo(仅当 Dashboard Vite 图必须脱离时再做) +- 把独立站 Node 打进默认 Windows/macOS/Linux 安装器(明确不做) diff --git a/docs/org-merge-plan.md b/docs/org-merge-plan.md index a6b50add..c622da7a 100644 --- a/docs/org-merge-plan.md +++ b/docs/org-merge-plan.md @@ -1,6 +1,6 @@ # 组织模块合并计划(openXYOS → FreeOS Dashboard) -**状态:** Phase 3 Open-12 宿主 UI **已完成**(Chat 永久不迁)。Phase 4 独立站导出 **已起步**:`freeos org export-standalone` 产出可运行 Vite + Docker 包(共享 `org-ui` + 本地 JWT IdentityBridge + 代理到宿主 `/api/org-module`)。自包含 org API 与默认安装器瘦身仍是 Phase 5。详见 [org-export.md](org-export.md)。 +**状态:** Phase 0–4 已落地(#55 宿主内 Organization + Open-12 宿主 UI + `export-standalone`)。**Phase 5 默认安装器瘦身已完成**:Windows/macOS/Linux 默认包不捆绑、不解压、不自动拉起 openXYOS Node;`FREEOS_ORG_SIDECAR` / `SHIP_OPENXYOS_RUNTIME=1` 仍是可选高级路径。残留:导出站自包含 org API、可选 `packages/org-ui` 抽包。详见 [org-export.md](org-export.md)。 **日期:** 2026-09-19 **依据:** Phase 0 迁移图、[architecture-integration.md](architecture-integration.md)、[asset-loop.md](asset-loop.md)、[ADR 001](adr/001-single-process-model.md)、#55 宿主内 Organization @@ -26,8 +26,8 @@ Phase 1 冻结了合同。Phase 2(通知公告)与 Phase 3 Open-12 宿主页 | **1** 合同冻结 | ADR + 本计划:单源双交付、包布局、API 归属、IdentityBridge、Phase 2 验收 | **本文** | | **2** 垂直切片 | **通知公告 Announcements** 按合同落地(Dashboard 路由 + 可导出同一页面) | **已完成** | | **3** Open-12 其余页 | 工作台、组织架构、员工、技能、智能体、任务、知识、反思、治理 UI、设置 | **已完成(Chat 除外)**:架构 / 员工 / 技能 / 治理 / 知识 / 任务 / 反思 / 设置 / 智能体 / 薄 Workspace overview 已迁。**Chat 永久不迁** | -| **4** 身份与导出 | 已迁页面走 IdentityBridge;`export-standalone` 产出可运行独立站(Vite SPA + 本地 JWT + 代理到宿主 org-module API) | **进行中**:可运行包已落地;自包含 Node API 仍是目标终态 | -| **5** 安装器瘦身 | 默认安装器去掉 sidecar / 保持零 Node;可选 `packages/org-ui` 抽取 | **未开始**(明确推迟;sidecar 仍可选) | +| **4** 身份与导出 | 已迁页面走 IdentityBridge;`export-standalone` 产出可运行独立站(Vite SPA + 本地 JWT + 代理到宿主 org-module API) | **已完成(可运行包)**;自包含 Node API 仍是目标终态 | +| **5** 安装器瘦身 | 默认安装器去掉 sidecar / 保持零 Node | **已完成**。残留:自包含 org API、可选 `packages/org-ui` 抽包 | Phase 0 已落地、Phase 1 **只冻结合同**。Phase 2 起才允许搬页面。 @@ -529,13 +529,35 @@ Phase 3 Open-12 宿主 UI 至此收口。下一步是 Phase 4 导出与 Identity 4. 部署物:`Dockerfile`、`docker-compose.yml`、导出目录 `README.md`、仓库 [org-export.md](org-export.md)。 5. Chat 仍不导出;`/chat`、`/experts`、`/system-settings` 为深链说明页。 -### 本阶段明确推迟(Phase 5) +### Phase 4 当时推迟、现已分流 -| 推迟项 | 原因 | +| 项 | 现状 | +|---|---| +| 从默认安装器摘掉 sidecar | **Phase 5 已完成。** 默认 NSIS / 绿包 / Docker 零 Node;`FREEOS_ORG_SIDECAR=1` / `SHIP_OPENXYOS_RUNTIME=1` 仍可选 | +| 自包含 org-module Node/Fastify 服务 | **残留。** 过渡期仍复用宿主 BFF;`server/proxy.mjs` 是占位 | +| `packages/org-ui` 抽包 | **残留。** 导出已拷贝 org-ui;不必先拆 monorepo | + +--- + +## Phase 5 进度:默认安装器瘦身 + +默认交付是 **一个 FreeOS 进程 + 宿主内 Organization**。客户不必再装第二套 Node/openXYOS。 + +### 已落地 + +1. 默认 Windows NSIS / 绿包 / Desktop CI **不** 打入 `openxyos-runtime.zip` 或 `org-sidecar`(`SKIP_ORG_SIDECAR=1`,`SHIP_OPENXYOS_RUNTIME` 默认 `0`)。 +2. Setup **不** `nsExec` 预配、不解压、不等 livez、不注册开机自启;缺 zip 不是安装失败。 +3. 桌面 / 主机启动默认不设 `OPENXYOS_BASE_URL`,不拉起 3780。Organization 首屏是 `/organization` + `/api/org-module/*`。 +4. `modules/openxyos` 仍保留给导出 / 开发 / `FREEOS_ORG_SIDECAR=1` 高级路径,不是默认运行时。 +5. 独立商业站走 `freeos org export-standalone`([org-export.md](org-export.md)),不是默认安装器。 + +### 残留(不挡默认安装) + +| 残留 | 说明 | |---|---| -| 从默认安装器摘掉 sidecar | 用户明确本波不做;`FREEOS_ORG_SIDECAR` 仍可选 | -| 自包含 org-module Node/Fastify 服务 | 过渡期复用宿主 BFF;`server/proxy.mjs` 是占位 | -| `packages/org-ui` 抽包 | 导出已拷贝 org-ui;不必先拆 monorepo | +| 导出站自包含 org API | 仍代理到 `FREEOS_UPSTREAM`;不是第二套默认桌面运行时 | +| `packages/org-ui` 抽包 | 仅当 Dashboard Vite 图必须脱离时再做 | +| 未迁商业 `App.tsx` 路由 | 合同 / 资产 / 考勤等仍不加售进默认 Dashboard | --- diff --git a/scripts/org-export/README.md b/scripts/org-export/README.md index 5fe08699..dd9d1770 100644 --- a/scripts/org-export/README.md +++ b/scripts/org-export/README.md @@ -39,7 +39,12 @@ Interim API: SPA + proxy to a FreeOS host (`FREEOS_UPSTREAM`). Target end-state: self-contained server in the export (see [docs/org-export.md](../../docs/org-export.md)). -## Phase 5 (deferred) +## Phase 5 (default installer) -- Slim the default installer (sidecar remains opt-in here) +Default FreeOS installers stay **zero-Node**. This export is an explicit +operator / commercial action — it does not change the desktop package. + +Residual (not default install): + +- Self-contained org API (export still proxies to `FREEOS_UPSTREAM`) - Optional `packages/org-ui` extraction if the Dashboard Vite graph must be left behind diff --git a/src/octop/cli/commands/org.py b/src/octop/cli/commands/org.py index a50a7c54..f4b463e5 100644 --- a/src/octop/cli/commands/org.py +++ b/src/octop/cli/commands/org.py @@ -55,8 +55,8 @@ def org_enable() -> None: """Turn on the organization module (does not start the sidecar).""" service = _service() service.set_enabled(True) - click.echo("Organization module enabled.") - click.echo(f"Start the sidecar with: {service.status().start_command}") + click.echo("Organization module enabled (in-host).") + click.echo("Node sidecar is optional. Set FREEOS_ORG_SIDECAR=1 only for advanced export/sync.") @org.command("disable") @@ -386,7 +386,7 @@ def export_standalone(out_dir: Path) -> None: Copies dashboard/src/org-ui plus a Vite + Docker shell (local JWT IdentityBridge, /api proxy to a FreeOS host). Chat is not exported. - Sidecar stays in the default installer (Phase 5). + Default FreeOS installers stay zero-Node; this export is opt-in. """ from octop.modules.org_os.export_standalone import write_standalone_scaffold diff --git a/src/octop/modules/org_os/export_standalone.py b/src/octop/modules/org_os/export_standalone.py index f593250d..9bf8ab73 100644 --- a/src/octop/modules/org_os/export_standalone.py +++ b/src/octop/modules/org_os/export_standalone.py @@ -138,7 +138,7 @@ def _modules_manifest() -> dict[str, Any]: }, }, "not_exported": ["chat"], - "todo": "Phase 5: slim default installer (sidecar stays opt-in); self-contained org API", + "todo": "self-contained org API (export still proxies to FREEOS_UPSTREAM); optional packages/org-ui extraction", } @@ -181,5 +181,5 @@ def write_standalone_scaffold(out_dir: Path) -> dict[str, Any]: "files": files, "auth": "standalone local JWT (openxyos.standalone.jwt)", "api": "proxies /api to FREEOS_UPSTREAM (interim); self-contained server is the target end-state", - "todo": "Phase 5: slim default installer (sidecar remains opt-in)", + "todo": "self-contained org API; optional packages/org-ui extraction", } diff --git a/tests/unit/desktop/test_nsis_uninstall.py b/tests/unit/desktop/test_nsis_uninstall.py index 399b8142..83c8530b 100644 --- a/tests/unit/desktop/test_nsis_uninstall.py +++ b/tests/unit/desktop/test_nsis_uninstall.py @@ -230,6 +230,11 @@ def test_nsis_runs_openxyos_provisioner_subprocess() -> None: assert "OPENXYOS_OPTIONAL_ABSENT" in provision assert 'File "/oname=openxyos-runtime.zip"' in provision assert "OPENXYOS_RUNTIME_ZIP_PRESENT is set but" in provision + create_dir = provision.index('CreateDirectory "$INSTDIR\\openxyos"') + ifdef = provision.index("!ifdef OPENXYOS_RUNTIME_ZIP_PRESENT") + absent = provision.index("OPENXYOS_OPTIONAL_ABSENT") + assert ifdef < create_dir < absent + assert "CreateDirectory" not in provision[absent:] assert "openxyos-runtime.zip missing" not in nsh assert "安装继续" in nsi assert "Setup continues" in nsi diff --git a/tests/unit/desktop/test_phase5_defaults.py b/tests/unit/desktop/test_phase5_defaults.py new file mode 100644 index 00000000..2f0a6613 --- /dev/null +++ b/tests/unit/desktop/test_phase5_defaults.py @@ -0,0 +1,98 @@ +"""Phase 5: default install is one FreeOS process; sidecar stays opt-in.""" + +from __future__ import annotations + +from pathlib import Path + +REPO = Path(__file__).resolve().parents[3] + + +def test_package_scripts_default_skip_org_sidecar() -> None: + package = (REPO / "desktop" / "portable" / "package.sh").read_text(encoding="utf-8") + assert "SHIP_OPENXYOS_RUNTIME:-0" in package or '"${SHIP_OPENXYOS_RUNTIME:-0}"' in package + assert "SKIP_ORG_SIDECAR=1" in package + task = (REPO / "desktop" / "src" / "build" / "windows" / "Taskfile.yml").read_text( + encoding="utf-8" + ) + assert 'SHIP_OPENXYOS_RUNTIME | default "0"' in task + workflow = (REPO / ".github" / "workflows" / "octop-desktop.yml").read_text(encoding="utf-8") + assert 'SKIP_ORG_SIDECAR: "1"' in workflow + assert "SHIP_OPENXYOS_RUNTIME=0" in workflow + assert "must not embed org-sidecar" in workflow + + +def test_nsis_default_macro_does_not_create_openxyos_dir() -> None: + nsh = (REPO / "desktop" / "src" / "build" / "windows" / "nsis" / "wails_tools.nsh").read_text( + encoding="utf-8" + ) + provision = nsh[ + nsh.index("!macro wails.provisionOpenXYOS") : nsh.index("!macro wails.openxyosFailDetail") + ] + assert "nsExec::Exec" not in provision + assert "Abort" not in provision + absent = provision[provision.index("OPENXYOS_OPTIONAL_ABSENT") :] + assert "CreateDirectory" not in absent + assert 'File "/oname=openxyos-runtime.zip"' not in absent + + +def test_docs_do_not_require_bundled_openxyos() -> None: + readme_cn = (REPO / "README_CN.md").read_text(encoding="utf-8") + assert "内置 openXYOS" not in readme_cn + assert "宿主内 Organization" in readme_cn + assert "docs/org-export.md" in readme_cn + readme = (REPO / "README.md").read_text(encoding="utf-8") + assert "docs/org-export.md" in readme + assert "FREEOS_ORG_SIDECAR=1" in readme + desktop = (REPO / "desktop" / "README.md").read_text(encoding="utf-8") + assert "including the sidecar" not in desktop + assert "with openXYOS" not in desktop + assert "SHIP_OPENXYOS_RUNTIME=1" in desktop + export_doc = (REPO / "docs" / "org-export.md").read_text(encoding="utf-8") + assert "**不** 捆绑 Node" in export_doc + assert "不** 自动拉起 openXYOS" in export_doc + assert "Phase 5" in export_doc + plan = (REPO / "docs" / "org-merge-plan.md").read_text(encoding="utf-8") + assert "Phase 5 默认安装器瘦身已完成" in plan or "**已完成**" in plan + table = plan[plan.index("| **5**") : plan.index("| **5**") + 240] + assert "已完成" in table + assert "未开始" not in table + changelog = (REPO / "CHANGELOG.md").read_text(encoding="utf-8") + assert "Organization Phase 5" in changelog + assert "单进程 FreeOS" in changelog + + +def test_adr_001_and_003_record_zero_node_default() -> None: + adr001 = (REPO / "docs" / "adr" / "001-single-process-model.md").read_text(encoding="utf-8") + assert "FREEOS_ORG_SIDECAR=1" in adr001 + assert "SHIP_OPENXYOS_RUNTIME=1" in adr001 + adr003 = (REPO / "docs" / "adr" / "003-org-ui-single-source-dual-delivery.md").read_text( + encoding="utf-8" + ) + assert "zero-Node" in adr003 + assert "org-export.md" in adr003 + + +def test_docker_compose_stays_single_python_process() -> None: + compose_text = (REPO / "docker" / "docker-compose.yml").read_text(encoding="utf-8") + assert "services:\n octop:" in compose_text + assert "openxyos:" not in compose_text + assert "3780" not in compose_text + assert "FREEOS_HOME" in compose_text + assert "single Python process" in compose_text + readme = (REPO / "docker" / "README.md").read_text(encoding="utf-8") + readme_cn = (REPO / "docker" / "README_CN.md").read_text(encoding="utf-8") + for body in (readme, readme_cn): + assert "FREEOS_HOME" in body + assert "OCTOP_HOME" in body + assert "sidecar" in body.lower() or "边车" in body + + +def test_org_enable_does_not_require_sidecar() -> None: + source = (REPO / "src" / "octop" / "cli" / "commands" / "org.py").read_text(encoding="utf-8") + assert "Organization module enabled (in-host)." in source + assert "Start the sidecar with:" not in source + assert "FREEOS_ORG_SIDECAR=1" in source + export = (REPO / "src" / "octop" / "modules" / "org_os" / "export_standalone.py").read_text( + encoding="utf-8" + ) + assert "slim default installer" not in export From 1e2a808aa1dac16dbbb9667abebed771eaf2a59e Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sat, 19 Sep 2026 05:36:28 +0000 Subject: [PATCH 2/2] docs: mark leftover sidecar-era changelog notes as superseded Unreleased repair bullets still describe 0.0.1 auto-start of local openXYOS. Point readers at 0.0.2 / Phase 5 so they are not treated as current default install behavior. Co-authored-by: XYAI Labs --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 906b0c6d..796e1162 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ ### 修复 +> 下面若干「启动即拉起本机 openXYOS」条目记录的是 0.0.1 边车时代问题。默认路径已被 [0.0.2] 与本文件 Unreleased 的 Phase 5 取代:安装与首屏不再捆绑或自动拉起 Node。 - Windows 安装预配不再把 Node 工作目录选到残留的嵌套 `openxyos\\openxyos`:顶层已有 `backend-dist/server.js` 与 `dist/index.html` 时必须用 live 根。嵌套 cwd 会让 `node backend-dist/server.js` 立刻退出且 stdout/stderr 为空,安装空等 90s 后以退出码 12 失败。`start-sidecar.ps1` / 预配 / 桌面 Go / Python 启动路径统一按此选择 cwd;每次启动截断 `start.log`;Shell.Application 若未刷新日志则改走 explorer / Start-Process,Node fail-fast 不再伪装成 livez 超时。 - Windows 安装详情不再把 openXYOS 预配的 UTF-8 Node/PowerShell 控制台(`[Error] POST /api/auth`、`[seed]`、中文 Server/WebSocket 状态)按系统 ANSI/GBK 打成乱码。NSIS 只用 `nsExec::Exec` 等退出码,详情页只显示本地化步骤结果;完整日志写入 `%LOCALAPPDATA%\\FreeOS\\openxyos\\provision.log` / `start.log`(UTF-8)。解压前停止并等待旧的 FreeOS openXYOS Node 退出;livez 通过即成功,不把启动期 auth 日志当失败。