Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@

### 新增

- 组织页双向互生长:FreeOS 资产经 `/api/freeos/ingest`(共享写入令牌,无需边车登录墙)落到 openXYOS 员工/人才/插件/技能表;控制面导出再装配回 FreeOS。循环在边车健康时不再把「已推送到控制面」写成 false。
- 组织页可从本地语料或 ima / 云知识库指针一键生产同事;本地模型一键下载后会注册到 Ollama 供应商。

### 变更

- 组织循环时间线的是/否改为中英文本地化,不再混用 `true`/`false`。
- 边车启动会写入并传递 `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,以及边车离线时的启动恢复。
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ uv run pytest tests/e2e/test_org_growth_loop.py tests/unit/test_org_loop.py test
| When | What happens |
|---|---|
| `OPENXYOS_BASE_URL` unset / sidecar down | Uses `tests/fixtures/org-loop/` and writes `{FREEOS_HOME}/openxyos-mirror/` |
| `OPENXYOS_BASE_URL` (or `FREEOS_ORG_SIDECAR_URL`) set | Also POST/PUT `/api/employees`, `/api/talent`, `/api/plugins`, `/api/module-settings` |
| `OPENXYOS_BASE_URL` (or `FREEOS_ORG_SIDECAR_URL`) set | Also POST `/api/freeos/ingest` (employees, talent, plugins, skills) when the sidecar is healthy |

High-risk tools (`outbound`, `delete`, `pay`, `prod`) are default-denied in the host tool path (`OrgGovernanceMiddleware` + `xyos-governance-mcp`). `execute` stays false until `freeos org governance approve <id>` and the agent re-checks with the same args.

Expand Down
22 changes: 22 additions & 0 deletions dashboard/src/api/modules/orgModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,15 @@ export interface OrgAssembleResult {
notes: string[];
}

export interface OrgProduceResult {
slug: string;
workspace: string;
distill_path: string;
copied: number;
spawned: Record<string, unknown>;
notes: string[];
}

export interface OrgPackResult {
pack: {
directory: string;
Expand All @@ -98,6 +107,7 @@ export interface OrgPackResult {
remote_applied: boolean;
mirrored: boolean;
notes: string[];
landed?: Record<string, unknown>;
};
}

Expand All @@ -117,6 +127,7 @@ export interface OrgLoopProof {
governance: Record<string, unknown>;
notes: string[];
updated_at?: string;
landed?: Record<string, unknown>;
}

export const orgModuleApi = {
Expand All @@ -133,6 +144,17 @@ export const orgModuleApi = {
request<OrgSidecarStart>("/org-module/sidecar/start", { method: "POST" }),
assemble: () =>
request<OrgAssembleResult>("/org-module/assemble", { method: "POST" }),
produce: (body: {
kb_id?: string;
distill_path?: string;
ima_url?: string;
name?: string;
}) =>
request<OrgProduceResult>("/org-module/produce", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body),
}),
pack: () => request<OrgPackResult>("/org-module/pack", { method: "POST" }),
runLoop: () =>
request<OrgLoopProof>("/org-module/loop/run", {
Expand Down
37 changes: 26 additions & 11 deletions dashboard/src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,19 +30,34 @@
"metricGovernance": "Governance engine",
"metricHealth": "Control-plane health",
"metricTenant": "Tenant",
"assembleTitle": "Assemble employees from a blueprint",
"assembleBody": "Compile an openXYOS blueprint (or the bundled fixture) into FreeOS digital employees and register them for chat.",
"assembleAction": "Assemble into FreeOS",
"assembleDone": "Assembled {{count}} digital employee(s)",
"packTitle": "Pack assets back to openXYOS",
"packBody": "Publish FreeOS skills, plugins, and MCP as an asset pack, mirror it, and push to the control plane when live.",
"packAction": "Pack and apply",
"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.",
"assembleAction": "Import into FreeOS",
"assembleDone": "Imported {{count}} digital employee(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.",
"packAction": "Push to openXYOS",
"packDone": "Asset pack published",
"packRemoteYes": "Attempted a live write to openXYOS",
"packRemoteYes": "Landed on the live openXYOS control plane",
"packRemoteNo": "Sidecar offline: wrote the local mirror only",
"loopTitle": "Run the organization loop",
"loopBody": "Blueprint → employees → pack → import → governance block, with a real timeline below.",
"loopAction": "Run org loop",
"loopTitle": "Run the growth loop",
"loopBody": "Produce on FreeOS, assemble into openXYOS, feed back, 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.",
"produceAction": "Produce from corpus",
"produceDone": "Produced {{name}}",
"produceKb": "Knowledge base id (optional)",
"produceIma": "ima / cloud knowledge URL (optional)",
"produceName": "Colleague name",
"landedTitle": "What landed",
"landedEmpty": "Nothing landed on the control plane yet.",
"yes": "Yes",
"no": "No",
"progressAssemble": "Importing from openXYOS…",
"progressPack": "Packing and pushing to openXYOS…",
"progressLoop": "Running the growth loop…",
"progressProduce": "Producing a colleague from the corpus…",
"loopOk": "Organization loop completed",
"loopPartial": "Loop ran; some steps did not finish (see timeline)",
"timelineTitle": "Loop timeline",
Expand Down
37 changes: 26 additions & 11 deletions dashboard/src/locales/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,19 +30,34 @@
"metricGovernance": "治理引擎",
"metricHealth": "控制面健康",
"metricTenant": "租户",
"assembleTitle": "从蓝图装配员工",
"assembleBody": "把 openXYOS 蓝图(或本地示例蓝图)编译成 FreeOS 数字员工,并注册到对话列表。",
"assembleAction": "装配到 FreeOS",
"assembleDone": "已装配 {{count}} 名数字员工",
"packTitle": "打包回流 openXYOS",
"packBody": "把 FreeOS 技能、插件与 MCP 打成资产包,镜像并尽量推回控制面。",
"packAction": "打包回流",
"assembleTitle": "把 openXYOS 装进 FreeOS",
"assembleBody": "从在线控制面导入 AI 员工、技能和插件(或使用捆绑蓝图),并注册到对话。",
"assembleAction": "导入到 FreeOS",
"assembleDone": "已导入 {{count}} 名数字员工到 FreeOS",
"packTitle": "把 FreeOS 资产推到 openXYOS",
"packBody": "打包 FreeOS 智能体、技能、插件与 MCP,写入控制面,并显示落地结果。",
"packAction": "推送到 openXYOS",
"packDone": "资产包已发布",
"packRemoteYes": "已尝试写入 live openXYOS",
"packRemoteYes": "已写入在线 openXYOS 控制面",
"packRemoteNo": "边车离线:仅写入本地镜像",
"loopTitle": "运行组织循环",
"loopBody": "一键跑通蓝图 → 员工 → 打包 → 回流 → 治理拦截,并在下方展示真实时间线。",
"loopAction": "运行 org loop",
"loopTitle": "运行自增长循环",
"loopBody": "在 FreeOS 生产、装配进 openXYOS、再回流,并在下方留下可核对的证据。",
"loopAction": "运行自增长循环",
"produceTitle": "从语料生产同事",
"produceBody": "蒸馏本地文件夹,或挂接 ima / 云知识库地址,编译成可用的 FreeOS 专家。",
"produceAction": "从语料生产",
"produceDone": "已生产 {{name}}",
"produceKb": "知识库编号(可选)",
"produceIma": "ima / 云知识库地址(可选)",
"produceName": "同事名称",
"landedTitle": "落地结果",
"landedEmpty": "控制面还没有落地记录。",
"yes": "是",
"no": "否",
"progressAssemble": "正在从 openXYOS 导入…",
"progressPack": "正在打包并推送到 openXYOS…",
"progressLoop": "正在运行自增长循环…",
"progressProduce": "正在从语料生产同事…",
"loopOk": "组织循环已跑通",
"loopPartial": "循环已执行,部分步骤未完成(见时间线)",
"timelineTitle": "循环时间线",
Expand Down
2 changes: 1 addition & 1 deletion dashboard/src/pages/Organization/Organization.module.less
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@

.actions {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 12px;
}

Expand Down
89 changes: 85 additions & 4 deletions dashboard/src/pages/Organization/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,14 @@ import {
type OrgLoopProof,
type OrgOverview,
type OrgPackResult,
type OrgProduceResult,
} from "../../api/modules/orgModule";
import { formatServerIsoDateTime } from "../../utils/formatMessageTime";
import { useServerTimezone } from "../../hooks/useServerTimezone";
import { message } from "../../utils/antdMessage";
import styles from "./Organization.module.less";

type ActionKey = "assemble" | "pack" | "loop" | "sidecar" | null;
type ActionKey = "assemble" | "pack" | "loop" | "sidecar" | "produce" | null;

function metric(value: number | undefined): string {
return typeof value === "number" ? String(value) : "—";
Expand All @@ -50,6 +51,9 @@ export default function OrganizationPage() {
);
const [sourceDest, setSourceDest] = useState("");
const [downloading, setDownloading] = useState(false);
const [produceName, setProduceName] = useState("");
const [produceIma, setProduceIma] = useState("");
const [landed, setLanded] = useState<Record<string, unknown> | null>(null);
const autoStartRef = useRef(false);
const iframeRef = useRef<HTMLIFrameElement | null>(null);

Expand All @@ -60,6 +64,10 @@ export default function OrganizationPage() {
const next = await orgModuleApi.overview();
setOverview(next);
if (next.module_toggles) setModuleToggles(next.module_toggles);
const proof = next.last_loop;
if (proof && typeof proof === "object" && proof.landed) {
setLanded(proof.landed as Record<string, unknown>);
}
return next;
} catch (err) {
if (!quiet) {
Expand Down Expand Up @@ -227,9 +235,22 @@ export default function OrganizationPage() {
);
});

const produce = () =>
runAction("produce", async () => {
const result: OrgProduceResult = await orgModuleApi.produce({
name: produceName.trim(),
ima_url: produceIma.trim(),
});
setLastActionNotes(result.notes);
message.success(
t("organization.produceDone", { name: result.slug || produceName }),
);
});

const packBack = () =>
runAction("pack", async () => {
const result: OrgPackResult = await orgModuleApi.pack();
if (result.applied.landed) setLanded(result.applied.landed);
setLastActionNotes([
...result.pack.notes,
...result.applied.notes,
Expand All @@ -244,12 +265,27 @@ export default function OrganizationPage() {
runAction("loop", async () => {
const proof = await orgModuleApi.runLoop();
setLoopProof(proof);
if (proof.landed) setLanded(proof.landed);
setLastActionNotes(proof.notes);
message.success(
proof.ok ? t("organization.loopOk") : t("organization.loopPartial"),
);
});

const flagLabel = (value: boolean | undefined) =>
value ? t("organization.yes") : t("organization.no");

const progressLabel =
busy === "assemble"
? t("organization.progressAssemble")
: busy === "pack"
? t("organization.progressPack")
: busy === "loop"
? t("organization.progressLoop")
: busy === "produce"
? t("organization.progressProduce")
: null;

const lastLoop = (loopProof ?? overview?.last_loop) as OrgLoopProof | null;
const lastSync = overview?.last_sync
? formatServerIsoDateTime(overview.last_sync, timeZone)
Expand Down Expand Up @@ -515,6 +551,12 @@ export default function OrganizationPage() {
</article>
</section>

{progressLabel && (
<section className={styles.recover}>
<p className={styles.recoverTitle}>{progressLabel}</p>
</section>
)}

<section className={styles.actions}>
<button
type="button"
Expand Down Expand Up @@ -567,6 +609,32 @@ export default function OrganizationPage() {
{t("organization.loopAction")}
</Button>
</button>
<div className={styles.action}>
<Users size={18} />
<p className={styles.actionTitle}>
{t("organization.produceTitle")}
</p>
<p className={styles.actionBody}>
{t("organization.produceBody")}
</p>
<Input
value={produceName}
onChange={(event) => setProduceName(event.target.value)}
placeholder={t("organization.produceName")}
/>
<Input
value={produceIma}
onChange={(event) => setProduceIma(event.target.value)}
placeholder={t("organization.produceIma")}
/>
<Button
loading={busy === "produce"}
disabled={busy !== null}
onClick={() => void produce()}
>
{t("organization.produceAction")}
</Button>
</div>
</section>

<section className={styles.timeline}>
Expand All @@ -576,7 +644,7 @@ export default function OrganizationPage() {
{lastLoop ? (
<ol className={styles.timelineList}>
<li>
{t("organization.timelineOk")}: {String(lastLoop.ok)}
{t("organization.timelineOk")}: {flagLabel(lastLoop.ok)}
</li>
{lastLoop.employees?.length ? (
<li>
Expand All @@ -592,11 +660,11 @@ export default function OrganizationPage() {
) : null}
<li>
{t("organization.timelineRemote")}:{" "}
{String(Boolean(lastLoop.remote_applied))}
{flagLabel(Boolean(lastLoop.remote_applied))}
</li>
<li>
{t("organization.timelineGovernance")}:{" "}
{String(Boolean(lastLoop.governance_blocked))}
{flagLabel(Boolean(lastLoop.governance_blocked))}
</li>
{(lastLoop.notes ?? []).slice(0, 6).map((note) => (
<li key={note}>{note}</li>
Expand All @@ -614,6 +682,19 @@ export default function OrganizationPage() {
)}
</section>

<section className={styles.timeline}>
<p className={styles.timelineTitle}>
{t("organization.landedTitle")}
</p>
{landed ? (
<pre className={styles.catalogDesc}>
{JSON.stringify(landed, null, 2)}
</pre>
) : (
<p>{t("organization.landedEmpty")}</p>
)}
</section>

<section className={styles.timeline}>
<p className={styles.timelineTitle}>
{t("organization.catalogTitle")}
Expand Down
6 changes: 6 additions & 0 deletions desktop/src/process.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ func hostLaunchEnv(root string, port int) map[string]string {
if bundle := resolveSidecarDir(root); bundle != "" {
env["FREEOS_OPENXYOS_HOME"] = bundle
}
if install := openxyosInstallDir(); install != "" {
env["FREEOS_OPENXYOS_INSTALL"] = install
}
if _, _, ingest, err := loadOrCreateSidecarSecrets(home); err == nil && ingest != "" {
env["FREEOS_INGEST_TOKEN"] = ingest
}
return env
}

Expand Down
Loading
Loading