cicy-desktop 2.1.169 → 2.1.171

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.169",
3
+ "version": "2.1.171",
4
4
  "description": "CiCy - AI-powered operating system browser",
5
5
  "main": "src/main.js",
6
6
  "bin": {
@@ -63,47 +63,26 @@ function readDockerTeams() { try { return JSON.parse(fs.readFileSync(DOCKER_TEAM
63
63
  function writeDockerTeams(obj) { try { fs.mkdirSync(path.dirname(DOCKER_TEAMS_FILE), { recursive: true }); fs.writeFileSync(DOCKER_TEAMS_FILE, JSON.stringify(obj, null, 2)); } catch {} }
64
64
  let dockerTeamReg = null; // { teamId, title, apiKey } — 缓存,appOpts 读它
65
65
 
66
- // 确保这个 docker( volume)有独立云端 team;返回 { teamId, title, apiKey } 或 null。
66
+ // docker 团队的权威来源 = cloud(w-10122 #197):POST /api/team/docker/register
67
+ // (deviceId, port) get-or-create。幂等;失效/换设备/丢本机缓存都不会建重复 team;kind='docker';
68
+ // 软删后同 (device,port) 可重建;title 云端缺省 'Docker :<port>'(不回退 owner 名)。
69
+ // 本机 docker-teams.json 只当 cloud 不可达时的 offline fallback(缓存 teamId+key)。
70
+ // mac(colima)/ windows(WSL)同一套(这里平台无关,只有 appDocker 分平台)。
67
71
  async function ensureDockerTeam() {
68
- if (!cc || !cc.loginToken || !cc.loginToken()) return null; // 未登录 → 先不带 key,登录后重建容器再补(绝不借 8008)
72
+ if (!cc || !cc.loginToken || !cc.loginToken()) return null; // 未登录 → 先不带 key,登录后重建容器再补
73
+ const cachedFallback = () => {
74
+ try { const c = readDockerTeams()[APP_VOLUME]; if (c && c.apiKey) { dockerTeamReg = { teamId: c.teamId, title: c.title, apiKey: c.apiKey }; return dockerTeamReg; } } catch {}
75
+ return null;
76
+ };
69
77
  try {
70
- const store = readDockerTeams();
71
- let rec = store[APP_VOLUME];
72
- if (!rec || !rec.teamId) {
73
- const created = await cc.createTeam({ title: "Docker 团队", kind: "cloud" });
74
- if (!created || !created.ok) return null;
75
- // 强制把云端标题设成 "Docker 团队":POST /api/teams 常忽略我们传的 title、
76
- // 回退成 owner/device 名(= 8008 的标题),卡片就显示成 8008 的了。PATCH 一下盖掉。
77
- try { await cc.renameTeam(created.teamId, "Docker 团队"); } catch {}
78
- store[APP_VOLUME] = { teamId: created.teamId, title: "Docker 团队", titleForced: true };
79
- writeDockerTeams(store);
80
- dockerTeamReg = { teamId: created.teamId, title: "Docker 团队", apiKey: created.apiKey };
78
+ const r = await cc.registerDockerTeam({ port: APP_PORT });
79
+ if (r && r.ok && r.apiKey) {
80
+ dockerTeamReg = { teamId: r.teamId, title: r.title || `Docker :${APP_PORT}`, apiKey: r.apiKey };
81
+ try { const store = readDockerTeams(); store[APP_VOLUME] = { teamId: r.teamId, title: dockerTeamReg.title, apiKey: r.apiKey }; writeDockerTeams(store); } catch {}
81
82
  return dockerTeamReg;
82
83
  }
83
- // 既有 team:若还没强制过标题(老数据/上面那个 bug 建的),补一次 PATCH "Docker
84
- // 团队",然后打上 titleForced 标记 —— 只补这一次,之后用户自己改名不会被覆盖。
85
- if (!rec.titleForced) {
86
- try { await cc.renameTeam(rec.teamId, "Docker 团队"); } catch {}
87
- rec.title = "Docker 团队"; rec.titleForced = true;
88
- store[APP_VOLUME] = rec; writeDockerTeams(store);
89
- }
90
- // 「如果没有就 create,有就直接用」(主人)。本机记了 teamId,但云端拿不到它的 key
91
- // (team 被删 / 换了账号 / 设备重置 —— 等于实际上没有了)→ 视同没有,重新 create 一个
92
- // 存回本机。这就是手动「清 docker-teams.json + 重建」做的事,自动化掉。
93
- const apiKey = await cc.getTeamApiKey(rec.teamId);
94
- if (!apiKey) {
95
- const created = await cc.createTeam({ title: "Docker 团队", kind: "cloud" });
96
- if (created && created.ok) {
97
- try { await cc.renameTeam(created.teamId, "Docker 团队"); } catch {}
98
- store[APP_VOLUME] = { teamId: created.teamId, title: "Docker 团队", titleForced: true };
99
- writeDockerTeams(store);
100
- dockerTeamReg = { teamId: created.teamId, title: "Docker 团队", apiKey: created.apiKey };
101
- return dockerTeamReg;
102
- }
103
- }
104
- dockerTeamReg = { teamId: rec.teamId, title: rec.title, apiKey };
105
- return dockerTeamReg;
106
- } catch (e) { return null; }
84
+ return cachedFallback(); // cloud 返回异常 offline 兜底
85
+ } catch (e) { return cachedFallback(); } // cloud 不可达 offline 兜底
107
86
  }
108
87
 
109
88
  let registered = false;
@@ -146,6 +125,21 @@ function register({ sidecarLogPath } = {}) {
146
125
  log.info("[docker-daemon] installed but :8009 down → auto-starting (bootstrap idempotent)");
147
126
  try { await appDocker.bootstrap(await appOpts()); } catch (e) { log.warn(`[docker-daemon] auto-start failed: ${e.message}`); }
148
127
  await refreshDockerStatus();
128
+ } else if (s.running) {
129
+ // 自愈:容器在跑,但很可能是「首次启动时还没登录 / key 还没就位就建好了」的没 key 容器
130
+ // —— runContainer 见到 :8009 健康就直接 adopt 不重建,key 永远进不去(Windows 实测的
131
+ // 'llm key 没拿到')。这里:已能拿到 key + 容器里确实没 key → 带 key 重建一次(volume
132
+ // 数据保留)。mac/win 同一套(hasGatewayKey 两个 docker 模块都实现了)。
133
+ try {
134
+ const opts = await appOpts();
135
+ if (opts.env && opts.env.CICY_AI_GATEWAY_LLM_API_KEY && appDocker.hasGatewayKey
136
+ && !(await appDocker.hasGatewayKey(APP_CONTAINER))) {
137
+ log.info("[docker-daemon] 容器在跑但没网关 key → 带 key 重建(数据保留)");
138
+ await appDocker.recreate(opts);
139
+ try { await registerAppTeam(); } catch {}
140
+ await refreshDockerStatus();
141
+ }
142
+ } catch (e) { log.warn(`[docker-daemon] key self-heal failed: ${e.message}`); }
149
143
  }
150
144
  } finally { _dockerDaemonBusy = false; }
151
145
  return _dockerStatusCache;
@@ -358,6 +358,34 @@ async function createTeam({ title = "", kind = "cloud" } = {}) {
358
358
  return { ok: false, status: res.status, reason: res.reason };
359
359
  }
360
360
 
361
+ // Docker 团队的权威来源(w-10122 #197):POST /api/team/docker/register —— cloud 按
362
+ // (deviceId, port) get-or-create。一机一 port 一 team(8009/8010…),幂等:同 (device,port)
363
+ // 返同一 key;不同 port 不同 team;kind='docker';软删后同 (device,port) 可重建。title 传了
364
+ // 就用桌面的,没传云端缺省 'Docker :<port>'(不回退 owner 名)。替代 createTeam + 本机缓存。
365
+ async function registerDockerTeam({ port, title = "" } = {}) {
366
+ const token = loginToken();
367
+ if (!token) return { ok: false, reason: "not_logged_in" };
368
+ if (!port) return { ok: false, reason: "port_required" };
369
+ const body = { deviceId: getDeviceId(), port: Number(port) };
370
+ if (title) body.title = title;
371
+ const res = await cloudFetch("/api/team/docker/register", { method: "POST", body });
372
+ if (res.ok && res.json && (res.json.success || res.json.teamId)) {
373
+ return {
374
+ ok: true,
375
+ teamId: res.json.teamId,
376
+ apiKey: res.json.apiKey,
377
+ title: res.json.title,
378
+ titleVersion: Number(res.json.titleVersion) || 0,
379
+ kind: res.json.kind || "docker",
380
+ port: res.json.port,
381
+ gatewayUrl: res.json.gatewayUrl || GATEWAY_URL,
382
+ protocols: res.json.protocols || ["anthropic", "openai"],
383
+ };
384
+ }
385
+ log.warn(`[cloud] docker team register failed status=${res.status} reason=${res.reason || ""}`);
386
+ return { ok: false, status: res.status, reason: res.reason, json: res.json };
387
+ }
388
+
361
389
  // 改某 team 的标题(PATCH /api/teams/:id {title})——和 DockerCard 改名同一个端点。
362
390
  // 用途:建完 docker 独立 team 后**强制**把云端标题设成 "Docker 团队"(POST /api/teams
363
391
  // 不保证用我们传的 title,常回退成 owner/device 名 = 8008 的标题,卡片就显示错了)。
@@ -466,6 +494,7 @@ module.exports = {
466
494
  registerTeam,
467
495
  listTeams,
468
496
  createTeam,
497
+ registerDockerTeam,
469
498
  renameTeam,
470
499
  getTeamApiKey,
471
500
  injectGatewayKey,
@@ -446,7 +446,13 @@ async function upgrade({ onProgress, port = 8009, container = "cicy-code-docker-
446
446
  return await _bootstrap({ onProgress, port, container, volume, env });
447
447
  }
448
448
 
449
+ // 容器里有没有注入网关 key(reconcile 自愈用):printenv 看 CICY_AI_GATEWAY_LLM_API_KEY。
450
+ async function hasGatewayKey(container = "cicy-code-docker-8009") {
451
+ try { const { stdout } = await dk(`exec ${container} printenv CICY_AI_GATEWAY_LLM_API_KEY`, { timeout: 8000 }); return /sk-/.test(String(stdout || "")); }
452
+ catch { return false; }
453
+ }
454
+
449
455
  module.exports = {
450
456
  bootstrap, status, restart, stop, dockerRestart, recreate, update, upgrade, runContainer, readContainerToken,
451
- vmExists, colimaInstalled, dockerCliInstalled, engineUp, imagePresent, probeHealth,
457
+ vmExists, colimaInstalled, dockerCliInstalled, engineUp, imagePresent, probeHealth, hasGatewayKey,
452
458
  };
@@ -727,7 +727,13 @@ async function upgrade({ onProgress, port = 8009, container = "cicy-code-docker"
727
727
  return await _bootstrap({ onProgress, port, container, volume, env });
728
728
  }
729
729
 
730
+ // 容器里有没有注入网关 key(reconcile 自愈用):printenv 看 CICY_AI_GATEWAY_LLM_API_KEY。
731
+ async function hasGatewayKey(container = "cicy-code-docker") {
732
+ try { const { stdout } = await wslRun(`docker exec ${container} printenv CICY_AI_GATEWAY_LLM_API_KEY`, { timeout: 8000 }); return /sk-/.test(String(stdout || "")); }
733
+ catch { return false; }
734
+ }
735
+
730
736
  module.exports = {
731
737
  bootstrap, status, restart, stop, dockerRestart, recreate, update, upgrade, runContainer, readContainerToken,
732
- distroInstalled, dockerInstalled, dockerEngineUp, imagePresent, probeHealth, wslRun,
738
+ distroInstalled, dockerInstalled, dockerEngineUp, imagePresent, probeHealth, wslRun, hasGatewayKey,
733
739
  };