From 77a424b534b7a01210b283b8184913751aab7508 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 17 Sep 2026 09:28:10 +0000 Subject: [PATCH 1/2] Fix blank openXYOS Organization embed from stale CORS and sidecar Always allow the sidecar listen origin even when CORS_ORIGIN is stale, replace a day-old FreeOS-openxyos Node on start, heal nested live layouts, and surface a preview error when livez is up but self-origin asset requests fail. Co-authored-by: XYAI Labs --- CHANGELOG.md | 1 + dashboard/src/api/modules/orgModule.ts | 1 + dashboard/src/locales/en.json | 2 + dashboard/src/locales/zh.json | 2 + .../Organization/Organization.module.less | 15 ++ .../src/pages/Organization/index.test.tsx | 16 +- dashboard/src/pages/Organization/index.tsx | 36 +++- .../pages/Organization/sidecarRecover.test.ts | 22 ++- .../src/pages/Organization/sidecarRecover.ts | 8 + .../src/build/windows/nsis/start-sidecar.ps1 | 108 ++++++++++- desktop/src/process_sidecar.go | 167 +++++++++++++++-- desktop/src/process_sidecar_test.go | 47 ++++- desktop/src/process_unix.go | 15 +- desktop/src/process_windows.go | 42 ++++- modules/openxyos/backend/routes/health.ts | 22 ++- .../openxyos/backend/utils/cors-origins.ts | 11 ++ modules/openxyos/frontend/src/main.tsx | 5 + modules/openxyos/scripts/test-cors-origins.ts | 23 ++- src/octop/modules/org_os/overview.py | 13 ++ src/octop/modules/org_os/service.py | 19 ++ src/octop/modules/org_os/sidecar_launch.py | 174 ++++++++++++++++-- .../unit/desktop/test_openxyos_provisioner.py | 11 ++ tests/unit/test_org_module.py | 53 ++++++ tests/unit/test_sidecar_launch.py | 59 ++++++ 24 files changed, 827 insertions(+), 45 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a8e0b3df..d58b1399 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ ### 修复 +- 组织页嵌入本机 openXYOS 不再因为边车「已连接」却空白:`CORS_ORIGIN` 即使漏了 `http://127.0.0.1:3780` / `localhost` / `[::1]`,服务端也会合并监听自源,模块脚本和登录不再 500。FreeOS 启动会先停掉 live 目录里旧的 Node,再用当前 `start-sidecar.ps1` 拉起;嵌套的 `openxyos\dist` 会在启动前提升到 live 根。若 livez 正常但自源请求失败,状态栏和预览区会明确提示,而不是一片白。 - Windows 安装成功后删除 `$INSTDIR\openxyos-runtime` 目录和 `openxyos-runtime.zip`(以及 `%LOCALAPPDATA%\FreeOS` 下同名暂存)。预配失败时保留这些文件以便排查。最终工作目录 `$INSTDIR\openxyos` 与 `%LOCALAPPDATA%\FreeOS\openxyos` 不受影响。 - ima 知识库挂接对齐官方 Agent Interface(https://ima.qq.com/agent-interface,落地页现发 ima-skill 1.1.10):用 Client ID / API Key 连接后,按 `search_knowledge_base` + `get_knowledge_base` 选知识库,按 `get_knowledge_list` / `search_knowledge` 自选文档;文件夹 ID 用接口返回的 `media_id`(不自造 `folder_` 前缀),选择会写入挂接。 - 模型页注册的本机 Ollama / GGUF 现在会出现在对话区模型选择器里,并走本机 `127.0.0.1:11434` 调用。注册会补齐 Ollama 供应商默认值、打开服务开关、热加载对话列表;Ollama 未启动时给出可操作错误,而不是静默失败。 diff --git a/dashboard/src/api/modules/orgModule.ts b/dashboard/src/api/modules/orgModule.ts index 795c8e85..f2c2f86d 100644 --- a/dashboard/src/api/modules/orgModule.ts +++ b/dashboard/src/api/modules/orgModule.ts @@ -52,6 +52,7 @@ export interface OrgControlPlane { export interface OrgOverview { enabled: boolean; sidecar_reachable: boolean; + sidecar_embed_ok?: boolean; sidecar_url: string; start_available: boolean; install_ready: boolean; diff --git a/dashboard/src/locales/en.json b/dashboard/src/locales/en.json index c2532a3f..dab0a7e8 100644 --- a/dashboard/src/locales/en.json +++ b/dashboard/src/locales/en.json @@ -20,6 +20,7 @@ "on": "Enabled", "off": "Disabled", "sidecarUp": "Local openXYOS connected", + "sidecarEmbedFailed": "Preview failed to load", "sidecarDown": "Opening local openXYOS", "sidecarOpening": "Opening local openXYOS", "lastSync": "Last sync", @@ -103,6 +104,7 @@ "disabled": "Organization module disabled", "previewTitle": "openXYOS preview", "previewOffline": "Opening local openXYOS (http://127.0.0.1:3780)…", + "previewBlank": "Local openXYOS is healthy, but the preview stayed blank: requests from its own origin (http://127.0.0.1:3780) were blocked by CORS, so scripts and styles never loaded. Fully quit and reopen FreeOS so the sidecar restarts with the current allow-list.", "previewLoading": "Opening local openXYOS…", "fullscreen": "Fullscreen", "exitFullscreen": "Exit fullscreen", diff --git a/dashboard/src/locales/zh.json b/dashboard/src/locales/zh.json index 6aec4802..71577adc 100644 --- a/dashboard/src/locales/zh.json +++ b/dashboard/src/locales/zh.json @@ -20,6 +20,7 @@ "on": "已启用", "off": "已关闭", "sidecarUp": "本机 openXYOS 已连接", + "sidecarEmbedFailed": "预览页未能加载", "sidecarDown": "正在打开本机 openXYOS", "sidecarOpening": "正在打开本机 openXYOS", "lastSync": "最近同步", @@ -103,6 +104,7 @@ "disabled": "组织模块已关闭", "previewTitle": "openXYOS 预览", "previewOffline": "正在打开本机 openXYOS(http://127.0.0.1:3780)…", + "previewBlank": "本机 openXYOS 健康检查已通过,但预览页是空白的:页面自己的地址(http://127.0.0.1:3780)被跨域拦截,脚本和样式没有加载。请完全退出并重新打开 FreeOS,用当前白名单重启本机服务。", "previewLoading": "正在打开本机 openXYOS…", "fullscreen": "全屏", "exitFullscreen": "退出全屏", diff --git a/dashboard/src/pages/Organization/Organization.module.less b/dashboard/src/pages/Organization/Organization.module.less index bae9a0b0..658dac7b 100644 --- a/dashboard/src/pages/Organization/Organization.module.less +++ b/dashboard/src/pages/Organization/Organization.module.less @@ -105,6 +105,21 @@ pointer-events: none; } +.previewBlank { + position: absolute; + inset: 0; + z-index: 1; + display: flex; + align-items: center; + justify-content: center; + padding: 32px 40px; + max-width: none; + text-align: center; + line-height: 1.6; + color: var(--fn-text-primary); + background: rgba(255, 255, 255, 0.94); +} + .drawerBody { display: flex; flex-direction: column; diff --git a/dashboard/src/pages/Organization/index.test.tsx b/dashboard/src/pages/Organization/index.test.tsx index 39b9eec2..c917f7e5 100644 --- a/dashboard/src/pages/Organization/index.test.tsx +++ b/dashboard/src/pages/Organization/index.test.tsx @@ -1,11 +1,12 @@ import { fireEvent, render, screen, waitFor } from "@testing-library/react"; import { describe, expect, it, vi } from "vitest"; import OrganizationPage from "./index"; -import type { OrgOverview } from "../../api/modules/orgModule"; +import { orgModuleApi, type OrgOverview } from "../../api/modules/orgModule"; const overview: OrgOverview = { enabled: true, sidecar_reachable: true, + sidecar_embed_ok: true, sidecar_url: "http://127.0.0.1:3780", start_available: false, install_ready: true, @@ -92,5 +93,18 @@ describe("OrganizationPage", () => { expect( screen.getAllByText("organization.downloadSource").length, ).toBeGreaterThan(0); + expect(screen.queryByTestId("org-preview-blank")).toBeNull(); + }); + + it("surfaces a blank-preview error when livez is up but embed origin fails", async () => { + vi.mocked(orgModuleApi.overview).mockResolvedValue({ + ...overview, + sidecar_embed_ok: false, + }); + render(); + expect(await screen.findByTestId("org-preview-blank")).toHaveTextContent( + "organization.previewBlank", + ); + expect(screen.getByText("organization.sidecarEmbedFailed")).toBeInTheDocument(); }); }); diff --git a/dashboard/src/pages/Organization/index.tsx b/dashboard/src/pages/Organization/index.tsx index c5145a9f..e40602a5 100644 --- a/dashboard/src/pages/Organization/index.tsx +++ b/dashboard/src/pages/Organization/index.tsx @@ -29,6 +29,7 @@ import { } from "../../utils/desktopFolder"; import { message } from "../../utils/antdMessage"; import { resolveOpenxyosSourceDest } from "./pickSourceDest"; +import { shouldShowPreviewBlank } from "./sidecarRecover"; import styles from "./Organization.module.less"; type ActionKey = "assemble" | "pack" | "loop" | "sidecar" | "produce" | null; @@ -110,6 +111,11 @@ export default function OrganizationPage() { }, [applyOverview, overview?.sidecar_reachable]); const sidecarUp = Boolean(overview?.sidecar_reachable); + const embedOk = overview?.sidecar_embed_ok; + const previewBlank = shouldShowPreviewBlank({ + sidecarUp, + embedOk, + }); const firstRun = useMemo(() => { if (!overview) return false; return ( @@ -186,11 +192,19 @@ export default function OrganizationPage() { useEffect(() => { if (autoStartRef.current) return; - if (!overview || overview.sidecar_reachable) return; - if (!canSilentStart) return; + if (!overview) return; + const embedBroken = + overview.sidecar_reachable && overview.sidecar_embed_ok === false; + if (overview.sidecar_reachable && !embedBroken) return; + if (!embedBroken && !canSilentStart) return; autoStartRef.current = true; void startSidecar(); - }, [canSilentStart, overview, overview?.sidecar_reachable]); + }, [ + canSilentStart, + overview, + overview?.sidecar_reachable, + overview?.sidecar_embed_ok, + ]); const toggleModule = async (key: string, enabled: boolean) => { const next = { ...moduleToggles, [key]: enabled }; @@ -404,9 +418,11 @@ export default function OrganizationPage() { {overview?.enabled ? t("organization.on") : t("organization.off")} - {sidecarUp - ? t("organization.sidecarUp") - : t("organization.sidecarOpening")} + {previewBlank + ? t("organization.sidecarEmbedFailed") + : sidecarUp + ? t("organization.sidecarUp") + : t("organization.sidecarOpening")} {t("organization.lastSync")}: {lastSync} @@ -449,6 +465,14 @@ export default function OrganizationPage() { {t("organization.previewOffline")} )} + {previewBlank && ( +
+ {t("organization.previewBlank")} +
+ )}