cicy-desktop 2.1.78 → 2.1.80
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/bin/cicy-desktop +7 -7
- package/package.json +6 -6
- package/src/backends/homepage-preload.js +22 -0
- package/src/backends/homepage-react/assets/index-CKpaMBKz.css +1 -0
- package/src/backends/homepage-react/assets/index-CSsNZgC5.js +365 -0
- package/src/backends/homepage-react/index.html +2 -2
- package/src/backends/homepage-window.js +52 -7
- package/src/backends/ipc.js +57 -0
- package/src/backends/local-teams.js +73 -26
- package/src/backends/sidecar-ipc.js +11 -0
- package/src/backends/webview-preload.js +5 -3
- package/src/backends/window-manager.js +13 -3
- package/src/chrome/chrome-launcher.js +5 -4
- package/src/chrome/debugger-port-resolver.js +1 -1
- package/src/cloud/cloud-client.js +237 -41
- package/src/cluster/types.js +0 -5
- package/src/extension/inject.js +1 -1
- package/src/main.js +282 -88
- package/src/master/chrome-config.js +2 -2
- package/src/preload-rpc.js +1 -1
- package/src/profiles/profile-store.js +321 -0
- package/src/profiles/trusted-origins-store.js +95 -0
- package/src/server/worker-observability-routes.js +0 -2
- package/src/sidecar/cicy-code.js +84 -23
- package/src/sidecar/localbin.js +20 -3
- package/src/sidecar/native.js +3 -3
- package/src/sidecar/version.js +45 -0
- package/src/tabbrowser/newtab-protocol.js +54 -0
- package/src/tabbrowser/tab-browser.html +151 -0
- package/src/tabbrowser/tab-shell-preload.js +28 -0
- package/src/tabbrowser/tab-shell.html +227 -0
- package/src/tools/account-tools.js +191 -25
- package/src/tools/chrome-tools.js +173 -37
- package/src/tools/device-tools.js +25 -0
- package/src/tools/index.js +2 -0
- package/src/tools/tab-browser-tools.js +453 -0
- package/src/tools/window-tools.js +64 -7
- package/src/utils/brand-host-electron.js +25 -0
- package/src/utils/context-menu-options.js +80 -0
- package/src/utils/cookie-logins.js +58 -0
- package/src/utils/ip-probe.js +50 -0
- package/src/utils/rpc-audit.js +53 -0
- package/src/utils/rpc-guard.js +189 -0
- package/src/utils/window-monitor.js +5 -15
- package/src/utils/window-registry.js +210 -0
- package/src/utils/window-thumbnails.js +126 -0
- package/src/utils/window-utils.js +146 -109
- package/workers/render/package-lock.json +6 -6
- package/workers/render/src/App.css +36 -2
- package/workers/render/src/App.jsx +587 -103
- package/src/backends/artifact-ipc.js +0 -142
- package/src/backends/homepage-react/assets/index-DE9m6JTn.css +0 -1
- package/src/backends/homepage-react/assets/index-DLYMzgf5.js +0 -365
- package/src/cluster/artifact-registry.js +0 -61
|
@@ -29,9 +29,46 @@ const GATEWAY_URL = process.env.CICY_GATEWAY_URL || "https://gateway.cicy-ai.com
|
|
|
29
29
|
const GLOBAL_JSON = path.join(os.homedir(), "cicy-ai", "global.json");
|
|
30
30
|
|
|
31
31
|
// The two provider slots the gateway key must land in, per the contract.
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
// Full item templates (主人 spec): cicy-code needs the complete provider
|
|
33
|
+
// entries — protocol, model list, defaultModel — not just a bare key, or the
|
|
34
|
+
// CLIs can't pick a model. apiKey/url are filled in at injection time.
|
|
35
|
+
const GATEWAY_PROVIDER_TEMPLATES = {
|
|
36
|
+
defaultAnthropic: {
|
|
37
|
+
key: "defaultAnthropic",
|
|
38
|
+
name: "CiCyAi",
|
|
39
|
+
protocol: "anthropic",
|
|
40
|
+
defaultModel: "deepseek-v4-pro",
|
|
41
|
+
defaultModels: {},
|
|
42
|
+
modelMapping: {},
|
|
43
|
+
models: [
|
|
44
|
+
"claude-opus-4-8",
|
|
45
|
+
"claude-opus-4-7",
|
|
46
|
+
"claude-opus-4-6",
|
|
47
|
+
"claude-haiku-4-5-20251001",
|
|
48
|
+
"claude-sonnet-4-6",
|
|
49
|
+
"deepseek-v4-pro",
|
|
50
|
+
"deepseek-v4-flash",
|
|
51
|
+
],
|
|
52
|
+
statusLabel: "Opus 4.8 (1M context)",
|
|
53
|
+
},
|
|
54
|
+
defaultOpenAi: {
|
|
55
|
+
key: "defaultOpenAi",
|
|
56
|
+
name: "CiCyAi",
|
|
57
|
+
protocol: "openai",
|
|
58
|
+
defaultModel: "deepseek-v4-pro",
|
|
59
|
+
modelMapping: {},
|
|
60
|
+
models: ["deepseek-v4-pro", "deepseek-v4-flash", "gpt-5.5"],
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
// Which provider slot each CLI routes to (providers.default in global.json).
|
|
65
|
+
// Filled in only where missing so a user's own routing override survives.
|
|
66
|
+
const GATEWAY_DEFAULT_ROUTING = {
|
|
67
|
+
cicy: "defaultAnthropic",
|
|
68
|
+
claude: "defaultAnthropic",
|
|
69
|
+
codex: "defaultOpenAi",
|
|
70
|
+
opencode: "defaultOpenAi",
|
|
71
|
+
stt: "defaultOpenAi",
|
|
35
72
|
};
|
|
36
73
|
|
|
37
74
|
// ── token / identity ────────────────────────────────────────────────────────
|
|
@@ -47,13 +84,46 @@ function loginToken() {
|
|
|
47
84
|
}
|
|
48
85
|
}
|
|
49
86
|
|
|
50
|
-
//
|
|
51
|
-
//
|
|
52
|
-
//
|
|
87
|
+
// A STABLE machine-level id, derived from the OS hardware/install UUID so it
|
|
88
|
+
// survives a wipe of ~/cicy-ai (deviceId lived there before → every wipe minted
|
|
89
|
+
// a NEW device in the cloud = zombie devices). Hashed so we never ship the raw
|
|
90
|
+
// machine id, and normalized to UUID shape across platforms. "" if unreadable.
|
|
91
|
+
function machineStableId() {
|
|
92
|
+
try {
|
|
93
|
+
const cp = require("child_process");
|
|
94
|
+
const fs = require("fs");
|
|
95
|
+
let raw = "";
|
|
96
|
+
if (process.platform === "darwin") {
|
|
97
|
+
const out = cp.execSync("ioreg -rd1 -c IOPlatformExpertDevice", { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
|
|
98
|
+
const m = out.match(/IOPlatformUUID"\s*=\s*"([^"]+)"/);
|
|
99
|
+
raw = m ? m[1] : "";
|
|
100
|
+
} else if (process.platform === "win32") {
|
|
101
|
+
const out = cp.execSync('reg query "HKLM\\SOFTWARE\\Microsoft\\Cryptography" /v MachineGuid', { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
|
|
102
|
+
const m = out.match(/MachineGuid\s+REG_SZ\s+([0-9a-fA-F-]+)/);
|
|
103
|
+
raw = m ? m[1] : "";
|
|
104
|
+
} else {
|
|
105
|
+
for (const p of ["/etc/machine-id", "/var/lib/dbus/machine-id"]) {
|
|
106
|
+
try { raw = fs.readFileSync(p, "utf8").trim(); if (raw) break; } catch {}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
raw = (raw || "").trim();
|
|
110
|
+
if (!raw) return "";
|
|
111
|
+
const h = crypto.createHash("sha256").update("cicy-device:" + raw).digest("hex");
|
|
112
|
+
return `${h.slice(0, 8)}-${h.slice(8, 12)}-${h.slice(12, 16)}-${h.slice(16, 20)}-${h.slice(20, 32)}`;
|
|
113
|
+
} catch {
|
|
114
|
+
return "";
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Stable per-machine device id, persisted in global.json so the SAME machine
|
|
119
|
+
// keeps one identity across restarts. Derived from machineStableId() so a wipe
|
|
120
|
+
// of global.json RE-DERIVES the same id (no zombie devices); a random UUID is
|
|
121
|
+
// only a last-resort fallback when the OS machine id is unreadable. A win box
|
|
122
|
+
// and a mac box each get their own (reported `platform` makes that explicit).
|
|
53
123
|
function getDeviceId() {
|
|
54
124
|
const c = readGlobalConfig(GLOBAL_JSON);
|
|
55
125
|
if (c && typeof c.deviceId === "string" && c.deviceId) return c.deviceId;
|
|
56
|
-
const id = crypto.randomUUID();
|
|
126
|
+
const id = machineStableId() || crypto.randomUUID();
|
|
57
127
|
updateGlobalConfig(GLOBAL_JSON, (cfg) => {
|
|
58
128
|
if (!cfg.deviceId) cfg.deviceId = id;
|
|
59
129
|
return cfg;
|
|
@@ -62,29 +132,105 @@ function getDeviceId() {
|
|
|
62
132
|
return readGlobalConfig(GLOBAL_JSON).deviceId || id;
|
|
63
133
|
}
|
|
64
134
|
|
|
65
|
-
//
|
|
66
|
-
//
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
135
|
+
// Detect egress public IP + that IP's geo region. Runs in the Electron MAIN
|
|
136
|
+
// process where global `fetch` goes DIRECT — it does NOT use the Electron
|
|
137
|
+
// session proxy (主人令: 出口 IP 探测不能走 proxy). Each request has its own
|
|
138
|
+
// timeout via AbortController; never throws (returns empty/partial on failure).
|
|
139
|
+
async function detectIpGeo({ timeoutMs = 4000 } = {}) {
|
|
140
|
+
const tryFetch = async (url) => {
|
|
141
|
+
const ctrl = new AbortController();
|
|
142
|
+
const t = setTimeout(() => ctrl.abort(), timeoutMs);
|
|
73
143
|
try {
|
|
74
|
-
const ctrl = new AbortController();
|
|
75
|
-
const t = setTimeout(() => ctrl.abort(), timeoutMs);
|
|
76
144
|
const r = await fetch(url, { signal: ctrl.signal, cache: "no-store" });
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
const j = await r.json();
|
|
80
|
-
if (j && typeof j.ip === "string" && j.ip) return j.ip;
|
|
145
|
+
if (!r.ok) return null;
|
|
146
|
+
return await r.json();
|
|
81
147
|
} catch (_) {
|
|
82
|
-
|
|
148
|
+
return null;
|
|
149
|
+
} finally {
|
|
150
|
+
clearTimeout(t);
|
|
83
151
|
}
|
|
152
|
+
};
|
|
153
|
+
const empty = { country: "", region: "", city: "" };
|
|
154
|
+
// ipinfo.io (https, no token for ip/city/region/country)
|
|
155
|
+
let j = await tryFetch("https://ipinfo.io/json");
|
|
156
|
+
if (j && typeof j.ip === "string" && j.ip) {
|
|
157
|
+
return { publicIp: j.ip, ipRegion: { country: j.country || "", region: j.region || "", city: j.city || "" } };
|
|
158
|
+
}
|
|
159
|
+
// ip-api.com fallback ({ query, country, regionName, city })
|
|
160
|
+
j = await tryFetch("http://ip-api.com/json");
|
|
161
|
+
if (j && typeof j.query === "string" && j.query) {
|
|
162
|
+
return { publicIp: j.query, ipRegion: { country: j.country || "", region: j.regionName || "", city: j.city || "" } };
|
|
163
|
+
}
|
|
164
|
+
// ipify last resort (ip only)
|
|
165
|
+
j = await tryFetch("https://api.ipify.org?format=json");
|
|
166
|
+
if (j && typeof j.ip === "string" && j.ip) {
|
|
167
|
+
return { publicIp: j.ip, ipRegion: { ...empty } };
|
|
168
|
+
}
|
|
169
|
+
return { publicIp: "", ipRegion: { ...empty } };
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Back-compat thin wrapper — some callers only want the IP string.
|
|
173
|
+
async function getPublicIp(opts) {
|
|
174
|
+
return (await detectIpGeo(opts)).publicIp;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// The persisted deviceInfo block in global.json (single source of truth).
|
|
178
|
+
function readDeviceInfo() {
|
|
179
|
+
const c = readGlobalConfig(GLOBAL_JSON) || {};
|
|
180
|
+
return c.deviceInfo && typeof c.deviceInfo === "object" ? c.deviceInfo : {};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Flatten ipRegion ({country,region,city}) → "US / California / San Jose" for a
|
|
184
|
+
// clean, human-readable cloud device record (matches the SPA's representation).
|
|
185
|
+
// Pass-through if already a string.
|
|
186
|
+
function flattenIpRegion(r) {
|
|
187
|
+
if (!r) return "";
|
|
188
|
+
if (typeof r === "string") return r;
|
|
189
|
+
if (typeof r === "object") {
|
|
190
|
+
return [r.country, r.region, r.city].map((x) => String(x || "").trim()).filter(Boolean).join(" / ");
|
|
84
191
|
}
|
|
85
192
|
return "";
|
|
86
193
|
}
|
|
87
194
|
|
|
195
|
+
// Instant, no-network: what the get_device_info RPC + cloud report read.
|
|
196
|
+
function getDeviceInfo() {
|
|
197
|
+
const di = readDeviceInfo();
|
|
198
|
+
return {
|
|
199
|
+
deviceId: getDeviceId(),
|
|
200
|
+
publicIp: di.publicIp || "",
|
|
201
|
+
ipRegion: di.ipRegion && typeof di.ipRegion === "object" ? di.ipRegion : { country: "", region: "", city: "" },
|
|
202
|
+
systemLanguage: di.systemLanguage || "",
|
|
203
|
+
detectedAt: di.detectedAt || "",
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Detect (network, no proxy, timeout) + merge systemLanguage (passed from the
|
|
208
|
+
// main process via electronApp.getLocale()) + persist to global.json. Never
|
|
209
|
+
// throws. Returns the persisted deviceInfo (incl. deviceId).
|
|
210
|
+
async function detectAndPersistDeviceInfo({ systemLanguage = "", timeoutMs = 4000 } = {}) {
|
|
211
|
+
let geo = { publicIp: "", ipRegion: { country: "", region: "", city: "" } };
|
|
212
|
+
try {
|
|
213
|
+
geo = await detectIpGeo({ timeoutMs });
|
|
214
|
+
} catch (_) {
|
|
215
|
+
/* non-fatal */
|
|
216
|
+
}
|
|
217
|
+
try {
|
|
218
|
+
updateGlobalConfig(GLOBAL_JSON, (cfg) => {
|
|
219
|
+
const prev = cfg.deviceInfo && typeof cfg.deviceInfo === "object" ? cfg.deviceInfo : {};
|
|
220
|
+
cfg.deviceInfo = {
|
|
221
|
+
publicIp: geo.publicIp || prev.publicIp || "",
|
|
222
|
+
ipRegion: geo.publicIp ? geo.ipRegion : prev.ipRegion || { country: "", region: "", city: "" },
|
|
223
|
+
systemLanguage: String(systemLanguage || "") || prev.systemLanguage || "",
|
|
224
|
+
detectedAt: new Date().toISOString(),
|
|
225
|
+
};
|
|
226
|
+
return cfg;
|
|
227
|
+
});
|
|
228
|
+
} catch (e) {
|
|
229
|
+
log.warn(`[cloud] persist deviceInfo failed: ${e.message}`);
|
|
230
|
+
}
|
|
231
|
+
return getDeviceInfo();
|
|
232
|
+
}
|
|
233
|
+
|
|
88
234
|
// ── HTTP helper ─────────────────────────────────────────────────────────────
|
|
89
235
|
|
|
90
236
|
async function cloudFetch(endpoint, { method = "GET", body = null } = {}) {
|
|
@@ -120,13 +266,21 @@ async function registerDevice() {
|
|
|
120
266
|
const token = loginToken();
|
|
121
267
|
if (!token) return { ok: false, reason: "not_logged_in" };
|
|
122
268
|
const deviceId = getDeviceId();
|
|
123
|
-
|
|
269
|
+
// Prefer the persisted deviceInfo (written by the startup task, which also has
|
|
270
|
+
// the OS locale). If nothing detected yet, detect now (no syslang available here).
|
|
271
|
+
let di = readDeviceInfo();
|
|
272
|
+
if (!di || !di.publicIp) {
|
|
273
|
+
di = await detectAndPersistDeviceInfo({ systemLanguage: (di && di.systemLanguage) || "" });
|
|
274
|
+
}
|
|
124
275
|
const body = {
|
|
125
276
|
deviceId,
|
|
126
277
|
platform: process.platform, // "win32" | "darwin" | "linux"
|
|
127
278
|
arch: process.arch, // "x64" | "arm64"
|
|
128
279
|
};
|
|
129
|
-
if (publicIp) body.publicIp = publicIp;
|
|
280
|
+
if (di.publicIp) body.publicIp = di.publicIp;
|
|
281
|
+
const region = flattenIpRegion(di.ipRegion);
|
|
282
|
+
if (region) body.ipRegion = region;
|
|
283
|
+
if (di.systemLanguage) body.systemLanguage = di.systemLanguage;
|
|
130
284
|
const res = await cloudFetch("/api/device/register", { method: "POST", body });
|
|
131
285
|
if (res.ok) {
|
|
132
286
|
log.info(`[cloud] device registered deviceId=${deviceId} platform=${body.platform}/${body.arch}`);
|
|
@@ -142,13 +296,18 @@ async function registerDevice() {
|
|
|
142
296
|
// teamId omitted → cloud creates a new team + key.
|
|
143
297
|
// teamId given → cloud returns that team's existing key (no rotation).
|
|
144
298
|
// Returns { ok, teamId, apiKey, gatewayUrl } on success.
|
|
145
|
-
async function registerTeam({ teamId = null, title = "" } = {}) {
|
|
299
|
+
async function registerTeam({ teamId = null, title = "", titleVersion = 0 } = {}) {
|
|
146
300
|
const token = loginToken();
|
|
147
301
|
if (!token) return { ok: false, reason: "not_logged_in" };
|
|
148
302
|
const deviceId = getDeviceId();
|
|
149
303
|
const body = { deviceId };
|
|
150
304
|
if (teamId != null) body.teamId = teamId;
|
|
151
305
|
if (title) body.title = title;
|
|
306
|
+
// 服务端权威版本号(w-10032 契约,commit 06698533;旧 titleUpdatedAt 时间戳契约已废)。
|
|
307
|
+
// 带上「最后一次从云端看到的」版本作为 base(没有就 0)。服务端乐观并发裁决:title 有变
|
|
308
|
+
// 且 base>=云端当前版本 → 采用本端 title、版本=当前+1;否则忽略(base 落后=云端在我们
|
|
309
|
+
// 上次同步后改过名 → 云端赢);相同 title 不 bump。客户端不再用自己的时钟。
|
|
310
|
+
body.titleVersion = Number(titleVersion) || 0;
|
|
152
311
|
const res = await cloudFetch("/api/team/register", { method: "POST", body });
|
|
153
312
|
if (res.ok && res.json) {
|
|
154
313
|
return {
|
|
@@ -157,6 +316,8 @@ async function registerTeam({ teamId = null, title = "" } = {}) {
|
|
|
157
316
|
apiKey: res.json.apiKey,
|
|
158
317
|
gatewayUrl: res.json.gatewayUrl || GATEWAY_URL,
|
|
159
318
|
protocols: res.json.protocols || ["anthropic", "openai"],
|
|
319
|
+
title: res.json.title, // cloud's authoritative title
|
|
320
|
+
titleVersion: Number(res.json.titleVersion) || 0, // cloud's authoritative version
|
|
160
321
|
};
|
|
161
322
|
}
|
|
162
323
|
log.warn(`[cloud] team register failed status=${res.status} reason=${res.reason || ""}`);
|
|
@@ -182,30 +343,62 @@ async function listTeams({ deviceId = null, kind = null } = {}) {
|
|
|
182
343
|
|
|
183
344
|
// ── gateway-key injection ─────────────────────────────────────────────────────
|
|
184
345
|
|
|
185
|
-
// Write the per-team gateway apiKey + url into the team's global.json
|
|
186
|
-
// providers.
|
|
187
|
-
//
|
|
188
|
-
//
|
|
189
|
-
//
|
|
346
|
+
// Write the per-team gateway apiKey + url into the team's global.json:
|
|
347
|
+
// providers.default CLI routing (cicy/claude→anthropic slot, codex/opencode/
|
|
348
|
+
// stt→openai slot) plus full provider items keyed defaultAnthropic /
|
|
349
|
+
// defaultOpenAi (protocol, model list, defaultModel — cicy-code can't drive an
|
|
350
|
+
// LLM from a bare key). Existing entries keep user-tuned fields (model lists,
|
|
351
|
+
// routing overrides); only apiKey/url are forced and missing fields filled.
|
|
352
|
+
// No-ops (no write) when everything is already in place, so calling this on
|
|
353
|
+
// every launch is free. `globalJsonPath` defaults to the user-global config,
|
|
354
|
+
// which is also the local team's config home on this machine.
|
|
190
355
|
function injectGatewayKey(apiKey, gatewayUrl = GATEWAY_URL, globalJsonPath = GLOBAL_JSON) {
|
|
191
356
|
if (!apiKey) throw new Error("injectGatewayKey: apiKey required");
|
|
192
|
-
|
|
357
|
+
// Pre-check (updateGlobalConfig always rewrites the file): skip the write
|
|
358
|
+
// when routing + both items are already exactly in place.
|
|
359
|
+
try {
|
|
360
|
+
const cur = readGlobalConfig(globalJsonPath);
|
|
361
|
+
const p = cur.providers;
|
|
362
|
+
// stt must specifically be on the gateway slot (defaultOpenAi), overriding
|
|
363
|
+
// cicy-code's seeded "cloudflare-ai" — so require the exact value here, not
|
|
364
|
+
// just "any truthy routing", or the pre-check would short-circuit the fix.
|
|
365
|
+
const routed = p && p.default && Object.keys(GATEWAY_DEFAULT_ROUTING).every((cli) => p.default[cli]) && p.default.stt === "defaultOpenAi";
|
|
366
|
+
const itemsOk = p && Array.isArray(p.items) && Object.entries(GATEWAY_PROVIDER_TEMPLATES).every(([key, tpl]) => {
|
|
367
|
+
const it = p.items.find((x) => x && x.key === key);
|
|
368
|
+
return it && it.apiKey === apiKey && it.url === gatewayUrl && Object.keys(tpl).every((f) => it[f] !== undefined);
|
|
369
|
+
});
|
|
370
|
+
if (routed && itemsOk) return { changed: false, config: cur };
|
|
371
|
+
} catch {}
|
|
372
|
+
let changed = false;
|
|
373
|
+
const next = updateGlobalConfig(globalJsonPath, (cfg) => {
|
|
193
374
|
if (!cfg.providers || typeof cfg.providers !== "object") cfg.providers = {};
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
for (const [
|
|
197
|
-
|
|
375
|
+
const p = cfg.providers;
|
|
376
|
+
if (!p.default || typeof p.default !== "object") p.default = {};
|
|
377
|
+
for (const [cli, slot] of Object.entries(GATEWAY_DEFAULT_ROUTING)) {
|
|
378
|
+
if (!p.default[cli]) { p.default[cli] = slot; changed = true; }
|
|
379
|
+
}
|
|
380
|
+
// Force STT onto the gateway slot. cicy-code seeds default.stt="cloudflare-ai"
|
|
381
|
+
// (direct-to-Cloudflare, needs .cf.prod creds), which the only-if-absent loop
|
|
382
|
+
// above never overwrites. Route it through the gateway instead so STT is
|
|
383
|
+
// metered via the team key and doesn't depend on local CF credentials.
|
|
384
|
+
if (p.default.stt !== "defaultOpenAi") { p.default.stt = "defaultOpenAi"; changed = true; }
|
|
385
|
+
if (!Array.isArray(p.items)) p.items = [];
|
|
386
|
+
for (const [key, tpl] of Object.entries(GATEWAY_PROVIDER_TEMPLATES)) {
|
|
387
|
+
let item = p.items.find((it) => it && it.key === key);
|
|
198
388
|
if (!item) {
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
389
|
+
p.items.push({ ...tpl, url: gatewayUrl, apiKey });
|
|
390
|
+
changed = true;
|
|
391
|
+
continue;
|
|
392
|
+
}
|
|
393
|
+
if (item.apiKey !== apiKey) { item.apiKey = apiKey; changed = true; }
|
|
394
|
+
if (item.url !== gatewayUrl) { item.url = gatewayUrl; changed = true; }
|
|
395
|
+
for (const [f, v] of Object.entries(tpl)) {
|
|
396
|
+
if (item[f] === undefined) { item[f] = Array.isArray(v) ? [...v] : (v && typeof v === "object" ? { ...v } : v); changed = true; }
|
|
205
397
|
}
|
|
206
398
|
}
|
|
207
399
|
return cfg;
|
|
208
400
|
});
|
|
401
|
+
return { changed, config: next };
|
|
209
402
|
}
|
|
210
403
|
|
|
211
404
|
// Convenience: register a team and immediately wire its key into the local
|
|
@@ -231,6 +424,9 @@ module.exports = {
|
|
|
231
424
|
loginToken,
|
|
232
425
|
getDeviceId,
|
|
233
426
|
getPublicIp,
|
|
427
|
+
detectIpGeo,
|
|
428
|
+
getDeviceInfo,
|
|
429
|
+
detectAndPersistDeviceInfo,
|
|
234
430
|
registerDevice,
|
|
235
431
|
registerTeam,
|
|
236
432
|
listTeams,
|
package/src/cluster/types.js
CHANGED
|
@@ -18,14 +18,9 @@ function createRuntimeSessionId(workerId, partition, accountIdx) {
|
|
|
18
18
|
return `${workerId}:runtime:${partition || `account-${accountIdx}`}`;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
function createArtifactId(workerId, kind, localId) {
|
|
22
|
-
return `${workerId}:artifact:${kind}:${localId}`;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
21
|
module.exports = {
|
|
26
22
|
toIsoString,
|
|
27
23
|
createWindowRef,
|
|
28
24
|
createAgentId,
|
|
29
25
|
createRuntimeSessionId,
|
|
30
|
-
createArtifactId,
|
|
31
26
|
};
|
package/src/extension/inject.js
CHANGED
|
@@ -11,7 +11,7 @@ window._g = window._g || {};
|
|
|
11
11
|
// ========================================
|
|
12
12
|
try {
|
|
13
13
|
const { ipcRenderer } = require('electron');
|
|
14
|
-
window.electronRPC = (tool, args) => ipcRenderer.invoke('rpc', tool, args || {});
|
|
14
|
+
window.electronRPC = (tool, args) => ipcRenderer.invoke('rpc:guarded', tool, args || {});
|
|
15
15
|
window._g.rpc = window.electronRPC;
|
|
16
16
|
console.log('[RPC] electronRPC ready');
|
|
17
17
|
} catch(e) {
|