cicy-desktop 2.1.277 → 2.1.278

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.
@@ -68,10 +68,12 @@ jobs:
68
68
  shell: bash
69
69
  env:
70
70
  GH_TOKEN: ${{ github.token }}
71
+ PUBLISH_OWNER: ${{ github.repository_owner }}
72
+ PUBLISH_REPO: ${{ github.event.repository.name }}
71
73
  CICY_OBFUSCATE: "1" # 发版混淆 JS(prebuild 主进程 + Vite 渲染层),仅 release CI
72
74
  run: |
73
75
  set -euo pipefail
74
- npm run build:linux -- --x64 --config.linux.target=AppImage --publish always
76
+ npm run build:linux -- --x64 --config.linux.target=AppImage --config.publish.owner="$PUBLISH_OWNER" --config.publish.repo="$PUBLISH_REPO" --publish always
75
77
 
76
78
  # AppImage + 版本指针传到 OSS —— app-updater 现已全走 OSS(不碰 GitHub),linux 也要在
77
79
  # OSS 上有 versioned AppImage(CiCy-Desktop-<ver>.AppImage)和自己的指针 linux-latest-version.txt。
@@ -139,7 +139,7 @@ jobs:
139
139
  GH_TOKEN: ${{ github.token }}
140
140
  run: |
141
141
  gh release create "${{ steps.meta.outputs.tag }}" \
142
- --repo "cicy-ai/cicy-desktop" \
142
+ --repo "${{ github.repository }}" \
143
143
  --title "CiCy Desktop v${{ steps.meta.outputs.version }}" \
144
144
  --generate-notes \
145
145
  --draft \
@@ -183,7 +183,7 @@ jobs:
183
183
  pkgbuild --root "$ROOT" --component-plist "$PLIST" --scripts "$SCR" \
184
184
  --identifier com.cicy.desktop --version "$VER" --install-location / "$PKG"
185
185
  gh release upload "${{ steps.meta.outputs.tag }}" "$PKG" \
186
- --repo "cicy-ai/cicy-desktop" --clobber
186
+ --repo "${{ github.repository }}" --clobber
187
187
  echo "uploaded $PKG ($(du -h "$PKG" | cut -f1))"
188
188
  done
189
189
 
@@ -75,7 +75,7 @@ jobs:
75
75
  GH_TOKEN: ${{ github.token }}
76
76
  run: |
77
77
  gh release create "${{ steps.meta.outputs.tag }}" \
78
- --repo "cicy-ai/cicy-desktop" \
78
+ --repo "${{ github.repository }}" \
79
79
  --title "CiCy Desktop v${{ steps.meta.outputs.version }}" \
80
80
  --generate-notes \
81
81
  --draft \
@@ -86,14 +86,16 @@ jobs:
86
86
  shell: powershell
87
87
  env:
88
88
  GH_TOKEN: ${{ github.token }}
89
+ PUBLISH_OWNER: ${{ github.repository_owner }}
90
+ PUBLISH_REPO: ${{ github.event.repository.name }}
89
91
  CICY_OBFUSCATE: "1" # 发版混淆 JS(prebuild 主进程 + Vite 渲染层),仅 release CI
90
92
  run: |
91
93
  if (Test-Path dist) { Remove-Item -Recurse -Force dist }
92
94
  if (Test-Path "build\logo.ico") {
93
- npm run build:win -- --config.win.icon=build/logo.ico --publish always
95
+ npm run build:win -- --config.win.icon=build/logo.ico --config.publish.owner="$env:PUBLISH_OWNER" --config.publish.repo="$env:PUBLISH_REPO" --publish always
94
96
  } else {
95
97
  Write-Host "::warning::no icon, using default"
96
- npm run build:win -- --publish always
98
+ npm run build:win -- --config.publish.owner="$env:PUBLISH_OWNER" --config.publish.repo="$env:PUBLISH_REPO" --publish always
97
99
  }
98
100
 
99
101
  # 推 exe 到 OSS。三件事(不再是"永远一个版本"):
@@ -179,6 +181,6 @@ jobs:
179
181
  GH_TOKEN: ${{ github.token }}
180
182
  run: |
181
183
  gh release edit "${{ steps.meta.outputs.tag }}" \
182
- --repo "cicy-ai/cicy-desktop" \
184
+ --repo "${{ github.repository }}" \
183
185
  --draft=false \
184
186
  --latest
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cicy-desktop",
3
- "version": "2.1.277",
3
+ "version": "2.1.278",
4
4
  "description": "CiCy - AI-powered operating system browser",
5
5
  "main": "src/main.js",
6
6
  "bin": {
@@ -0,0 +1,8 @@
1
+ // Copyright 2026 CiCy AI
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ function shouldSkipCicyUpdate({ latest, current, platformReady }) {
5
+ return Boolean(latest && current && latest === current && platformReady);
6
+ }
7
+
8
+ module.exports = { shouldSkipCicyUpdate };
@@ -71,16 +71,46 @@ fi
71
71
  [ -n "$ver" ] || { log "could not resolve cicy-code@${want}"; exit 1; }
72
72
  dest="$RT/$ver"
73
73
 
74
- if [ -x "$dest/bin/cicy-code" ]; then
75
- log "v$ver already installed → repointing"
76
- else
74
+ case "$(uname -s):$(uname -m)" in
75
+ Linux:x86_64) platform_pkg="cicy-code-linux-x64" ;;
76
+ Linux:aarch64|Linux:arm64) platform_pkg="cicy-code-linux-arm64" ;;
77
+ *) log "unsupported platform: $(uname -s)-$(uname -m)"; exit 1 ;;
78
+ esac
79
+
80
+ platform_installed() {
81
+ [ -f "$dest/lib/node_modules/$platform_pkg/package.json" ] ||
82
+ [ -f "$dest/lib/node_modules/cicy-code/node_modules/$platform_pkg/package.json" ]
83
+ }
84
+
85
+ install_package() {
86
+ spec="$1"
87
+ if npm install -g "$spec" --prefix "$dest" --registry "$REG" \
88
+ --fetch-retries=2 --fetch-timeout=60000 --fetch-retry-maxtimeout=30000; then
89
+ return 0
90
+ fi
91
+ alt="$NPM_CN"; [ "$REG" = "$NPM_CN" ] && alt="$NPM_OFFICIAL"
92
+ log "install from $REG failed → falling back to $alt"
93
+ npm install -g "$spec" --prefix "$dest" --registry "$alt" \
94
+ --fetch-retries=2 --fetch-timeout=60000 --fetch-retry-maxtimeout=30000
95
+ REG="$alt"
96
+ }
97
+
98
+ if [ ! -x "$dest/bin/cicy-code" ]; then
77
99
  log "installing v$ver from $REG"
78
100
  rm -rf "$dest"
79
101
  mkdir -p "$dest"
80
- npm install -g "cicy-code@$ver" --prefix "$dest" --registry "$REG" \
81
- --fetch-retries=2 --fetch-timeout=60000 --fetch-retry-maxtimeout=30000
102
+ install_package "cicy-code@$ver"
82
103
  fi
83
104
 
105
+ if ! platform_installed; then
106
+ log "repairing missing $platform_pkg@$ver"
107
+ install_package "$platform_pkg@$ver"
108
+ fi
109
+
110
+ [ -x "$dest/bin/cicy-code" ] || { log "incomplete runtime: missing bin/cicy-code"; exit 1; }
111
+ platform_installed || { log "incomplete runtime: missing $platform_pkg@$ver"; exit 1; }
112
+ log "v$ver runtime complete → repointing"
113
+
84
114
  mkdir -p "$(dirname "$LINK")"
85
115
  ln -sfn "$dest/bin/cicy-code" "$LINK"
86
116
  # Keep the npm-global bin (first on PATH) following the canonical link too, so
@@ -21,6 +21,7 @@ const fs = require("fs");
21
21
  const docker = require("./docker"); // shared: downloads, waitUntil, probeHealth, launchElevated, ensureWsl…
22
22
  const log = require("electron-log"); // persisted main.log — bootstrap timing/failures land here
23
23
  const { t } = require("../i18n"); // 打开/读 token 的可见日志走 i18n
24
+ const { shouldSkipCicyUpdate } = require("./cicy-runtime-health");
24
25
 
25
26
  // Dedicated distro name — NEVER reuse/clobber a user's own "Ubuntu" distro.
26
27
  const DISTRO = process.env.CICY_WSL_DISTRO || "cicy-code-wsl";
@@ -985,13 +986,36 @@ async function _bootstrap({ onProgress, port = 8008, container = "cicy-code-dock
985
986
  done();
986
987
  } else done(true);
987
988
 
988
- // 7) Health — the ONLY path to ok:true.
989
+ // 7) Health — the ONLY path to ok:true. A short failed probe triggers the
990
+ // runtime updater once: it repairs the common partial-install state where the
991
+ // JS launcher exists but cicy-code-linux-<arch> is missing from the volume.
989
992
  begin("wait-health");
990
993
  emit({ phase: "container", status: "running", message: "等待 cicy-code 就绪…" });
991
- const healthy = await docker.waitUntil(() => probeHealth(port), { totalMs: 120000, everyMs: 3000 });
994
+ let healthy = await docker.waitUntil(() => probeHealth(port), { totalMs: 15000, everyMs: 3000 });
995
+ if (!healthy) {
996
+ emit({ phase: "container", status: "running", message: "服务尚未响应,正在检查并修复运行时完整性…" });
997
+ try {
998
+ const repaired = await update({ onProgress: emit, container, port });
999
+ healthy = Boolean(repaired && repaired.ok && await probeHealth(port));
1000
+ } catch (e) {
1001
+ log.warn(`[bootstrap] runtime auto-repair failed: ${e.message}`);
1002
+ }
1003
+ }
1004
+ if (!healthy) healthy = await docker.waitUntil(() => probeHealth(port), { totalMs: 105000, everyMs: 3000 });
992
1005
  if (healthy) { done(); await ensureAutostart(); await ensureDesktopShortcut(volume, port); } // survive reboot + desktop shortcut
993
1006
  else fail("health_timeout");
994
- emit({ phase: healthy ? "done" : "container", status: healthy ? "done" : "error", message: healthy ? "Docker cicy-code 已就绪 🎉" : `服务起来了但 :${port} 还没响应——稍等或点「重试」` });
1007
+ let healthMessage = "Docker cicy-code 已就绪 🎉";
1008
+ if (!healthy) {
1009
+ let tail = "";
1010
+ try {
1011
+ const r = await wslRun(`docker logs --tail 60 ${container}`, { timeout: 15000 });
1012
+ tail = String((r.stderr || "") + (r.stdout || "")).trim().slice(-1200);
1013
+ } catch (e) {
1014
+ tail = String((e && (e.stderr || e.stdout)) || "").trim().slice(-1200);
1015
+ }
1016
+ healthMessage = `服务未能响应 :${port}。${tail ? `\n\n容器日志:\n${tail}` : "请点「重试」。"}`;
1017
+ }
1018
+ emit({ phase: healthy ? "done" : "container", status: healthy ? "done" : "error", message: healthMessage });
995
1019
  finish(healthy, healthy ? null : "health_timeout");
996
1020
  return { ok: healthy, container };
997
1021
  }
@@ -1060,6 +1084,18 @@ async function installedCicyVersion(container) {
1060
1084
  } catch { return null; }
1061
1085
  }
1062
1086
 
1087
+ // True only when the launcher AND this Linux architecture's optional binary
1088
+ // package are present. The version pointer alone is insufficient: npm can leave
1089
+ // a partial global install that has bin/cicy-code but cannot start.
1090
+ async function cicyRuntimePlatformReady(container) {
1091
+ const check = `target=$(readlink -f "$HOME/.local/bin/cicy-code" 2>/dev/null || true); ` +
1092
+ `dest=$(dirname "$(dirname "$target")" 2>/dev/null); ` +
1093
+ `case "$(uname -m)" in x86_64) p=cicy-code-linux-x64;; aarch64|arm64) p=cicy-code-linux-arm64;; *) exit 1;; esac; ` +
1094
+ `[ -x "$target" ] && { [ -f "$dest/lib/node_modules/$p/package.json" ] || [ -f "$dest/lib/node_modules/cicy-code/node_modules/$p/package.json" ]; }`;
1095
+ try { await wslRun(`docker exec ${container} bash -lc '${check}'`, { timeout: 15000 }); return true; }
1096
+ catch { return false; }
1097
+ }
1098
+
1063
1099
  // Update cicy-code IN PLACE. SMART path (避免容器内 npm view 卡 ~2min):
1064
1100
  // 1. 在宿主机解析最新版本号(host 网络,不过容器代理/DNS)
1065
1101
  // 2. 和容器里已装版本比对——一样就直接「已是最新」,根本不 docker exec
@@ -1079,10 +1115,12 @@ async function update({ onProgress, container = "cicy-code-docker", port = 8008
1079
1115
  emit({ phase: "image", status: "running", message: t("docker.updating.checking") });
1080
1116
  let net = "unknown";
1081
1117
  try { net = await require("./net-detect").detect(); } catch {}
1082
- const [latest, current] = await Promise.all([resolveLatestCicy(net), installedCicyVersion(container)]);
1083
- log.info(`[wsl-docker] update: net=${net} latest=${latest} current=${current}`);
1118
+ const [latest, current, platformReady] = await Promise.all([
1119
+ resolveLatestCicy(net), installedCicyVersion(container), cicyRuntimePlatformReady(container),
1120
+ ]);
1121
+ log.info(`[wsl-docker] update: net=${net} latest=${latest} current=${current} platformReady=${platformReady}`);
1084
1122
  // 2) 已是最新 → 秒回,根本不进容器装(你点更新等 2 分钟的就是这种「其实已最新」的空跑)。
1085
- if (latest && current && latest === current) {
1123
+ if (shouldSkipCicyUpdate({ latest, current, platformReady })) {
1086
1124
  emit({ phase: "done", status: "done", message: t("docker.updating.alreadyLatest", { v: current }) });
1087
1125
  return { ok: true, alreadyLatest: true, version: current };
1088
1126
  }
@@ -0,0 +1,69 @@
1
+ const test = require("node:test");
2
+ const assert = require("node:assert/strict");
3
+ const fs = require("node:fs");
4
+ const os = require("node:os");
5
+ const path = require("node:path");
6
+ const { spawnSync } = require("node:child_process");
7
+
8
+ const script = path.join(__dirname, "..", "src", "sidecar", "container-scripts", "cicy-code-update.sh");
9
+
10
+ function writeExecutable(file, content) {
11
+ fs.writeFileSync(file, content, { mode: 0o755 });
12
+ }
13
+
14
+ function runUpdate({ complete = false } = {}) {
15
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), "cicy-code-update-"));
16
+ const home = path.join(root, "home");
17
+ const store = path.join(root, "store");
18
+ const bin = path.join(root, "bin");
19
+ const dest = path.join(store, "2.3.563");
20
+ fs.mkdirSync(path.join(dest, "bin"), { recursive: true });
21
+ fs.mkdirSync(bin, { recursive: true });
22
+ writeExecutable(path.join(dest, "bin", "cicy-code"), "#!/bin/sh\nexit 0\n");
23
+ if (complete) {
24
+ fs.mkdirSync(path.join(dest, "lib", "node_modules", "cicy-code-linux-x64"), { recursive: true });
25
+ fs.writeFileSync(path.join(dest, "lib", "node_modules", "cicy-code-linux-x64", "package.json"), "{}\n");
26
+ }
27
+ writeExecutable(path.join(bin, "supervisorctl"), "#!/bin/sh\nexit 1\n");
28
+ writeExecutable(path.join(bin, "npm"), `#!/bin/sh
29
+ set -eu
30
+ printf '%s\\n' "$*" >> "${root}/npm.log"
31
+ prefix=""
32
+ prev=""
33
+ for arg in "$@"; do
34
+ if [ "$prev" = "--prefix" ]; then prefix="$arg"; break; fi
35
+ prev="$arg"
36
+ done
37
+ [ -n "$prefix" ]
38
+ mkdir -p "$prefix/lib/node_modules/cicy-code-linux-x64"
39
+ printf '{}\\n' > "$prefix/lib/node_modules/cicy-code-linux-x64/package.json"
40
+ `);
41
+ const result = spawnSync("bash", [script, "2.3.563"], {
42
+ encoding: "utf8",
43
+ env: {
44
+ ...process.env,
45
+ HOME: home,
46
+ CICY_CODE_STORE: store,
47
+ NPM_REGISTRY: "https://registry.npmjs.org",
48
+ PATH: `${bin}:${process.env.PATH}`,
49
+ },
50
+ });
51
+ return {
52
+ ...result,
53
+ platformPackage: path.join(dest, "lib", "node_modules", "cicy-code-linux-x64", "package.json"),
54
+ npmLog: fs.existsSync(path.join(root, "npm.log")) ? fs.readFileSync(path.join(root, "npm.log"), "utf8") : "",
55
+ };
56
+ }
57
+
58
+ test("repairs an existing cicy-code runtime when its linux-x64 package is missing", () => {
59
+ const result = runUpdate();
60
+ assert.equal(result.status, 0, result.stderr);
61
+ assert.equal(fs.existsSync(result.platformPackage), true);
62
+ assert.match(result.npmLog, /cicy-code-linux-x64@2\.3\.563/);
63
+ });
64
+
65
+ test("does not reinstall an existing runtime when its platform package is present", () => {
66
+ const result = runUpdate({ complete: true });
67
+ assert.equal(result.status, 0, result.stderr);
68
+ assert.equal(result.npmLog, "");
69
+ });
@@ -0,0 +1,12 @@
1
+ const test = require("node:test");
2
+ const assert = require("node:assert/strict");
3
+
4
+ const { shouldSkipCicyUpdate } = require("../src/sidecar/cicy-runtime-health");
5
+
6
+ test("does not skip an update when the recorded version matches but the platform package is missing", () => {
7
+ assert.equal(shouldSkipCicyUpdate({ latest: "2.3.563", current: "2.3.563", platformReady: false }), false);
8
+ });
9
+
10
+ test("skips an update only when the recorded version matches and the runtime is complete", () => {
11
+ assert.equal(shouldSkipCicyUpdate({ latest: "2.3.563", current: "2.3.563", platformReady: true }), true);
12
+ });