diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0ffa743e..796e1162 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、边车系统设置。
@@ -22,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 日志当失败。
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