cicy-desktop 2.1.224 → 2.1.225

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.224",
3
+ "version": "2.1.225",
4
4
  "description": "CiCy - AI-powered operating system browser",
5
5
  "main": "src/main.js",
6
6
  "bin": {
@@ -472,7 +472,18 @@ async function syncNameToCloud(id) {
472
472
  try {
473
473
  if (!cc.loginToken || !cc.loginToken()) return; // not logged in
474
474
  const node = readNodes()[id];
475
- if (!node || !isLocalOrigin(node.base_url || "")) return;
475
+ if (!node) return;
476
+ // 自定义(远程 URL)团队:同步 {title, host_url} 到云端(kind=custom,复用 /api/teams)。
477
+ // docker 节点是 localhost(local-origin),不会进这;custom = 非 local-origin 的远程节点。
478
+ if (node.base_url && !isLocalOrigin(node.base_url)) {
479
+ const reg = await cc.registerCustomTeam({ teamId: node.cloud_team_id || null, title: node.name || "", hostUrl: node.base_url });
480
+ if (reg && reg.ok && reg.teamId && reg.teamId !== node.cloud_team_id) {
481
+ await writeNodes((nodes) => { if (nodes[id]) nodes[id].cloud_team_id = reg.teamId; return nodes; });
482
+ log.info(`[local-teams] custom team synced ${id} → cloud teamId=${reg.teamId}`);
483
+ }
484
+ return;
485
+ }
486
+ if (!isLocalOrigin(node.base_url || "")) return;
476
487
  // Docker 节点有自己独立的云端 team(POST /api/teams,sidecar ensureDockerTeam 管),
477
488
  // 绝不走这里的 device-register —— 否则会按本机 deviceId 复用回 8008 那个 team(40),
478
489
  // cloud_team_id 被覆盖、又和 8008 串名。按 is_docker 标记 OR 端口(docker app port)
@@ -530,6 +541,35 @@ async function syncNameToCloud(id) {
530
541
  } catch (e) { log.warn(`[local-teams] cloud title-sync ${id} failed: ${e.message}`); }
531
542
  }
532
543
 
544
+ // 从云端拉取 kind=custom 团队,本地没有的按 host_url materialize 一张卡片。**只增**(这版
545
+ // 删除不同步)。addTeam 内部按 base_url dedup,已有的不会重复建。best-effort。
546
+ async function pullCustomTeams() {
547
+ let cc;
548
+ try { cc = require("../cloud/cloud-client"); } catch { return; }
549
+ try {
550
+ if (!cc.loginToken || !cc.loginToken()) return;
551
+ const list = await cc.listTeams();
552
+ if (!list || !list.ok || !Array.isArray(list.teams)) return;
553
+ const nodes = readNodes();
554
+ const stripUrl = (u) => { let s = String(u || "").trim(); try { const x = new URL(s); x.search = ""; x.hash = ""; s = x.toString(); } catch {} return normaliseUrl(s.replace(/\/$/, "")); };
555
+ const have = new Set(Object.values(nodes).map((n) => stripUrl(n?.base_url)).filter(Boolean));
556
+ const haveCloudIds = new Set(Object.values(nodes).map((n) => n?.cloud_team_id).filter(Boolean));
557
+ let n = 0;
558
+ for (const t of list.teams) {
559
+ if (t.kind !== "custom") continue;
560
+ const host = String(t.host_url || t.hostUrl || "").trim();
561
+ if (!host) continue;
562
+ const tid = t.teamId || t.id || null;
563
+ if (have.has(stripUrl(host)) || (tid && haveCloudIds.has(tid))) continue; // 本地已有
564
+ try {
565
+ const r = await addTeam({ base_url: host, name: t.title || t.name || host, cloud_team_id: tid });
566
+ if (r && r.ok) { n++; log.info(`[local-teams] materialized custom team ${host} (cloud teamId=${tid})`); }
567
+ } catch (e) { log.warn(`[local-teams] materialize custom ${host} failed: ${e.message}`); }
568
+ }
569
+ if (n) log.info(`[local-teams] pulled ${n} custom team(s) from cloud`);
570
+ } catch (e) { log.warn(`[local-teams] pullCustomTeams failed: ${e.message}`); }
571
+ }
572
+
533
573
  // Sync EVERY existing local-origin team to cloud. Runs once at startup (after
534
574
  // login) so teams that were created BEFORE the cloud-client existed — or that
535
575
  // live on a freshly-deployed machine (e.g. a Windows box whose 本地团队 predates
@@ -541,11 +581,14 @@ async function syncAllLocalTeams() {
541
581
  const cc = require("../cloud/cloud-client");
542
582
  if (!cc.loginToken || !cc.loginToken()) return; // logged out → no-op
543
583
  const nodes = readNodes();
544
- const ids = Object.keys(nodes).filter((id) => isLocalOrigin(nodes[id]?.base_url || ""));
584
+ // 所有带 base_url 的节点都过一遍 —— syncNameToCloud 内部按类型分流:local-origin
585
+ // registerTeam,custom(远程 URL)→ registerCustomTeam,docker → 跳过。
586
+ const ids = Object.keys(nodes).filter((id) => !!(nodes[id]?.base_url));
545
587
  for (const id of ids) {
546
588
  try { await syncNameToCloud(id); } catch {}
547
589
  }
548
- if (ids.length) log.info(`[local-teams] startup cloud-sync of ${ids.length} local team(s)`);
590
+ if (ids.length) log.info(`[local-teams] startup cloud-sync of ${ids.length} team(s)`);
591
+ try { await pullCustomTeams(); } catch {}
549
592
  } catch (e) { log.warn(`[local-teams] startup cloud-sync failed: ${e.message}`); }
550
593
  }
551
594
 
@@ -343,6 +343,29 @@ async function listTeams({ deviceId = null, kind = null } = {}) {
343
343
  return { ok: false, status: res.status, reason: res.reason, teams: [] };
344
344
  }
345
345
 
346
+ // ── ③b custom teams(w-10122 v2.2.21+:复用 /api/teams,无新端点)──────────────
347
+ // 自定义(远程 URL)团队的云端同步 —— 就俩字段 {title, host_url}。首次 POST /api/teams
348
+ // {title,kind:custom,host_url}(cloud get-or-create by owner+host_url);已有 teamId 则
349
+ // PATCH 改名/换址。custom apiKey 恒为 ''(远程节点自带 key,不依赖)。
350
+ async function registerCustomTeam({ teamId = null, title = "", hostUrl = "" } = {}) {
351
+ const token = loginToken();
352
+ if (!token) return { ok: false, reason: "not_logged_in" };
353
+ if (!hostUrl) return { ok: false, reason: "no_host_url" };
354
+ if (teamId != null) {
355
+ const res = await cloudFetch(`/api/teams/${encodeURIComponent(teamId)}`, { method: "PATCH", body: { title: title || "", host_url: hostUrl } });
356
+ if (res.ok) { const t = (res.json && (res.json.team || res.json)) || {}; return { ok: true, teamId, name: t.title || t.name || title, hostUrl: t.host_url || hostUrl }; }
357
+ log.warn(`[cloud] custom team patch failed status=${res.status} reason=${res.reason || ""}`);
358
+ return { ok: false, status: res.status, reason: res.reason };
359
+ }
360
+ const res = await cloudFetch("/api/teams", { method: "POST", body: { title: title || "", kind: "custom", host_url: hostUrl } });
361
+ const t = res.ok && res.json ? (res.json.team || res.json) : null;
362
+ if (t && (t.teamId || t.id)) {
363
+ return { ok: true, teamId: t.teamId || t.id, name: t.title || t.name || title, hostUrl: t.host_url || hostUrl };
364
+ }
365
+ log.warn(`[cloud] custom team create failed status=${res.status} reason=${res.reason || ""}`);
366
+ return { ok: false, status: res.status, reason: res.reason, json: res.json };
367
+ }
368
+
346
369
  // 建一个全新的独立 team(POST /api/teams)——不按 device 复用,每次建一个新的。
347
370
  // Docker 用它:一机可以有多个 docker 容器,各自一个独立 team(8008 那个 local team
348
371
  // 不动)。返回 { teamId, apiKey(=team token,既网关 key 也凭证), title, kind }。
@@ -493,6 +516,7 @@ module.exports = {
493
516
  registerDevice,
494
517
  registerTeam,
495
518
  listTeams,
519
+ registerCustomTeam,
496
520
  createTeam,
497
521
  registerDockerTeam,
498
522
  renameTeam,