From 73f0e39c7f29143bb3b914ef013725868945c2a1 Mon Sep 17 00:00:00 2001
From: Cursor Agent
Date: Wed, 16 Sep 2026 05:18:26 +0000
Subject: [PATCH] Align Organization copy with employee/colleague/expert
glossary
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
员工 is a department assignment on the control plane, 同事 are
same-org peers on FreeOS, and 专家 / 智能助手 are not yet in a
department. Confine corpus distill writes to FREEOS_HOME/org-corpus/default
so user distill_path cannot reach the filesystem.
---
CHANGELOG.md | 5 +-
README.md | 4 +-
dashboard/src/locales/en.json | 24 +++----
dashboard/src/locales/zh.json | 24 +++----
docs/asset-loop.md | 6 +-
src/octop/api/routers/org_module.py | 4 +-
src/octop/i18n/en.json | 8 +--
src/octop/i18n/zh.json | 10 +--
src/octop/modules/org_os/assets/importer.py | 4 +-
src/octop/modules/org_os/catalog.py | 4 +-
src/octop/modules/org_os/empower.py | 74 +++++++++++++++------
src/octop/modules/org_os/loop/run.py | 2 +-
src/octop/modules/org_os/notes.py | 10 +--
src/octop/modules/org_os/overview.py | 2 +-
tests/unit/test_org_module.py | 17 +++++
tests/unit/test_org_notes.py | 4 +-
16 files changed, 127 insertions(+), 75 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 54cd2301..859df7e1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,7 +9,8 @@
### 新增
- 组织页双向互生长:FreeOS 资产经 `/api/freeos/ingest`(共享写入令牌,无需边车登录墙)落到 openXYOS 员工/人才/插件/技能表;控制面导出再装配回 FreeOS。循环在边车健康时不再把「已推送到控制面」写成 false。
-- 组织页可从本地语料或 ima / 云知识库指针一键生产同事;本地模型一键下载后会注册到 Ollama 供应商。
+- 组织页可从本地语料或 ima / 云知识库指针一键生产尚未入部门的专家 / 智能助手;本地模型一键下载后会注册到 Ollama 供应商。
+- 组织页用语对齐:员工=已入部门;同事=同一组织的同伴;专家 / 智能助手=尚未入部门。导入把部门员工登记为同事;导出把专家写入控制面员工。
### 变更
@@ -17,7 +18,7 @@
- 边车启动会写入并传递 `FREEOS_INGEST_TOKEN`;Python 启动器同时识别 `%LOCALAPPDATA%\FreeOS\openxyos`、`$INSTDIR\openxyos` 与 portable `org-sidecar`。
- Windows 安装包写入预构建 openXYOS 运行包(`$INSTDIR\openxyos` + `openxyos-runtime.zip`),首次启动再落到 `%LOCALAPPDATA%\FreeOS\openxyos` / `~/.freeos/openxyos`,自动拉起 `http://127.0.0.1:3780`,组织页不必再点「启动边车」。
- 项目创建用系统文件夹选择器(Wails 原生对话框,任意盘符);项目/任务/对话区默认展示实时对话列表,并提供建群/群聊入口。
-- Organization OS 首页双循环:FreeOS 数据面与 openXYOS 控制面的实时状态、一键装配员工 / 打包回流 / 运行 org loop,以及边车离线时的启动恢复。
+- Organization OS 首页双循环:FreeOS 数据面与 openXYOS 控制面的实时状态、一键导入部门员工为同事 / 打包专家为员工 / 运行 org loop,以及边车离线时的启动恢复。
- FreeOS 官网源码:`website/`(Vite + React 静态站,正式域名 https://freeos.cnxy.tech)。视觉与章节节奏对齐 XYAI Labs(天蓝→亮光、毛玻璃、01/02/…、底座 / 系统 / 生态),文案仍是 FreeOS 产品自己的话;圆形标志 + XYAI 机器人六姿态,无 Octop 红或章鱼形象。构建产物可整包上传到 `/www/wwwroot/freeos.cnxy.tech`。
### 变更
diff --git a/README.md b/README.md
index 7861e8fd..2b81ef3a 100644
--- a/README.md
+++ b/README.md
@@ -19,13 +19,13 @@
Architecture
-**FreeOS** is an independent open-source host. The data plane is derived from [Octop](https://github.com/TencentCloud/Octop) (MIT). The organization control plane comes from [openXYOS](https://github.com/XYAIStudio/openXYOS) (Apache-2.0). They are complementary: FreeOS **produces** AI employees, skills, plugins, and MCPs; those assets **assemble into openXYOS**; openXYOS blueprints, catalog, policies, and talent **feed back** into FreeOS to spawn or upgrade more runtime agents.
+**FreeOS** is an independent open-source host. The data plane is derived from [Octop](https://github.com/TencentCloud/Octop) (MIT). The organization control plane comes from [openXYOS](https://github.com/XYAIStudio/openXYOS) (Apache-2.0). They are complementary: FreeOS **produces** experts / assistants (not yet in a department), skills, plugins, and MCPs; those assets **assemble into openXYOS** as department employees; openXYOS blueprints, catalog, policies, and talent **feed back** into FreeOS as colleagues.
The web shell, README banner, favicons, and PWA icons use the FreeOS circular mark (gray ring, yellow / green / red teardrops, blue center).
## Run the self-growth loop
-This is the product path. One command compiles a blueprint into an AI employee, promotes it `draft → market → recruit → shadow → active`, registers a FreeOS chat agent, publishes an asset pack, applies that pack onto openXYOS-shaped surfaces (live HTTP when `OPENXYOS_BASE_URL` is set, otherwise a durable local mirror), imports the control plane back, and proves governance still **blocks** high-risk tools.
+This is the product path. One command compiles a blueprint into a colleague, promotes it `draft → market → recruit → shadow → active`, registers a FreeOS chat agent, publishes an asset pack, applies that pack onto openXYOS-shaped surfaces as department employees (live HTTP when `OPENXYOS_BASE_URL` is set, otherwise a durable local mirror), imports the control plane back, and proves governance still **blocks** high-risk tools.
```bash
git clone https://github.com/XYAIStudio/FreeOS.git
diff --git a/dashboard/src/locales/en.json b/dashboard/src/locales/en.json
index 7660e674..8523afb7 100644
--- a/dashboard/src/locales/en.json
+++ b/dashboard/src/locales/en.json
@@ -22,7 +22,7 @@
"controlPlane": "Control plane",
"flowUp": "Assets back",
"flowDown": "Blueprints out",
- "metricEmployees": "Digital employees",
+ "metricEmployees": "Colleagues / experts",
"metricSkills": "Org skills",
"metricMcp": "MCP / connectors",
"metricTasks": "Tasks",
@@ -31,25 +31,25 @@
"metricHealth": "Control-plane health",
"metricTenant": "Tenant",
"assembleTitle": "Bring openXYOS into FreeOS",
- "assembleBody": "Import AI employees, skills, and plugins from the live control plane (or bundled blueprints) and register them for chat.",
+ "assembleBody": "Import department employees, skills, and plugins from the live control plane (or bundled blueprints) and register them as colleagues.",
"assembleAction": "Import into FreeOS",
- "assembleDone": "Imported {{count}} digital employee(s) into FreeOS",
+ "assembleDone": "Imported {{count}} colleague(s) into FreeOS",
"packTitle": "Send FreeOS assets to openXYOS",
- "packBody": "Pack FreeOS agents, skills, plugins, and MCP, then land them on the control plane. You will see what was written.",
+ "packBody": "Pack experts / assistants not yet in a department, plus skills, plugins, and MCP, then land them on the control plane as department employees. You will see what was written.",
"packAction": "Push to openXYOS",
"packDone": "Asset pack published",
"packRemoteYes": "Landed on the live openXYOS control plane",
"packRemoteNo": "Sidecar offline: wrote the local mirror only",
"loopTitle": "Run the growth loop",
- "loopBody": "Produce on FreeOS, assemble into openXYOS, feed back, and leave durable proof below.",
+ "loopBody": "Produce experts / assistants on FreeOS, land them on the control plane as department employees, feed back as colleagues, and leave durable proof below.",
"loopAction": "Run growth loop",
- "produceTitle": "Produce a colleague from a corpus",
- "produceBody": "Distill a local folder or attach an ima / cloud knowledge URL, then compile a usable FreeOS expert.",
+ "produceTitle": "Produce an expert from a corpus",
+ "produceBody": "Distill a local folder or attach an ima / cloud knowledge URL, then compile a FreeOS expert / assistant that is not yet in a department.",
"produceAction": "Produce from corpus",
"produceDone": "Produced {{name}}",
"produceKb": "Knowledge base id (optional)",
"produceIma": "ima / cloud knowledge URL (optional)",
- "produceName": "Colleague name",
+ "produceName": "Expert name",
"landedTitle": "What landed",
"landedEmpty": "Nothing landed on the control plane yet.",
"yes": "Yes",
@@ -57,24 +57,24 @@
"progressAssemble": "Importing from openXYOS…",
"progressPack": "Packing and pushing to openXYOS…",
"progressLoop": "Running the growth loop…",
- "progressProduce": "Producing a colleague from the corpus…",
+ "progressProduce": "Producing an expert from the corpus…",
"loopOk": "Organization loop completed",
"loopPartial": "Loop ran; some steps did not finish (see timeline)",
"timelineTitle": "Loop timeline",
"timelineEmpty": "No loop proof yet. Run the organization loop to see real results here.",
"timelineOk": "Loop result",
- "timelineEmployees": "Employees",
+ "timelineEmployees": "Colleagues",
"timelineSkills": "Skills",
"timelineRemote": "Pushed to control plane",
"timelineGovernance": "High-risk tool blocked by governance",
"emptyTitle": "First look at Organization",
- "emptyBody": "FreeOS on the left owns employees, skills, MCP, and tasks. openXYOS on the right owns blueprints, modules, and approvals. Start the sidecar, or run the loop once to see a real cycle.",
+ "emptyBody": "FreeOS on the left owns colleagues, experts / assistants not yet in a department, skills, MCP, and tasks. openXYOS on the right owns department employees, blueprints, modules, and approvals. Start the sidecar, or run the loop once to see a real cycle.",
"startSidecarTitle": "openXYOS sidecar is offline",
"startSidecarBody": "Live approvals and remote apply need the sidecar. Start it here; if no launcher is bundled, use the command beside the button.",
"startSidecarAction": "Start sidecar",
"sidecarStarted": "Sidecar is reachable",
"sidecarStartPending": "Start attempted; refresh in a moment",
- "bridgeHint": "FreeOS produces employees, skills, plugins, and MCPs. Those assets assemble into openXYOS; org blueprints and policies feed back into FreeOS.",
+ "bridgeHint": "FreeOS produces experts / assistants not yet in a department, plus skills, plugins, and MCPs. Those assets assemble into openXYOS as department employees; org blueprints and policies feed back as colleagues.",
"home": "Data home",
"sidecar": "Sidecar URL",
"notesTitle": "How this module is wired",
diff --git a/dashboard/src/locales/zh.json b/dashboard/src/locales/zh.json
index 48a45841..ba6d18a7 100644
--- a/dashboard/src/locales/zh.json
+++ b/dashboard/src/locales/zh.json
@@ -22,7 +22,7 @@
"controlPlane": "控制面",
"flowUp": "资产回流",
"flowDown": "蓝图下发",
- "metricEmployees": "数字员工",
+ "metricEmployees": "同事 / 专家",
"metricSkills": "组织技能",
"metricMcp": "MCP / 连接器",
"metricTasks": "任务",
@@ -31,25 +31,25 @@
"metricHealth": "控制面健康",
"metricTenant": "租户",
"assembleTitle": "把 openXYOS 装进 FreeOS",
- "assembleBody": "从在线控制面导入 AI 员工、技能和插件(或使用捆绑蓝图),并注册到对话。",
+ "assembleBody": "从在线控制面导入已入部门的员工、技能和插件(或使用捆绑蓝图),注册为本组织同事。",
"assembleAction": "导入到 FreeOS",
- "assembleDone": "已导入 {{count}} 名数字员工到 FreeOS",
+ "assembleDone": "已导入 {{count}} 名同事到 FreeOS",
"packTitle": "把 FreeOS 资产推到 openXYOS",
- "packBody": "打包 FreeOS 智能体、技能、插件与 MCP,写入控制面,并显示落地结果。",
+ "packBody": "打包尚未入部门的专家 / 智能助手、技能、插件与 MCP,写入控制面成为部门员工,并显示落地结果。",
"packAction": "推送到 openXYOS",
"packDone": "资产包已发布",
"packRemoteYes": "已写入在线 openXYOS 控制面",
"packRemoteNo": "边车离线:仅写入本地镜像",
"loopTitle": "运行自增长循环",
- "loopBody": "在 FreeOS 生产、装配进 openXYOS、再回流,并在下方留下可核对的证据。",
+ "loopBody": "在 FreeOS 生产专家 / 智能助手,写入控制面成为部门员工,再回流为本组织同事,并在下方留下可核对的证据。",
"loopAction": "运行自增长循环",
- "produceTitle": "从语料生产同事",
- "produceBody": "蒸馏本地文件夹,或挂接 ima / 云知识库地址,编译成可用的 FreeOS 专家。",
+ "produceTitle": "从语料生产专家",
+ "produceBody": "蒸馏本地文件夹,或挂接 ima / 云知识库地址,编译成尚未入部门的 FreeOS 专家 / 智能助手。",
"produceAction": "从语料生产",
"produceDone": "已生产 {{name}}",
"produceKb": "知识库编号(可选)",
"produceIma": "ima / 云知识库地址(可选)",
- "produceName": "同事名称",
+ "produceName": "专家名称",
"landedTitle": "落地结果",
"landedEmpty": "控制面还没有落地记录。",
"yes": "是",
@@ -57,24 +57,24 @@
"progressAssemble": "正在从 openXYOS 导入…",
"progressPack": "正在打包并推送到 openXYOS…",
"progressLoop": "正在运行自增长循环…",
- "progressProduce": "正在从语料生产同事…",
+ "progressProduce": "正在从语料生产专家…",
"loopOk": "组织循环已跑通",
"loopPartial": "循环已执行,部分步骤未完成(见时间线)",
"timelineTitle": "循环时间线",
"timelineEmpty": "还没有循环记录。运行组织循环后,这里会显示真实结果。",
"timelineOk": "循环结果",
- "timelineEmployees": "员工",
+ "timelineEmployees": "同事",
"timelineSkills": "技能数",
"timelineRemote": "已推送到控制面",
"timelineGovernance": "高风险工具被治理拦截",
"emptyTitle": "第一次打开组织",
- "emptyBody": "左侧 FreeOS 负责员工、技能、MCP 与任务;右侧 openXYOS 负责蓝图、模块与审批。先启动边车,或直接运行组织循环看一次真实闭环。",
+ "emptyBody": "左侧 FreeOS 负责同事、尚未入部门的专家 / 智能助手、技能、MCP 与任务;右侧 openXYOS 负责部门员工、蓝图、模块与审批。先启动边车,或直接运行组织循环看一次真实闭环。",
"startSidecarTitle": "openXYOS 边车未运行",
"startSidecarBody": "控制面离线时无法做 live 审批与远程回流。可一键启动本机边车;若启动器不在当前环境,请按旁边的命令手动启动。",
"startSidecarAction": "启动边车",
"sidecarStarted": "边车已可达",
"sidecarStartPending": "已尝试启动,请稍候再刷新",
- "bridgeHint": "FreeOS 生产员工、技能、插件与 MCP;这些资产组装进 openXYOS,组织蓝图与策略再回流到 FreeOS。",
+ "bridgeHint": "FreeOS 生产尚未入部门的专家 / 智能助手、技能、插件与 MCP;推入 openXYOS 后成为部门员工;组织蓝图与策略再回流,登记为本组织同事。",
"home": "数据目录",
"sidecar": "边车地址",
"notesTitle": "模块如何接入",
diff --git a/docs/asset-loop.md b/docs/asset-loop.md
index 77e30329..2d76761f 100644
--- a/docs/asset-loop.md
+++ b/docs/asset-loop.md
@@ -2,9 +2,9 @@
FreeOS **grows its own AI workforce**. The loop is the product:
-1. **Produce** runtime assets on FreeOS (the data plane): AI employees, skills, plugins, MCP connectors.
-2. **Assemble** those assets into openXYOS (the control plane) so org modules get stronger.
-3. **Feed back** openXYOS org structure, blueprints, governance, the module catalog, and the talent market into FreeOS to spawn or upgrade more agents and skills.
+1. **Produce** runtime assets on FreeOS (the data plane): experts / assistants not yet in a department, skills, plugins, MCP connectors.
+2. **Assemble** those assets into openXYOS (the control plane) as department employees so org modules get stronger.
+3. **Feed back** openXYOS org structure, blueprints, governance, the module catalog, and the talent market into FreeOS as colleagues — same-organization peers you can chat with.
4. **Repeat.** Each cycle adds colleagues and capabilities that are usable in FreeOS chat and governed.
openXYOS does **not** replace FreeOS chat. FreeOS still runs the agents. Governance **blocks** high-risk tools until a human approves.
diff --git a/src/octop/api/routers/org_module.py b/src/octop/api/routers/org_module.py
index f00f4dbf..9db1d0ee 100644
--- a/src/octop/api/routers/org_module.py
+++ b/src/octop/api/routers/org_module.py
@@ -134,7 +134,7 @@ async def org_module_start_sidecar(
return payload
-@router.post("/assemble", summary="Assemble digital employees from an openXYOS blueprint")
+@router.post("/assemble", summary="Import department employees from openXYOS as FreeOS colleagues")
async def org_module_assemble(
request: Request,
server: OctopServer = Depends(get_server),
@@ -154,7 +154,7 @@ class OrgProduceBody(BaseModel):
name: str = ""
-@router.post("/produce", summary="Produce a FreeOS colleague from a knowledge corpus")
+@router.post("/produce", summary="Produce a FreeOS expert / assistant from a knowledge corpus")
async def org_module_produce(
request: Request,
body: OrgProduceBody,
diff --git a/src/octop/i18n/en.json b/src/octop/i18n/en.json
index 38dd45fb..e0fd2274 100644
--- a/src/octop/i18n/en.json
+++ b/src/octop/i18n/en.json
@@ -713,14 +713,14 @@
"generated_skills": "generated {count} module skills from the openXYOS catalog",
"compiled_blueprint": "compiled blueprint {name} → {slug}",
"compiled_workspace": "compiled blueprint → {path}",
- "spawned_agent": "spawned FreeOS agent {agent_id}",
+ "spawned_agent": "spawned FreeOS assistant {agent_id}",
"imported_live": "imported openXYOS catalog/policies from the live sidecar",
"sidecar_offline_compile": "sidecar offline; compiling bundled openXYOS blueprint fixtures",
"wrote_policies": "wrote imported governance policies (fail-closed; engine loads them)",
"local_mirror": "Local mirror is the durable record. HTTP apply is best-effort.",
"nothing_auto_enabled": "Nothing is auto-enabled on the control plane.",
"hero": "FreeOS does the work. openXYOS owns organization and governance.",
- "planes": "Data plane: employees / skills / MCP / tasks. Control plane: blueprints / modules / governance.",
+ "planes": "Data plane: colleagues / experts / skills / MCP / tasks. Control plane: department employees / blueprints / modules / governance.",
"sidecar_offline": "openXYOS sidecar is offline — start it to sync blueprints and approvals.",
"mirror_only": "OPENXYOS_BASE_URL unset; applied to local mirror only.",
"local_mirror_short": "Local mirror is the durable record.",
@@ -732,13 +732,13 @@
"ingest_denied_mirror": "sidecar rejected the ingest token; mirror is complete",
"public_ok": "control plane accepted drafts on public routes",
"control_rejected": "control plane at {url} did not accept drafts; mirror is complete",
- "loop_done": "Loop produced employees, published an asset pack, applied it, and imported back.",
+ "loop_done": "Loop produced colleagues, published an asset pack, applied department employees, and imported back.",
"pack_review": "Not installed into the sidecar. Review openxyos/ then enable per tenant.",
"pack_tenant": "One tenant = one workspace; do not unpack this pack into a shared sandbox.",
"pack_redact": "Agent .env values are redacted except tenant/slug/schema keys.",
"corpus_pointer": "attached cloud corpus pointer → {path}",
"corpus_distilled": "distilled {count} corpus files → {path}",
- "corpus_empty": "no knowledge mount; producing a corpus-ready colleague workspace",
+ "corpus_empty": "no knowledge mount; producing a corpus-ready expert workspace",
"permissions_unreachable": "sidecar permissions unreachable; pass --policies or retry when signed in"
},
"sidecar": {
diff --git a/src/octop/i18n/zh.json b/src/octop/i18n/zh.json
index eb86e7da..272da1ed 100644
--- a/src/octop/i18n/zh.json
+++ b/src/octop/i18n/zh.json
@@ -713,14 +713,14 @@
"generated_skills": "已从 openXYOS 目录生成 {count} 个模块技能",
"compiled_blueprint": "已编译蓝图 {name} → {slug}",
"compiled_workspace": "已编译蓝图 → {path}",
- "spawned_agent": "已生成 FreeOS 智能体 {agent_id}",
+ "spawned_agent": "已生成 FreeOS 智能助手 {agent_id}",
"imported_live": "已从在线边车导入 openXYOS 目录与策略",
"sidecar_offline_compile": "边车离线;正在编译捆绑的 openXYOS 蓝图示例",
"wrote_policies": "已写入导入的治理策略(默认关闭;引擎会加载)",
"local_mirror": "本地镜像是可靠记录。HTTP 写入只是尽力而为。",
"nothing_auto_enabled": "控制面不会自动启用任何模块。",
"hero": "FreeOS 负责干活,openXYOS 负责组织与治理。",
- "planes": "数据面:员工 / 技能 / MCP / 任务。控制面:蓝图 / 模块 / 治理。",
+ "planes": "数据面:同事 / 专家 / 技能 / MCP / 任务。控制面:部门员工 / 蓝图 / 模块 / 治理。",
"sidecar_offline": "openXYOS 边车离线 — 启动后才能同步蓝图与审批。",
"mirror_only": "未设置 OPENXYOS_BASE_URL;仅写入本地镜像。",
"local_mirror_short": "本地镜像是可靠记录。",
@@ -732,13 +732,13 @@
"ingest_denied_mirror": "边车拒绝了写入令牌;本地镜像已写完",
"public_ok": "控制面已通过公开路由接受草稿",
"control_rejected": "控制面 {url} 未接受草稿;本地镜像已写完",
- "loop_done": "循环已生成员工、发布资产包、写入并回流。",
+ "loop_done": "循环已生成同事、发布资产包、写入部门员工并回流。",
"pack_review": "尚未装进边车。请审阅 openxyos/ 后再按租户启用。",
"pack_tenant": "一个租户对应一个工作区;不要把此包解到共享沙箱。",
- "pack_redact": "智能体 .env 已脱敏,仅保留租户/别名/模式键。",
+ "pack_redact": "专家 / 智能助手 .env 已脱敏,仅保留租户/别名/模式键。",
"corpus_pointer": "已挂接云知识库指针 → {path}",
"corpus_distilled": "已蒸馏 {count} 个语料文件 → {path}",
- "corpus_empty": "没有知识库挂载;正在生成可供语料使用的同事工作区",
+ "corpus_empty": "没有知识库挂载;正在生成可供语料使用的专家工作区",
"permissions_unreachable": "边车权限接口不可达;请传入策略或登录后再试"
},
"sidecar": {
diff --git a/src/octop/modules/org_os/assets/importer.py b/src/octop/modules/org_os/assets/importer.py
index 3c53b5e4..a2c45ac4 100644
--- a/src/octop/modules/org_os/assets/importer.py
+++ b/src/octop/modules/org_os/assets/importer.py
@@ -97,7 +97,7 @@ def import_openxyos_assets(
if record is not None:
spawned = spawn_colleague_agent(home, record)
result.agents.append(spawned.agent_id)
- result.notes.append(f"spawned FreeOS agent {spawned.agent_id}")
+ result.notes.append(f"spawned FreeOS assistant {spawned.agent_id}")
if from_sidecar:
client = OpenXyosControlClient(sidecar_url, home=home)
@@ -144,7 +144,7 @@ def import_openxyos_assets(
if record is not None:
spawned = spawn_colleague_agent(home, record)
result.agents.append(spawned.agent_id)
- result.notes.append(f"spawned FreeOS agent {spawned.agent_id}")
+ result.notes.append(f"spawned FreeOS assistant {spawned.agent_id}")
for item in exported.get("skills") or []:
if not isinstance(item, dict):
continue
diff --git a/src/octop/modules/org_os/catalog.py b/src/octop/modules/org_os/catalog.py
index a5afd4ff..d6e2bc29 100644
--- a/src/octop/modules/org_os/catalog.py
+++ b/src/octop/modules/org_os/catalog.py
@@ -52,8 +52,8 @@ class OrgCapability(TypedDict):
"key": "employees",
"label": "People & agents",
"label_zh": "人机资源",
- "description": "Human employees, AI employees, and talent market.",
- "description_zh": "人类员工、AI 员工、备选员工与人才市场",
+ "description": "Department employees and the talent market.",
+ "description_zh": "已入部门的员工与人才市场",
"locked": False,
},
{
diff --git a/src/octop/modules/org_os/empower.py b/src/octop/modules/org_os/empower.py
index 06730269..68533729 100644
--- a/src/octop/modules/org_os/empower.py
+++ b/src/octop/modules/org_os/empower.py
@@ -2,9 +2,11 @@
from __future__ import annotations
+import os
from pathlib import Path
from typing import Any
+from octop.infra.utils.host_dirs import assert_safe_host_path
from octop.modules.org_os.apply.apply import apply_asset_pack
from octop.modules.org_os.assets.importer import import_openxyos_assets
from octop.modules.org_os.assets.pack import publish_asset_pack
@@ -16,6 +18,41 @@
from octop.modules.org_os.service import OrgModuleService
+def _corpus_dest(home: Path) -> str:
+ """Return ``{FREEOS_HOME}/org-corpus/default`` only — no user path fragments."""
+ home_s = os.path.realpath(os.fspath(home))
+ dest_s = os.path.realpath(os.path.join(home_s, "org-corpus", "default"))
+ if dest_s != home_s and not dest_s.startswith(home_s + os.sep):
+ raise ValueError("corpus dest is outside FREEOS_HOME")
+ dest = assert_safe_host_path(dest_s, restrict_to_root=home_s)
+ dest_s = os.path.realpath(os.fspath(dest))
+ os.makedirs(dest_s, exist_ok=True)
+ return dest_s
+
+
+def _corpus_excerpts(dest_s: str) -> list[dict[str, str]]:
+ references: list[dict[str, str]] = []
+ if not os.path.isdir(dest_s):
+ return references
+ for child in sorted(os.listdir(dest_s)):
+ if not child.endswith(".md") or child in {".", ".."}:
+ continue
+ if os.sep in child or "/" in child or "\\" in child:
+ continue
+ path_s = os.path.realpath(os.path.join(dest_s, child))
+ if not path_s.startswith(dest_s + os.sep) or not os.path.isfile(path_s):
+ continue
+ try:
+ with open(path_s, encoding="utf-8") as handle:
+ excerpt = handle.read()[:1500]
+ except OSError:
+ continue
+ references.append({"name": os.path.splitext(child)[0], "excerpt": excerpt})
+ if len(references) >= 12:
+ break
+ return references
+
+
def assemble_from_blueprint(service: OrgModuleService) -> dict[str, Any]:
"""Import control-plane catalog/policies (when live) and compile real blueprints."""
tid = service.tenant_id() or "default"
@@ -93,44 +130,41 @@ def produce_from_corpus(
ima_url: str = "",
name: str = "",
) -> dict[str, Any]:
- """Build a digital employee from a local/cloud knowledge mount (ima pointer OK)."""
+ """Build an expert / assistant from a local/cloud knowledge mount (ima pointer OK)."""
from octop.infra.knowledge.local_mount import (
attach_cloud_pointer,
distill_readonly,
load_mounts,
)
+ # Caller-supplied distill_path is accepted but never joined into filesystem
+ # paths — only ``{FREEOS_HOME}/org-corpus/`` is written.
+ _ = distill_path
tid = service.tenant_id() or "default"
- dest = (
- Path(distill_path) if distill_path else (service.home / "org-corpus" / (kb_id or "default"))
- )
- dest.mkdir(parents=True, exist_ok=True)
+ dest_s = _corpus_dest(service.home)
notes: list[str] = []
copied = 0
mounts = load_mounts(service.home)
mount = mounts.get(kb_id) if kb_id else None
cloud = ima_url or (mount.cloud_url if mount is not None else "")
if cloud:
- pointer = attach_cloud_pointer(cloud, str(dest), provider="ima")
+ pointer = attach_cloud_pointer(cloud, dest_s, provider="ima")
copied = int(pointer.get("copied") or 0)
- notes.append(f"attached cloud corpus pointer → {dest}")
+ notes.append(f"attached cloud corpus pointer → {dest_s}")
elif mount is not None and mount.source_path:
- distilled = distill_readonly(mount.source_path, str(dest))
+ distilled = distill_readonly(mount.source_path, dest_s)
copied = int(distilled.get("copied") or 0)
- notes.append(f"distilled {copied} corpus files → {dest}")
+ notes.append(f"distilled {copied} corpus files → {dest_s}")
else:
- notes.append("no knowledge mount; producing a corpus-ready colleague workspace")
+ notes.append("no knowledge mount; producing a corpus-ready expert workspace")
- references: list[dict[str, str]] = []
- for path in sorted(dest.glob("*.md"))[:12]:
- excerpt = path.read_text(encoding="utf-8", errors="replace")[:1500]
- references.append({"name": path.stem, "excerpt": excerpt})
+ references = _corpus_excerpts(dest_s)
capabilities = [item["name"] for item in references] or ["corpus-research", "org-writing"]
- colleague = name.strip() or (kb_id.replace("_", " ").strip() if kb_id else "Corpus Analyst")
+ expert = name.strip() or (kb_id.replace("_", " ").strip() if kb_id else "Corpus Analyst")
compiled = compile_blueprint(
{
"schema": "openxyos.agent-blueprint.v1",
- "name": colleague,
+ "name": expert,
"positioning": "Produces work from a distilled local or ima corpus.",
"industry": "knowledge",
"capabilities": capabilities,
@@ -145,20 +179,20 @@ def produce_from_corpus(
register_compiled(
store,
slug=compiled.slug,
- name=colleague,
+ name=expert,
workspace=compiled.workspace,
lifecycle="draft",
)
record = store.get(compiled.slug)
if record is None:
- raise RuntimeError(f"failed to register colleague {compiled.slug}")
+ raise RuntimeError(f"failed to register expert {compiled.slug}")
spawned = spawn_colleague_agent(service.home, record)
notes.append(f"compiled blueprint → {compiled.workspace}")
- notes.append(f"spawned FreeOS agent {spawned.agent_id}")
+ notes.append(f"spawned FreeOS assistant {spawned.agent_id}")
return {
"slug": compiled.slug,
"workspace": str(compiled.workspace),
- "distill_path": str(dest),
+ "distill_path": dest_s,
"copied": copied,
"spawned": spawned.to_dict(),
"notes": notes,
diff --git a/src/octop/modules/org_os/loop/run.py b/src/octop/modules/org_os/loop/run.py
index badb0d4a..5a8659a6 100644
--- a/src/octop/modules/org_os/loop/run.py
+++ b/src/octop/modules/org_os/loop/run.py
@@ -218,7 +218,7 @@ def run_growth_loop(
*imported.notes,
*applied.notes,
*feedback.notes,
- "Loop produced employees, published an asset pack, applied it, and imported back.",
+ "Loop produced colleagues, published an asset pack, applied department employees, and imported back.",
],
)
proof.ok = bool(
diff --git a/src/octop/modules/org_os/notes.py b/src/octop/modules/org_os/notes.py
index f61faba9..82d35e3b 100644
--- a/src/octop/modules/org_os/notes.py
+++ b/src/octop/modules/org_os/notes.py
@@ -24,7 +24,7 @@
("path",),
),
(
- re.compile(r"^spawned FreeOS agent (.+)$"),
+ re.compile(r"^spawned FreeOS assistant (.+)$"),
"org.notes.spawned_agent",
("agent_id",),
),
@@ -88,8 +88,8 @@
),
(
re.compile(
- r"^Data plane: employees / skills / MCP / tasks\. "
- r"Control plane: blueprints / modules / governance\.$"
+ r"^Data plane: colleagues / experts / skills / MCP / tasks\. "
+ r"Control plane: department employees / blueprints / modules / governance\.$"
),
"org.notes.planes",
(),
@@ -153,7 +153,7 @@
),
(
re.compile(
- r"^Loop produced employees, published an asset pack, applied it, and imported back\.$"
+ r"^Loop produced colleagues, published an asset pack, applied department employees, and imported back\.$"
),
"org.notes.loop_done",
(),
@@ -186,7 +186,7 @@
("count", "path"),
),
(
- re.compile(r"^no knowledge mount; producing a corpus-ready colleague workspace$"),
+ re.compile(r"^no knowledge mount; producing a corpus-ready expert workspace$"),
"org.notes.corpus_empty",
(),
),
diff --git a/src/octop/modules/org_os/overview.py b/src/octop/modules/org_os/overview.py
index 59c4c6c6..98c77a8c 100644
--- a/src/octop/modules/org_os/overview.py
+++ b/src/octop/modules/org_os/overview.py
@@ -118,7 +118,7 @@ def build_overview(
}
notes = [
"FreeOS does the work. openXYOS owns organization and governance.",
- "Data plane: employees / skills / MCP / tasks. Control plane: blueprints / modules / governance.",
+ "Data plane: colleagues / experts / skills / MCP / tasks. Control plane: department employees / blueprints / modules / governance.",
]
if not status.sidecar.reachable:
notes.append("openXYOS sidecar is offline — start it to sync blueprints and approvals.")
diff --git a/tests/unit/test_org_module.py b/tests/unit/test_org_module.py
index 4763a976..66e18f33 100644
--- a/tests/unit/test_org_module.py
+++ b/tests/unit/test_org_module.py
@@ -222,6 +222,23 @@ def test_produce_from_corpus_spawns_colleague(tmp_path: Path) -> None:
assert result["spawned"]["agent_id"]
+def test_produce_from_corpus_ignores_outside_distill_path(tmp_path: Path) -> None:
+ from octop.modules.org_os.empower import produce_from_corpus
+
+ outside = tmp_path.parent / "outside-corpus"
+ service = OrgModuleService(config_path=tmp_path / "config.json", home=tmp_path)
+ result = produce_from_corpus(
+ service,
+ name="Safe Analyst",
+ distill_path=str(outside / ".." / "etc"),
+ kb_id="../etc",
+ )
+ dest = Path(result["distill_path"])
+ assert dest == (tmp_path / "org-corpus" / "default").resolve()
+ assert dest.is_relative_to(tmp_path.resolve())
+ assert not outside.exists()
+
+
def test_governance_enable_persists(tmp_path: Path) -> None:
config = tmp_path / "config.json"
service = OrgModuleService(config_path=config, home=tmp_path)
diff --git a/tests/unit/test_org_notes.py b/tests/unit/test_org_notes.py
index b1d922ca..4622249e 100644
--- a/tests/unit/test_org_notes.py
+++ b/tests/unit/test_org_notes.py
@@ -8,7 +8,7 @@ def test_localize_loop_notes_zh() -> None:
"started but not reachable yet; check logs/org-sidecar.log",
"Local mirror is the durable record. HTTP apply is best-effort.",
"control plane accepted the FreeOS ingest",
- "Loop produced employees, published an asset pack, applied it, and imported back.",
+ "Loop produced colleagues, published an asset pack, applied department employees, and imported back.",
],
"zh",
)
@@ -16,7 +16,7 @@ def test_localize_loop_notes_zh() -> None:
assert "尚未可达" in notes[1]
assert "本地镜像" in notes[2]
assert "控制面已接受" in notes[3]
- assert "循环已生成员工" in notes[4]
+ assert "循环已生成同事" in notes[4]
def test_localize_unknown_note_passthrough() -> None: