cicy-desktop 2.1.328 → 2.1.330

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cicy-desktop",
3
- "version": "2.1.328",
3
+ "version": "2.1.330",
4
4
  "description": "CiCy - AI-powered operating system browser",
5
5
  "main": "src/main.js",
6
6
  "bin": {
@@ -21,7 +21,7 @@ const os = require("os");
21
21
  const { readGlobalConfig, updateGlobalConfig } = require("./utils/global-json");
22
22
  const https = require("https");
23
23
  const log = require("electron-log");
24
- const { R2_RELEASES_BASE } = require("./sidecar/mirrors");
24
+ const { R2_RELEASES_BASE, buildUrlList } = require("./sidecar/mirrors");
25
25
 
26
26
  // ── 版本比较:>0 a 新于 b ─────────────────────────────────────────────────────
27
27
  function cmpVer(a, b) {
@@ -74,25 +74,76 @@ function headOk(url, redirects = 0) {
74
74
  }
75
75
 
76
76
  // ── 最新版本号 ────────────────────────────────────────────────────────────────
77
+ // R2 优先(win/mac/linux 各自指针,两条 CI 版本可能不同步,所以各读各的),GitHub 兜底。
78
+ //
79
+ // 原先这里是「一律读 OSS,彻底不碰 GitHub」,理由是仓库可能私有。代价是 R2 一旦不可达,
80
+ // 自更新就整条断掉 —— 而 fleet 里确实有一批 Windows 节点连 r2.deepfetch.de5.net 直接
81
+ // ECONNRESET(GitHub 反而 ~300ms 可达),它们因此永远停在旧版、只能人工推包。
82
+ // 现在 R2 仍是首选(仓库若再转私有,这条路不受影响),失败才回退 GitHub Release;
83
+ // 两边都不通才算真失败。
84
+ const GH_REPO = "cicy-ai/cicy-desktop";
85
+
86
+ function fetchLatestVersionFromGitHub() {
87
+ return new Promise((resolve, reject) => {
88
+ const req = https.get(
89
+ `https://api.github.com/repos/${GH_REPO}/releases/latest`,
90
+ { headers: { "User-Agent": "cicy-desktop-updater", Accept: "application/vnd.github+json" }, timeout: 10000 },
91
+ (res) => {
92
+ if (res.statusCode !== 200) { res.resume(); return reject(new Error(`GitHub HTTP ${res.statusCode}`)); }
93
+ let body = "";
94
+ res.setEncoding("utf8");
95
+ res.on("data", (d) => (body += d));
96
+ res.on("end", () => {
97
+ try {
98
+ const tag = String((JSON.parse(body) || {}).tag_name || "").replace(/^v/, "");
99
+ tag ? resolve(tag) : reject(new Error("no tag_name"));
100
+ } catch (e) { reject(e); }
101
+ });
102
+ },
103
+ );
104
+ req.on("error", reject);
105
+ req.on("timeout", () => { req.destroy(); reject(new Error("timeout")); });
106
+ });
107
+ }
108
+
77
109
  async function fetchLatestVersion() {
78
- // 版本清单**一律读 OSS**(win/mac/linux 各自指针),与网络判定无关 —— 彻底不碰 GitHub,
79
- // 这样 cicy-desktop 仓库私有也不影响更新检查。win/mac 是两条独立 CI、版本可能不同步,读错
80
- // 平台会 404,所以各读各的指针文件。
81
110
  const file = process.platform === "win32" ? "win-latest-version.txt"
82
111
  : process.platform === "darwin" ? "mac-latest-version.txt"
83
112
  : "linux-latest-version.txt";
84
- return (await getText(`${R2_RELEASES_BASE}/${file}`)).trim().replace(/^v/, "");
113
+ try {
114
+ return (await getText(`${R2_RELEASES_BASE}/${file}`)).trim().replace(/^v/, "");
115
+ } catch (e) {
116
+ log.warn(`[app-updater] R2 版本指针不可达(${e.message}),回退 GitHub Release`);
117
+ return await fetchLatestVersionFromGitHub();
118
+ }
85
119
  }
86
120
 
87
- // ── 安装包 URL(分平台 + 分网络)+ 本地文件名 ────────────────────────────────
121
+ // ── 安装包来源(有序候选)+ 本地文件名 ──────────────────────────────────────
122
+ // 返回 { file, urls } —— urls 是按优先级排好的候选:R2 → GitHub 直连 → ghproxy 镜像。
123
+ // 调用方依次尝试,第一个能下的算数(见 check() 的 HEAD 探测和 downloadUpdate 的重试)。
124
+ //
125
+ // 注意 Windows 两边**文件名不同**:R2 上是 cicy-desktop-<ver>.exe(CI 传的版本化副本),
126
+ // GitHub Release 里是 electron-builder 产出的 CiCy-Desktop-Setup-<ver>.exe。
127
+ // mac 的 pkg 和 linux 的 AppImage 两边同名。`file` 只是本地落盘名,保持原样不变。
88
128
  function assetFor(version) {
89
129
  const plat = process.platform;
90
130
  const arch = process.arch === "arm64" ? "arm64" : "x64";
91
- // 三端安装包**全部从 OSS 拉**,与网络无关 —— 不再拼 GitHub 下载 URL,仓库私有也能更新。
92
131
  const file = plat === "win32" ? `cicy-desktop-${version}.exe`
93
132
  : plat === "darwin" ? `cicy-desktop-${version}-${arch}.pkg`
94
133
  : `CiCy-Desktop-${version}.AppImage`;
95
- return { url: `${R2_RELEASES_BASE}/${file}`, file };
134
+ const ghName = plat === "win32" ? `CiCy-Desktop-Setup-${version}.exe` : file;
135
+ const ghUrl = `https://github.com/${GH_REPO}/releases/download/v${version}/${ghName}`;
136
+ // buildUrlList("global") = [直连, ...镜像];R2 排在最前面。
137
+ const urls = [`${R2_RELEASES_BASE}/${file}`, ...buildUrlList(ghUrl, "global")];
138
+ return { file, urls, url: urls[0] };
139
+ }
140
+
141
+ // 第一个 HEAD 得到 200/206 的候选;都不行返回 null。
142
+ async function firstReachable(urls) {
143
+ for (const u of urls) {
144
+ if (await headOk(u)) return u;
145
+ }
146
+ return null;
96
147
  }
97
148
 
98
149
  // ── 下载(带进度,跟随重定向)──────────────────────────────────────────────────
@@ -179,9 +230,10 @@ async function check() {
179
230
  if (latest && cmpVer(latest, current) > 0) {
180
231
  // 「先出包再更新版本」客户端保险:版本号涨了不代表包传完了。先 HEAD 确认本平台安装包
181
232
  // 真能下(200),否则当成「还没就绪」继续显已是最新,避免用户点下载 404。
182
- const { url } = assetFor(latest);
183
- const ready = await headOk(url);
233
+ const { urls } = assetFor(latest);
234
+ const ready = await firstReachable(urls);
184
235
  if (ready) {
236
+ log.info(`[app-updater] ${latest} 安装包就绪:${ready}`);
185
237
  broadcast({ status: "available", version: latest, current, progress: null, filePath: null, autoUpdate: getAutoUpdate(), auto: false });
186
238
  if (getAutoUpdate()) {
187
239
  log.info(`[app-updater] auto-update on → downloading ${latest} and installing without asking`);
@@ -189,7 +241,7 @@ async function check() {
189
241
  await downloadUpdate();
190
242
  if (_state.status === "ready") installNow();
191
243
  }
192
- } else { log.info(`[app-updater] ${latest} 版本号已更新但安装包未就位(HEAD 非 200):${url} — 暂不提示更新`); broadcast({ status: "up-to-date", version: current, current }); }
244
+ } else { log.info(`[app-updater] ${latest} 版本号已更新但所有来源都拿不到安装包(HEAD 非 200):${urls.join(" , ")} — 暂不提示更新`); broadcast({ status: "up-to-date", version: current, current }); }
193
245
  } else {
194
246
  broadcast({ status: "up-to-date", version: latest || current, current });
195
247
  }
@@ -208,16 +260,26 @@ async function downloadUpdate() {
208
260
  if (!version) { broadcast({ status: "error", error: "no version" }); return _state; }
209
261
  _downloading = true;
210
262
  try {
211
- const { url, file } = assetFor(version);
263
+ const { urls, file } = assetFor(version);
212
264
  // 下到 ~/Downloads(用户能直接找到安装包);目录不存在则建。
213
265
  const dir = app.getPath("downloads");
214
266
  try { fs.mkdirSync(dir, { recursive: true }); } catch {}
215
267
  const dest = path.join(dir, file);
216
- log.info(`[app-updater] downloading ${url} → ${dest}`);
217
268
  broadcast({ status: "downloading", progress: { percent: 0, transferred: 0, total: 0 }, filePath: null });
218
- await download(url, dest, (p) => broadcast({ status: "downloading", progress: p }));
219
- broadcast({ status: "ready", filePath: dest, progress: { percent: 100 } });
220
- return _state;
269
+ // 逐个候选试(R2 GitHub 镜像):一个源断了就换下一个,全断才算失败。
270
+ let lastErr = null;
271
+ for (const url of urls) {
272
+ try {
273
+ log.info(`[app-updater] downloading ${url} → ${dest}`);
274
+ await download(url, dest, (p) => broadcast({ status: "downloading", progress: p }));
275
+ broadcast({ status: "ready", filePath: dest, progress: { percent: 100 } });
276
+ return _state;
277
+ } catch (e) {
278
+ lastErr = e;
279
+ log.warn(`[app-updater] 源失败(${url}):${e.message}`);
280
+ }
281
+ }
282
+ throw lastErr || new Error("no source available");
221
283
  } catch (e) {
222
284
  log.warn("[app-updater] download failed:", e.message);
223
285
  broadcast({ status: "error", error: e.message });
@@ -316,6 +316,14 @@ contextBridge.exposeInMainWorld("cicy", {
316
316
  cloud: {
317
317
  fetch: (url, opts) => logInvoke("cloud:fetch", { url, ...(opts || {}) }),
318
318
  },
319
+ // "+ 面板" dropdown config — which panels the menu offers, in what order, under
320
+ // what name. get() returns every built-in with its current title/enabled state
321
+ // (so the settings UI can render the full list, not just the enabled ones);
322
+ // set() persists and returns the merged result.
323
+ panelMenu: {
324
+ get: () => logInvoke("panelMenu:get"),
325
+ set: (items) => logInvoke("panelMenu:set", items),
326
+ },
319
327
  // CiCy Hub — email sign-in, then every cicy-code instance of that account.
320
328
  hub: {
321
329
  status: () => logInvoke("hub:status"),