cicy-desktop 2.1.165 → 2.1.166

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.
@@ -6,7 +6,7 @@
6
6
  <link rel="icon" type="image/svg+xml" href="./favicon.svg" />
7
7
  <link rel="icon" type="image/png" sizes="256x256" href="./favicon-256.png" />
8
8
  <title>CiCy Desktop</title>
9
- <script type="module" crossorigin src="./assets/index-SQ278w82.js"></script>
9
+ <script type="module" crossorigin src="./assets/index-C8HjxypR.js"></script>
10
10
  <link rel="stylesheet" crossorigin href="./assets/index-CGcN1St7.css">
11
11
  </head>
12
12
  <body>
@@ -224,7 +224,7 @@ async function openTeam(id, opts = {}) {
224
224
  if (!node) return { ok: false, error: "team not found" };
225
225
  const baseUrl = (node.base_url || "").replace(/\/$/, "");
226
226
  if (!baseUrl) return { ok: false, error: "no base_url" };
227
- // opts.token (a LIVE-read token, e.g. the :8008 container's own) takes
227
+ // opts.token (a LIVE-read token, e.g. the :8009 container's own) takes
228
228
  // precedence over any stored token — the Docker team stores none.
229
229
  const token = (opts && opts.token) || node.api_token || "";
230
230
  const url = token ? `${baseUrl}/?token=${encodeURIComponent(token)}` : baseUrl;
@@ -439,7 +439,7 @@ async function syncNameToCloud(id) {
439
439
  // cloud_team_id 被覆盖、又和 8008 串名。按 is_docker 标记 OR 端口(docker app port)
440
440
  // 跳过(端口判断不依赖标记的写入时机,更稳)。
441
441
  {
442
- const DOCKER_PORT = String(process.env.CICY_DOCKER_APP_PORT || 8008);
442
+ const DOCKER_PORT = String(process.env.CICY_DOCKER_APP_PORT || 8009);
443
443
  let isDockerNode = !!node.is_docker;
444
444
  try { if (new URL(node.base_url).port === DOCKER_PORT) isDockerNode = true; } catch {}
445
445
  if (isDockerNode) return;
@@ -524,10 +524,10 @@ async function addTeam(spec) {
524
524
  // pass it, leaving the swap URL with no `?token=` and stranding the user
525
525
  // at a login screen. Auto-fill from local global.json (top-level api_token)
526
526
  // so the common case "Just Works", even when spec.api_token is empty.
527
- // skipTokenAutofill: the :8008 Docker team must NEVER store a token — its token
528
- // is read LIVE from the container on every open (主人: teams.json 不存 8008
527
+ // skipTokenAutofill: the :8009 Docker team must NEVER store a token — its token
528
+ // is read LIVE from the container on every open (主人: teams.json 不存 8009
529
529
  // token / docker 的 token 是实时拿的). Without this guard the auto-fill below
530
- // back-fills the HOST 8008 token, which 8008 rejects → endless login screen.
530
+ // back-fills the HOST 8008 token, which 8009 rejects → endless login screen.
531
531
  if (!spec.api_token && !spec.skipTokenAutofill) {
532
532
  try {
533
533
  const host = new URL(baseUrl).hostname;
@@ -567,19 +567,19 @@ async function addTeam(spec) {
567
567
  // addTeam fires syncNameToCloud(id) below (fire-and-forget). If is_docker isn't
568
568
  // already on the node, that first sync sees a plain local team, device-registers
569
569
  // it, and the cloud hands back THIS DEVICE's shared team (= the 8008 team) — so
570
- // :8008 and :8008 end up on one cloud_team_id and renaming one renames both
570
+ // :8009 and :8008 end up on one cloud_team_id and renaming one renames both
571
571
  // ("串名"). Marking is_docker here (explicit spec OR by the docker app port, which
572
572
  // doesn't depend on the caller setting a flag) closes that window for ALL creation
573
573
  // paths (sidecar registerAppTeam, a cloud deeplink, a manual add). cloud_team_id
574
574
  // (the node's OWN independent team) is written the same atomic way when known.
575
- const DOCKER_PORT = String(process.env.CICY_DOCKER_APP_PORT || 8008);
575
+ const DOCKER_PORT = String(process.env.CICY_DOCKER_APP_PORT || 8009);
576
576
  const isDockerNode = !!spec.is_docker || (port != null && String(port) === DOCKER_PORT);
577
577
 
578
578
  const now = new Date().toISOString();
579
579
  const patch = {
580
580
  name: spec.name !== undefined ? String(spec.name || unnamedName()) : undefined,
581
581
  base_url: baseUrl,
582
- // skipTokenAutofill → force-clear any stored token (Docker :8008 reads live).
582
+ // skipTokenAutofill → force-clear any stored token (Docker :8009 reads live).
583
583
  api_token: spec.skipTokenAutofill ? "" : (spec.api_token !== undefined ? String(spec.api_token || "") : undefined),
584
584
  install_source: spec.install_source ?? undefined,
585
585
  install_os: spec.install_os ?? undefined,
@@ -33,16 +33,24 @@ const APP_DOCKER_SUPPORTED = process.platform === "win32" || process.platform ==
33
33
 
34
34
  const PORT = Number(process.env.CICY_CODE_PORT || 8008);
35
35
 
36
- // docker-only(主人: native 退役):cicy-code 只在 docker 容器里跑,:8008 就是它 ——
37
- // app 都跟 :8008 说话。darwin=Colima / win32=WSL2,数据在命名卷里隔离(不动用户文件)
38
- // homepage docker 卡负责首次安装(缺 colima/wsl 时引导装)+ 状态。一台机可跑多个
39
- // docker(不同端口),各自独立容器 + + 云端 team(teams.json 里各自的 node 条目)。
40
- const APP_PORT = Number(process.env.CICY_DOCKER_APP_PORT || 8008);
36
+ // Docker-版 cicy-code: a SECOND, optional instance that runs inside Docker on
37
+ // :8009 (its own container + volume), alongside the native local daemon on
38
+ // :8008. The homepage "Docker cicy-code" card owns its lifecycle; if Docker
39
+ // Desktop is missing the card installs it first (installer downloads to the
40
+ // user's Desktop). The whole cicy home is persisted to a named volume so the
41
+ // entire container state survives recreation (主人: "把整个 docker 挂出来").
42
+ const APP_PORT = Number(process.env.CICY_DOCKER_APP_PORT || 8009);
43
+ // container / volume 名都带上 port —— 一台机可以跑多个 docker(不同端口),各自
44
+ // 独立容器 + 独立 volume(数据隔离)+ 独立云端 team。docker-teams.json 也按 volume
45
+ // (含 port)区分。
41
46
  const APP_CONTAINER = process.env.CICY_DOCKER_APP_CONTAINER || `cicy-code-docker-${APP_PORT}`;
42
47
  const APP_VOLUME = process.env.CICY_DOCKER_APP_VOLUME || `cicy-team-${APP_PORT}`;
43
48
  const APP_MOUNT = process.env.CICY_DOCKER_APP_MOUNT || "/home/cicy";
44
- // 容器的 LLM 网关 key:优先 ensureDockerTeam 缓存的本 docker team apiKey,回退本机
45
- // global.json 里的 gateway key(readLocalGatewayKey),保证容器始终有 LLM key 可用。
49
+ // 8008 and 8009 are ONE team (主人), so :8009 reaches the LLM through the cicy
50
+ // gateway using 8008's TEAM key the `sk-cicy-…` apiKey already minted in 8008's
51
+ // global.json providers (NOT the api_token, which is only the local access
52
+ // credential). 8008 is up by the time the Docker card is used, so the key is
53
+ // ready — we just read it and pass it to the container. Same key ⇒ same billing.
46
54
  const GATEWAY_ENDPOINT = process.env.CICY_AI_GATEWAY_LLM_ENDPOINT || "https://gateway.cicy-ai.com";
47
55
  function readLocalGatewayKey() {
48
56
  try {
@@ -57,36 +65,45 @@ function readLocalGatewayKey() {
57
65
  } catch { return ""; }
58
66
  }
59
67
 
60
- // ── Docker 云端 team(统一到 teams.json)─────────────────────────────────────
61
- // docker-only(主人): native 退役,docker cicy-code 就是 :8008 本地团队。一机多 docker
62
- // = 不同端口 = teams.json 里不同 node 条目(127-0-0-1-<port>),各自 cloud_team_id。
63
- // 不再单独存 docker-teams.json —— teamId 统一落在 teams.json node.cloud_team_id
64
- // ( registerAppTeam addTeam cloud_team_id 写盘,和 node 同一次 writeNodes)。
65
- // 这里只:读已有 teamId / 没有就 POST /api/teams 建一个 + 取网关 key,缓存给 appOpts。
68
+ // ── Docker 独立云端 team ──────────────────────────────────────────────────────
69
+ // 主人令(2026-06-21):每个 Docker 容器要有自己独立的云端 team(8008 那个本机
70
+ // local team 不动)。云端是「一 deviceId local team」,装不下一机多 docker,所以
71
+ // docker POST /api/teams 建**独立 team**(不按 device)。首次建一次、把 teamId 存
72
+ // 本机(~/cicy-ai/db/docker-teams.json,按 volume 区分多个 docker);之后用 teamId
73
+ // token。容器用这个 team token 当网关 key(appOpts env);teamId DockerCard
74
+ // 账单 + 改名(PATCH /api/teams/:id,和私有云卡同一套)。
66
75
  const cc = (() => { try { return require("../cloud/cloud-client"); } catch { return null; } })();
67
- const TEAMS_JSON = path.join(os.homedir(), "cicy-ai", "db", "teams.json");
68
- function readTeamsJson() { try { return JSON.parse(fs.readFileSync(TEAMS_JSON, "utf8")) || {}; } catch { return {}; } }
76
+ const DOCKER_TEAMS_FILE = path.join(os.homedir(), "cicy-ai", "db", "docker-teams.json");
77
+ function readDockerTeams() { try { return JSON.parse(fs.readFileSync(DOCKER_TEAMS_FILE, "utf8")) || {}; } catch { return {}; } }
78
+ function writeDockerTeams(obj) { try { fs.mkdirSync(path.dirname(DOCKER_TEAMS_FILE), { recursive: true }); fs.writeFileSync(DOCKER_TEAMS_FILE, JSON.stringify(obj, null, 2)); } catch {} }
69
79
  let dockerTeamReg = null; // { teamId, title, apiKey } — 缓存,appOpts 读它
70
- const APP_TEAM_TITLE = "本地团队"; // 只有一个 cicy-code(:8008)= 本地团队
71
80
 
72
- // 确保本 docker(按 node = 127-0-0-1-<APP_PORT>,支持一机多 docker)有云端 team;返回
73
- // { teamId, title, apiKey } 或 null。READ-ONLY teams.json —— teamId 的落盘走
74
- // registerAppTeam 的 addTeam,避免和 local-teams 的 writeNodes 抢写。
81
+ // 确保这个 docker(按 volume)有独立云端 team;返回 { teamId, title, apiKey } 或 null。
75
82
  async function ensureDockerTeam() {
76
- if (!cc || !cc.loginToken || !cc.loginToken()) return null; // 未登录 → appOpts 回退本地 key
83
+ if (!cc || !cc.loginToken || !cc.loginToken()) return null; // 未登录 → appOpts 回退 8008 key
77
84
  try {
78
- const nodeKey = `127-0-0-1-${APP_PORT}`; // 本 docker 在 teams.json 的 node key
79
- const node = readTeamsJson()[nodeKey];
80
- const teamId = node && node.cloud_team_id;
81
- if (!teamId) {
82
- const created = await cc.createTeam({ title: APP_TEAM_TITLE, kind: "cloud" });
85
+ const store = readDockerTeams();
86
+ let rec = store[APP_VOLUME];
87
+ if (!rec || !rec.teamId) {
88
+ const created = await cc.createTeam({ title: "Docker 团队", kind: "cloud" });
83
89
  if (!created || !created.ok) return null;
84
- try { await cc.renameTeam(created.teamId, APP_TEAM_TITLE); } catch {} // 盖掉云端 owner/device 回退名
85
- dockerTeamReg = { teamId: created.teamId, title: APP_TEAM_TITLE, apiKey: created.apiKey };
86
- return dockerTeamReg; // teamId registerAppTeam.addTeam(cloud_team_id) 写进 teams.json
90
+ // 强制把云端标题设成 "Docker 团队":POST /api/teams 常忽略我们传的 title、
91
+ // 回退成 owner/device 名(= 8008 的标题),卡片就显示成 8008 的了。PATCH 一下盖掉。
92
+ try { await cc.renameTeam(created.teamId, "Docker 团队"); } catch {}
93
+ store[APP_VOLUME] = { teamId: created.teamId, title: "Docker 团队", titleForced: true };
94
+ writeDockerTeams(store);
95
+ dockerTeamReg = { teamId: created.teamId, title: "Docker 团队", apiKey: created.apiKey };
96
+ return dockerTeamReg;
87
97
  }
88
- const apiKey = await cc.getTeamApiKey(teamId);
89
- dockerTeamReg = { teamId, title: (node.name || APP_TEAM_TITLE), apiKey };
98
+ // 既有 team:若还没强制过标题(老数据/上面那个 bug 建的),补一次 PATCH 成 "Docker
99
+ // 团队",然后打上 titleForced 标记 —— 只补这一次,之后用户自己改名不会被覆盖。
100
+ if (!rec.titleForced) {
101
+ try { await cc.renameTeam(rec.teamId, "Docker 团队"); } catch {}
102
+ rec.title = "Docker 团队"; rec.titleForced = true;
103
+ store[APP_VOLUME] = rec; writeDockerTeams(store);
104
+ }
105
+ const apiKey = await cc.getTeamApiKey(rec.teamId);
106
+ dockerTeamReg = { teamId: rec.teamId, title: rec.title, apiKey };
90
107
  return dockerTeamReg;
91
108
  } catch (e) { return null; }
92
109
  }
@@ -120,29 +137,14 @@ function register({ sidecarLogPath } = {}) {
120
137
  _dockerDaemonBusy = true;
121
138
  try {
122
139
  const s = await refreshDockerStatus();
123
- // 没启动的给我启动: distro installed but :8008 not healthy (and WSL not unknown)
140
+ // 没启动的给我启动: distro installed but :8009 not healthy (and WSL not unknown)
124
141
  // → bring it up. bootstrap is idempotent: it skips done steps and just runs
125
142
  // startEngine + the container. Skip when not installed (would silently pull
126
143
  // the 444MB rootfs) or unknown (WSL not answering — let the next tick retry).
127
144
  if (s.installed && !s.running && !s.unknown) {
128
- log.info("[docker-daemon] installed but :8008 down → auto-starting (bootstrap idempotent)");
129
- try { await appDocker.bootstrap(await appOpts()); } catch (e) { log.warn(`[docker-daemon] auto-start failed: ${e.message}`); }
145
+ log.info("[docker-daemon] installed but :8009 down → auto-starting (bootstrap idempotent)");
146
+ try { await appDocker.bootstrap(appOpts()); } catch (e) { log.warn(`[docker-daemon] auto-start failed: ${e.message}`); }
130
147
  await refreshDockerStatus();
131
- } else if (s.running && cc && cc.loginToken && cc.loginToken()) {
132
- // Running but maybe KEYLESS: the container first came up at the 2s tick BEFORE login
133
- // restored, so it started without CICY_AI_GATEWAY_LLM_API_KEY; --restart unless-stopped
134
- // then revived it keyless forever (reconcile skips bootstrap while healthy → key never
135
- // re-injected → cicy-code can't reach the LLM). Once a key is resolvable, if the live
136
- // container lacks it, recreate WITH the key.
137
- try {
138
- const opts = await appOpts();
139
- if (opts.env.CICY_AI_GATEWAY_LLM_API_KEY && appDocker.hasGatewayKey &&
140
- !(await appDocker.hasGatewayKey(APP_CONTAINER))) {
141
- log.info("[docker-daemon] running container has no gateway key → recreating with key");
142
- await appDocker.recreate(opts);
143
- await refreshDockerStatus();
144
- }
145
- } catch (e) { log.warn(`[docker-daemon] key recheck failed: ${e.message}`); }
146
148
  }
147
149
  } finally { _dockerDaemonBusy = false; }
148
150
  return _dockerStatusCache;
@@ -209,8 +211,8 @@ function register({ sidecarLogPath } = {}) {
209
211
  }
210
212
  });
211
213
 
212
- // ---- Docker-版 cicy-code on :8008 — WSL2 + Ubuntu + Docker Engine (方案 A) ----
213
- // Card states (主人: 状态分清楚): running(:8008 healthy)→打开 / dockerRunning
214
+ // ---- Docker-版 cicy-code on :8009 — WSL2 + Ubuntu + Docker Engine (方案 A) ----
215
+ // Card states (主人: 状态分清楚): running(:8009 healthy)→打开 / dockerRunning
214
216
  // (engine up)→启动 / installed(Ubuntu present)→启动 Docker / else→下载安装.
215
217
  ipcMain.handle("docker:app-status", () => {
216
218
  // NON-BLOCKING: return what the background daemon already computed (memory →
@@ -232,37 +234,35 @@ function register({ sidecarLogPath } = {}) {
232
234
  return s;
233
235
  });
234
236
 
235
- // Common run options for the :8008 instance: its own container/volume + the
237
+ // Common run options for the :8009 instance: its own container/volume + the
236
238
  // LLM gateway env keyed by the 8008 team's token. (WSL: whole-home mount via
237
239
  // -v <volume>:/home/cicy inside wsl-docker.)
238
- const appOpts = async () => {
239
- // 先确保 docker team 的网关 key 拿到(ensureDockerTeam 填 dockerTeamReg)—— 必须在
240
- // 建容器前 await,否则容器在 2s 自启时 dockerTeamReg 还空 → 无 key 启动(就是那个
241
- // "里面 llm api key 没有")。未登录/未建成功时回退本地 key,保证容器仍有 LLM key。
242
- await ensureDockerTeam().catch(() => {});
240
+ const appOpts = () => {
241
+ // Docker 独立 team 的网关 key 优先(ensureDockerTeam 已缓存);未登录/未建成功
242
+ // 时回退 8008 key,保证容器仍有 LLM key 可用。
243
243
  const gwKey = (dockerTeamReg && dockerTeamReg.apiKey) || readLocalGatewayKey();
244
244
  const env = { CICY_AI_GATEWAY_LLM_ENDPOINT: GATEWAY_ENDPOINT };
245
245
  if (gwKey) env.CICY_AI_GATEWAY_LLM_API_KEY = gwKey;
246
246
  return { port: APP_PORT, container: APP_CONTAINER, volume: APP_VOLUME, env };
247
247
  };
248
- // Register the running :8008 instance as a (custom) team so the card's "打开"
248
+ // Register the running :8009 instance as a (custom) team so the card's "打开"
249
249
  // reuses the token-injected open/reload flow. addTeam dedups by host:port.
250
- // Upsert the :8008 team with the CONTAINER's OWN live token. Critical: never
250
+ // Upsert the :8009 team with the CONTAINER's OWN live token. Critical: never
251
251
  // fall back to the host 8008 token (addTeam auto-fills global.json on an empty
252
- // api_token — that's the host credential, which 8008 rejects → login screen).
252
+ // api_token — that's the host credential, which 8009 rejects → login screen).
253
253
  // Returns the team id, or {ok:false} when the container token can't be read.
254
- // Register the :8008 team WITHOUT a token. 主人: teams.json 不存 8008 的 token;
254
+ // Register the :8009 team WITHOUT a token. 主人: teams.json 不存 8009 的 token;
255
255
  // docker 的 token 是实时拿的. skipTokenAutofill stops addTeam from back-filling
256
- // the HOST 8008 token (the bug that made 8008 verify with 8008's token → login).
256
+ // the HOST 8008 token (the bug that made 8009 verify with 8008's token → login).
257
257
  const registerAppTeam = async () => {
258
258
  const lt = require("./local-teams");
259
259
  await ensureDockerTeam(); // 确保独立云端 team 存在 + 拿到 teamId/title
260
- const title = (dockerTeamReg && dockerTeamReg.title) || APP_TEAM_TITLE;
260
+ const title = (dockerTeamReg && dockerTeamReg.title) || "Docker 团队";
261
261
  // is_docker + cloud_team_id are passed INTO addTeam so they land in the SAME
262
262
  // writeNodes as the node — before addTeam's fire-and-forget syncNameToCloud
263
- // runs. That's what stops the freshly-created :8008 node from device-registering
263
+ // runs. That's what stops the freshly-created :8009 node from device-registering
264
264
  // into THIS device's shared (8008) team and 串名. (addTeam also self-detects
265
- // is_docker by the :8008 port, so this is belt-and-suspenders.)
265
+ // is_docker by the :8009 port, so this is belt-and-suspenders.)
266
266
  const r = await lt.addTeam({
267
267
  base_url: `http://127.0.0.1:${APP_PORT}`,
268
268
  name: title,
@@ -296,13 +296,14 @@ function register({ sidecarLogPath } = {}) {
296
296
  });
297
297
 
298
298
  // One-click bootstrap (方案 A): ensure WSL2 → Ubuntu → Docker Engine → load
299
- // image → start :8008 container → health. Streams phase/progress on
299
+ // image → start :8009 container → health. Streams phase/progress on
300
300
  // 'docker:app-progress'. Idempotent + resumable → the modal's 重试 just re-runs.
301
301
  ipcMain.handle("docker:app-bootstrap", async (e) => {
302
302
  if (!APP_DOCKER_SUPPORTED) return { ok: false, error: "Docker cicy-code 仅支持 Windows / macOS" };
303
303
  try {
304
+ await ensureDockerTeam(); // 启动前先确保独立云端 team + 拿到它的网关 key(appOpts 用)
304
305
  const result = await appDocker.bootstrap({
305
- ...(await appOpts()), // appOpts 内部已 await ensureDockerTeam 拿网关 key
306
+ ...appOpts(),
306
307
  onProgress: (ev) => { try { e.sender.send("docker:app-progress", ev); } catch {} },
307
308
  });
308
309
  if (result && result.ok) await registerAppTeam();
@@ -328,7 +329,8 @@ function register({ sidecarLogPath } = {}) {
328
329
  // volume 数据)。换 key 的唯一途径。渲染端已 confirm。
329
330
  ipcMain.handle("docker:app-recreate", async () => {
330
331
  try {
331
- await appDocker.recreate({ ...(await appOpts()) }); // appOpts 内部 await ensureDockerTeam
332
+ await ensureDockerTeam();
333
+ await appDocker.recreate({ ...appOpts() });
332
334
  await registerAppTeam();
333
335
  return { ok: true };
334
336
  } catch (e) { return { ok: false, error: e.message }; }
@@ -352,12 +354,13 @@ function register({ sidecarLogPath } = {}) {
352
354
  catch (e) { return { ok: false, error: e.message }; }
353
355
  });
354
356
 
355
- // ⋯ menu → 升级: re-pull the latest R2 image, re-create the :8008 container.
357
+ // ⋯ menu → 升级: re-pull the latest R2 image, re-create the :8009 container.
356
358
  ipcMain.handle("docker:app-upgrade", async (e) => {
357
359
  if (!APP_DOCKER_SUPPORTED) return { ok: false, error: "Docker cicy-code 仅支持 Windows / macOS" };
358
360
  try {
361
+ await ensureDockerTeam();
359
362
  const result = await appDocker.upgrade({
360
- ...(await appOpts()), // appOpts 内部 await ensureDockerTeam 拿网关 key
363
+ ...appOpts(),
361
364
  onProgress: (ev) => { try { e.sender.send("docker:app-progress", ev); } catch {} },
362
365
  });
363
366
  if (result && result.ok) await registerAppTeam();
@@ -126,7 +126,7 @@
126
126
  "title": "Docker cicy-code",
127
127
  "install": "Install",
128
128
  "start": "Start",
129
- "running": "Running · :8008",
129
+ "running": "Running · :8009",
130
130
  "notRunning": "Stopped · click Start",
131
131
  "notInstalled": "Not installed · click Install",
132
132
  "working": "Working…",
@@ -158,9 +158,7 @@
158
158
  "dockerRestart": "Restart Docker",
159
159
  "dockerRestarted": "Docker container restarted",
160
160
  "recreate": "Rebuild Docker",
161
- "billing": "Billing",
162
- "opsGroup": "Docker container",
163
- "openNoToken": "Service not ready yet — wait a few seconds and click Open again (or menu → Restart)."
161
+ "billing": "Billing"
164
162
  },
165
163
  "common": {
166
164
  "close": "Close",
@@ -125,7 +125,7 @@
125
125
  "title": "Docker cicy-code",
126
126
  "install": "Installer",
127
127
  "start": "Démarrer",
128
- "running": "En cours · :8008",
128
+ "running": "En cours · :8009",
129
129
  "notRunning": "Arrêté · cliquez sur Démarrer",
130
130
  "notInstalled": "Non installé · cliquez pour installer",
131
131
  "working": "En cours…",
@@ -157,9 +157,7 @@
157
157
  "dockerRestart": "Redémarrer Docker",
158
158
  "dockerRestarted": "Conteneur Docker redémarré",
159
159
  "recreate": "Reconstruire Docker",
160
- "billing": "Facturation",
161
- "opsGroup": "Conteneur Docker",
162
- "openNoToken": "Service pas encore prêt — patientez puis recliquez sur Ouvrir (ou menu → Redémarrer)."
160
+ "billing": "Facturation"
163
161
  },
164
162
  "common": {
165
163
  "close": "Fermer",
@@ -125,7 +125,7 @@
125
125
  "title": "Docker cicy-code",
126
126
  "install": "インストール",
127
127
  "start": "起動",
128
- "running": "実行中 · :8008",
128
+ "running": "実行中 · :8009",
129
129
  "notRunning": "停止中 · 「起動」をクリック",
130
130
  "notInstalled": "未インストール · クリックでインストール",
131
131
  "working": "処理中…",
@@ -157,9 +157,7 @@
157
157
  "dockerRestart": "Docker を再起動",
158
158
  "dockerRestarted": "Docker コンテナを再起動しました",
159
159
  "recreate": "Docker を再構築",
160
- "billing": "請求",
161
- "opsGroup": "Docker コンテナ",
162
- "openNoToken": "サービスがまだ準備できていません。数秒待ってから「開く」を再度クリック(またはメニュー→再起動)。"
160
+ "billing": "請求"
163
161
  },
164
162
  "common": {
165
163
  "close": "閉じる",
@@ -126,7 +126,7 @@
126
126
  "title": "Docker cicy-code",
127
127
  "install": "一键安装",
128
128
  "start": "启动",
129
- "running": "运行中 · :8008",
129
+ "running": "运行中 · :8009",
130
130
  "notRunning": "未启动 · 点「启动」",
131
131
  "notInstalled": "未安装 · 点安装",
132
132
  "working": "处理中…",
@@ -158,9 +158,7 @@
158
158
  "dockerRestart": "重启 Docker",
159
159
  "dockerRestarted": "已重启 Docker 容器",
160
160
  "recreate": "重建 Docker",
161
- "billing": "帐单",
162
- "opsGroup": "Docker 容器",
163
- "openNoToken": "服务还没就绪,稍等几秒再点「打开」(或用卡片菜单「重启」)。"
161
+ "billing": "帐单"
164
162
  },
165
163
  "common": {
166
164
  "close": "关闭",
package/src/main.js CHANGED
@@ -817,9 +817,6 @@ X-GNOME-Autostart-enabled=true
817
817
  let _sidecarWatchdogTimer = null;
818
818
  function startSidecarWatchdog({ intervalMs = 30_000 } = {}) {
819
819
  if (_sidecarWatchdogTimer) return;
820
- // 主人: native 退役,全平台只有 docker。:8008 由 docker daemon(sidecar-ipc reconcile)
821
- // + 容器 --restart 保活,没有 native 要 watchdog 去检查/重启。直接不跑。
822
- return;
823
820
  let consecutiveFailures = 0;
824
821
  let restartInFlight = false;
825
822
 
@@ -880,36 +877,9 @@ electronApp.whenReady().then(async () => {
880
877
  setupAppIcons();
881
878
  ensureDesktopLauncher();
882
879
  ensureAutoLaunch();
883
- // Start bundled cicy-code daemon as a sidecar. Reuses an existing
884
- // instance on :8008 if one is already running; no-op on Windows.
885
- cicyCodeSidecar
886
- .start({ logPath: path.join(os.homedir(), "logs", "cicy-code-sidecar.log") })
887
- .then((c) => { if (c) log.info(`[Sidecar] cicy-code spawned pid=${c.pid}`); })
888
- .catch((e) => log.warn(`[Sidecar] cicy-code start failed: ${e.message}`));
889
- startSidecarWatchdog();
890
-
891
- // Auto-register the local sidecar as 本地团队 once :8008 answers (主人:
892
- // "本地团队没有占位" — a fresh install must show its local team without any
893
- // manual step). addTeam upserts by host:port + auto-fills api_token from
894
- // global.json, so re-runs are no-ops; addTeam itself then triggers the
895
- // cloud team register + gateway-key injection when logged in. A fresh boot
896
- // may npm-seed the runtime first, so probe for up to ~90s before giving up.
897
- (async () => {
898
- const sidecarPort = Number(process.env.CICY_CODE_PORT || 8008);
899
- const lt = require("./backends/local-teams");
900
- for (let i = 0; i < 30; i++) {
901
- try {
902
- if (await cicyCodeSidecar.probeExisting(sidecarPort)) {
903
- const r = await lt.addTeam({ base_url: `http://127.0.0.1:${sidecarPort}`, name: "本地团队" });
904
- if (r && r.ok) log.info(`[Sidecar] local team ${r.upserted ? "refreshed" : "registered"} (${r.id})`);
905
- else log.warn(`[Sidecar] local team auto-register failed: ${r && r.error}`);
906
- return;
907
- }
908
- } catch (e) { log.warn(`[Sidecar] local team auto-register error: ${e.message}`); }
909
- await new Promise((res) => setTimeout(res, 3000));
910
- }
911
- log.warn(`[Sidecar] local team auto-register gave up — :${sidecarPort} never came up`);
912
- })();
880
+ // 主人: native :8008 退役 —— 不再起本机 cicy-code、不再 watchdog 保活、不再把它
881
+ // 自动注册成「本地团队」。cicy-code 只在 docker 容器里跑(Docker 卡,:8009 那套不变,
882
+ // 由 sidecar-ipc 自己管理)。cicyCodeSidecar 仍保留供 :8008 探活/版本查询用。
913
883
 
914
884
  // Backend launcher: app menu + IPC handlers. Menu adds a Backends top-level
915
885
  // entry; IPC powers the launcher window (src/backends/launcher.html).
@@ -100,8 +100,8 @@ async function startFromRuntime({ logPath, port }) {
100
100
  async function start({ logPath, port = DEFAULT_PORT, force = false, version = null } = {}) {
101
101
  if (child && !force) return child;
102
102
 
103
- // 主人: native 退役,全平台 docker-only。cicy-code 只在 docker 容器里跑(:8008,命名卷
104
- // 隔离,sidecar-ipc docker daemon 负责拉起 + 保活)。这里不再起任何 native。
103
+ // 主人: native :8008 退役 —— 不再在本机起 cicy-codedocker team(:8009 容器)不变,
104
+ // 用户用那张 Docker 卡。这里直接返回,native 一律不起。
105
105
  return null;
106
106
 
107
107
  if (!force && await probeExisting(port)) {
@@ -4,7 +4,7 @@
4
4
  // Docker 引擎」的方案,在 Mac 上就是 Colima:一个 Apache-2.0、纯 CLI、无 GUI、
5
5
  // 无授权、无 root 的轻量 Linux VM(基于 Lima),里面跑标准 Docker 引擎。Lima 会
6
6
  // 把 VM 内监听 127.0.0.1 的端口自动转发到宿主 127.0.0.1,所以 VM 里发布在
7
- // :8008 的容器,Mac 上 127.0.0.1:8008 直接可达——和 WSL2 的 localhost 转发等价。
7
+ // :8009 的容器,Mac 上 127.0.0.1:8009 直接可达——和 WSL2 的 localhost 转发等价。
8
8
  //
9
9
  // 与 wsl-docker.js 完全同接口(bootstrap/status/restart/stop/dockerRestart/
10
10
  // recreate/update/upgrade/runContainer/readContainerToken),由 sidecar-ipc.js
@@ -220,7 +220,7 @@ const probeHealth = docker.probeHealth;
220
220
  // • Apple Silicon 加 --platform linux/amd64(rosetta 跑 x86 容器);
221
221
  // • /home/cicy 必须用 docker **named volume**,不能用 host bind-mount!真机实测:
222
222
  // bind-mount 会用空的宿主目录**遮住镜像里预装的 /home/cicy**(cicy-code 装在那),
223
- // entrypoint 找不到就试图全局 npm 重装 → EACCES 崩溃,:8008 起不来。named volume
223
+ // entrypoint 找不到就试图全局 npm 重装 → EACCES 崩溃,:8009 起不来。named volume
224
224
  // 首次挂载会**从镜像内容预填充**,容器才看得到预装的 cicy-code(和 WSL 一致)。
225
225
  // Shared folder bind: the CURRENT macOS user's ~/Desktop/Share ↔
226
226
  // /home/cicy/cicy-ai/Share in the container. Auto-created (with a readme) on every
@@ -255,7 +255,7 @@ function shareMountArg() {
255
255
  } catch { return ""; }
256
256
  }
257
257
 
258
- async function runContainer({ port = 8008, container = "cicy-code-docker-8008", volume = "cicy-team-8008", env = {} } = {}) {
258
+ async function runContainer({ port = 8009, container = "cicy-code-docker-8009", volume = "cicy-team-8009", env = {} } = {}) {
259
259
  if (await probeHealth(port)) return { adopted: true };
260
260
  try { await dk(`rm -f ${container}`, { timeout: 20000 }); } catch {} // 替换同名残留容器
261
261
  const envArgs = Object.entries(env || {})
@@ -273,7 +273,7 @@ async function runContainer({ port = 8008, container = "cicy-code-docker-8008",
273
273
  // 该卷的 _data/cicy-ai/global.json(最稳,避开 busy 容器的慢 exec);② 退回 docker
274
274
  // exec。重试到 entrypoint 把 global.json 写出来为止;真读不到返回 ""(调用方不得拿
275
275
  // 错/宿主 token 去开,会卡登录)。
276
- async function readContainerToken(port = 8008, container = "cicy-code-docker-8008", volume = "cicy-team-8008") {
276
+ async function readContainerToken(port = 8009, container = "cicy-code-docker-8009", volume = "cicy-team-8009") {
277
277
  for (let attempt = 1; attempt <= 5; attempt++) {
278
278
  try { const { stdout } = await sh(`colima ssh -p ${PROFILE} -- sudo cat /var/lib/docker/volumes/${volume}/_data/cicy-ai/global.json 2>/dev/null`, { timeout: 10000 });
279
279
  const m = String(stdout).match(/"api_token"\s*:\s*"(cicy_[A-Za-z0-9]+)"/);
@@ -324,7 +324,7 @@ async function ensureDesktopShortcut(_volume, _port) { /* no-op on darwin: named
324
324
  // 与 wsl status 同形 { wsl, distro, engineUp, running },供 sidecar-ipc 复用:
325
325
  // wsl → 平台/依赖就绪(brew+colima+docker CLI 都在)
326
326
  // distro → VM(profile)已创建
327
- async function status(port = 8008) {
327
+ async function status(port = 8009) {
328
328
  const deps = (await colimaInstalled()) && (await dockerCliInstalled());
329
329
  const vm = deps && (await vmExists());
330
330
  const up = vm && (await engineUp());
@@ -344,7 +344,7 @@ async function bootstrap(opts = {}) {
344
344
  return _bootstrapInFlight;
345
345
  }
346
346
 
347
- async function _bootstrap({ onProgress, port = 8008, container = "cicy-code-docker-8008", volume = "cicy-team-8008", env = {} } = {}) {
347
+ async function _bootstrap({ onProgress, port = 8009, container = "cicy-code-docker-8009", volume = "cicy-team-8009", env = {} } = {}) {
348
348
  const emit = (ev) => { try { onProgress && onProgress(ev); } catch {} };
349
349
 
350
350
  // 0) 快路径:已健康 → 秒返回(幂等)。
@@ -396,7 +396,7 @@ async function _bootstrap({ onProgress, port = 8008, container = "cicy-code-dock
396
396
  // ---- 生命周期(卡片 ⋯ 菜单)--------------------------------------------
397
397
 
398
398
  // 仅重启容器内的 cicy-code 进程(supervisor),cron/sshd 等不动;退回整容器重启。
399
- async function restart({ container = "cicy-code-docker-8008", port = 8008, volume = "cicy-team-8008" } = {}) {
399
+ async function restart({ container = "cicy-code-docker-8009", port = 8009, volume = "cicy-team-8009" } = {}) {
400
400
  await startVM({});
401
401
  try { await dk(`exec ${container} supervisorctl -c /etc/supervisor/supervisord.conf restart cicy-code`, { timeout: 30000 }); }
402
402
  catch { try { await dk(`restart ${container}`, { timeout: 60000 }); } catch {} }
@@ -406,36 +406,32 @@ async function restart({ container = "cicy-code-docker-8008", port = 8008, volum
406
406
  }
407
407
 
408
408
  // 原地更新 cicy-code(镜像内的 cicy-code-update.sh,不重建容器)。
409
- async function update({ onProgress, container = "cicy-code-docker-8008", port = 8008 } = {}) {
409
+ async function update({ onProgress, container = "cicy-code-docker-8009", port = 8009 } = {}) {
410
410
  const emit = (ev) => { try { onProgress && onProgress(ev); } catch {} };
411
411
  await startVM({});
412
- emit({ phase: "image", status: "running", message: "更新 cicy-code(npm 拉取最新版)…" });
413
- // 容器里的 cicy-code 是 npm 全局装的(~/.npm-global/bin/cicy-code),没有 update 脚本。
414
- // 直接 npm 装最新版(CN 走 npmmirror),再 docker restart 让容器以新版本重起 cicy-code。
412
+ emit({ phase: "image", status: "running", message: "更新 cicy-code(拉取最新版)…" });
415
413
  try {
416
- await shStream(`docker --context ${CTX} exec ${container} bash -lc "npm install -g cicy-code@latest --registry https://registry.npmmirror.com"`,
414
+ await shStream(`docker --context ${CTX} exec ${container} bash -lc "command -v cicy-code-update.sh >/dev/null && cicy-code-update.sh || /usr/local/bin/cicy-code-update.sh"`,
417
415
  { emit, phase: "image", timeout: 300000 });
418
- emit({ phase: "container", status: "running", message: "重启容器以应用新版本…" });
419
- await dk(`restart ${container}`, { timeout: 60000 });
420
416
  } catch (e) { emit({ phase: "done", status: "error", message: `更新失败:${e.message}(试试「升级」重装)` }); return { ok: false, reason: "update_failed" }; }
421
417
  const healthy = await docker.waitUntil(() => probeHealth(port), { totalMs: 120000, everyMs: 3000 });
422
- emit({ phase: "done", status: healthy ? "done" : "error", message: healthy ? "cicy-code 已更新到最新 🎉" : "更新了但 :8008 还没响应——稍等或点重试" });
418
+ emit({ phase: "done", status: healthy ? "done" : "error", message: healthy ? "cicy-code 已更新到最新 🎉" : "更新了但 :8009 还没响应——稍等或点重试" });
423
419
  return { ok: healthy };
424
420
  }
425
421
 
426
- async function stop({ container = "cicy-code-docker-8008" } = {}) {
422
+ async function stop({ container = "cicy-code-docker-8009" } = {}) {
427
423
  try { await dk(`stop ${container}`, { timeout: 30000 }); } catch {}
428
424
  }
429
425
 
430
426
  // docker restart 整个容器(entrypoint 重跑、重读 volume global.json)。
431
- async function dockerRestart({ container = "cicy-code-docker-8008" } = {}) {
427
+ async function dockerRestart({ container = "cicy-code-docker-8009" } = {}) {
432
428
  await dk(`restart ${container}`, { timeout: 45000 });
433
429
  return true;
434
430
  }
435
431
 
436
432
  // 重建:强删占该端口的任何容器 + 目标容器,再 docker run(用新 env,如新 docker team
437
433
  // 网关 key)。保留 bind-mount 宿主目录(数据/api_token 不丢)。破坏性 → 调用方要 confirm。
438
- async function recreate({ port = 8008, container = "cicy-code-docker-8008", volume = "cicy-team-8008", env = {} } = {}) {
434
+ async function recreate({ port = 8009, container = "cicy-code-docker-8009", volume = "cicy-team-8009", env = {} } = {}) {
439
435
  try { await dk(`ps -aq --filter publish=${port} | xargs -r docker --context ${CTX} rm -f 2>/dev/null; docker --context ${CTX} rm -f ${container} 2>/dev/null; true`, { timeout: 30000 }); } catch {}
440
436
  const r = await runContainer({ port, container, volume, env });
441
437
  try { await ensureDesktopShortcut(volume, port); } catch {}
@@ -443,21 +439,14 @@ async function recreate({ port = 8008, container = "cicy-code-docker-8008", volu
443
439
  }
444
440
 
445
441
  // 升级 = 删 VM 重装(重置;cicy-team 数据随之重置,实例重新 seed 出新 token)。
446
- async function upgrade({ onProgress, port = 8008, container = "cicy-code-docker-8008", volume = "cicy-team-8008", env = {} } = {}) {
442
+ async function upgrade({ onProgress, port = 8009, container = "cicy-code-docker-8009", volume = "cicy-team-8009", env = {} } = {}) {
447
443
  const emit = (ev) => { try { onProgress && onProgress(ev); } catch {} };
448
444
  emit({ phase: "install-docker", status: "running", message: "升级 = 重装运行环境(会重置容器数据)…" });
449
445
  try { await sh(`colima delete -f -p ${PROFILE} 2>/dev/null; true`, { timeout: 120000 }); } catch {}
450
446
  return await _bootstrap({ onProgress, port, container, volume, env });
451
447
  }
452
448
 
453
- // 容器 env 里有没有网关 LLM key —— daemon 用它判断运行中的容器是否需要"带 key 重建"
454
- // (容器可能在登录前就先无 key 起来了)。key 未设时 printenv 非零退出 → catch → false。
455
- async function hasGatewayKey(container = "cicy-code-docker-8008") {
456
- try { const { stdout } = await dk(`exec ${container} printenv CICY_AI_GATEWAY_LLM_API_KEY`, { timeout: 8000 }); return /sk-/.test(String(stdout || "")); }
457
- catch { return false; }
458
- }
459
-
460
449
  module.exports = {
461
450
  bootstrap, status, restart, stop, dockerRestart, recreate, update, upgrade, runContainer, readContainerToken,
462
- vmExists, colimaInstalled, dockerCliInstalled, engineUp, imagePresent, probeHealth, hasGatewayKey,
451
+ vmExists, colimaInstalled, dockerCliInstalled, engineUp, imagePresent, probeHealth,
463
452
  };
@@ -441,7 +441,7 @@ function desktopDir() {
441
441
  // Start the container. Returns a sidecar child token { docker:true, container,
442
442
  // id } or null when Docker isn't ready (homepage guides the user to install
443
443
  // Docker Desktop). `container`/`volume` are parameterized so a SECOND instance
444
- // (the Docker-版 cicy-code on :8008) can run alongside the native local one
444
+ // (the Docker-版 cicy-code on :8009) can run alongside the native local one
445
445
  // without a name/volume collision.
446
446
  async function start({ port = 8008, container = CONTAINER, volume = VOLUME, mountTarget = "/home/cicy/cicy-ai", env = {} } = {}) {
447
447
  // Something already serves a healthy cicy-code on :port (a legacy-named