cicy-desktop 2.1.367 → 2.1.369

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -39,7 +39,7 @@
39
39
  setTimeout(check, 5000);
40
40
  })();
41
41
  </script>
42
- <script type="module" crossorigin src="./assets/index-BWZkvmvO.js"></script>
42
+ <script type="module" crossorigin src="./assets/index-BMeWzitz.js"></script>
43
43
  <link rel="stylesheet" crossorigin href="./assets/index-CVk0uc2g.css">
44
44
  </head>
45
45
  <body>
@@ -143,7 +143,7 @@ async function sidecarToken() {
143
143
  if (!tok && process.platform === "win32") {
144
144
  try {
145
145
  tok = String(
146
- (await require("../sidecar/wsl-docker").readContainerToken(SIDECAR_PORT)) || ""
146
+ (await require("../sidecar/app-docker").readContainerToken(SIDECAR_PORT)) || ""
147
147
  ).trim();
148
148
  } catch (e) {
149
149
  log.warn(`[hub] sidecar token: ${e.message}`);
@@ -318,7 +318,7 @@ async function openTeam(id, opts = {}) {
318
318
  let token = (opts && opts.token) || "";
319
319
  if (!token) {
320
320
  if (isWinDockerTeam) {
321
- try { token = String(await require("../sidecar/wsl-docker").readContainerToken(localPort) || "").trim(); } catch (e) { log.warn(`[local-teams] open ${id}: container token read failed: ${e.message}`); }
321
+ try { token = String(await require("../sidecar/app-docker").readContainerToken(localPort) || "").trim(); } catch (e) { log.warn(`[local-teams] open ${id}: container token read failed: ${e.message}`); }
322
322
  if (!token) return { ok: false, error: "container_token_unavailable", hint: "cicy-code 容器还没就绪(读不到容器内的 token)。稍等几秒或用卡片菜单「重启」。" };
323
323
  } else if (isLocalUrl) {
324
324
  token = readGlobal()?.api_token || node.api_token || "";
@@ -34,7 +34,8 @@ const hostMihomo = require("../sidecar/host-mihomo"); // 宿主侧 mihomo ——
34
34
  // 按平台分发 Docker-版 cicy-code 的运行层:darwin → Colima,win32 → WSL2。两者同接口
35
35
  // (bootstrap/status/restart/stop/dockerRestart/recreate/update/upgrade/runContainer/
36
36
  // readContainerToken),所以下面的 handler 共用一份逻辑,只换底层模块。
37
- const appDocker = process.platform === "darwin" ? colimaDocker : wslDocker;
37
+ // win32 上再分:老机器 WSL2,新机器 QEMU(不靠 WSL,:8009)—— 见 sidecar/linux-backend.js。
38
+ const appDocker = require("../sidecar/app-docker");
38
39
  // Docker-版 cicy-code 只剩 Windows(WSL2)。(2026-06 回调): macOS 改回 native cicy-code
39
40
  // (:8008,colima 在 16G mac 上压垮内存被 jetsam 杀)→ darwin 不再走 docker,这里不放行,
40
41
  // 所有 docker:app-* handler / reconcile / chrome-proxy 在 mac 上全部 no-op,DockerCard 不显示。
@@ -115,7 +116,7 @@ async function ensureDockerTeam() {
115
116
  return null;
116
117
  };
117
118
  try {
118
- const r = await cc.registerDockerTeam({ port: APP_PORT });
119
+ const r = await cc.registerDockerTeam({ port: APP_PORT, title: "本机团队" });
119
120
  if (r && r.ok && r.apiKey) {
120
121
  dockerTeamReg = { teamId: r.teamId, title: r.title || `Docker :${APP_PORT}`, apiKey: r.apiKey };
121
122
  try { const store = readDockerTeams(); store[APP_VOLUME] = { ...(store[APP_VOLUME] || {}), teamId: r.teamId, title: dockerTeamReg.title, apiKey: r.apiKey }; writeDockerTeams(store); } catch {}
@@ -551,7 +552,9 @@ function register({ sidecarLogPath } = {}) {
551
552
  const registerAppTeam = async () => {
552
553
  const lt = require("./local-teams");
553
554
  await ensureDockerTeam(); // 确保独立云端 team 存在 + 拿到 teamId/title
554
- const title = (dockerTeamReg && dockerTeamReg.title) || "Docker 团队";
555
+ // 默认名「本机团队」(2026-10-21);云端还是系统缺省名(老的 Docker 团队 / Docker :<port>)时也换成它。
556
+ const t0 = dockerTeamReg && dockerTeamReg.title;
557
+ const title = (!t0 || /^\s*docker\s*(团队|team)?\s*(:\s*\d+)?\s*$/i.test(t0)) ? "本机团队" : t0;
555
558
  // is_docker + cloud_team_id are passed INTO addTeam so they land in the SAME
556
559
  // writeNodes as the node — before addTeam's fire-and-forget syncNameToCloud
557
560
  // runs. That's what stops the freshly-created :8008 node from device-registering
@@ -582,6 +585,9 @@ function register({ sidecarLogPath } = {}) {
582
585
  // 否则 → WSL 卷 \\wsl$\<distro>\…\volumes\<vol>\_data(= 容器 /home/cicy)。
583
586
  ipcMain.handle("docker:open-dir", async (_e, which) => {
584
587
  if (!APP_DOCKER_SUPPORTED) return { ok: false, error: "unsupported" };
588
+ // QEMU 后端没有宿主共享目录(Windows 上的 QEMU 不支持 virtio-9p/virtiofs):C:\projects 没挂进容器,
589
+ // 卷在虚拟机磁盘里,资源管理器打不开。如实说,别打开一个和容器无关的目录。
590
+ if (appDocker.backend === "qemu") return { ok: false, error: "qemu_no_host_share", hint: "QEMU 版容器的文件在虚拟机里,请在 cicy-code 里或首页 Debian 终端里访问" };
585
591
  try {
586
592
  const { shell } = require("electron");
587
593
  let p;
@@ -236,14 +236,14 @@
236
236
  "menu": "Port settings",
237
237
  "title": "Port settings",
238
238
  "sub": "Extra ports (besides :8008) published so Windows can reach in-container services",
239
- "mainFixed": ":8008 · cicy-code (fixed)",
239
+ "mainFixed": ":{{p}} · cicy-code (fixed)",
240
240
  "add": "+ Add port",
241
241
  "ph": "Port 1-65535",
242
242
  "save": "Save & rebuild",
243
243
  "saving": "Saving…",
244
244
  "cancel": "Cancel",
245
245
  "hint": "Saving rebuilds the container (volume data kept) with a brief interruption",
246
- "invalid": "Invalid port (1-65535, not 8008, no duplicates)",
246
+ "invalid": "Invalid port (1-65535, not {{p}}, no duplicates)",
247
247
  "none": "No extra ports",
248
248
  "remove": "Remove"
249
249
  },
@@ -236,14 +236,14 @@
236
236
  "menu": "Réglage des ports",
237
237
  "title": "Réglage des ports",
238
238
  "sub": "Ports supplémentaires (hors :8008) publiés pour que Windows atteigne les services du conteneur",
239
- "mainFixed": ":8008 · cicy-code (fixe)",
239
+ "mainFixed": ":{{p}} · cicy-code (fixe)",
240
240
  "add": "+ Ajouter un port",
241
241
  "ph": "Port 1-65535",
242
242
  "save": "Enregistrer et reconstruire",
243
243
  "saving": "Enregistrement…",
244
244
  "cancel": "Annuler",
245
245
  "hint": "L'enregistrement reconstruit le conteneur (données du volume conservées), brève interruption",
246
- "invalid": "Port invalide (1-65535, pas 8008, sans doublon)",
246
+ "invalid": "Port invalide (1-65535, pas {{p}}, sans doublon)",
247
247
  "none": "Aucun port supplémentaire",
248
248
  "remove": "Retirer"
249
249
  },
@@ -236,14 +236,14 @@
236
236
  "menu": "ポート設定",
237
237
  "title": "ポート設定",
238
238
  "sub": ":8008 以外に公開し、Windows からコンテナ内サービスへ直接到達できるポート",
239
- "mainFixed": ":8008 · cicy-code(固定)",
239
+ "mainFixed": ":{{p}} · cicy-code(固定)",
240
240
  "add": "+ ポートを追加",
241
241
  "ph": "ポート 1-65535",
242
242
  "save": "保存して再構築",
243
243
  "saving": "保存中…",
244
244
  "cancel": "キャンセル",
245
245
  "hint": "保存するとコンテナを再構築します(ボリュームのデータは保持、短時間中断)",
246
- "invalid": "無効なポート(1-65535、8008 不可、重複不可)",
246
+ "invalid": "無効なポート(1-65535、{{p}} 不可、重複不可)",
247
247
  "none": "追加ポートなし",
248
248
  "remove": "削除"
249
249
  },
@@ -209,7 +209,7 @@
209
209
  "recreateOk": "确定重建",
210
210
  "runningWslBroken": "运行中 · ⚠ WSL 管理异常(能打开;更新/重启需修复 WSL)",
211
211
  "openFailed": "打开失败",
212
- "openTitle": "打开 Docker 团队",
212
+ "openTitle": "打开本机团队",
213
213
  "repairWsl": "修复 WSL",
214
214
  "wslBrokenRepair": "WSL 管理异常 · 点「修复 WSL」重装",
215
215
  "openWslDir": "打开 WSL 目录",
@@ -236,14 +236,14 @@
236
236
  "menu": "端口设置",
237
237
  "title": "端口设置",
238
238
  "sub": "除 :8008 外,额外发布、可从 Windows 直达容器内服务的端口",
239
- "mainFixed": ":8008 · cicy-code(固定)",
239
+ "mainFixed": ":{{p}} · cicy-code(固定)",
240
240
  "add": "+ 添加端口",
241
241
  "ph": "端口号 1-65535",
242
242
  "save": "保存并重建",
243
243
  "saving": "保存中…",
244
244
  "cancel": "取消",
245
245
  "hint": "保存会重建容器(volume 数据保留),会有短暂中断",
246
- "invalid": "有端口无效(1-65535,不能是 8008,不能重复)",
246
+ "invalid": "有端口无效(1-65535,不能是 {{p}},不能重复)",
247
247
  "none": "暂无额外端口",
248
248
  "remove": "移除"
249
249
  },
package/src/main.js CHANGED
@@ -6,6 +6,9 @@ const { default: contextMenu } = require("electron-context-menu");
6
6
  const fs = require("fs");
7
7
  const os = require("os");
8
8
  const path = require("path");
9
+ // 必须最先:决定 Windows 上 Docker-版 cicy-code 走 WSL(老机器)还是 QEMU(新机器 :8009),
10
+ // 并在任何模块读 CICY_DOCKER_APP_PORT 之前把端口定下来。见 sidecar/linux-backend.js。
11
+ require("./sidecar/linux-backend").init();
9
12
  const cicyCodeSidecar = require("./sidecar/cicy-code");
10
13
  const backendsIPC = require("./backends/ipc");
11
14
  const { openHomepage } = require("./backends/homepage-window");
@@ -0,0 +1,9 @@
1
+ // Copyright 2026 CiCy AI
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ // Docker-版 cicy-code 的运行层(同接口):darwin → Colima,win32 → WSL2(老机器)或 QEMU(新机器)。
5
+ // 选哪个见 linux-backend.js。调用方一律经这里拿,别再直接 require("./wsl-docker")。
6
+ const backend = require("./linux-backend").backend();
7
+ module.exports = process.platform === "darwin" ? require("./colima-docker")
8
+ : backend === "qemu" ? require("./qemu-docker")
9
+ : require("./wsl-docker");
@@ -219,7 +219,16 @@ function headETag(url, hops = 5) {
219
219
  function imageEtagPath() { return path.join(downloadsDir(), "cicy-code-latest.etag"); }
220
220
  function readLoadedImageEtag() { try { return fs.readFileSync(imageEtagPath(), "utf8").trim(); } catch { return ""; } }
221
221
  function writeLoadedImageEtag(v) { try { fs.writeFileSync(imageEtagPath(), String(v || "")); } catch {} }
222
- async function remoteImageEtag() { return headETag(R2_TARBALL); }
222
+ // 国内网络到 R2 公开域名(*.de5.net)会被重置;dl.cicy-ai.com 是同一个桶经 Cloudflare Worker 的只读镜像
223
+ // (仓库 ~/projects/cicy-dl-mirror,ETag/Range 原样透传)。R2 HEAD 不通时一律改走它。
224
+ const R2_MIRROR_TARBALL = process.env.CICY_DOCKER_MIRROR_URL || "https://dl.cicy-ai.com/images/cicy-code-latest.tar.gz";
225
+ async function remoteImageEtag() { return (await headETag(R2_TARBALL)) || headETag(R2_MIRROR_TARBALL); }
226
+ // 先 HEAD 一下 R2(15s 超时),通就用 R2,不通直接换镜像 —— 别让 curl 在被重置的 R2 上重试 8 轮(每轮几十秒)。
227
+ async function pickTarballSource() {
228
+ if ((await headSize(R2_TARBALL)) > 0) return { url: R2_TARBALL, alt: R2_MIRROR_TARBALL };
229
+ if ((await headSize(R2_MIRROR_TARBALL)) > 0) return { url: R2_MIRROR_TARBALL, alt: null };
230
+ return { url: R2_TARBALL, alt: R2_MIRROR_TARBALL };
231
+ }
223
232
  // Drop the cached tarball so a stale copy on disk can't be reused (downloadImageTarball
224
233
  // skips a complete file by size, which a same-size new image would wrongly satisfy).
225
234
  function clearImageTarball() { try { fs.unlinkSync(imageTarballPath()); } catch {} }
@@ -403,22 +412,23 @@ function curlDownload(url, dest, { emit, phase = "image", label = "下载镜像"
403
412
  // (much faster here); falls back to the node downloader if curl is unavailable.
404
413
  async function downloadImageTarball({ emit } = {}) {
405
414
  const dest = imageTarballPath();
415
+ const src = await pickTarballSource(); // R2 不通(国内)→ dl.cicy-ai.com 镜像
406
416
  // REUSE a complete tarball already on disk (staged into ~/Downloads, or a prior
407
417
  // run) BEFORE curlDownload touches it — curlDownload doesn't skip a complete file
408
418
  // (and a failed curl truncates it). headSize uses node http so it works even when
409
419
  // curl can't resolve the host. Skips the ~500MB re-download per new user.
410
420
  try {
411
- const expected = await headSize(R2_TARBALL);
421
+ const expected = await headSize(src.url);
412
422
  const have = fs.statSync(dest).size;
413
423
  if (expected > 0 && have === expected) {
414
424
  emit && emit({ phase: "image", status: "skip", message: "镜像:已有完整包,跳过下载", progress: 100, received: have, total: expected });
415
425
  return dest;
416
426
  }
417
427
  } catch {}
418
- try { await curlDownload(R2_TARBALL, dest, { emit, phase: "image", label: "下载镜像" }); }
428
+ try { await curlDownload(src.url, dest, { emit, phase: "image", label: "下载镜像" }); }
419
429
  catch (e) {
420
430
  emit && emit({ phase: "image", status: "running", message: `curl 下载失败(${e.message}),改用内置下载…` });
421
- await ensureDownloaded(R2_TARBALL, dest, null, { emit, phase: "image", label: "下载镜像" });
431
+ await ensureDownloaded(src.url, dest, src.alt, { emit, phase: "image", label: "下载镜像" });
422
432
  }
423
433
  return dest;
424
434
  }
@@ -0,0 +1,55 @@
1
+ // Copyright 2026 CiCy AI
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ // Windows 上 Docker-版 cicy-code 跑在哪:WSL2(老机器)还是 QEMU(新机器,不再用 WSL)。
5
+ //
6
+ // 规则:2026-10-21 起新机器走 QEMU、端口 8009;2026-10-22 用户定「所有的机器全部用 QEMU」——
7
+ // 新判定一律 qemu(不再看机器上有没有 WSL)。已经落盘的判定不自动改:老机器的 wsl 由人逐台迁
8
+ // (停 WSL 容器 → 改 linux-backend.json → 重启 desktop),避免一升级就整队断服。
9
+ // 判定结果写进 ~/cicy-ai/db/linux-backend.json。CICY_LINUX_BACKEND=wsl|qemu 可强制覆盖。
10
+ //
11
+ // 必须在 main.js 最早处 init():sidecar-ipc 等模块在 require 时就读 CICY_DOCKER_APP_PORT。
12
+
13
+ const fs = require("fs");
14
+ const os = require("os");
15
+ const path = require("path");
16
+ const { execFileSync } = require("child_process");
17
+
18
+ const FILE = path.join(os.homedir(), "cicy-ai", "db", "linux-backend.json");
19
+ const DISTRO = process.env.CICY_WSL_DISTRO || "cicy-code-wsl";
20
+ const QEMU_PORT = 8009;
21
+ let _backend = null;
22
+
23
+ // 老机器的证据:WSL 注册表里有我们的发行版,或者之前算过的 docker 状态说装过。只读,不碰 wsl.exe。
24
+ function hasWslInstall() {
25
+ try {
26
+ execFileSync("reg", ["query", "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Lxss", "/s", "/f", DISTRO, "/d", "/e"], { windowsHide: true, timeout: 8000, stdio: "ignore" });
27
+ return true;
28
+ } catch {}
29
+ try {
30
+ const j = JSON.parse(fs.readFileSync(path.join(os.homedir(), "cicy-ai", "db", "docker-status.json"), "utf8"));
31
+ if (j && (j.installed || j.running) && Number(j.port || 8008) === 8008) return true;
32
+ } catch {}
33
+ return false;
34
+ }
35
+
36
+ function decide() {
37
+ if (process.platform !== "win32") return "native";
38
+ const forced = String(process.env.CICY_LINUX_BACKEND || "").toLowerCase();
39
+ if (forced === "wsl" || forced === "qemu") return forced;
40
+ try { const j = JSON.parse(fs.readFileSync(FILE, "utf8")); if (j && (j.backend === "wsl" || j.backend === "qemu")) return j.backend; } catch {}
41
+ const b = "qemu";
42
+ try { fs.mkdirSync(path.dirname(FILE), { recursive: true }); fs.writeFileSync(FILE, JSON.stringify({ backend: b, decidedAt: new Date().toISOString(), hadWsl: hasWslInstall() }, null, 2)); } catch {}
43
+ return b;
44
+ }
45
+
46
+ function init() {
47
+ if (_backend) return _backend;
48
+ _backend = decide();
49
+ if (_backend === "qemu" && !process.env.CICY_DOCKER_APP_PORT) process.env.CICY_DOCKER_APP_PORT = String(QEMU_PORT);
50
+ if (_backend === "qemu" && !process.env.CICY_SIDECAR_PORT) process.env.CICY_SIDECAR_PORT = process.env.CICY_DOCKER_APP_PORT;
51
+ return _backend;
52
+ }
53
+ function backend() { return _backend || init(); }
54
+
55
+ module.exports = { init, backend, QEMU_PORT, hasWslInstall };