deskbuddy 0.2.0-beta.6 → 0.2.0-beta.8
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/dist/cli.js +1673 -819
- package/dist/hook.cjs +174 -12
- package/dist/launcher.cjs +229 -0
- package/dist/opencode.mjs +29 -2
- package/hook/hook.cjs +174 -12
- package/hook/launcher.cjs +229 -0
- package/hook/opencode.d.mts +2 -0
- package/hook/opencode.mjs +29 -2
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/commands/daemon.ts
|
|
4
|
-
import { writeFileSync as
|
|
4
|
+
import { writeFileSync as writeFileSync18, rmSync as rmSync14 } from "fs";
|
|
5
|
+
|
|
6
|
+
// src/core/api.ts
|
|
7
|
+
import { execFileSync } from "child_process";
|
|
8
|
+
import { createHash, randomBytes, randomUUID as randomUUID2 } from "crypto";
|
|
9
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
10
|
+
import { release } from "os";
|
|
5
11
|
|
|
6
12
|
// src/core/config.ts
|
|
7
13
|
import { randomUUID } from "crypto";
|
|
@@ -89,7 +95,8 @@ function loadConfig() {
|
|
|
89
95
|
remoteCwd: parsed.remoteCwd,
|
|
90
96
|
// 不认识的值一律回落默认,不能让一个手滑写错的配置变成"全放行"或"全拦"
|
|
91
97
|
approvalPolicy: parsed.approvalPolicy === "all" ? "all" : parsed.approvalPolicy === "commands" ? "commands" : void 0,
|
|
92
|
-
codexAppServer: typeof parsed.codexAppServer === "string" ? parsed.codexAppServer : void 0
|
|
98
|
+
codexAppServer: typeof parsed.codexAppServer === "string" ? parsed.codexAppServer : void 0,
|
|
99
|
+
autoUpdate: typeof parsed.autoUpdate === "boolean" ? parsed.autoUpdate : void 0
|
|
93
100
|
};
|
|
94
101
|
} catch {
|
|
95
102
|
return fallback;
|
|
@@ -110,16 +117,531 @@ function ensureClientId() {
|
|
|
110
117
|
return config.clientId;
|
|
111
118
|
}
|
|
112
119
|
|
|
120
|
+
// src/core/hookFile.ts
|
|
121
|
+
import { copyFileSync, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, renameSync as renameSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
122
|
+
import { createRequire } from "module";
|
|
123
|
+
import { dirname, join as join2 } from "path";
|
|
124
|
+
import { fileURLToPath } from "url";
|
|
125
|
+
var here = dirname(fileURLToPath(import.meta.url));
|
|
126
|
+
function packagedHookPath() {
|
|
127
|
+
const candidates = [
|
|
128
|
+
join2(here, "hook.cjs"),
|
|
129
|
+
// dist/cli.js 旁边(发布形态)
|
|
130
|
+
join2(here, "..", "hook", "hook.cjs"),
|
|
131
|
+
// dist/../hook/
|
|
132
|
+
join2(here, "..", "..", "hook", "hook.cjs")
|
|
133
|
+
// src/core/../../hook/(开发形态)
|
|
134
|
+
];
|
|
135
|
+
for (const c of candidates) {
|
|
136
|
+
if (existsSync2(c)) return c;
|
|
137
|
+
}
|
|
138
|
+
throw new Error("\u627E\u4E0D\u5230\u5305\u5185\u7684 hook.cjs\uFF0C\u5B89\u88C5\u5305\u53EF\u80FD\u4E0D\u5B8C\u6574\uFF0C\u8BF7\u91CD\u65B0\u5B89\u88C5 deskbuddy");
|
|
139
|
+
}
|
|
140
|
+
function packageVersion() {
|
|
141
|
+
const require2 = createRequire(import.meta.url);
|
|
142
|
+
for (const c of ["../package.json", "../../package.json", "../../../package.json"]) {
|
|
143
|
+
try {
|
|
144
|
+
const pkg = require2(c);
|
|
145
|
+
if (pkg?.name === "deskbuddy" && pkg.version) return pkg.version;
|
|
146
|
+
} catch {
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return "0.0.0-dev";
|
|
150
|
+
}
|
|
151
|
+
function hookVersionPath(target = installedHookPath()) {
|
|
152
|
+
return join2(dirname(target), "hook-version");
|
|
153
|
+
}
|
|
154
|
+
function installedHookVersion(target = installedHookPath()) {
|
|
155
|
+
try {
|
|
156
|
+
return readFileSync2(hookVersionPath(target), "utf8").trim() || null;
|
|
157
|
+
} catch {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
function atomicCopy(from, to) {
|
|
162
|
+
const tmp = `${to}.${process.pid}.tmp`;
|
|
163
|
+
copyFileSync(from, tmp);
|
|
164
|
+
renameSync2(tmp, to);
|
|
165
|
+
}
|
|
166
|
+
function syncHookFile(opts = {}) {
|
|
167
|
+
const target = opts.target ?? installedHookPath();
|
|
168
|
+
const version = opts.version ?? packageVersion();
|
|
169
|
+
const SIBLINGS = ["gate.cjs", "opencode.mjs", "wake.cjs", "launcher.cjs"];
|
|
170
|
+
const siblingMissing = () => SIBLINGS.some((name) => {
|
|
171
|
+
if (existsSync2(join2(dirname(target), name))) return false;
|
|
172
|
+
try {
|
|
173
|
+
return existsSync2(join2(dirname(opts.source ?? packagedHookPath()), name));
|
|
174
|
+
} catch {
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
if (installedHookVersion(target) === version && existsSync2(target) && !siblingMissing()) return false;
|
|
179
|
+
const source = opts.source ?? packagedHookPath();
|
|
180
|
+
mkdirSync2(dirname(target), { recursive: true });
|
|
181
|
+
atomicCopy(source, target);
|
|
182
|
+
for (const name of SIBLINGS) {
|
|
183
|
+
const from = join2(dirname(source), name);
|
|
184
|
+
if (existsSync2(from)) atomicCopy(from, join2(dirname(target), name));
|
|
185
|
+
}
|
|
186
|
+
writeFileSync2(hookVersionPath(target), version, "utf8");
|
|
187
|
+
return true;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// src/core/api.ts
|
|
191
|
+
var SERVICES = {
|
|
192
|
+
authCode: "com.shifeng.api.service.api.v2.deskbuddy.deskbuddyauthcodeapi",
|
|
193
|
+
authToken: "com.shifeng.api.service.api.v2.deskbuddy.deskbuddyauthtokenapi",
|
|
194
|
+
sessionReport: "com.shifeng.api.service.api.v2.deskbuddy.deskbuddysessionreportapi",
|
|
195
|
+
toolRegistry: "com.shifeng.api.service.api.v2.deskbuddy.deskbuddytoolregistryapi",
|
|
196
|
+
/** App 扫码确认时调的,CLI 用不到,列在这里只为对照 */
|
|
197
|
+
confirmAuth: "com.shifeng.api.service.api.v2.deskbuddy.deskbuddyconfirmauthapi"
|
|
198
|
+
};
|
|
199
|
+
var DIRS_SERVICE = "com.shifeng.api.service.api.v2.deskbuddy.deskbuddyclientdirsreportapi";
|
|
200
|
+
var INSTALL_REDEEM_SERVICE = "com.shifeng.api.service.api.v2.deskbuddy.deskbuddyinstallredeemapi";
|
|
201
|
+
var CMD_RESULT_SERVICE = "com.shifeng.api.service.api.v2.deskbuddy.deskbuddycommandresultapi";
|
|
202
|
+
var APPROVAL_SERVICES = {
|
|
203
|
+
create: "com.shifeng.api.service.api.v2.deskbuddy.deskbuddyapprovalcreateapi",
|
|
204
|
+
cancel: "com.shifeng.api.service.api.v2.deskbuddy.deskbuddyapprovalcancelapi",
|
|
205
|
+
sync: "com.shifeng.api.service.api.v2.deskbuddy.deskbuddyapprovalsyncapi"
|
|
206
|
+
};
|
|
207
|
+
var APP_ID = process.env.DESKBUDDY_APPID || "deskbuddy-cli";
|
|
208
|
+
var APP_SECRET = process.env.DESKBUDDY_APPSECRET || "3aeb6e0afc144e188dd3ede5c7865643";
|
|
209
|
+
var GATEWAY_VERSION = "1.0";
|
|
210
|
+
var DEFAULT_TIMEOUT_MS = 1e4;
|
|
211
|
+
function sign(p) {
|
|
212
|
+
const source = [p.appId, p.appSecret, p.timestamp, p.deviceId, p.deviceName, p.nonce, p.body].join("_");
|
|
213
|
+
return createHash("md5").update(source, "utf8").digest("hex");
|
|
214
|
+
}
|
|
215
|
+
function buildGatewayBody(service, data, requestId) {
|
|
216
|
+
return JSON.stringify({
|
|
217
|
+
service,
|
|
218
|
+
version: GATEWAY_VERSION,
|
|
219
|
+
requestId: requestId || randomUUID2(),
|
|
220
|
+
base: { encode: "false" },
|
|
221
|
+
// data 是业务 JSON 序列化后的字符串
|
|
222
|
+
data: JSON.stringify(data ?? {})
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
function buildHeaders(cfg, body, opts = {}) {
|
|
226
|
+
const timestamp = opts.timestamp || String(Date.now());
|
|
227
|
+
const nonce = opts.nonce || randomBytes(8).toString("hex");
|
|
228
|
+
return {
|
|
229
|
+
"Content-Type": "application/json;charset=UTF-8",
|
|
230
|
+
"Content-Length": String(Buffer.byteLength(body)),
|
|
231
|
+
"ShiFeng-AppId": APP_ID,
|
|
232
|
+
"ShiFeng-Timestamp": timestamp,
|
|
233
|
+
"ShiFeng-Device-ID": cfg.clientId,
|
|
234
|
+
"ShiFeng-Device-Name": cfg.hostName,
|
|
235
|
+
"ShiFeng-Nonce": nonce,
|
|
236
|
+
"ShiFeng-Signature": sign({
|
|
237
|
+
appId: APP_ID,
|
|
238
|
+
appSecret: APP_SECRET,
|
|
239
|
+
timestamp,
|
|
240
|
+
deviceId: cfg.clientId,
|
|
241
|
+
deviceName: cfg.hostName,
|
|
242
|
+
nonce,
|
|
243
|
+
body
|
|
244
|
+
})
|
|
245
|
+
// 没有 ShiFeng-Token:鉴权靠 clientId ↔ userId 绑定关系,CLI 不持有用户凭证
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
function isSuccessCode(code) {
|
|
249
|
+
return code === "0000";
|
|
250
|
+
}
|
|
251
|
+
var ApiError = class extends Error {
|
|
252
|
+
constructor(code, message) {
|
|
253
|
+
super(`${code}: ${message}`);
|
|
254
|
+
this.code = code;
|
|
255
|
+
this.name = "ApiError";
|
|
256
|
+
}
|
|
257
|
+
code;
|
|
258
|
+
};
|
|
259
|
+
function humanOs(platform = process.platform) {
|
|
260
|
+
const known = {
|
|
261
|
+
darwin: "macOS",
|
|
262
|
+
win32: "Windows",
|
|
263
|
+
linux: "Linux"
|
|
264
|
+
};
|
|
265
|
+
return known[platform] ?? platform;
|
|
266
|
+
}
|
|
267
|
+
var defaultOsVersionDeps = {
|
|
268
|
+
runSwVers: () => execFileSync("sw_vers", ["-productVersion"], {
|
|
269
|
+
encoding: "utf8",
|
|
270
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
271
|
+
timeout: 2e3
|
|
272
|
+
}),
|
|
273
|
+
readOsRelease: () => readFileSync3("/etc/os-release", "utf8"),
|
|
274
|
+
kernelRelease: () => release()
|
|
275
|
+
};
|
|
276
|
+
function productOsVersion(platform = process.platform, deps = defaultOsVersionDeps) {
|
|
277
|
+
try {
|
|
278
|
+
if (platform === "darwin") {
|
|
279
|
+
const v = deps.runSwVers().trim();
|
|
280
|
+
if (v) return v;
|
|
281
|
+
} else if (platform === "linux") {
|
|
282
|
+
const m = /^PRETTY_NAME=(.*)$/m.exec(deps.readOsRelease());
|
|
283
|
+
if (m) {
|
|
284
|
+
const v = m[1].trim().replace(/^(["'])(.*)\1$/, "$2").trim();
|
|
285
|
+
if (v) return v;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
} catch {
|
|
289
|
+
}
|
|
290
|
+
try {
|
|
291
|
+
return deps.kernelRelease();
|
|
292
|
+
} catch {
|
|
293
|
+
return "";
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
var machineInfoCache = null;
|
|
297
|
+
function machineInfo() {
|
|
298
|
+
if (!machineInfoCache) {
|
|
299
|
+
machineInfoCache = {
|
|
300
|
+
os: humanOs(),
|
|
301
|
+
osVersion: productOsVersion(),
|
|
302
|
+
arch: process.arch,
|
|
303
|
+
cliVersion: packageVersion()
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
return machineInfoCache;
|
|
307
|
+
}
|
|
308
|
+
async function call(service, data, timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
309
|
+
const cfg = loadConfig();
|
|
310
|
+
const body = buildGatewayBody(service, data);
|
|
311
|
+
const controller = new AbortController();
|
|
312
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
313
|
+
try {
|
|
314
|
+
const res = await fetch(`${cfg.apiBase}/gateway`, {
|
|
315
|
+
method: "POST",
|
|
316
|
+
headers: buildHeaders(cfg, body),
|
|
317
|
+
body,
|
|
318
|
+
// 与签名用的是同一个字符串
|
|
319
|
+
signal: controller.signal
|
|
320
|
+
});
|
|
321
|
+
const json = await res.json();
|
|
322
|
+
if (!isSuccessCode(json?.code)) {
|
|
323
|
+
throw new ApiError(String(json?.code), json?.message ?? "");
|
|
324
|
+
}
|
|
325
|
+
return json?.data;
|
|
326
|
+
} finally {
|
|
327
|
+
clearTimeout(timer);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
var api = {
|
|
331
|
+
authCode: (clientId, hostName) => call(SERVICES.authCode, { clientId, hostName, ...machineInfo() }),
|
|
332
|
+
authToken: (authCode) => call(SERVICES.authToken, { authCode }),
|
|
333
|
+
toolRegistry: () => call(SERVICES.toolRegistry, {}),
|
|
334
|
+
// 🔴 2 秒超时:这条调用发生在 hook 链路上,慢一点就是拖慢用户的 agent
|
|
335
|
+
report: (payload) => call(SERVICES.sessionReport, payload, 2e3),
|
|
336
|
+
approvalCreate: (payload) => call(APPROVAL_SERVICES.create, payload),
|
|
337
|
+
approvalCancel: (approvalId, reason) => call(APPROVAL_SERVICES.cancel, { approvalId, reason }),
|
|
338
|
+
approvalSync: (approvalIds) => call(APPROVAL_SERVICES.sync, { approvalIds }),
|
|
339
|
+
dirsReport: (payload) => call(DIRS_SERVICE, payload),
|
|
340
|
+
installRedeem: (installCode, clientId, hostName) => call(INSTALL_REDEEM_SERVICE, { installCode, clientId, hostName, ...machineInfo() }),
|
|
341
|
+
// 2 秒超时、失败不重试(spec §4.1):回报丢了只是设备少一句提示,任务状态仍由会话快照负责
|
|
342
|
+
commandResult: (payload) => call(CMD_RESULT_SERVICE, payload, 2e3)
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
// src/core/versions.ts
|
|
346
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
347
|
+
import { cpSync, existsSync as existsSync3, mkdirSync as mkdirSync3, readdirSync, readFileSync as readFileSync4, renameSync as renameSync3, rmSync, writeFileSync as writeFileSync3 } from "fs";
|
|
348
|
+
import { basename, dirname as dirname2, join as join3, relative, resolve, sep } from "path";
|
|
349
|
+
|
|
350
|
+
// src/core/semver.ts
|
|
351
|
+
var SEMVER_RE = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?$/;
|
|
352
|
+
var MAX_LEN = 64;
|
|
353
|
+
function isValidVersion(v) {
|
|
354
|
+
return typeof v === "string" && v.length <= MAX_LEN && SEMVER_RE.test(v);
|
|
355
|
+
}
|
|
356
|
+
function parse(v) {
|
|
357
|
+
const m = SEMVER_RE.exec(v);
|
|
358
|
+
if (!m) return null;
|
|
359
|
+
return { nums: [Number(m[1]), Number(m[2]), Number(m[3])], pre: m[4] ? m[4].split(".") : [] };
|
|
360
|
+
}
|
|
361
|
+
function cmpIdent(a, b) {
|
|
362
|
+
const an = /^\d+$/.test(a);
|
|
363
|
+
const bn = /^\d+$/.test(b);
|
|
364
|
+
if (an && bn) return Math.sign(Number(a) - Number(b));
|
|
365
|
+
if (an) return -1;
|
|
366
|
+
if (bn) return 1;
|
|
367
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
368
|
+
}
|
|
369
|
+
function compareVersions(a, b) {
|
|
370
|
+
const pa = parse(a);
|
|
371
|
+
const pb = parse(b);
|
|
372
|
+
if (!pa || !pb) throw new Error(`\u4E0D\u662F\u5408\u6CD5\u7684\u7248\u672C\u53F7\uFF1A${!pa ? a : b}`);
|
|
373
|
+
for (let i = 0; i < 3; i++) {
|
|
374
|
+
if (pa.nums[i] !== pb.nums[i]) return Math.sign(pa.nums[i] - pb.nums[i]);
|
|
375
|
+
}
|
|
376
|
+
if (pa.pre.length === 0 || pb.pre.length === 0) return pa.pre.length === pb.pre.length ? 0 : pa.pre.length === 0 ? 1 : -1;
|
|
377
|
+
for (let i = 0; i < Math.max(pa.pre.length, pb.pre.length); i++) {
|
|
378
|
+
if (i >= pa.pre.length) return -1;
|
|
379
|
+
if (i >= pb.pre.length) return 1;
|
|
380
|
+
const c = cmpIdent(pa.pre[i], pb.pre[i]);
|
|
381
|
+
if (c !== 0) return c;
|
|
382
|
+
}
|
|
383
|
+
return 0;
|
|
384
|
+
}
|
|
385
|
+
function semverGt(a, b) {
|
|
386
|
+
return compareVersions(a, b) > 0;
|
|
387
|
+
}
|
|
388
|
+
function semverLt(a, b) {
|
|
389
|
+
return compareVersions(a, b) < 0;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// src/core/versions.ts
|
|
393
|
+
function versionsDir(home = deskbuddyHome()) {
|
|
394
|
+
return join3(home, "versions");
|
|
395
|
+
}
|
|
396
|
+
function currentJsonPath(home) {
|
|
397
|
+
return join3(versionsDir(home), "current.json");
|
|
398
|
+
}
|
|
399
|
+
function failedJsonPath(home) {
|
|
400
|
+
return join3(versionsDir(home), "failed.json");
|
|
401
|
+
}
|
|
402
|
+
function crashesJsonPath(home) {
|
|
403
|
+
return join3(versionsDir(home), "crashes.json");
|
|
404
|
+
}
|
|
405
|
+
function launcherPath(home = deskbuddyHome()) {
|
|
406
|
+
return join3(home, "launcher.cjs");
|
|
407
|
+
}
|
|
408
|
+
function versionDir(version, home) {
|
|
409
|
+
if (!isValidVersion(version)) throw new Error(`\u4E0D\u662F\u5408\u6CD5\u7684\u7248\u672C\u53F7\uFF1A${version}`);
|
|
410
|
+
return join3(versionsDir(home), version);
|
|
411
|
+
}
|
|
412
|
+
function versionCliPath(version, home) {
|
|
413
|
+
return join3(versionDir(version, home), "node_modules", "deskbuddy", "dist", "cli.js");
|
|
414
|
+
}
|
|
415
|
+
function atomicWriteJson(path, value) {
|
|
416
|
+
mkdirSync3(dirname2(path), { recursive: true });
|
|
417
|
+
const tmp = `${path}.${process.pid}.tmp`;
|
|
418
|
+
writeFileSync3(tmp, JSON.stringify(value, null, 2), "utf8");
|
|
419
|
+
renameSync3(tmp, path);
|
|
420
|
+
}
|
|
421
|
+
function isRef(v) {
|
|
422
|
+
const r = v;
|
|
423
|
+
return !!r && typeof r.version === "string" && typeof r.cliPath === "string" && r.cliPath !== "";
|
|
424
|
+
}
|
|
425
|
+
function readCurrent(home) {
|
|
426
|
+
try {
|
|
427
|
+
const raw = JSON.parse(readFileSync4(currentJsonPath(home), "utf8"));
|
|
428
|
+
if (!isRef(raw)) return null;
|
|
429
|
+
return {
|
|
430
|
+
version: raw.version,
|
|
431
|
+
cliPath: raw.cliPath,
|
|
432
|
+
previous: isRef(raw.previous) ? { version: raw.previous.version, cliPath: raw.previous.cliPath } : null,
|
|
433
|
+
state: raw.state === "verifying" ? "verifying" : "good",
|
|
434
|
+
switchedAt: typeof raw.switchedAt === "number" ? raw.switchedAt : 0
|
|
435
|
+
};
|
|
436
|
+
} catch {
|
|
437
|
+
return null;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
function writeCurrent(cur, home) {
|
|
441
|
+
atomicWriteJson(currentJsonPath(home), cur);
|
|
442
|
+
}
|
|
443
|
+
function readFailed(home) {
|
|
444
|
+
try {
|
|
445
|
+
const raw = JSON.parse(readFileSync4(failedJsonPath(home), "utf8"));
|
|
446
|
+
const versions = Array.isArray(raw.versions) ? raw.versions.filter((v) => typeof v === "string") : [];
|
|
447
|
+
const attempts = {};
|
|
448
|
+
if (raw.attempts && typeof raw.attempts === "object") {
|
|
449
|
+
for (const [k, v] of Object.entries(raw.attempts)) if (typeof v === "number") attempts[k] = v;
|
|
450
|
+
}
|
|
451
|
+
return { versions, attempts };
|
|
452
|
+
} catch {
|
|
453
|
+
return { versions: [], attempts: {} };
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
function recordAttemptFailure(version, now, home) {
|
|
457
|
+
const f = readFailed(home);
|
|
458
|
+
f.attempts[version] = now;
|
|
459
|
+
atomicWriteJson(failedJsonPath(home), f);
|
|
460
|
+
}
|
|
461
|
+
function markGood(version, home) {
|
|
462
|
+
const cur = readCurrent(home);
|
|
463
|
+
if (!cur || cur.version !== version || cur.state !== "verifying") return false;
|
|
464
|
+
writeCurrent({ ...cur, state: "good" }, home);
|
|
465
|
+
try {
|
|
466
|
+
rmSync(crashesJsonPath(home), { force: true });
|
|
467
|
+
} catch {
|
|
468
|
+
}
|
|
469
|
+
return true;
|
|
470
|
+
}
|
|
471
|
+
function versionDirOf(cliPath, home) {
|
|
472
|
+
const rel = relative(versionsDir(home), resolve(cliPath));
|
|
473
|
+
if (!rel || rel.startsWith("..") || rel.startsWith(sep) || /^[a-zA-Z]:/.test(rel)) return null;
|
|
474
|
+
const first = rel.split(/[\\/]/)[0];
|
|
475
|
+
return isValidVersion(first) ? first : null;
|
|
476
|
+
}
|
|
477
|
+
function cleanupVersions(cur, home) {
|
|
478
|
+
const keep = /* @__PURE__ */ new Set();
|
|
479
|
+
for (const ref of [cur, cur.previous]) {
|
|
480
|
+
if (!ref) continue;
|
|
481
|
+
const d = versionDirOf(ref.cliPath, home);
|
|
482
|
+
if (d) keep.add(d);
|
|
483
|
+
}
|
|
484
|
+
const removed = [];
|
|
485
|
+
let names = [];
|
|
486
|
+
try {
|
|
487
|
+
names = readdirSync(versionsDir(home), { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
488
|
+
} catch {
|
|
489
|
+
return removed;
|
|
490
|
+
}
|
|
491
|
+
for (const name of names) {
|
|
492
|
+
const ours = isValidVersion(name) || /^\.tmp-/.test(name);
|
|
493
|
+
if (!ours || keep.has(name)) continue;
|
|
494
|
+
try {
|
|
495
|
+
rmSync(join3(versionsDir(home), name), { recursive: true, force: true });
|
|
496
|
+
removed.push(name);
|
|
497
|
+
} catch {
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
return removed;
|
|
501
|
+
}
|
|
502
|
+
function cliReportsVersion(cliPath, version, nodePath = process.execPath) {
|
|
503
|
+
try {
|
|
504
|
+
const out = execFileSync2(nodePath, [cliPath, "--version"], {
|
|
505
|
+
encoding: "utf8",
|
|
506
|
+
timeout: 3e4,
|
|
507
|
+
windowsHide: true,
|
|
508
|
+
shell: false,
|
|
509
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
510
|
+
});
|
|
511
|
+
return out.trim() === version;
|
|
512
|
+
} catch {
|
|
513
|
+
return false;
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
function installedPackageVersion(dir) {
|
|
517
|
+
try {
|
|
518
|
+
const pkg = JSON.parse(readFileSync4(join3(dir, "node_modules", "deskbuddy", "package.json"), "utf8"));
|
|
519
|
+
return pkg.name === "deskbuddy" && typeof pkg.version === "string" ? pkg.version : null;
|
|
520
|
+
} catch {
|
|
521
|
+
return null;
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
function readPkg(dir) {
|
|
525
|
+
try {
|
|
526
|
+
return JSON.parse(readFileSync4(join3(dir, "package.json"), "utf8"));
|
|
527
|
+
} catch {
|
|
528
|
+
return null;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
function findDepDir(name, fromDir) {
|
|
532
|
+
let d = fromDir;
|
|
533
|
+
for (; ; ) {
|
|
534
|
+
const c = join3(d, "node_modules", name);
|
|
535
|
+
if (existsSync3(join3(c, "package.json"))) return c;
|
|
536
|
+
const up = dirname2(d);
|
|
537
|
+
if (up === d) return null;
|
|
538
|
+
d = up;
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
function copyRunningCli(pkgRoot, version, home) {
|
|
542
|
+
const dest = versionDir(version, home);
|
|
543
|
+
const tmp = join3(versionsDir(home), `.tmp-${version}-${process.pid}`);
|
|
544
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
545
|
+
const nm = join3(tmp, "node_modules");
|
|
546
|
+
mkdirSync3(nm, { recursive: true });
|
|
547
|
+
const copied = /* @__PURE__ */ new Set();
|
|
548
|
+
const copyPkg = (name, from) => {
|
|
549
|
+
copied.add(name);
|
|
550
|
+
cpSync(from, join3(nm, name), {
|
|
551
|
+
recursive: true,
|
|
552
|
+
dereference: true,
|
|
553
|
+
// 包自己的嵌套 node_modules 不整个搬:依赖由下面的递归平铺
|
|
554
|
+
filter: (src) => !(src !== from && basename(src) === "node_modules" && dirname2(src) === from)
|
|
555
|
+
});
|
|
556
|
+
};
|
|
557
|
+
copyPkg("deskbuddy", pkgRoot);
|
|
558
|
+
const queue = [{ dir: pkgRoot, pkg: readPkg(pkgRoot) ?? {} }];
|
|
559
|
+
while (queue.length > 0) {
|
|
560
|
+
const { dir, pkg } = queue.shift();
|
|
561
|
+
const deps = [
|
|
562
|
+
...Object.keys(pkg.dependencies ?? {}).map((n) => [n, false]),
|
|
563
|
+
...Object.keys(pkg.optionalDependencies ?? {}).map((n) => [n, true])
|
|
564
|
+
];
|
|
565
|
+
for (const [name, optional] of deps) {
|
|
566
|
+
if (copied.has(name)) continue;
|
|
567
|
+
const found = findDepDir(name, dir);
|
|
568
|
+
if (!found) {
|
|
569
|
+
if (optional) continue;
|
|
570
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
571
|
+
throw new Error(`\u627E\u4E0D\u5230\u4F9D\u8D56 ${name}`);
|
|
572
|
+
}
|
|
573
|
+
copyPkg(name, found);
|
|
574
|
+
queue.push({ dir: found, pkg: readPkg(found) ?? {} });
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
rmSync(dest, { recursive: true, force: true });
|
|
578
|
+
renameSync3(tmp, dest);
|
|
579
|
+
return versionCliPath(version, home);
|
|
580
|
+
}
|
|
581
|
+
function registerRunningCli(running, deps = {}) {
|
|
582
|
+
const home = deps.home;
|
|
583
|
+
const now = deps.now ?? Date.now();
|
|
584
|
+
const verify = deps.verify ?? ((p, v) => cliReportsVersion(p, v));
|
|
585
|
+
if (!isValidVersion(running.version) || running.version.startsWith("0.0.0")) {
|
|
586
|
+
throw new Error(`\u5F00\u53D1\u7248\uFF08${running.version}\uFF09\u4E0D\u767B\u8BB0`);
|
|
587
|
+
}
|
|
588
|
+
let cliPath = resolve(running.cliPath);
|
|
589
|
+
if (!existsSync3(cliPath)) throw new Error(`\u5165\u53E3\u6587\u4EF6\u4E0D\u5B58\u5728\uFF1A${cliPath}`);
|
|
590
|
+
let how = "already";
|
|
591
|
+
let note;
|
|
592
|
+
if (versionDirOf(cliPath, home) !== running.version) {
|
|
593
|
+
const distDir = dirname2(cliPath);
|
|
594
|
+
const pkgRoot = dirname2(distDir);
|
|
595
|
+
const pkg = readPkg(pkgRoot);
|
|
596
|
+
const copyable = basename(distDir) === "dist" && pkg?.name === "deskbuddy" && pkg.version === running.version && !existsSync3(join3(pkgRoot, "src"));
|
|
597
|
+
how = "inPlace";
|
|
598
|
+
if (copyable) {
|
|
599
|
+
const target = versionCliPath(running.version, home);
|
|
600
|
+
try {
|
|
601
|
+
if (!(installedPackageVersion(versionDir(running.version, home)) === running.version && verify(target, running.version))) {
|
|
602
|
+
copyRunningCli(pkgRoot, running.version, home);
|
|
603
|
+
}
|
|
604
|
+
if (verify(target, running.version)) {
|
|
605
|
+
cliPath = target;
|
|
606
|
+
how = "copied";
|
|
607
|
+
} else note = "\u590D\u5236\u51FA\u6765\u7684\u90A3\u4EFD\u8DD1\u4E0D\u8D77\u6765";
|
|
608
|
+
} catch (e) {
|
|
609
|
+
note = `\u590D\u5236\u5931\u8D25\uFF1A${e.message}`;
|
|
610
|
+
}
|
|
611
|
+
} else note = "\u4E0D\u662F\u53D1\u5E03\u5F62\u6001\u7684\u5B89\u88C5\u5305";
|
|
612
|
+
}
|
|
613
|
+
const old = readCurrent(home);
|
|
614
|
+
const from = deps.from && isValidVersion(deps.from.version) && deps.from.version !== running.version && existsSync3(deps.from.cliPath) ? deps.from : null;
|
|
615
|
+
const changed = !!old && old.version !== running.version || !old && !!from;
|
|
616
|
+
const prevRef = old && old.version !== running.version ? { version: old.version, cliPath: old.cliPath } : from;
|
|
617
|
+
const current = {
|
|
618
|
+
version: running.version,
|
|
619
|
+
cliPath,
|
|
620
|
+
previous: changed ? prevRef : old?.previous ?? from,
|
|
621
|
+
// 版本变了一律 verifying(新版起不来要能退回去);同版本重复登记保持原状态
|
|
622
|
+
state: changed ? "verifying" : old && old.cliPath === cliPath ? old.state : from ? "verifying" : "good",
|
|
623
|
+
switchedAt: changed || !old ? now : old.switchedAt
|
|
624
|
+
};
|
|
625
|
+
writeCurrent(current, home);
|
|
626
|
+
if (changed) {
|
|
627
|
+
try {
|
|
628
|
+
rmSync(crashesJsonPath(home), { force: true });
|
|
629
|
+
} catch {
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
return { current, how, note };
|
|
633
|
+
}
|
|
634
|
+
|
|
113
635
|
// src/daemon/autostart.ts
|
|
114
636
|
import { execFile, spawn } from "child_process";
|
|
115
|
-
import { existsSync as
|
|
637
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync5, rmSync as rmSync2, writeFileSync as writeFileSync4 } from "fs";
|
|
116
638
|
import { homedir as homedir2 } from "os";
|
|
117
|
-
import { fileURLToPath } from "url";
|
|
118
|
-
import { basename, dirname, join as
|
|
639
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
640
|
+
import { basename as basename2, dirname as dirname3, join as join4 } from "path";
|
|
119
641
|
import { promisify } from "util";
|
|
120
642
|
|
|
121
643
|
// src/core/winproc.ts
|
|
122
|
-
import { execFileSync } from "child_process";
|
|
644
|
+
import { execFileSync as execFileSync3 } from "child_process";
|
|
123
645
|
function winSplitArgs(cmdline) {
|
|
124
646
|
const out = [];
|
|
125
647
|
let cur = "";
|
|
@@ -213,7 +735,7 @@ var cache = null;
|
|
|
213
735
|
function winProcs(now = Date.now()) {
|
|
214
736
|
if (cache && now - cache.at < (cache.procs ? CACHE_MS : FAIL_CACHE_MS)) return cache.procs;
|
|
215
737
|
try {
|
|
216
|
-
const out =
|
|
738
|
+
const out = execFileSync3("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", WIN_PS_SCRIPT], {
|
|
217
739
|
encoding: "utf8",
|
|
218
740
|
stdio: ["ignore", "pipe", "ignore"],
|
|
219
741
|
maxBuffer: 32 * 1024 * 1024,
|
|
@@ -235,8 +757,8 @@ var SYSTEMD_UNIT = "deskbuddy.service";
|
|
|
235
757
|
var SCHTASK_NAME = "DeskBuddyDaemon";
|
|
236
758
|
var CLI_ENTRY = "cli.js";
|
|
237
759
|
function defaultCliPath(argv1 = process.argv[1]) {
|
|
238
|
-
const sibling =
|
|
239
|
-
if (typeof argv1 === "string" &&
|
|
760
|
+
const sibling = fileURLToPath2(new URL("./" + CLI_ENTRY, import.meta.url));
|
|
761
|
+
if (typeof argv1 === "string" && basename2(argv1) === CLI_ENTRY) return argv1;
|
|
240
762
|
return sibling;
|
|
241
763
|
}
|
|
242
764
|
function xmlEscape(s) {
|
|
@@ -275,8 +797,8 @@ RestartSec=10
|
|
|
275
797
|
WantedBy=default.target
|
|
276
798
|
`;
|
|
277
799
|
}
|
|
278
|
-
function winStartupVbsPath(appData = process.env.APPDATA ??
|
|
279
|
-
return
|
|
800
|
+
function winStartupVbsPath(appData = process.env.APPDATA ?? join4(homedir2(), "AppData", "Roaming")) {
|
|
801
|
+
return join4(appData, "Microsoft", "Windows", "Start Menu", "Programs", "Startup", "DeskBuddyDaemon.vbs");
|
|
280
802
|
}
|
|
281
803
|
function vbsQuote(s) {
|
|
282
804
|
return '"' + s.replace(/"/g, '""') + '"';
|
|
@@ -294,16 +816,25 @@ function isOurDaemonCmdline(cmdline) {
|
|
|
294
816
|
const i = argv.findIndex((a) => /(?:^|[\\/])deskbuddy[\\/]dist[\\/]cli\.js$/i.test(a));
|
|
295
817
|
return i >= 1 && argv[i + 1] === "daemon";
|
|
296
818
|
}
|
|
819
|
+
function isOurLauncherCmdline(cmdline) {
|
|
820
|
+
const argv = winSplitArgs(cmdline);
|
|
821
|
+
const i = argv.findIndex((a) => /(?:^|[\\/])launcher\.cjs$/i.test(a));
|
|
822
|
+
return i >= 1 && argv[i + 1] === "daemon";
|
|
823
|
+
}
|
|
297
824
|
async function stopPidFileDaemon() {
|
|
825
|
+
await stopByPidFile(launcherPidPath(), isOurLauncherCmdline);
|
|
826
|
+
await stopByPidFile(pidFilePath(), isOurDaemonCmdline);
|
|
827
|
+
}
|
|
828
|
+
async function stopByPidFile(file, ours) {
|
|
298
829
|
let pid = 0;
|
|
299
830
|
try {
|
|
300
|
-
pid = Number(
|
|
831
|
+
pid = Number(readFileSync5(file, "utf8").trim());
|
|
301
832
|
} catch {
|
|
302
833
|
return;
|
|
303
834
|
}
|
|
304
835
|
if (!Number.isInteger(pid) || pid <= 0 || pid === process.pid) return;
|
|
305
836
|
const p = winProcs()?.find((x) => x.pid === pid);
|
|
306
|
-
if (!p || !
|
|
837
|
+
if (!p || !ours(p.cmdline)) return;
|
|
307
838
|
try {
|
|
308
839
|
process.kill(pid);
|
|
309
840
|
} catch {
|
|
@@ -319,33 +850,69 @@ async function stopPidFileDaemon() {
|
|
|
319
850
|
}
|
|
320
851
|
}
|
|
321
852
|
function launchAgentPath(home = homedir2()) {
|
|
322
|
-
return
|
|
853
|
+
return join4(home, "Library", "LaunchAgents", `${LAUNCH_LABEL}.plist`);
|
|
323
854
|
}
|
|
324
855
|
function systemdUnitPath(home = homedir2()) {
|
|
325
|
-
return
|
|
856
|
+
return join4(home, ".config", "systemd", "user", SYSTEMD_UNIT);
|
|
326
857
|
}
|
|
327
858
|
function write(path, content) {
|
|
328
|
-
|
|
329
|
-
|
|
859
|
+
mkdirSync4(dirname3(path), { recursive: true });
|
|
860
|
+
writeFileSync4(path, content, "utf8");
|
|
330
861
|
}
|
|
331
862
|
var BOOTSTRAP_RETRIES = 10;
|
|
332
863
|
var BOOTSTRAP_RETRY_MS = 500;
|
|
333
|
-
|
|
864
|
+
var AUTO_BOOTSTRAP_RETRIES = 120;
|
|
865
|
+
async function retryAsync(fn, retries, delayMs) {
|
|
866
|
+
for (let i = 0; ; i++) {
|
|
867
|
+
try {
|
|
868
|
+
await fn();
|
|
869
|
+
return;
|
|
870
|
+
} catch (e) {
|
|
871
|
+
if (i >= retries) throw e;
|
|
872
|
+
await new Promise((r) => setTimeout(r, delayMs));
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
function shouldFallbackSpawn(daemonPid, alive) {
|
|
877
|
+
return !(daemonPid && daemonPid > 0 && alive(daemonPid));
|
|
878
|
+
}
|
|
879
|
+
function readDaemonPid() {
|
|
880
|
+
try {
|
|
881
|
+
const n = Number(readFileSync5(pidFilePath(), "utf8").trim());
|
|
882
|
+
return Number.isInteger(n) && n > 0 ? n : null;
|
|
883
|
+
} catch {
|
|
884
|
+
return null;
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
function pidAlive(pid) {
|
|
888
|
+
try {
|
|
889
|
+
process.kill(pid, 0);
|
|
890
|
+
return true;
|
|
891
|
+
} catch (e) {
|
|
892
|
+
return e.code === "EPERM";
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
function fallbackSpawn(t) {
|
|
896
|
+
if (!shouldFallbackSpawn(readDaemonPid(), pidAlive)) return "\u65E7\u5B88\u62A4\u8FDB\u7A0B\u4ECD\u5728\u8FD0\u884C\uFF0C\u672A\u53E6\u8D77";
|
|
897
|
+
try {
|
|
898
|
+
spawn(t.execPath, [t.cliPath, "daemon"], { detached: true, stdio: "ignore", windowsHide: true, shell: false }).unref();
|
|
899
|
+
return "\u5DF2\u76F4\u63A5\u62C9\u8D77\u5B88\u62A4\u8FDB\u7A0B\u515C\u5E95\uFF08\u672C\u6B21\u5F00\u673A\u5185\u6709\u6548\uFF09";
|
|
900
|
+
} catch (e) {
|
|
901
|
+
return `\u76F4\u63A5\u62C9\u8D77\u5B88\u62A4\u8FDB\u7A0B\u4E5F\u5931\u8D25\u4E86\uFF1A${e.message}`;
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
async function installAutostart(t, platform = process.platform, opts = {}) {
|
|
334
905
|
try {
|
|
335
906
|
if (platform === "darwin") {
|
|
336
907
|
const p = launchAgentPath();
|
|
337
908
|
write(p, launchAgentPlist(t));
|
|
338
909
|
await run("launchctl", ["bootout", `gui/${process.getuid?.() ?? 501}/${LAUNCH_LABEL}`]).catch(() => {
|
|
339
910
|
});
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
if (i >= BOOTSTRAP_RETRIES) throw e;
|
|
346
|
-
await new Promise((r) => setTimeout(r, BOOTSTRAP_RETRY_MS));
|
|
347
|
-
}
|
|
348
|
-
}
|
|
911
|
+
await retryAsync(
|
|
912
|
+
() => run("launchctl", ["bootstrap", `gui/${process.getuid?.() ?? 501}`, p]),
|
|
913
|
+
opts.auto ? AUTO_BOOTSTRAP_RETRIES : BOOTSTRAP_RETRIES,
|
|
914
|
+
BOOTSTRAP_RETRY_MS
|
|
915
|
+
);
|
|
349
916
|
return { ok: true, message: "\u5DF2\u8BBE\u7F6E\u5F00\u673A\u81EA\u542F\uFF08launchd\uFF09" };
|
|
350
917
|
}
|
|
351
918
|
if (platform === "linux") {
|
|
@@ -357,8 +924,8 @@ async function installAutostart(t, platform = process.platform) {
|
|
|
357
924
|
}
|
|
358
925
|
if (platform === "win32") {
|
|
359
926
|
const p = winStartupVbsPath();
|
|
360
|
-
|
|
361
|
-
|
|
927
|
+
mkdirSync4(dirname3(p), { recursive: true });
|
|
928
|
+
writeFileSync4(p, "\uFEFF" + winStartupVbs(t), "utf16le");
|
|
362
929
|
await run("schtasks", ["/delete", "/f", "/tn", SCHTASK_NAME], { windowsHide: true }).catch(() => {
|
|
363
930
|
});
|
|
364
931
|
await stopPidFileDaemon();
|
|
@@ -367,6 +934,9 @@ async function installAutostart(t, platform = process.platform) {
|
|
|
367
934
|
}
|
|
368
935
|
return { ok: false, message: `\u6682\u4E0D\u652F\u6301\u5728 ${platform} \u4E0A\u8BBE\u7F6E\u5F00\u673A\u81EA\u542F\uFF0C\u8BF7\u81EA\u884C\u5E38\u9A7B\u8FD0\u884C deskbuddy daemon` };
|
|
369
936
|
} catch (e) {
|
|
937
|
+
if (opts.auto && platform !== "win32") {
|
|
938
|
+
return { ok: false, message: `\u5F00\u673A\u81EA\u542F\u6CA1\u8BBE\u7F6E\u6210\u529F\uFF08${e.message}\uFF09\uFF0C${fallbackSpawn(t)}` };
|
|
939
|
+
}
|
|
370
940
|
return { ok: false, message: `\u5F00\u673A\u81EA\u542F\u6CA1\u8BBE\u7F6E\u6210\u529F\uFF08${e.message}\uFF09\uFF0C\u53EF\u4EE5\u624B\u52A8\u8DD1 deskbuddy daemon` };
|
|
371
941
|
}
|
|
372
942
|
}
|
|
@@ -376,44 +946,405 @@ async function uninstallAutostart(platform = process.platform) {
|
|
|
376
946
|
await run("launchctl", ["bootout", `gui/${process.getuid?.() ?? 501}/${LAUNCH_LABEL}`]).catch(() => {
|
|
377
947
|
});
|
|
378
948
|
const p = launchAgentPath();
|
|
379
|
-
if (
|
|
949
|
+
if (existsSync4(p)) rmSync2(p, { force: true });
|
|
380
950
|
return { ok: true, message: "\u5DF2\u53D6\u6D88\u5F00\u673A\u81EA\u542F" };
|
|
381
951
|
}
|
|
382
952
|
if (platform === "linux") {
|
|
383
953
|
await run("systemctl", ["--user", "disable", "--now", SYSTEMD_UNIT]).catch(() => {
|
|
384
954
|
});
|
|
385
955
|
const p = systemdUnitPath();
|
|
386
|
-
if (
|
|
956
|
+
if (existsSync4(p)) rmSync2(p, { force: true });
|
|
387
957
|
await run("systemctl", ["--user", "daemon-reload"]).catch(() => {
|
|
388
958
|
});
|
|
389
959
|
return { ok: true, message: "\u5DF2\u53D6\u6D88\u5F00\u673A\u81EA\u542F" };
|
|
390
960
|
}
|
|
391
|
-
if (platform === "win32") {
|
|
392
|
-
await run("schtasks", ["/delete", "/f", "/tn", SCHTASK_NAME], { windowsHide: true }).catch(() => {
|
|
393
|
-
});
|
|
394
|
-
const p = winStartupVbsPath();
|
|
395
|
-
if (
|
|
396
|
-
await stopPidFileDaemon();
|
|
397
|
-
return { ok: true, message: "\u5DF2\u53D6\u6D88\u5F00\u673A\u81EA\u542F" };
|
|
961
|
+
if (platform === "win32") {
|
|
962
|
+
await run("schtasks", ["/delete", "/f", "/tn", SCHTASK_NAME], { windowsHide: true }).catch(() => {
|
|
963
|
+
});
|
|
964
|
+
const p = winStartupVbsPath();
|
|
965
|
+
if (existsSync4(p)) rmSync2(p, { force: true });
|
|
966
|
+
await stopPidFileDaemon();
|
|
967
|
+
return { ok: true, message: "\u5DF2\u53D6\u6D88\u5F00\u673A\u81EA\u542F" };
|
|
968
|
+
}
|
|
969
|
+
return { ok: true, message: "" };
|
|
970
|
+
} catch (e) {
|
|
971
|
+
return { ok: false, message: `\u53D6\u6D88\u5F00\u673A\u81EA\u542F\u65F6\u51FA\u9519\uFF08${e.message}\uFF09\uFF0C\u53EF\u80FD\u9700\u8981\u624B\u52A8\u6E05\u7406` };
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
function pidFilePath() {
|
|
975
|
+
return join4(deskbuddyHome(), "daemon.pid");
|
|
976
|
+
}
|
|
977
|
+
function launcherPidPath() {
|
|
978
|
+
return join4(deskbuddyHome(), "launcher.pid");
|
|
979
|
+
}
|
|
980
|
+
function autostartTarget(out = () => {
|
|
981
|
+
}, from = null) {
|
|
982
|
+
const legacy = { execPath: process.execPath, cliPath: defaultCliPath() };
|
|
983
|
+
try {
|
|
984
|
+
const r = registerRunningCli({ version: packageVersion(), cliPath: defaultCliPath() }, { from });
|
|
985
|
+
syncHookFile();
|
|
986
|
+
const launcher = launcherPath();
|
|
987
|
+
if (!existsSync4(launcher)) throw new Error("launcher.cjs \u6CA1\u6709\u540C\u6B65\u5230 " + deskbuddyHome());
|
|
988
|
+
if (r.note) out(`\u26A0\uFE0F \u5F53\u524D\u7248\u672C\u6CA1\u80FD\u590D\u5236\u8FDB ${deskbuddyHome()}/versions\uFF08${r.note}\uFF09\uFF0C\u5148\u539F\u5730\u767B\u8BB0`);
|
|
989
|
+
const removed = cleanupVersions(r.current);
|
|
990
|
+
if (removed.length > 0) out(`\u5DF2\u6E05\u7406\u65E7\u7248\u672C\uFF1A${removed.join(", ")}`);
|
|
991
|
+
return { execPath: process.execPath, cliPath: launcher };
|
|
992
|
+
} catch (e) {
|
|
993
|
+
out(`\u26A0\uFE0F \u6CA1\u80FD\u63A5\u5165\u81EA\u52A8\u5347\u7EA7\uFF08${e.message}\uFF09\uFF0C\u5F00\u673A\u81EA\u542F\u4ECD\u76F4\u63A5\u6307\u5411\u5F53\u524D\u7248\u672C`);
|
|
994
|
+
return legacy;
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
// src/daemon/autoUpdate.ts
|
|
999
|
+
import { execFile as execFile2, execFileSync as execFileSync4, spawn as spawn2 } from "child_process";
|
|
1000
|
+
import { closeSync, existsSync as existsSync6, mkdirSync as mkdirSync5, openSync, rmSync as rmSync3 } from "fs";
|
|
1001
|
+
import { dirname as dirname5, join as join6, posix, win32 } from "path";
|
|
1002
|
+
|
|
1003
|
+
// src/wrap/pty.ts
|
|
1004
|
+
import { chmodSync, existsSync as existsSync5, statSync } from "fs";
|
|
1005
|
+
import { createRequire as createRequire2 } from "module";
|
|
1006
|
+
import { dirname as dirname4, join as join5 } from "path";
|
|
1007
|
+
var req = createRequire2(import.meta.url);
|
|
1008
|
+
function fixSpawnHelper() {
|
|
1009
|
+
if (process.platform !== "darwin") return;
|
|
1010
|
+
try {
|
|
1011
|
+
const dir = join5(dirname4(req.resolve("node-pty/package.json")), "prebuilds", `darwin-${process.arch}`);
|
|
1012
|
+
const helper = join5(dir, "spawn-helper");
|
|
1013
|
+
if (existsSync5(helper) && (statSync(helper).mode & 73) === 0) chmodSync(helper, 493);
|
|
1014
|
+
} catch {
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
function loadPty() {
|
|
1018
|
+
try {
|
|
1019
|
+
fixSpawnHelper();
|
|
1020
|
+
const m = req("node-pty");
|
|
1021
|
+
return typeof m?.spawn === "function" ? m : null;
|
|
1022
|
+
} catch {
|
|
1023
|
+
return null;
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
function loadHeadless() {
|
|
1027
|
+
try {
|
|
1028
|
+
const m = req("@xterm/headless");
|
|
1029
|
+
return typeof m?.Terminal === "function" ? m.Terminal : null;
|
|
1030
|
+
} catch {
|
|
1031
|
+
return null;
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
function screenLines(term) {
|
|
1035
|
+
const b = term.buffer.active;
|
|
1036
|
+
const out = [];
|
|
1037
|
+
for (let i = b.viewportY; i < b.viewportY + term.rows; i++) {
|
|
1038
|
+
const l = b.getLine(i);
|
|
1039
|
+
out.push(l ? l.translateToString(true) : "");
|
|
1040
|
+
}
|
|
1041
|
+
return out;
|
|
1042
|
+
}
|
|
1043
|
+
function screenLinesUndimmed(term) {
|
|
1044
|
+
const b = term.buffer.active;
|
|
1045
|
+
const out = [];
|
|
1046
|
+
for (let i = b.viewportY; i < b.viewportY + term.rows; i++) {
|
|
1047
|
+
const l = b.getLine(i);
|
|
1048
|
+
if (!l) {
|
|
1049
|
+
out.push("");
|
|
1050
|
+
continue;
|
|
1051
|
+
}
|
|
1052
|
+
if (typeof l.getCell !== "function") {
|
|
1053
|
+
out.push(l.translateToString(true));
|
|
1054
|
+
continue;
|
|
1055
|
+
}
|
|
1056
|
+
let s = "";
|
|
1057
|
+
for (let x = 0; x < term.cols; x++) {
|
|
1058
|
+
const c = l.getCell(x);
|
|
1059
|
+
if (!c) break;
|
|
1060
|
+
const w = c.getWidth();
|
|
1061
|
+
if (w === 0) continue;
|
|
1062
|
+
s += c.isDim() ? " ".repeat(w) : c.getChars() || " ";
|
|
1063
|
+
}
|
|
1064
|
+
out.push(s.trimEnd());
|
|
1065
|
+
}
|
|
1066
|
+
return out;
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
// src/daemon/autoUpdate.ts
|
|
1070
|
+
var PACKAGE_NAME = "deskbuddy";
|
|
1071
|
+
var REGISTRIES = ["https://registry.npmjs.org/", "https://registry.npmmirror.com/"];
|
|
1072
|
+
var FIRST_CHECK_MS = 60 * 1e3;
|
|
1073
|
+
var CHECK_INTERVAL_MS = 60 * 60 * 1e3;
|
|
1074
|
+
var IDLE_POLL_MS = 30 * 1e3;
|
|
1075
|
+
var IDLE_MAX_WAIT_MS = 24 * 60 * 60 * 1e3;
|
|
1076
|
+
var RETRY_AFTER_FAIL_MS = 6 * 60 * 60 * 1e3;
|
|
1077
|
+
var INSTALL_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
1078
|
+
var SWITCH_GRACE_MS = 10 * 60 * 1e3;
|
|
1079
|
+
var SWITCH_EXIT_GRACE_MS = 5 * 1e3;
|
|
1080
|
+
function decideUpdate(current, cli, cfg, failed, now) {
|
|
1081
|
+
const none = (reason2, target2 = "") => ({ action: "none", target: target2, reason: reason2 });
|
|
1082
|
+
if (cfg.autoUpdate === false) return none("\u672C\u5730\u5DF2\u5173\u95ED\u81EA\u52A8\u5347\u7EA7");
|
|
1083
|
+
if (!isValidVersion(current) || current.startsWith("0.0.0")) return none(`\u5F53\u524D\u662F\u5F00\u53D1\u7248\uFF08${current}\uFF09\uFF0C\u4E0D\u81EA\u52A8\u5347\u7EA7`);
|
|
1084
|
+
const latest = typeof cli?.latestVersion === "string" ? cli.latestVersion.trim() : "";
|
|
1085
|
+
if (!latest) return none("\u670D\u52A1\u7AEF\u6CA1\u6709\u6307\u5B9A\u7248\u672C");
|
|
1086
|
+
if (!isValidVersion(latest)) return none(`\u670D\u52A1\u7AEF\u4E0B\u53D1\u7684\u7248\u672C\u53F7\u4E0D\u5408\u6CD5\uFF1A${latest.slice(0, 64)}`);
|
|
1087
|
+
const minRaw = typeof cli?.minVersion === "string" ? cli.minVersion.trim() : "";
|
|
1088
|
+
const min = isValidVersion(minRaw) ? minRaw : "";
|
|
1089
|
+
let target = "";
|
|
1090
|
+
let reason = "";
|
|
1091
|
+
if (min && semverLt(current, min)) {
|
|
1092
|
+
target = compareVersions(latest, min) >= 0 ? latest : min;
|
|
1093
|
+
reason = `\u5F53\u524D ${current} \u4F4E\u4E8E\u6700\u4F4E\u7248\u672C ${min}\uFF0C\u5F3A\u5236\u5347\u7EA7\u5230 ${target}`;
|
|
1094
|
+
} else if (semverGt(latest, current)) {
|
|
1095
|
+
target = latest;
|
|
1096
|
+
reason = `\u6709\u65B0\u7248\u672C ${latest}\uFF08\u5F53\u524D ${current}\uFF09`;
|
|
1097
|
+
} else {
|
|
1098
|
+
return none(`\u5DF2\u662F\u6700\u65B0\uFF08\u5F53\u524D ${current}\uFF0C\u670D\u52A1\u7AEF\u6307\u5B9A ${latest}\uFF09`);
|
|
1099
|
+
}
|
|
1100
|
+
if (failed.versions.includes(target)) return none(`${target} \u66FE\u7ECF\u56DE\u6EDA\u8FC7\uFF0C\u4E0D\u518D\u81EA\u52A8\u5347\u5230\u5B83`, target);
|
|
1101
|
+
const at = failed.attempts[target];
|
|
1102
|
+
if (typeof at === "number" && now - at < RETRY_AFTER_FAIL_MS) {
|
|
1103
|
+
return none(`${target} \u4E0A\u6B21\u5B89\u88C5\u5931\u8D25\uFF0C${Math.ceil((RETRY_AFTER_FAIL_MS - (now - at)) / 6e4)} \u5206\u949F\u540E\u518D\u8BD5`, target);
|
|
1104
|
+
}
|
|
1105
|
+
return { action: "update", target, reason };
|
|
1106
|
+
}
|
|
1107
|
+
function npmCliCandidates(execPath = process.execPath, platform = process.platform) {
|
|
1108
|
+
if (platform === "win32") return [win32.join(win32.dirname(execPath), "node_modules", "npm", "bin", "npm-cli.js")];
|
|
1109
|
+
const dir = posix.dirname(execPath);
|
|
1110
|
+
return [
|
|
1111
|
+
posix.join(dir, "..", "lib", "node_modules", "npm", "bin", "npm-cli.js"),
|
|
1112
|
+
posix.join(dir, "..", "libexec", "lib", "node_modules", "npm", "bin", "npm-cli.js")
|
|
1113
|
+
];
|
|
1114
|
+
}
|
|
1115
|
+
function npmCliPath(execPath = process.execPath, platform = process.platform, exists = existsSync6) {
|
|
1116
|
+
return npmCliCandidates(execPath, platform).find((p) => exists(p)) ?? null;
|
|
1117
|
+
}
|
|
1118
|
+
function npmInstallArgs(npmCli, dir, target, registry) {
|
|
1119
|
+
if (!isValidVersion(target)) throw new Error(`\u4E0D\u662F\u5408\u6CD5\u7684\u7248\u672C\u53F7\uFF1A${target}`);
|
|
1120
|
+
if (!REGISTRIES.includes(registry)) throw new Error(`\u4E0D\u5141\u8BB8\u7684\u6E90\uFF1A${registry}`);
|
|
1121
|
+
return [npmCli, "install", "--prefix", dir, "--no-audit", "--no-fund", "--omit=dev", `${PACKAGE_NAME}@${target}`, "--registry", registry];
|
|
1122
|
+
}
|
|
1123
|
+
var defaultExec = (file, args, opts) => new Promise((resolve6, reject) => {
|
|
1124
|
+
execFile2(file, args, { ...opts, maxBuffer: 16 * 1024 * 1024 }, (err, _stdout, stderr) => {
|
|
1125
|
+
if (err) {
|
|
1126
|
+
const tail = String(stderr || "").trim().split("\n").slice(-3).join(" | ");
|
|
1127
|
+
reject(new Error(`${err.message.split("\n")[0]}${tail ? ` \u2014 ${tail.slice(0, 300)}` : ""}`));
|
|
1128
|
+
} else resolve6();
|
|
1129
|
+
});
|
|
1130
|
+
});
|
|
1131
|
+
function ptyLoadsIn(dir, execPath = process.execPath) {
|
|
1132
|
+
try {
|
|
1133
|
+
execFileSync4(execPath, ["-e", "const m=require(process.argv[1]);if(typeof m.spawn!=='function')process.exit(3)", join6(dir, "node_modules", "node-pty")], {
|
|
1134
|
+
timeout: 3e4,
|
|
1135
|
+
windowsHide: true,
|
|
1136
|
+
shell: false,
|
|
1137
|
+
stdio: "ignore"
|
|
1138
|
+
});
|
|
1139
|
+
return true;
|
|
1140
|
+
} catch {
|
|
1141
|
+
return false;
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
async function installVersion(target, deps) {
|
|
1145
|
+
const execPath = deps.execPath ?? process.execPath;
|
|
1146
|
+
const verifyCli = deps.verifyCli ?? ((p, v) => cliReportsVersion(p, v, execPath));
|
|
1147
|
+
const dir = versionDir(target, deps.home);
|
|
1148
|
+
const cliPath = versionCliPath(target, deps.home);
|
|
1149
|
+
const oldPtyOk = deps.oldPtyOk ?? (() => loadPty() !== null);
|
|
1150
|
+
const newPtyOk = deps.newPtyOk ?? ((d) => ptyLoadsIn(d, execPath));
|
|
1151
|
+
const verified = () => installedPackageVersion(dir) === target && existsSync6(cliPath) && verifyCli(cliPath, target) && (!oldPtyOk() || newPtyOk(dir));
|
|
1152
|
+
if (verified()) {
|
|
1153
|
+
deps.log(`${target} \u5DF2\u5728\u672C\u673A\u88C5\u597D\uFF0C\u76F4\u63A5\u7528`);
|
|
1154
|
+
return cliPath;
|
|
1155
|
+
}
|
|
1156
|
+
const platform = deps.platform ?? process.platform;
|
|
1157
|
+
const npmCli = npmCliPath(execPath, platform);
|
|
1158
|
+
if (!npmCli) throw new Error(`\u627E\u4E0D\u5230 node \u81EA\u5E26\u7684 npm\uFF08${npmCliCandidates(execPath, platform).join(" / ")}\uFF09`);
|
|
1159
|
+
const exec = deps.exec ?? defaultExec;
|
|
1160
|
+
const errors = [];
|
|
1161
|
+
for (const registry of REGISTRIES) {
|
|
1162
|
+
rmSync3(dir, { recursive: true, force: true });
|
|
1163
|
+
mkdirSync5(dir, { recursive: true });
|
|
1164
|
+
deps.log(`\u5B89\u88C5 ${PACKAGE_NAME}@${target}\uFF08\u6E90 ${registry}\uFF09`);
|
|
1165
|
+
try {
|
|
1166
|
+
await exec(execPath, npmInstallArgs(npmCli, dir, target, registry), {
|
|
1167
|
+
timeout: INSTALL_TIMEOUT_MS,
|
|
1168
|
+
shell: false,
|
|
1169
|
+
windowsHide: true,
|
|
1170
|
+
env: { ...process.env, npm_config_update_notifier: "false", npm_config_fund: "false" }
|
|
1171
|
+
});
|
|
1172
|
+
} catch (e) {
|
|
1173
|
+
errors.push(`${registry} ${e.message}`);
|
|
1174
|
+
deps.log(`\u4ECE ${registry} \u5B89\u88C5\u5931\u8D25\uFF1A${e.message}`);
|
|
1175
|
+
continue;
|
|
1176
|
+
}
|
|
1177
|
+
const got = installedPackageVersion(dir);
|
|
1178
|
+
if (got !== target) {
|
|
1179
|
+
errors.push(`${registry} \u88C5\u51FA\u6765\u7684\u7248\u672C\u662F ${got ?? "(\u65E0)"}`);
|
|
1180
|
+
continue;
|
|
1181
|
+
}
|
|
1182
|
+
if (!verifyCli(cliPath, target)) {
|
|
1183
|
+
errors.push(`${registry} \u88C5\u597D\u7684 cli.js --version \u4E0D\u662F ${target}`);
|
|
1184
|
+
continue;
|
|
1185
|
+
}
|
|
1186
|
+
if (oldPtyOk() && !newPtyOk(dir)) {
|
|
1187
|
+
errors.push(`${registry} \u88C5\u597D\u7684\u7248\u672C\u52A0\u8F7D\u4E0D\u4E86 node-pty`);
|
|
1188
|
+
continue;
|
|
398
1189
|
}
|
|
399
|
-
return
|
|
400
|
-
} catch (e) {
|
|
401
|
-
return { ok: false, message: `\u53D6\u6D88\u5F00\u673A\u81EA\u542F\u65F6\u51FA\u9519\uFF08${e.message}\uFF09\uFF0C\u53EF\u80FD\u9700\u8981\u624B\u52A8\u6E05\u7406` };
|
|
1190
|
+
return cliPath;
|
|
402
1191
|
}
|
|
1192
|
+
rmSync3(dir, { recursive: true, force: true });
|
|
1193
|
+
throw new Error(errors.join("\uFF1B"));
|
|
403
1194
|
}
|
|
404
|
-
|
|
405
|
-
|
|
1195
|
+
var FROM_VERSION_ENV = "DESKBUDDY_UPDATE_FROM_VERSION";
|
|
1196
|
+
var FROM_CLI_ENV = "DESKBUDDY_UPDATE_FROM_CLI";
|
|
1197
|
+
function switchFromEnv(env = process.env) {
|
|
1198
|
+
const version = env[FROM_VERSION_ENV] ?? "";
|
|
1199
|
+
const cliPath = env[FROM_CLI_ENV] ?? "";
|
|
1200
|
+
return isValidVersion(version) && cliPath ? { version, cliPath } : null;
|
|
1201
|
+
}
|
|
1202
|
+
function spawnSwitch(cliPath, logFile, from, onExit, execPath = process.execPath) {
|
|
1203
|
+
let fd = "ignore";
|
|
1204
|
+
try {
|
|
1205
|
+
mkdirSync5(dirname5(logFile), { recursive: true });
|
|
1206
|
+
fd = openSync(logFile, "a");
|
|
1207
|
+
} catch {
|
|
1208
|
+
fd = "ignore";
|
|
1209
|
+
}
|
|
1210
|
+
const child = spawn2(execPath, [cliPath, "update", "--auto"], {
|
|
1211
|
+
detached: true,
|
|
1212
|
+
stdio: ["ignore", fd, fd],
|
|
1213
|
+
windowsHide: true,
|
|
1214
|
+
shell: false,
|
|
1215
|
+
env: { ...process.env, [FROM_VERSION_ENV]: from.version, [FROM_CLI_ENV]: from.cliPath }
|
|
1216
|
+
});
|
|
1217
|
+
child.on("exit", (code) => onExit?.(code));
|
|
1218
|
+
child.on("error", () => onExit?.(null));
|
|
1219
|
+
child.unref();
|
|
1220
|
+
if (typeof fd === "number") closeSync(fd);
|
|
406
1221
|
}
|
|
1222
|
+
var AutoUpdater = class {
|
|
1223
|
+
#d;
|
|
1224
|
+
#running = false;
|
|
1225
|
+
#stopped = false;
|
|
1226
|
+
#switched = null;
|
|
1227
|
+
#first = null;
|
|
1228
|
+
#timer = null;
|
|
1229
|
+
constructor(deps) {
|
|
1230
|
+
this.#d = deps;
|
|
1231
|
+
}
|
|
1232
|
+
start() {
|
|
1233
|
+
this.#first = setTimeout(() => void this.check(), this.#d.firstCheckMs ?? FIRST_CHECK_MS);
|
|
1234
|
+
this.#first.unref?.();
|
|
1235
|
+
this.#timer = setInterval(() => void this.check(), this.#d.intervalMs ?? CHECK_INTERVAL_MS);
|
|
1236
|
+
this.#timer.unref?.();
|
|
1237
|
+
}
|
|
1238
|
+
stop() {
|
|
1239
|
+
this.#stopped = true;
|
|
1240
|
+
if (this.#first) clearTimeout(this.#first);
|
|
1241
|
+
if (this.#timer) clearInterval(this.#timer);
|
|
1242
|
+
}
|
|
1243
|
+
#now() {
|
|
1244
|
+
return this.#d.now ? this.#d.now() : Date.now();
|
|
1245
|
+
}
|
|
1246
|
+
/** 查一次并在需要时走完整个升级。正在进行中的一轮没结束就跳过。返回这一轮的结局(给测试和日志) */
|
|
1247
|
+
async check() {
|
|
1248
|
+
if (this.#switched) {
|
|
1249
|
+
if (this.#now() - this.#switched.at < (this.#d.switchGraceMs ?? SWITCH_GRACE_MS)) return "busy";
|
|
1250
|
+
this.#d.log(`\u4EA4\u7ED9 ${this.#switched.target} \u5207\u6362\u4E4B\u540E\u5B88\u62A4\u8FDB\u7A0B\u4E00\u76F4\u6CA1\u88AB\u91CD\u542F\uFF0C\u89C6\u4E3A\u5207\u6362\u5931\u8D25`);
|
|
1251
|
+
(this.#d.recordFailure ?? ((t, n) => recordAttemptFailure(t, n)))(this.#switched.target, this.#now());
|
|
1252
|
+
this.#switched = null;
|
|
1253
|
+
this.#d.endDrain?.();
|
|
1254
|
+
}
|
|
1255
|
+
if (this.#running) return "busy";
|
|
1256
|
+
this.#running = true;
|
|
1257
|
+
try {
|
|
1258
|
+
return await this.#check();
|
|
1259
|
+
} catch (e) {
|
|
1260
|
+
this.#d.log(`\u81EA\u52A8\u5347\u7EA7\u51FA\u9519\uFF1A${e?.stack ?? String(e)}`);
|
|
1261
|
+
return "failed";
|
|
1262
|
+
} finally {
|
|
1263
|
+
this.#running = false;
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
async #check() {
|
|
1267
|
+
const log2 = this.#d.log;
|
|
1268
|
+
let policy;
|
|
1269
|
+
try {
|
|
1270
|
+
policy = await this.#d.fetchPolicy();
|
|
1271
|
+
} catch (e) {
|
|
1272
|
+
log2(`\u7248\u672C\u68C0\u67E5\u5931\u8D25\uFF1A${e.message}`);
|
|
1273
|
+
return "failed";
|
|
1274
|
+
}
|
|
1275
|
+
const decision = decideUpdate(
|
|
1276
|
+
this.#d.currentVersion,
|
|
1277
|
+
policy,
|
|
1278
|
+
this.#d.config(),
|
|
1279
|
+
(this.#d.failed ?? (() => readFailed()))(),
|
|
1280
|
+
this.#now()
|
|
1281
|
+
);
|
|
1282
|
+
log2(`\u7248\u672C\u68C0\u67E5\uFF1A${decision.action === "update" ? "\u5347\u7EA7" : "\u4E0D\u5347\u7EA7"} \u2014\u2014 ${decision.reason}`);
|
|
1283
|
+
if (decision.action !== "update") return "none";
|
|
1284
|
+
const target = decision.target;
|
|
1285
|
+
let cliPath;
|
|
1286
|
+
try {
|
|
1287
|
+
cliPath = await this.#d.install(target);
|
|
1288
|
+
} catch (e) {
|
|
1289
|
+
log2(`\u5B89\u88C5 ${target} \u5931\u8D25\uFF1A${e.message}\uFF1B6 \u5C0F\u65F6\u5185\u4E0D\u518D\u91CD\u8BD5\u8FD9\u4E00\u7248`);
|
|
1290
|
+
(this.#d.recordFailure ?? ((t, n) => recordAttemptFailure(t, n)))(target, this.#now());
|
|
1291
|
+
return "failed";
|
|
1292
|
+
}
|
|
1293
|
+
log2(`${target} \u5DF2\u88C5\u597D\u5E76\u6821\u9A8C\u901A\u8FC7\uFF0C\u7B49\u5B88\u62A4\u8FDB\u7A0B\u7A7A\u95F2\u540E\u5207\u6362`);
|
|
1294
|
+
const sleep2 = this.#d.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms).unref?.()));
|
|
1295
|
+
const deadline = this.#now() + (this.#d.idleMaxWaitMs ?? IDLE_MAX_WAIT_MS);
|
|
1296
|
+
let lastWhy = "";
|
|
1297
|
+
for (; ; ) {
|
|
1298
|
+
if (this.#stopped) return "stopped";
|
|
1299
|
+
const st = this.#d.idle();
|
|
1300
|
+
if (st.idle) break;
|
|
1301
|
+
if (st.why !== lastWhy) {
|
|
1302
|
+
log2(`\u7B49\u5F85\u7A7A\u95F2\uFF1A${st.why}`);
|
|
1303
|
+
lastWhy = st.why;
|
|
1304
|
+
}
|
|
1305
|
+
if (this.#now() >= deadline) {
|
|
1306
|
+
log2(`\u7B49\u4E86 24 \u5C0F\u65F6\u4ECD\u4E0D\u7A7A\u95F2\uFF0C\u653E\u5F03\u8FD9\u4E00\u8F6E\uFF0C\u4E0B\u6B21\u68C0\u67E5\u518D\u5224\u5B9A`);
|
|
1307
|
+
return "timeout";
|
|
1308
|
+
}
|
|
1309
|
+
await sleep2(this.#d.idlePollMs ?? IDLE_POLL_MS);
|
|
1310
|
+
}
|
|
1311
|
+
if (this.#stopped) return "stopped";
|
|
1312
|
+
this.#d.beginDrain?.();
|
|
1313
|
+
log2(`\u5B88\u62A4\u8FDB\u7A0B\u7A7A\u95F2\uFF0C\u7528 ${target} \u6267\u884C update --auto \u5B8C\u6210\u5207\u6362\uFF08\u5B88\u62A4\u8FDB\u7A0B\u4F1A\u88AB\u91CD\u542F\uFF09`);
|
|
1314
|
+
const mine = { target, at: this.#now() };
|
|
1315
|
+
this.#switched = mine;
|
|
1316
|
+
const onExit = (code) => {
|
|
1317
|
+
const t = setTimeout(() => {
|
|
1318
|
+
if (this.#switched !== mine || this.#stopped) return;
|
|
1319
|
+
this.#switched = null;
|
|
1320
|
+
this.#d.endDrain?.();
|
|
1321
|
+
log2(`\u5207\u6362\u5B50\u8FDB\u7A0B\u5DF2\u9000\u51FA\uFF08\u9000\u51FA\u7801 ${code ?? "\u65E0"}\uFF09\u800C\u5B88\u62A4\u8FDB\u7A0B\u4ECD\u5728\u8FD0\u884C\uFF0C\u89C6\u4E3A\u5207\u6362\u5230 ${target} \u5931\u8D25\uFF0C\u6062\u590D\u63A5\u6307\u4EE4`);
|
|
1322
|
+
(this.#d.recordFailure ?? ((t2, n) => recordAttemptFailure(t2, n)))(target, this.#now());
|
|
1323
|
+
}, this.#d.switchExitGraceMs ?? SWITCH_EXIT_GRACE_MS);
|
|
1324
|
+
t.unref?.();
|
|
1325
|
+
};
|
|
1326
|
+
try {
|
|
1327
|
+
this.#d.switchTo(cliPath, target, onExit);
|
|
1328
|
+
} catch (e) {
|
|
1329
|
+
this.#switched = null;
|
|
1330
|
+
this.#d.endDrain?.();
|
|
1331
|
+
log2(`\u5207\u6362\u5230 ${target} \u5931\u8D25\uFF1A${e.message}`);
|
|
1332
|
+
(this.#d.recordFailure ?? ((t, n) => recordAttemptFailure(t, n)))(target, this.#now());
|
|
1333
|
+
return "failed";
|
|
1334
|
+
}
|
|
1335
|
+
return "switched";
|
|
1336
|
+
}
|
|
1337
|
+
};
|
|
407
1338
|
|
|
408
1339
|
// src/daemon/index.ts
|
|
409
1340
|
import { randomBytes as randomBytes7 } from "crypto";
|
|
410
|
-
import { existsSync as
|
|
411
|
-
import { basename as
|
|
1341
|
+
import { existsSync as existsSync13 } from "fs";
|
|
1342
|
+
import { basename as basename7 } from "path";
|
|
412
1343
|
import WebSocket2 from "ws";
|
|
413
1344
|
|
|
414
1345
|
// src/adapters/ocPlugin.ts
|
|
415
|
-
import { existsSync as
|
|
416
|
-
import { dirname as
|
|
1346
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync6, readFileSync as readFileSync6, renameSync as renameSync4, unlinkSync, writeFileSync as writeFileSync5 } from "fs";
|
|
1347
|
+
import { dirname as dirname6, join as join7 } from "path";
|
|
417
1348
|
import { pathToFileURL } from "url";
|
|
418
1349
|
var OC_MARKER = "// deskbuddy:opencode-plugin";
|
|
419
1350
|
function ocPluginSource(pluginModule, nodePath, platform = process.platform) {
|
|
@@ -427,26 +1358,26 @@ function ocPluginSource(pluginModule, nodePath, platform = process.platform) {
|
|
|
427
1358
|
].join("\n");
|
|
428
1359
|
}
|
|
429
1360
|
function ocPluginModule(hookPath) {
|
|
430
|
-
return
|
|
1361
|
+
return join7(dirname6(hookPath), "opencode.mjs");
|
|
431
1362
|
}
|
|
432
1363
|
function installOcPlugin(pluginPath, hookPath, nodePath) {
|
|
433
|
-
if (
|
|
1364
|
+
if (existsSync7(pluginPath) && !readFileSync6(pluginPath, "utf8").includes(OC_MARKER)) {
|
|
434
1365
|
throw new Error(`${pluginPath} \u5DF2\u5B58\u5728\u4E14\u4E0D\u662F DeskBuddy \u751F\u6210\u7684\uFF0C\u8BF7\u5148\u632A\u8D70\u518D\u91CD\u8BD5`);
|
|
435
1366
|
}
|
|
436
|
-
|
|
1367
|
+
mkdirSync6(dirname6(pluginPath), { recursive: true });
|
|
437
1368
|
const tmp = pluginPath + ".deskbuddy.tmp";
|
|
438
|
-
|
|
439
|
-
|
|
1369
|
+
writeFileSync5(tmp, ocPluginSource(ocPluginModule(hookPath), nodePath), "utf8");
|
|
1370
|
+
renameSync4(tmp, pluginPath);
|
|
440
1371
|
}
|
|
441
1372
|
function uninstallOcPlugin(pluginPath) {
|
|
442
1373
|
try {
|
|
443
|
-
if (
|
|
1374
|
+
if (readFileSync6(pluginPath, "utf8").includes(OC_MARKER)) unlinkSync(pluginPath);
|
|
444
1375
|
} catch {
|
|
445
1376
|
}
|
|
446
1377
|
}
|
|
447
1378
|
function isOcPluginInstalled(pluginPath) {
|
|
448
1379
|
try {
|
|
449
|
-
return
|
|
1380
|
+
return readFileSync6(pluginPath, "utf8").includes(OC_MARKER);
|
|
450
1381
|
} catch {
|
|
451
1382
|
return false;
|
|
452
1383
|
}
|
|
@@ -454,16 +1385,16 @@ function isOcPluginInstalled(pluginPath) {
|
|
|
454
1385
|
var OC_NOTICE = " \u5DF2\u5F00\u7740\u7684 opencode \u7A97\u53E3\u91CD\u5F00\u540E\u751F\u6548";
|
|
455
1386
|
|
|
456
1387
|
// src/core/state.ts
|
|
457
|
-
import { existsSync as
|
|
458
|
-
import { join as
|
|
1388
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync8, readdirSync as readdirSync3, readFileSync as readFileSync8, renameSync as renameSync6, rmSync as rmSync5, writeFileSync as writeFileSync7 } from "fs";
|
|
1389
|
+
import { join as join9 } from "path";
|
|
459
1390
|
|
|
460
1391
|
// src/core/process.ts
|
|
461
|
-
import { execFileSync as
|
|
1392
|
+
import { execFileSync as execFileSync5 } from "child_process";
|
|
462
1393
|
var WIN = process.platform === "win32";
|
|
463
1394
|
function processName(pid) {
|
|
464
1395
|
if (WIN) return winProcs()?.find((p) => p.pid === pid)?.name ?? null;
|
|
465
1396
|
try {
|
|
466
|
-
const out =
|
|
1397
|
+
const out = execFileSync5("ps", ["-p", String(pid), "-o", "comm="], {
|
|
467
1398
|
encoding: "utf8",
|
|
468
1399
|
stdio: ["ignore", "pipe", "ignore"]
|
|
469
1400
|
});
|
|
@@ -492,27 +1423,27 @@ function isAlive(pid, expectName) {
|
|
|
492
1423
|
}
|
|
493
1424
|
|
|
494
1425
|
// src/core/ended.ts
|
|
495
|
-
import { closeSync, mkdirSync as
|
|
496
|
-
import { join as
|
|
1426
|
+
import { closeSync as closeSync2, mkdirSync as mkdirSync7, openSync as openSync2, readdirSync as readdirSync2, readFileSync as readFileSync7, readSync, realpathSync, renameSync as renameSync5, rmSync as rmSync4, statSync as statSync2, writeFileSync as writeFileSync6 } from "fs";
|
|
1427
|
+
import { join as join8 } from "path";
|
|
497
1428
|
var ENDED_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
498
1429
|
var SID_RE = /^[A-Za-z0-9_-]{1,64}$/;
|
|
499
1430
|
function endedDir(home = deskbuddyHome()) {
|
|
500
|
-
return
|
|
1431
|
+
return join8(home, "ended");
|
|
501
1432
|
}
|
|
502
1433
|
function writeEnded(s, now = Date.now()) {
|
|
503
1434
|
if (!SID_RE.test(s.sessionId)) return;
|
|
504
1435
|
try {
|
|
505
1436
|
const dir = endedDir();
|
|
506
|
-
|
|
1437
|
+
mkdirSync7(dir, { recursive: true, mode: 448 });
|
|
507
1438
|
const rec = { sessionId: s.sessionId, tool: s.tool, cwd: s.cwd ?? "", interactive: s.interactive === true, endedAt: now };
|
|
508
|
-
const file =
|
|
1439
|
+
const file = join8(dir, `${s.sessionId}.json`);
|
|
509
1440
|
const tmp = `${file}.${process.pid}.tmp`;
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
for (const n of
|
|
513
|
-
const f =
|
|
1441
|
+
writeFileSync6(tmp, JSON.stringify(rec), { encoding: "utf8", mode: 384 });
|
|
1442
|
+
renameSync5(tmp, file);
|
|
1443
|
+
for (const n of readdirSync2(dir)) {
|
|
1444
|
+
const f = join8(dir, n);
|
|
514
1445
|
try {
|
|
515
|
-
if (now -
|
|
1446
|
+
if (now - statSync2(f).mtimeMs > ENDED_TTL_MS) rmSync4(f, { force: true });
|
|
516
1447
|
} catch {
|
|
517
1448
|
}
|
|
518
1449
|
}
|
|
@@ -522,7 +1453,7 @@ function writeEnded(s, now = Date.now()) {
|
|
|
522
1453
|
function readEnded(sessionId, now = Date.now()) {
|
|
523
1454
|
if (!SID_RE.test(sessionId)) return null;
|
|
524
1455
|
try {
|
|
525
|
-
const r = JSON.parse(
|
|
1456
|
+
const r = JSON.parse(readFileSync7(join8(endedDir(), `${sessionId}.json`), "utf8"));
|
|
526
1457
|
if (!r || r.sessionId !== sessionId || typeof r.endedAt !== "number" || now - r.endedAt > ENDED_TTL_MS) return null;
|
|
527
1458
|
return r;
|
|
528
1459
|
} catch {
|
|
@@ -537,14 +1468,14 @@ function projectDirName(cwd) {
|
|
|
537
1468
|
function readChunk(file, fromEnd, max) {
|
|
538
1469
|
let fd;
|
|
539
1470
|
try {
|
|
540
|
-
const size =
|
|
1471
|
+
const size = statSync2(file).size;
|
|
541
1472
|
const len = Math.min(size, max);
|
|
542
1473
|
const buf = Buffer.alloc(len);
|
|
543
|
-
fd =
|
|
1474
|
+
fd = openSync2(file, "r");
|
|
544
1475
|
readSync(fd, buf, 0, len, fromEnd ? size - len : 0);
|
|
545
1476
|
return buf.toString("utf8");
|
|
546
1477
|
} finally {
|
|
547
|
-
if (fd !== void 0)
|
|
1478
|
+
if (fd !== void 0) closeSync2(fd);
|
|
548
1479
|
}
|
|
549
1480
|
}
|
|
550
1481
|
function cwdsIn(text) {
|
|
@@ -563,14 +1494,14 @@ function claudeTranscriptCwd(sessionId, home = claudeHome()) {
|
|
|
563
1494
|
if (!SID_RE.test(sessionId)) return "";
|
|
564
1495
|
let dirs = [];
|
|
565
1496
|
try {
|
|
566
|
-
dirs =
|
|
1497
|
+
dirs = readdirSync2(join8(home, "projects"));
|
|
567
1498
|
} catch {
|
|
568
1499
|
return "";
|
|
569
1500
|
}
|
|
570
1501
|
for (const d of dirs) {
|
|
571
|
-
const file =
|
|
1502
|
+
const file = join8(home, "projects", d, `${sessionId}.jsonl`);
|
|
572
1503
|
try {
|
|
573
|
-
|
|
1504
|
+
statSync2(file);
|
|
574
1505
|
} catch {
|
|
575
1506
|
continue;
|
|
576
1507
|
}
|
|
@@ -606,7 +1537,7 @@ function resolveEndedCwd(sessionId, tool, d) {
|
|
|
606
1537
|
}
|
|
607
1538
|
function isDirSync(p) {
|
|
608
1539
|
try {
|
|
609
|
-
return
|
|
1540
|
+
return statSync2(p).isDirectory();
|
|
610
1541
|
} catch {
|
|
611
1542
|
return false;
|
|
612
1543
|
}
|
|
@@ -614,15 +1545,15 @@ function isDirSync(p) {
|
|
|
614
1545
|
|
|
615
1546
|
// src/core/state.ts
|
|
616
1547
|
function stateDir() {
|
|
617
|
-
return
|
|
1548
|
+
return join9(deskbuddyHome(), "state");
|
|
618
1549
|
}
|
|
619
1550
|
function statePath(sessionId) {
|
|
620
1551
|
const safe = sessionId.replace(/[^A-Za-z0-9_-]/g, "");
|
|
621
|
-
return
|
|
1552
|
+
return join9(stateDir(), `${safe || "unknown"}.json`);
|
|
622
1553
|
}
|
|
623
1554
|
function readState(sessionId) {
|
|
624
1555
|
try {
|
|
625
|
-
return JSON.parse(
|
|
1556
|
+
return JSON.parse(readFileSync8(statePath(sessionId), "utf8"));
|
|
626
1557
|
} catch {
|
|
627
1558
|
return null;
|
|
628
1559
|
}
|
|
@@ -633,293 +1564,62 @@ function retireState(s, now = Date.now()) {
|
|
|
633
1564
|
}
|
|
634
1565
|
function removeState(sessionId) {
|
|
635
1566
|
try {
|
|
636
|
-
|
|
637
|
-
} catch {
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
|
-
function readAliveStates(tool) {
|
|
641
|
-
if (!existsSync4(stateDir())) return [];
|
|
642
|
-
const alive = [];
|
|
643
|
-
for (const file of readdirSync2(stateDir())) {
|
|
644
|
-
if (!file.endsWith(".json")) continue;
|
|
645
|
-
let s;
|
|
646
|
-
try {
|
|
647
|
-
s = JSON.parse(readFileSync5(join5(stateDir(), file), "utf8"));
|
|
648
|
-
} catch {
|
|
649
|
-
rmSync3(join5(stateDir(), file), { force: true });
|
|
650
|
-
continue;
|
|
651
|
-
}
|
|
652
|
-
if (s.tool !== tool) continue;
|
|
653
|
-
if (s.pid && !isAlive(s.pid, s.pidName)) {
|
|
654
|
-
retireState(s);
|
|
655
|
-
continue;
|
|
656
|
-
}
|
|
657
|
-
alive.push(s);
|
|
658
|
-
}
|
|
659
|
-
return alive;
|
|
660
|
-
}
|
|
661
|
-
function noticeDir() {
|
|
662
|
-
return join5(deskbuddyHome(), "notice");
|
|
663
|
-
}
|
|
664
|
-
function writeNotice(sessionId, text, now = Date.now()) {
|
|
665
|
-
try {
|
|
666
|
-
const safe = sessionId.replace(/[^A-Za-z0-9_-]/g, "");
|
|
667
|
-
if (!safe) return;
|
|
668
|
-
mkdirSync5(noticeDir(), { recursive: true });
|
|
669
|
-
const file = join5(noticeDir(), `${safe}.json`);
|
|
670
|
-
writeFileSync5(file + ".tmp", JSON.stringify({ text, at: now }), "utf8");
|
|
671
|
-
renameSync4(file + ".tmp", file);
|
|
672
|
-
} catch {
|
|
673
|
-
}
|
|
674
|
-
}
|
|
675
|
-
var NOTICE_TTL_MS = 60 * 60 * 1e3;
|
|
676
|
-
var ALIAS_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
677
|
-
function resolveAlias(sessionId, now = Date.now()) {
|
|
678
|
-
let map = {};
|
|
679
|
-
try {
|
|
680
|
-
map = JSON.parse(readFileSync5(join5(deskbuddyHome(), "alias.json"), "utf8")) || {};
|
|
681
|
-
} catch {
|
|
682
|
-
return sessionId;
|
|
683
|
-
}
|
|
684
|
-
let cur = sessionId;
|
|
685
|
-
for (let i = 0; i < 5; i++) {
|
|
686
|
-
const a = map[cur];
|
|
687
|
-
if (!a || typeof a.to !== "string" || !a.to || typeof a.at !== "number" || now - a.at >= ALIAS_TTL_MS) break;
|
|
688
|
-
cur = a.to;
|
|
689
|
-
}
|
|
690
|
-
return cur;
|
|
691
|
-
}
|
|
692
|
-
|
|
693
|
-
// src/core/api.ts
|
|
694
|
-
import { execFileSync as execFileSync3 } from "child_process";
|
|
695
|
-
import { createHash, randomBytes, randomUUID as randomUUID2 } from "crypto";
|
|
696
|
-
import { readFileSync as readFileSync7 } from "fs";
|
|
697
|
-
import { release } from "os";
|
|
698
|
-
|
|
699
|
-
// src/core/hookFile.ts
|
|
700
|
-
import { copyFileSync, existsSync as existsSync5, mkdirSync as mkdirSync6, readFileSync as readFileSync6, renameSync as renameSync5, writeFileSync as writeFileSync6 } from "fs";
|
|
701
|
-
import { createRequire } from "module";
|
|
702
|
-
import { dirname as dirname3, join as join6 } from "path";
|
|
703
|
-
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
704
|
-
var here = dirname3(fileURLToPath2(import.meta.url));
|
|
705
|
-
function packagedHookPath() {
|
|
706
|
-
const candidates = [
|
|
707
|
-
join6(here, "hook.cjs"),
|
|
708
|
-
// dist/cli.js 旁边(发布形态)
|
|
709
|
-
join6(here, "..", "hook", "hook.cjs"),
|
|
710
|
-
// dist/../hook/
|
|
711
|
-
join6(here, "..", "..", "hook", "hook.cjs")
|
|
712
|
-
// src/core/../../hook/(开发形态)
|
|
713
|
-
];
|
|
714
|
-
for (const c of candidates) {
|
|
715
|
-
if (existsSync5(c)) return c;
|
|
716
|
-
}
|
|
717
|
-
throw new Error("\u627E\u4E0D\u5230\u5305\u5185\u7684 hook.cjs\uFF0C\u5B89\u88C5\u5305\u53EF\u80FD\u4E0D\u5B8C\u6574\uFF0C\u8BF7\u91CD\u65B0\u5B89\u88C5 deskbuddy");
|
|
718
|
-
}
|
|
719
|
-
function packageVersion() {
|
|
720
|
-
const require2 = createRequire(import.meta.url);
|
|
721
|
-
for (const c of ["../package.json", "../../package.json", "../../../package.json"]) {
|
|
722
|
-
try {
|
|
723
|
-
const pkg = require2(c);
|
|
724
|
-
if (pkg?.name === "deskbuddy" && pkg.version) return pkg.version;
|
|
725
|
-
} catch {
|
|
726
|
-
}
|
|
727
|
-
}
|
|
728
|
-
return "0.0.0-dev";
|
|
729
|
-
}
|
|
730
|
-
function hookVersionPath(target = installedHookPath()) {
|
|
731
|
-
return join6(dirname3(target), "hook-version");
|
|
732
|
-
}
|
|
733
|
-
function installedHookVersion(target = installedHookPath()) {
|
|
734
|
-
try {
|
|
735
|
-
return readFileSync6(hookVersionPath(target), "utf8").trim() || null;
|
|
736
|
-
} catch {
|
|
737
|
-
return null;
|
|
738
|
-
}
|
|
739
|
-
}
|
|
740
|
-
function atomicCopy(from, to) {
|
|
741
|
-
const tmp = `${to}.${process.pid}.tmp`;
|
|
742
|
-
copyFileSync(from, tmp);
|
|
743
|
-
renameSync5(tmp, to);
|
|
744
|
-
}
|
|
745
|
-
function syncHookFile(opts = {}) {
|
|
746
|
-
const target = opts.target ?? installedHookPath();
|
|
747
|
-
const version = opts.version ?? packageVersion();
|
|
748
|
-
const SIBLINGS = ["gate.cjs", "opencode.mjs", "wake.cjs"];
|
|
749
|
-
const siblingMissing = () => SIBLINGS.some((name) => {
|
|
750
|
-
if (existsSync5(join6(dirname3(target), name))) return false;
|
|
751
|
-
try {
|
|
752
|
-
return existsSync5(join6(dirname3(opts.source ?? packagedHookPath()), name));
|
|
753
|
-
} catch {
|
|
754
|
-
return false;
|
|
755
|
-
}
|
|
756
|
-
});
|
|
757
|
-
if (installedHookVersion(target) === version && existsSync5(target) && !siblingMissing()) return false;
|
|
758
|
-
const source = opts.source ?? packagedHookPath();
|
|
759
|
-
mkdirSync6(dirname3(target), { recursive: true });
|
|
760
|
-
atomicCopy(source, target);
|
|
761
|
-
for (const name of SIBLINGS) {
|
|
762
|
-
const from = join6(dirname3(source), name);
|
|
763
|
-
if (existsSync5(from)) atomicCopy(from, join6(dirname3(target), name));
|
|
764
|
-
}
|
|
765
|
-
writeFileSync6(hookVersionPath(target), version, "utf8");
|
|
766
|
-
return true;
|
|
767
|
-
}
|
|
768
|
-
|
|
769
|
-
// src/core/api.ts
|
|
770
|
-
var SERVICES = {
|
|
771
|
-
authCode: "com.shifeng.api.service.api.v2.deskbuddy.deskbuddyauthcodeapi",
|
|
772
|
-
authToken: "com.shifeng.api.service.api.v2.deskbuddy.deskbuddyauthtokenapi",
|
|
773
|
-
sessionReport: "com.shifeng.api.service.api.v2.deskbuddy.deskbuddysessionreportapi",
|
|
774
|
-
toolRegistry: "com.shifeng.api.service.api.v2.deskbuddy.deskbuddytoolregistryapi",
|
|
775
|
-
/** App 扫码确认时调的,CLI 用不到,列在这里只为对照 */
|
|
776
|
-
confirmAuth: "com.shifeng.api.service.api.v2.deskbuddy.deskbuddyconfirmauthapi"
|
|
777
|
-
};
|
|
778
|
-
var DIRS_SERVICE = "com.shifeng.api.service.api.v2.deskbuddy.deskbuddyclientdirsreportapi";
|
|
779
|
-
var INSTALL_REDEEM_SERVICE = "com.shifeng.api.service.api.v2.deskbuddy.deskbuddyinstallredeemapi";
|
|
780
|
-
var CMD_RESULT_SERVICE = "com.shifeng.api.service.api.v2.deskbuddy.deskbuddycommandresultapi";
|
|
781
|
-
var APPROVAL_SERVICES = {
|
|
782
|
-
create: "com.shifeng.api.service.api.v2.deskbuddy.deskbuddyapprovalcreateapi",
|
|
783
|
-
cancel: "com.shifeng.api.service.api.v2.deskbuddy.deskbuddyapprovalcancelapi",
|
|
784
|
-
sync: "com.shifeng.api.service.api.v2.deskbuddy.deskbuddyapprovalsyncapi"
|
|
785
|
-
};
|
|
786
|
-
var APP_ID = process.env.DESKBUDDY_APPID || "deskbuddy-cli";
|
|
787
|
-
var APP_SECRET = process.env.DESKBUDDY_APPSECRET || "3aeb6e0afc144e188dd3ede5c7865643";
|
|
788
|
-
var GATEWAY_VERSION = "1.0";
|
|
789
|
-
var DEFAULT_TIMEOUT_MS = 1e4;
|
|
790
|
-
function sign(p) {
|
|
791
|
-
const source = [p.appId, p.appSecret, p.timestamp, p.deviceId, p.deviceName, p.nonce, p.body].join("_");
|
|
792
|
-
return createHash("md5").update(source, "utf8").digest("hex");
|
|
793
|
-
}
|
|
794
|
-
function buildGatewayBody(service, data, requestId) {
|
|
795
|
-
return JSON.stringify({
|
|
796
|
-
service,
|
|
797
|
-
version: GATEWAY_VERSION,
|
|
798
|
-
requestId: requestId || randomUUID2(),
|
|
799
|
-
base: { encode: "false" },
|
|
800
|
-
// data 是业务 JSON 序列化后的字符串
|
|
801
|
-
data: JSON.stringify(data ?? {})
|
|
802
|
-
});
|
|
803
|
-
}
|
|
804
|
-
function buildHeaders(cfg, body, opts = {}) {
|
|
805
|
-
const timestamp = opts.timestamp || String(Date.now());
|
|
806
|
-
const nonce = opts.nonce || randomBytes(8).toString("hex");
|
|
807
|
-
return {
|
|
808
|
-
"Content-Type": "application/json;charset=UTF-8",
|
|
809
|
-
"Content-Length": String(Buffer.byteLength(body)),
|
|
810
|
-
"ShiFeng-AppId": APP_ID,
|
|
811
|
-
"ShiFeng-Timestamp": timestamp,
|
|
812
|
-
"ShiFeng-Device-ID": cfg.clientId,
|
|
813
|
-
"ShiFeng-Device-Name": cfg.hostName,
|
|
814
|
-
"ShiFeng-Nonce": nonce,
|
|
815
|
-
"ShiFeng-Signature": sign({
|
|
816
|
-
appId: APP_ID,
|
|
817
|
-
appSecret: APP_SECRET,
|
|
818
|
-
timestamp,
|
|
819
|
-
deviceId: cfg.clientId,
|
|
820
|
-
deviceName: cfg.hostName,
|
|
821
|
-
nonce,
|
|
822
|
-
body
|
|
823
|
-
})
|
|
824
|
-
// 没有 ShiFeng-Token:鉴权靠 clientId ↔ userId 绑定关系,CLI 不持有用户凭证
|
|
825
|
-
};
|
|
826
|
-
}
|
|
827
|
-
function isSuccessCode(code) {
|
|
828
|
-
return code === "0000";
|
|
829
|
-
}
|
|
830
|
-
var ApiError = class extends Error {
|
|
831
|
-
constructor(code, message) {
|
|
832
|
-
super(`${code}: ${message}`);
|
|
833
|
-
this.code = code;
|
|
834
|
-
this.name = "ApiError";
|
|
835
|
-
}
|
|
836
|
-
code;
|
|
837
|
-
};
|
|
838
|
-
function humanOs(platform = process.platform) {
|
|
839
|
-
const known = {
|
|
840
|
-
darwin: "macOS",
|
|
841
|
-
win32: "Windows",
|
|
842
|
-
linux: "Linux"
|
|
843
|
-
};
|
|
844
|
-
return known[platform] ?? platform;
|
|
845
|
-
}
|
|
846
|
-
var defaultOsVersionDeps = {
|
|
847
|
-
runSwVers: () => execFileSync3("sw_vers", ["-productVersion"], {
|
|
848
|
-
encoding: "utf8",
|
|
849
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
850
|
-
timeout: 2e3
|
|
851
|
-
}),
|
|
852
|
-
readOsRelease: () => readFileSync7("/etc/os-release", "utf8"),
|
|
853
|
-
kernelRelease: () => release()
|
|
854
|
-
};
|
|
855
|
-
function productOsVersion(platform = process.platform, deps = defaultOsVersionDeps) {
|
|
856
|
-
try {
|
|
857
|
-
if (platform === "darwin") {
|
|
858
|
-
const v = deps.runSwVers().trim();
|
|
859
|
-
if (v) return v;
|
|
860
|
-
} else if (platform === "linux") {
|
|
861
|
-
const m = /^PRETTY_NAME=(.*)$/m.exec(deps.readOsRelease());
|
|
862
|
-
if (m) {
|
|
863
|
-
const v = m[1].trim().replace(/^(["'])(.*)\1$/, "$2").trim();
|
|
864
|
-
if (v) return v;
|
|
865
|
-
}
|
|
866
|
-
}
|
|
867
|
-
} catch {
|
|
868
|
-
}
|
|
869
|
-
try {
|
|
870
|
-
return deps.kernelRelease();
|
|
1567
|
+
rmSync5(statePath(sessionId), { force: true });
|
|
871
1568
|
} catch {
|
|
872
|
-
return "";
|
|
873
1569
|
}
|
|
874
1570
|
}
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
}
|
|
1571
|
+
function readAliveStates(tool) {
|
|
1572
|
+
if (!existsSync8(stateDir())) return [];
|
|
1573
|
+
const alive = [];
|
|
1574
|
+
for (const file of readdirSync3(stateDir())) {
|
|
1575
|
+
if (!file.endsWith(".json")) continue;
|
|
1576
|
+
let s;
|
|
1577
|
+
try {
|
|
1578
|
+
s = JSON.parse(readFileSync8(join9(stateDir(), file), "utf8"));
|
|
1579
|
+
} catch {
|
|
1580
|
+
rmSync5(join9(stateDir(), file), { force: true });
|
|
1581
|
+
continue;
|
|
1582
|
+
}
|
|
1583
|
+
if (s.tool !== tool) continue;
|
|
1584
|
+
if (s.pid && !isAlive(s.pid, s.pidName)) {
|
|
1585
|
+
retireState(s);
|
|
1586
|
+
continue;
|
|
1587
|
+
}
|
|
1588
|
+
alive.push(s);
|
|
884
1589
|
}
|
|
885
|
-
return
|
|
1590
|
+
return alive;
|
|
886
1591
|
}
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
1592
|
+
function noticeDir() {
|
|
1593
|
+
return join9(deskbuddyHome(), "notice");
|
|
1594
|
+
}
|
|
1595
|
+
function writeNotice(sessionId, text, now = Date.now()) {
|
|
892
1596
|
try {
|
|
893
|
-
const
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
const json = await res.json();
|
|
901
|
-
if (!isSuccessCode(json?.code)) {
|
|
902
|
-
throw new ApiError(String(json?.code), json?.message ?? "");
|
|
903
|
-
}
|
|
904
|
-
return json?.data;
|
|
905
|
-
} finally {
|
|
906
|
-
clearTimeout(timer);
|
|
1597
|
+
const safe = sessionId.replace(/[^A-Za-z0-9_-]/g, "");
|
|
1598
|
+
if (!safe) return;
|
|
1599
|
+
mkdirSync8(noticeDir(), { recursive: true });
|
|
1600
|
+
const file = join9(noticeDir(), `${safe}.json`);
|
|
1601
|
+
writeFileSync7(file + ".tmp", JSON.stringify({ text, at: now }), "utf8");
|
|
1602
|
+
renameSync6(file + ".tmp", file);
|
|
1603
|
+
} catch {
|
|
907
1604
|
}
|
|
908
1605
|
}
|
|
909
|
-
var
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
1606
|
+
var NOTICE_TTL_MS = 60 * 60 * 1e3;
|
|
1607
|
+
var ALIAS_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
1608
|
+
function resolveAlias(sessionId, now = Date.now()) {
|
|
1609
|
+
let map = {};
|
|
1610
|
+
try {
|
|
1611
|
+
map = JSON.parse(readFileSync8(join9(deskbuddyHome(), "alias.json"), "utf8")) || {};
|
|
1612
|
+
} catch {
|
|
1613
|
+
return sessionId;
|
|
1614
|
+
}
|
|
1615
|
+
let cur = sessionId;
|
|
1616
|
+
for (let i = 0; i < 5; i++) {
|
|
1617
|
+
const a = map[cur];
|
|
1618
|
+
if (!a || typeof a.to !== "string" || !a.to || typeof a.at !== "number" || now - a.at >= ALIAS_TTL_MS) break;
|
|
1619
|
+
cur = a.to;
|
|
1620
|
+
}
|
|
1621
|
+
return cur;
|
|
1622
|
+
}
|
|
923
1623
|
|
|
924
1624
|
// src/core/report.ts
|
|
925
1625
|
async function postReport(payload) {
|
|
@@ -930,7 +1630,7 @@ async function postReport(payload) {
|
|
|
930
1630
|
}
|
|
931
1631
|
|
|
932
1632
|
// src/core/snapshot.ts
|
|
933
|
-
import { basename as
|
|
1633
|
+
import { basename as basename3 } from "path";
|
|
934
1634
|
var KEEPALIVE_MS = 2 * 60 * 1e3;
|
|
935
1635
|
var MAX_SESSIONS = 20;
|
|
936
1636
|
var CAPS = ["cmd_result", "window_inject", "resume_ended"];
|
|
@@ -939,7 +1639,7 @@ function buildSnapshot(states, tool, cfg, nowMs, remote) {
|
|
|
939
1639
|
sessionId: s.sessionId,
|
|
940
1640
|
title: s.title,
|
|
941
1641
|
// 隐私:只带目录名,不带全路径——全路径会暴露公司目录结构与用户名
|
|
942
|
-
project:
|
|
1642
|
+
project: basename3(s.project || ""),
|
|
943
1643
|
state: s.state,
|
|
944
1644
|
startedAt: s.startedAt,
|
|
945
1645
|
updatedAt: s.updatedAt,
|
|
@@ -951,24 +1651,60 @@ function buildSnapshot(states, tool, cfg, nowMs, remote) {
|
|
|
951
1651
|
|
|
952
1652
|
// src/core/dirs.ts
|
|
953
1653
|
import { createHmac, randomBytes as randomBytes2 } from "crypto";
|
|
954
|
-
import { chmodSync, existsSync as
|
|
955
|
-
import { basename as
|
|
1654
|
+
import { chmodSync as chmodSync2, existsSync as existsSync9, mkdirSync as mkdirSync9, readFileSync as readFileSync9, writeFileSync as writeFileSync8 } from "fs";
|
|
1655
|
+
import { basename as basename4, join as join10 } from "path";
|
|
1656
|
+
|
|
1657
|
+
// src/core/dirFilter.ts
|
|
1658
|
+
import { homedir as homedir3, tmpdir } from "os";
|
|
1659
|
+
import { posix as posix2, win32 as win322 } from "path";
|
|
1660
|
+
function norm(p, win) {
|
|
1661
|
+
if (win) return win322.resolve(p).replace(/[\\/]+$/, "").toLowerCase();
|
|
1662
|
+
return posix2.resolve(p).replace(/\/+$/, "");
|
|
1663
|
+
}
|
|
1664
|
+
function isUnder(p, base, win) {
|
|
1665
|
+
return p === base || p.startsWith(base + (win ? "\\" : "/"));
|
|
1666
|
+
}
|
|
1667
|
+
function isAutoWorkspace(p, opts = {}) {
|
|
1668
|
+
if (!p) return false;
|
|
1669
|
+
const win = (opts.platform ?? process.platform) === "win32";
|
|
1670
|
+
const P = win ? win322 : posix2;
|
|
1671
|
+
const home = norm(opts.home ?? homedir3(), win);
|
|
1672
|
+
const full = norm(p, win);
|
|
1673
|
+
const name = P.basename(full);
|
|
1674
|
+
const parent = P.dirname(full);
|
|
1675
|
+
if (parent === norm(P.join(home, "WorkBuddy"), win) && /^\d{4}-?\d{2}-?\d{2}/.test(name)) return true;
|
|
1676
|
+
return P.dirname(parent) === norm(P.join(home, "Documents", "Qoder"), win) && /^\d{4}-\d{2}-\d{2}$/.test(P.basename(parent)) && /^[0-9a-f]{8}$/i.test(name);
|
|
1677
|
+
}
|
|
1678
|
+
function isReportableDir(p, opts = {}) {
|
|
1679
|
+
if (!p) return false;
|
|
1680
|
+
const win = (opts.platform ?? process.platform) === "win32";
|
|
1681
|
+
const P = win ? win322 : posix2;
|
|
1682
|
+
const full = norm(p, win);
|
|
1683
|
+
if (full === norm(P.parse(P.resolve(p)).root, win)) return false;
|
|
1684
|
+
if (full === norm(opts.home ?? homedir3(), win)) return false;
|
|
1685
|
+
const tmps = [opts.tmp ?? tmpdir()];
|
|
1686
|
+
if (!win) tmps.push("/tmp", "/private/tmp", "/var/folders", "/private/var/folders");
|
|
1687
|
+
for (const t of tmps) if (t && isUnder(full, norm(t, win), win)) return false;
|
|
1688
|
+
return !isAutoWorkspace(p, opts);
|
|
1689
|
+
}
|
|
1690
|
+
|
|
1691
|
+
// src/core/dirs.ts
|
|
956
1692
|
var DIRS_REPORT_MAX = 10;
|
|
957
1693
|
function dirsPath() {
|
|
958
|
-
return
|
|
1694
|
+
return join10(deskbuddyHome(), "dirs.json");
|
|
959
1695
|
}
|
|
960
1696
|
function dirSecret() {
|
|
961
|
-
const file =
|
|
1697
|
+
const file = join10(deskbuddyHome(), "dir-secret");
|
|
962
1698
|
try {
|
|
963
|
-
const s2 =
|
|
1699
|
+
const s2 = readFileSync9(file, "utf8").trim();
|
|
964
1700
|
if (s2) return s2;
|
|
965
1701
|
} catch {
|
|
966
1702
|
}
|
|
967
1703
|
const s = randomBytes2(32).toString("hex");
|
|
968
1704
|
try {
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
1705
|
+
mkdirSync9(deskbuddyHome(), { recursive: true });
|
|
1706
|
+
writeFileSync8(file, s, { encoding: "utf8", mode: 384 });
|
|
1707
|
+
chmodSync2(file, 384);
|
|
972
1708
|
} catch {
|
|
973
1709
|
}
|
|
974
1710
|
return s;
|
|
@@ -978,7 +1714,7 @@ function dirRef(path) {
|
|
|
978
1714
|
}
|
|
979
1715
|
function readMap() {
|
|
980
1716
|
try {
|
|
981
|
-
const m = JSON.parse(
|
|
1717
|
+
const m = JSON.parse(readFileSync9(dirsPath(), "utf8"));
|
|
982
1718
|
const out = {};
|
|
983
1719
|
for (const [k, v] of Object.entries(m ?? {})) if (typeof v === "number") out[k] = v;
|
|
984
1720
|
return out;
|
|
@@ -986,17 +1722,17 @@ function readMap() {
|
|
|
986
1722
|
return {};
|
|
987
1723
|
}
|
|
988
1724
|
}
|
|
989
|
-
function recentDirs(limit = DIRS_REPORT_MAX, exists =
|
|
990
|
-
const list = Object.entries(readMap()).filter(([p]) => p && exists(p)).sort((a, b) => b[1] - a[1]).slice(0, limit);
|
|
1725
|
+
function recentDirs(limit = DIRS_REPORT_MAX, exists = existsSync9, reportable = (p) => isReportableDir(p)) {
|
|
1726
|
+
const list = Object.entries(readMap()).filter(([p]) => p && reportable(p) && exists(p)).sort((a, b) => b[1] - a[1]).slice(0, limit);
|
|
991
1727
|
const counts = /* @__PURE__ */ new Map();
|
|
992
|
-
for (const [p] of list) counts.set(
|
|
1728
|
+
for (const [p] of list) counts.set(basename4(p), (counts.get(basename4(p)) ?? 0) + 1);
|
|
993
1729
|
return list.map(([p, at]) => {
|
|
994
1730
|
const ref = dirRef(p);
|
|
995
|
-
const name =
|
|
996
|
-
return { path: p, ref, name: (counts.get(
|
|
1731
|
+
const name = basename4(p) || "/";
|
|
1732
|
+
return { path: p, ref, name: (counts.get(basename4(p)) ?? 0) > 1 ? `${name} \xB7 ${ref.slice(0, 4)}` : name, lastUsedAt: at };
|
|
997
1733
|
});
|
|
998
1734
|
}
|
|
999
|
-
function dirOf(ref, exists =
|
|
1735
|
+
function dirOf(ref, exists = existsSync9) {
|
|
1000
1736
|
for (const p of Object.keys(readMap())) if (dirRef(p) === ref && exists(p)) return p;
|
|
1001
1737
|
return "";
|
|
1002
1738
|
}
|
|
@@ -1074,12 +1810,12 @@ function isExpired(cmd, now = Date.now()) {
|
|
|
1074
1810
|
}
|
|
1075
1811
|
|
|
1076
1812
|
// src/daemon/executor.ts
|
|
1077
|
-
import { execFile as
|
|
1078
|
-
import { homedir as
|
|
1079
|
-
import { join as
|
|
1813
|
+
import { execFile as execFile3, execFileSync as execFileSync7 } from "child_process";
|
|
1814
|
+
import { homedir as homedir5 } from "os";
|
|
1815
|
+
import { join as join12 } from "path";
|
|
1080
1816
|
|
|
1081
1817
|
// src/daemon/gate/settings.ts
|
|
1082
|
-
import { existsSync as
|
|
1818
|
+
import { existsSync as existsSync10 } from "fs";
|
|
1083
1819
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
1084
1820
|
var GATE_HOOK_TIMEOUT_S = 7 * 24 * 60 * 60;
|
|
1085
1821
|
function waitModeFor(tool) {
|
|
@@ -1106,7 +1842,7 @@ function defaultGateScript() {
|
|
|
1106
1842
|
fileURLToPath3(new URL("./gate.cjs", import.meta.url)),
|
|
1107
1843
|
fileURLToPath3(new URL("../../../hook/gate.cjs", import.meta.url))
|
|
1108
1844
|
];
|
|
1109
|
-
return candidates.find((p) =>
|
|
1845
|
+
return candidates.find((p) => existsSync10(p)) ?? candidates[0];
|
|
1110
1846
|
}
|
|
1111
1847
|
function gateCommand(t, platform = process.platform) {
|
|
1112
1848
|
if (platform === "win32") return winGateCommand(t);
|
|
@@ -1134,23 +1870,24 @@ function codexGateArgs(t) {
|
|
|
1134
1870
|
}
|
|
1135
1871
|
|
|
1136
1872
|
// src/daemon/locate.ts
|
|
1137
|
-
import {
|
|
1138
|
-
import {
|
|
1139
|
-
import {
|
|
1873
|
+
import { execFileSync as execFileSync6 } from "child_process";
|
|
1874
|
+
import { accessSync, constants, existsSync as existsSync11, lstatSync, readFileSync as readFileSync10, statSync as statSync3 } from "fs";
|
|
1875
|
+
import { homedir as homedir4 } from "os";
|
|
1876
|
+
import { delimiter, dirname as dirname7, join as join11, win32 as win323 } from "path";
|
|
1140
1877
|
var defaultLocateDeps = () => ({
|
|
1141
|
-
home:
|
|
1878
|
+
home: homedir4(),
|
|
1142
1879
|
path: process.env.PATH ?? "",
|
|
1143
1880
|
nodePath: process.execPath,
|
|
1144
1881
|
isExecutable: (p) => {
|
|
1145
1882
|
try {
|
|
1146
|
-
if (!
|
|
1883
|
+
if (!statSync3(p).isFile()) return false;
|
|
1147
1884
|
accessSync(p, constants.X_OK);
|
|
1148
1885
|
return true;
|
|
1149
1886
|
} catch {
|
|
1150
1887
|
return false;
|
|
1151
1888
|
}
|
|
1152
1889
|
},
|
|
1153
|
-
exists: (p) =>
|
|
1890
|
+
exists: (p) => existsSync11(p),
|
|
1154
1891
|
lexists: (p) => {
|
|
1155
1892
|
try {
|
|
1156
1893
|
lstatSync(p);
|
|
@@ -1164,36 +1901,75 @@ var defaultLocateDeps = () => ({
|
|
|
1164
1901
|
localAppData: process.env.LOCALAPPDATA ?? "",
|
|
1165
1902
|
readText: (p) => {
|
|
1166
1903
|
try {
|
|
1167
|
-
return
|
|
1904
|
+
return readFileSync10(p, "utf8");
|
|
1168
1905
|
} catch {
|
|
1169
1906
|
return null;
|
|
1170
1907
|
}
|
|
1171
1908
|
},
|
|
1172
1909
|
// 🔴 daemon 自己也不能找到垫片:远程任务要的是真程序(窗口接管 spec 2026-09-30 §3.1)
|
|
1173
|
-
skipDirs: [
|
|
1910
|
+
skipDirs: [join11(deskbuddyHome(), "bin")],
|
|
1911
|
+
winAppHints: (product, exe) => winInstallDirs(product, exe)
|
|
1174
1912
|
});
|
|
1913
|
+
function installDirsFromProcs(procs, exe) {
|
|
1914
|
+
const out = [];
|
|
1915
|
+
for (const p of procs ?? []) {
|
|
1916
|
+
const first = winSplitArgs(p.cmdline)[0] ?? "";
|
|
1917
|
+
if (win323.basename(first).toLowerCase() !== exe.toLowerCase()) continue;
|
|
1918
|
+
const dir = win323.dirname(first);
|
|
1919
|
+
if (win323.isAbsolute(dir) && !out.some((d) => d.toLowerCase() === dir.toLowerCase())) out.push(dir);
|
|
1920
|
+
}
|
|
1921
|
+
return out;
|
|
1922
|
+
}
|
|
1923
|
+
function parseUninstallDirs(out) {
|
|
1924
|
+
const dirs = [];
|
|
1925
|
+
for (const raw of out.split(/\r?\n/)) {
|
|
1926
|
+
let v = raw.trim().replace(/,\s*-?\d+$/, "").replace(/^"|"$/g, "").trim();
|
|
1927
|
+
if (!v || !win323.isAbsolute(v)) continue;
|
|
1928
|
+
if (/\.exe$/i.test(v)) v = win323.dirname(v);
|
|
1929
|
+
v = v.replace(/[\\/]+$/, "");
|
|
1930
|
+
if (!dirs.some((d) => d.toLowerCase() === v.toLowerCase())) dirs.push(v);
|
|
1931
|
+
}
|
|
1932
|
+
return dirs;
|
|
1933
|
+
}
|
|
1934
|
+
function winInstallDirs(product, exe) {
|
|
1935
|
+
const fromProcs = installDirsFromProcs(winProcs(), exe);
|
|
1936
|
+
let fromReg = [];
|
|
1937
|
+
try {
|
|
1938
|
+
const name = product.replace(/'/g, "''");
|
|
1939
|
+
const script = `[Console]::OutputEncoding=[Text.Encoding]::UTF8;Get-ItemProperty 'HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\*','HKLM:\\Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\*','HKLM:\\Software\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\*' -ErrorAction SilentlyContinue | Where-Object { $_.DisplayName -like '${name}*' } | ForEach-Object { $_.InstallLocation; $_.DisplayIcon }`;
|
|
1940
|
+
const out = execFileSync6("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", script], {
|
|
1941
|
+
encoding: "utf8",
|
|
1942
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
1943
|
+
windowsHide: true,
|
|
1944
|
+
timeout: 1e4
|
|
1945
|
+
});
|
|
1946
|
+
fromReg = parseUninstallDirs(out);
|
|
1947
|
+
} catch {
|
|
1948
|
+
}
|
|
1949
|
+
return [...fromProcs, ...fromReg.filter((d) => !fromProcs.some((p) => p.toLowerCase() === d.toLowerCase()))];
|
|
1950
|
+
}
|
|
1175
1951
|
var isWin = (d) => (d.platform ?? process.platform) === "win32";
|
|
1176
1952
|
var WIN_EXTS = [".exe", ".cmd", ".bat"];
|
|
1177
1953
|
function extraBinDirs(home, nodePath = "", win) {
|
|
1178
1954
|
if (win) {
|
|
1179
1955
|
return [
|
|
1180
|
-
|
|
1181
|
-
...win.appData ? [
|
|
1182
|
-
|
|
1183
|
-
...win.localAppData ? [
|
|
1184
|
-
|
|
1185
|
-
...nodePath ? [
|
|
1956
|
+
join11(home, ".local", "bin"),
|
|
1957
|
+
...win.appData ? [join11(win.appData, "npm")] : [],
|
|
1958
|
+
join11(home, ".bun", "bin"),
|
|
1959
|
+
...win.localAppData ? [join11(win.localAppData, "Volta", "bin")] : [],
|
|
1960
|
+
join11(home, "scoop", "shims"),
|
|
1961
|
+
...nodePath ? [dirname7(nodePath)] : []
|
|
1186
1962
|
];
|
|
1187
1963
|
}
|
|
1188
1964
|
return [
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1965
|
+
join11(home, ".local", "bin"),
|
|
1966
|
+
join11(home, ".claude", "local"),
|
|
1967
|
+
join11(home, ".npm-global", "bin"),
|
|
1968
|
+
join11(home, ".bun", "bin"),
|
|
1969
|
+
join11(home, ".volta", "bin"),
|
|
1194
1970
|
"/opt/homebrew/bin",
|
|
1195
1971
|
"/usr/local/bin",
|
|
1196
|
-
...nodePath ? [
|
|
1972
|
+
...nodePath ? [dirname7(nodePath)] : []
|
|
1197
1973
|
];
|
|
1198
1974
|
}
|
|
1199
1975
|
function whichIn(name, d) {
|
|
@@ -1204,7 +1980,7 @@ function whichAllIn(name, d) {
|
|
|
1204
1980
|
const out = [];
|
|
1205
1981
|
for (const dir of binDirs(d)) {
|
|
1206
1982
|
for (const ext of exts) {
|
|
1207
|
-
const p =
|
|
1983
|
+
const p = join11(dir, name + ext);
|
|
1208
1984
|
if (d.isExecutable(p)) out.push(p);
|
|
1209
1985
|
}
|
|
1210
1986
|
}
|
|
@@ -1212,9 +1988,9 @@ function whichAllIn(name, d) {
|
|
|
1212
1988
|
}
|
|
1213
1989
|
function binDirs(d) {
|
|
1214
1990
|
const win = isWin(d);
|
|
1215
|
-
const
|
|
1991
|
+
const sep3 = win ? ";" : delimiter;
|
|
1216
1992
|
const all = [
|
|
1217
|
-
...d.path.split(
|
|
1993
|
+
...d.path.split(sep3).filter(Boolean),
|
|
1218
1994
|
...extraBinDirs(d.home, d.nodePath, win ? { appData: d.appData, localAppData: d.localAppData } : void 0)
|
|
1219
1995
|
];
|
|
1220
1996
|
if (!d.skipDirs || d.skipDirs.length === 0) return all;
|
|
@@ -1228,7 +2004,7 @@ function binDirs(d) {
|
|
|
1228
2004
|
function parseNpmCmdShim(shimPath, text) {
|
|
1229
2005
|
const m = text.match(/"%~?dp0%?\\?([^"%]+)"\s+%\*/i);
|
|
1230
2006
|
if (!m) return null;
|
|
1231
|
-
return
|
|
2007
|
+
return join11(dirname7(shimPath), m[1].replace(/^\\/, ""));
|
|
1232
2008
|
}
|
|
1233
2009
|
function resolveWinLaunch(file, d) {
|
|
1234
2010
|
const lower = file.toLowerCase();
|
|
@@ -1246,20 +2022,21 @@ function resolveWinLaunch(file, d) {
|
|
|
1246
2022
|
return null;
|
|
1247
2023
|
}
|
|
1248
2024
|
function appDirs(home) {
|
|
1249
|
-
return ["/Applications",
|
|
2025
|
+
return ["/Applications", join11(home, "Applications")];
|
|
1250
2026
|
}
|
|
1251
2027
|
function winAppDirs(d, product) {
|
|
1252
2028
|
return [
|
|
1253
|
-
...d.localAppData ? [
|
|
1254
|
-
|
|
2029
|
+
...d.localAppData ? [join11(d.localAppData, "Programs", product)] : [],
|
|
2030
|
+
join11("C:\\Program Files", product),
|
|
2031
|
+
join11("C:\\Program Files (x86)", product)
|
|
1255
2032
|
];
|
|
1256
2033
|
}
|
|
1257
2034
|
var WORKBUDDY_CLI_IN_WIN_APP = "resources/app.asar.unpacked/cli/bin/codebuddy";
|
|
1258
2035
|
var QODER_CN_WORKER_IN_WIN_APP = "resources/app.asar.unpacked/node_modules/@qoder-ai/qoder-cn-agent-sdk/dist/_worker/qoder-worker-runtime.obf.mjs";
|
|
1259
2036
|
function winClaudeExeCandidates(d) {
|
|
1260
2037
|
return [
|
|
1261
|
-
...d.home ? [
|
|
1262
|
-
...d.appData ? [
|
|
2038
|
+
...d.home ? [join11(d.home, ".local", "bin", "claude.exe")] : [],
|
|
2039
|
+
...d.appData ? [join11(d.appData, "npm", "node_modules", "@anthropic-ai", "claude-code", "bin", "claude.exe")] : []
|
|
1263
2040
|
];
|
|
1264
2041
|
}
|
|
1265
2042
|
function locateWin(tool, d) {
|
|
@@ -1277,26 +2054,29 @@ function locateWin(tool, d) {
|
|
|
1277
2054
|
case "codex":
|
|
1278
2055
|
return plain("codex");
|
|
1279
2056
|
case "workbuddy": {
|
|
1280
|
-
|
|
1281
|
-
for (const
|
|
1282
|
-
const
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
2057
|
+
const inApp = (dirs) => {
|
|
2058
|
+
for (const dir of dirs) {
|
|
2059
|
+
for (const ext of [".exe", ".cmd", ""]) {
|
|
2060
|
+
const p = join11(dir, WORKBUDDY_CLI_IN_WIN_APP + ext);
|
|
2061
|
+
if (d.exists(p)) {
|
|
2062
|
+
const r = resolveWinLaunch(p, d);
|
|
2063
|
+
if (r) return r;
|
|
2064
|
+
}
|
|
1286
2065
|
}
|
|
1287
2066
|
}
|
|
1288
|
-
|
|
1289
|
-
|
|
2067
|
+
return null;
|
|
2068
|
+
};
|
|
2069
|
+
return inApp(winAppDirs(d, "WorkBuddy")) ?? plain("codebuddy") ?? inApp(d.winAppHints?.("WorkBuddy", "WorkBuddy.exe") ?? []);
|
|
1290
2070
|
}
|
|
1291
2071
|
case "qoder": {
|
|
1292
2072
|
for (const dir of winAppDirs(d, "Qoder CN")) {
|
|
1293
|
-
const p =
|
|
2073
|
+
const p = join11(dir, QODER_CN_WORKER_IN_WIN_APP);
|
|
1294
2074
|
if (d.exists(p)) return { file: d.nodePath, prefixArgs: [p] };
|
|
1295
2075
|
}
|
|
1296
2076
|
return plain("qodercli");
|
|
1297
2077
|
}
|
|
1298
2078
|
case "opencode": {
|
|
1299
|
-
const p =
|
|
2079
|
+
const p = join11(d.home, ".opencode", "bin", "opencode.exe");
|
|
1300
2080
|
if (d.exists(p)) return { file: p, prefixArgs: [] };
|
|
1301
2081
|
return plain("opencode");
|
|
1302
2082
|
}
|
|
@@ -1319,20 +2099,20 @@ function locate(tool, d = defaultLocateDeps()) {
|
|
|
1319
2099
|
return plain("codex");
|
|
1320
2100
|
case "workbuddy": {
|
|
1321
2101
|
for (const dir of appDirs(d.home)) {
|
|
1322
|
-
const p =
|
|
2102
|
+
const p = join11(dir, WORKBUDDY_CLI_IN_APP);
|
|
1323
2103
|
if (d.isExecutable(p)) return { file: p, prefixArgs: [] };
|
|
1324
2104
|
}
|
|
1325
2105
|
return plain("codebuddy");
|
|
1326
2106
|
}
|
|
1327
2107
|
case "qoder": {
|
|
1328
2108
|
for (const dir of appDirs(d.home)) {
|
|
1329
|
-
const p =
|
|
2109
|
+
const p = join11(dir, QODER_CN_WORKER_IN_APP);
|
|
1330
2110
|
if (d.exists(p)) return { file: d.nodePath, prefixArgs: [p] };
|
|
1331
2111
|
}
|
|
1332
2112
|
return plain("qodercli");
|
|
1333
2113
|
}
|
|
1334
2114
|
case "opencode": {
|
|
1335
|
-
const p =
|
|
2115
|
+
const p = join11(d.home, ".opencode", "bin", "opencode");
|
|
1336
2116
|
if (d.isExecutable(p)) return { file: p, prefixArgs: [] };
|
|
1337
2117
|
return plain("opencode");
|
|
1338
2118
|
}
|
|
@@ -1349,7 +2129,7 @@ function findWt(d = defaultLocateDeps()) {
|
|
|
1349
2129
|
if (!isWin(d)) return null;
|
|
1350
2130
|
const hit = whichIn("wt", d);
|
|
1351
2131
|
if (hit) return hit;
|
|
1352
|
-
const alias = d.localAppData ?
|
|
2132
|
+
const alias = d.localAppData ? join11(d.localAppData, "Microsoft", "WindowsApps", "wt.exe") : "";
|
|
1353
2133
|
return alias && (d.exists(alias) || (d.lexists?.(alias) ?? false)) ? alias : null;
|
|
1354
2134
|
}
|
|
1355
2135
|
|
|
@@ -1424,14 +2204,14 @@ var TOOL_BINARY = {
|
|
|
1424
2204
|
qoder: "qodercli",
|
|
1425
2205
|
opencode: "opencode"
|
|
1426
2206
|
};
|
|
1427
|
-
function buildSpawnPlan(cmd, cwd, gate, home =
|
|
2207
|
+
function buildSpawnPlan(cmd, cwd, gate, home = homedir5()) {
|
|
1428
2208
|
const file = TOOL_BINARY[cmd.tool];
|
|
1429
2209
|
if (!file) return null;
|
|
1430
2210
|
switch (cmd.tool) {
|
|
1431
2211
|
case "codex":
|
|
1432
2212
|
return { file, args: codexPlan(cmd, gate), cwd };
|
|
1433
2213
|
case "workbuddy":
|
|
1434
|
-
return { file, args: workbuddyPlan(cmd, gate), cwd, env: { CODEBUDDY_CONFIG_DIR:
|
|
2214
|
+
return { file, args: workbuddyPlan(cmd, gate), cwd, env: { CODEBUDDY_CONFIG_DIR: join12(home, ".workbuddy") } };
|
|
1435
2215
|
case "qoder":
|
|
1436
2216
|
return gate ? { file, args: qoderPlan(cmd, gate), cwd } : null;
|
|
1437
2217
|
case "opencode":
|
|
@@ -1531,8 +2311,8 @@ function startCommand(plan, tool, limitMs = EXEC_TIMEOUT_MS) {
|
|
|
1531
2311
|
}, Math.max(0, remaining));
|
|
1532
2312
|
};
|
|
1533
2313
|
let timedOut = false;
|
|
1534
|
-
const done = new Promise((
|
|
1535
|
-
child =
|
|
2314
|
+
const done = new Promise((resolve6) => {
|
|
2315
|
+
child = execFile3(
|
|
1536
2316
|
plan.file,
|
|
1537
2317
|
plan.args,
|
|
1538
2318
|
{
|
|
@@ -1549,10 +2329,10 @@ function startCommand(plan, tool, limitMs = EXEC_TIMEOUT_MS) {
|
|
|
1549
2329
|
const out = String(stdout7 ?? "");
|
|
1550
2330
|
if (err || timedOut) {
|
|
1551
2331
|
const msg = timedOut ? `\u6267\u884C\u8D85\u8FC7 ${Math.round(limitMs / 1e3)} \u79D2\u6D3B\u52A8\u65F6\u95F4\uFF0C\u5DF2\u7EC8\u6B62` : err.message;
|
|
1552
|
-
|
|
2332
|
+
resolve6({ ok: false, sessionId: parseSessionId(tool, out), stdout: out, error: msg });
|
|
1553
2333
|
return;
|
|
1554
2334
|
}
|
|
1555
|
-
|
|
2335
|
+
resolve6({ ok: true, sessionId: parseSessionId(tool, out), stdout: out });
|
|
1556
2336
|
}
|
|
1557
2337
|
);
|
|
1558
2338
|
child?.stdin?.end();
|
|
@@ -1588,7 +2368,7 @@ function killTree(child) {
|
|
|
1588
2368
|
if (!child) return;
|
|
1589
2369
|
if (process.platform === "win32" && child.pid) {
|
|
1590
2370
|
try {
|
|
1591
|
-
|
|
2371
|
+
execFileSync7("taskkill", ["/pid", String(child.pid), "/T", "/F"], { stdio: "ignore", windowsHide: true });
|
|
1592
2372
|
return;
|
|
1593
2373
|
} catch {
|
|
1594
2374
|
}
|
|
@@ -1598,8 +2378,8 @@ function killTree(child) {
|
|
|
1598
2378
|
|
|
1599
2379
|
// src/daemon/gate/approvals.ts
|
|
1600
2380
|
import { createHash as createHash2 } from "crypto";
|
|
1601
|
-
import { mkdirSync as
|
|
1602
|
-
import { join as
|
|
2381
|
+
import { mkdirSync as mkdirSync10, readFileSync as readFileSync11, renameSync as renameSync7, writeFileSync as writeFileSync9 } from "fs";
|
|
2382
|
+
import { join as join13 } from "path";
|
|
1603
2383
|
var RETAIN_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
1604
2384
|
function fingerprint(toolName, toolInput) {
|
|
1605
2385
|
let input = "";
|
|
@@ -1611,7 +2391,7 @@ function fingerprint(toolName, toolInput) {
|
|
|
1611
2391
|
return createHash2("sha256").update(toolName).update("\0").update(input).digest("hex");
|
|
1612
2392
|
}
|
|
1613
2393
|
function storePath() {
|
|
1614
|
-
return
|
|
2394
|
+
return join13(deskbuddyHome(), "approvals.json");
|
|
1615
2395
|
}
|
|
1616
2396
|
var ApprovalStore = class _ApprovalStore {
|
|
1617
2397
|
#records;
|
|
@@ -1620,7 +2400,7 @@ var ApprovalStore = class _ApprovalStore {
|
|
|
1620
2400
|
}
|
|
1621
2401
|
static load() {
|
|
1622
2402
|
try {
|
|
1623
|
-
const raw = JSON.parse(
|
|
2403
|
+
const raw = JSON.parse(readFileSync11(storePath(), "utf8"));
|
|
1624
2404
|
return new _ApprovalStore(Array.isArray(raw) ? raw : []);
|
|
1625
2405
|
} catch {
|
|
1626
2406
|
return new _ApprovalStore();
|
|
@@ -1628,10 +2408,10 @@ var ApprovalStore = class _ApprovalStore {
|
|
|
1628
2408
|
}
|
|
1629
2409
|
save() {
|
|
1630
2410
|
try {
|
|
1631
|
-
|
|
2411
|
+
mkdirSync10(deskbuddyHome(), { recursive: true });
|
|
1632
2412
|
const tmp = storePath() + ".tmp";
|
|
1633
|
-
|
|
1634
|
-
|
|
2413
|
+
writeFileSync9(tmp, JSON.stringify(this.#records), { encoding: "utf8", mode: 384 });
|
|
2414
|
+
renameSync7(tmp, storePath());
|
|
1635
2415
|
} catch {
|
|
1636
2416
|
}
|
|
1637
2417
|
}
|
|
@@ -1671,7 +2451,7 @@ var ApprovalStore = class _ApprovalStore {
|
|
|
1671
2451
|
};
|
|
1672
2452
|
|
|
1673
2453
|
// src/daemon/gate/backend.ts
|
|
1674
|
-
import { basename as
|
|
2454
|
+
import { basename as basename5 } from "path";
|
|
1675
2455
|
function serverBackend(client = api) {
|
|
1676
2456
|
return {
|
|
1677
2457
|
create: (r) => client.approvalCreate({
|
|
@@ -1680,7 +2460,7 @@ function serverBackend(client = api) {
|
|
|
1680
2460
|
sessionId: r.sessionId,
|
|
1681
2461
|
kind: r.kind,
|
|
1682
2462
|
summary: r.summary,
|
|
1683
|
-
cwdName:
|
|
2463
|
+
cwdName: basename5(r.cwd),
|
|
1684
2464
|
source: r.mode === "window" ? "window" : "remote",
|
|
1685
2465
|
...r.remember ? { remember: r.remember } : {}
|
|
1686
2466
|
}),
|
|
@@ -1712,11 +2492,11 @@ function mapStatus(s) {
|
|
|
1712
2492
|
|
|
1713
2493
|
// src/daemon/gate/coordinator.ts
|
|
1714
2494
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
1715
|
-
import { existsSync as
|
|
1716
|
-
import { basename as
|
|
2495
|
+
import { existsSync as existsSync12, realpathSync as realpathSync2 } from "fs";
|
|
2496
|
+
import { basename as basename6, dirname as dirname8, join as join14 } from "path";
|
|
1717
2497
|
|
|
1718
2498
|
// src/daemon/gate/classify.ts
|
|
1719
|
-
import { isAbsolute, relative, resolve, sep } from "path";
|
|
2499
|
+
import { isAbsolute, relative as relative2, resolve as resolve2, sep as sep2 } from "path";
|
|
1720
2500
|
var SUMMARY_MAX = 200;
|
|
1721
2501
|
var READ_TOOLS = /* @__PURE__ */ new Set([
|
|
1722
2502
|
"Read",
|
|
@@ -1757,13 +2537,13 @@ var PROTECTED_DIRS = /* @__PURE__ */ new Set([
|
|
|
1757
2537
|
]);
|
|
1758
2538
|
var PROTECTED_FILES = /* @__PURE__ */ new Set([".envrc", ".mcp.json", ".gitmodules", ".gitattributes"]);
|
|
1759
2539
|
function isProtectedPath(cwd, p) {
|
|
1760
|
-
const segs =
|
|
2540
|
+
const segs = resolve2(cwd || "/", p).split(sep2).filter(Boolean).map((s) => s.toLowerCase());
|
|
1761
2541
|
if (segs.some((s) => PROTECTED_DIRS.has(s))) return true;
|
|
1762
2542
|
return PROTECTED_FILES.has(segs[segs.length - 1] ?? "");
|
|
1763
2543
|
}
|
|
1764
2544
|
function isInside(cwd, p) {
|
|
1765
2545
|
if (!cwd || !p) return false;
|
|
1766
|
-
const rel =
|
|
2546
|
+
const rel = relative2(resolve2(cwd), resolve2(cwd, p));
|
|
1767
2547
|
if (rel === "") return true;
|
|
1768
2548
|
return !rel.startsWith("..") && !isAbsolute(rel);
|
|
1769
2549
|
}
|
|
@@ -1820,12 +2600,12 @@ function classify(toolName, toolInput, cwd, policy = "commands", opts = {}) {
|
|
|
1820
2600
|
const inside = (p) => {
|
|
1821
2601
|
if (!roots.some((r) => isInside(r, p))) return false;
|
|
1822
2602
|
if (!real2) return true;
|
|
1823
|
-
const rp = real2(
|
|
2603
|
+
const rp = real2(resolve2(roots[0] ?? "/", p));
|
|
1824
2604
|
return roots.some((r) => isInside(real2(r), rp));
|
|
1825
2605
|
};
|
|
1826
2606
|
const rel = (p) => {
|
|
1827
2607
|
const r = roots.find((x) => isInside(x, p)) ?? roots[0] ?? "";
|
|
1828
|
-
return
|
|
2608
|
+
return relative2(resolve2(r), resolve2(r, p));
|
|
1829
2609
|
};
|
|
1830
2610
|
const input = toolInput && typeof toolInput === "object" ? toolInput : {};
|
|
1831
2611
|
if (READ_TOOLS.has(toolName)) return { action: "auto" };
|
|
@@ -1838,7 +2618,7 @@ function classify(toolName, toolInput, cwd, policy = "commands", opts = {}) {
|
|
|
1838
2618
|
if (paths) {
|
|
1839
2619
|
const allInside = paths.length > 0 && paths.every(inside);
|
|
1840
2620
|
const touchesProtected = paths.some(
|
|
1841
|
-
(p) => isProtectedPath(roots[0] ?? "/", p) || (real2 ? isProtectedPath("/", real2(
|
|
2621
|
+
(p) => isProtectedPath(roots[0] ?? "/", p) || (real2 ? isProtectedPath("/", real2(resolve2(roots[0] ?? "/", p))) : false)
|
|
1842
2622
|
);
|
|
1843
2623
|
if (policy === "commands" && allInside && !touchesProtected) return { action: "auto" };
|
|
1844
2624
|
const shown = paths.map((p) => inside(p) ? rel(p) : p);
|
|
@@ -1861,14 +2641,14 @@ function describe(toolName, toolInput, cwd) {
|
|
|
1861
2641
|
}
|
|
1862
2642
|
|
|
1863
2643
|
// src/daemon/gate/procs.ts
|
|
1864
|
-
import { execFileSync as
|
|
2644
|
+
import { execFileSync as execFileSync8 } from "child_process";
|
|
1865
2645
|
function listDescendants(rootPid) {
|
|
1866
2646
|
if (process.platform === "win32") {
|
|
1867
2647
|
return descendantsOf(rootPid, (winProcs() ?? []).map((p) => ({ pid: p.pid, ppid: p.ppid, args: p.cmdline })));
|
|
1868
2648
|
}
|
|
1869
2649
|
let out = "";
|
|
1870
2650
|
try {
|
|
1871
|
-
out =
|
|
2651
|
+
out = execFileSync8("ps", ["-A", "-ww", "-o", "pid=,ppid=,args="], {
|
|
1872
2652
|
encoding: "utf8",
|
|
1873
2653
|
stdio: ["ignore", "pipe", "ignore"],
|
|
1874
2654
|
maxBuffer: 16 * 1024 * 1024
|
|
@@ -2000,7 +2780,7 @@ var ApprovalCoordinator = class {
|
|
|
2000
2780
|
store.save();
|
|
2001
2781
|
let early = null;
|
|
2002
2782
|
let settle;
|
|
2003
|
-
const decided = new Promise((
|
|
2783
|
+
const decided = new Promise((resolve6) => settle = resolve6);
|
|
2004
2784
|
this.#waiters.set(rec.approvalId, (r) => {
|
|
2005
2785
|
early = r;
|
|
2006
2786
|
settle(r);
|
|
@@ -2024,8 +2804,8 @@ var ApprovalCoordinator = class {
|
|
|
2024
2804
|
}
|
|
2025
2805
|
run2.onWaitStart?.();
|
|
2026
2806
|
try {
|
|
2027
|
-
return await new Promise((
|
|
2028
|
-
void decided.then(
|
|
2807
|
+
return await new Promise((resolve6) => {
|
|
2808
|
+
void decided.then(resolve6);
|
|
2029
2809
|
const onAbort = () => {
|
|
2030
2810
|
if (!this.#waiters.delete(rec.approvalId)) return;
|
|
2031
2811
|
if (rec.status === "pending") {
|
|
@@ -2034,7 +2814,7 @@ var ApprovalCoordinator = class {
|
|
|
2034
2814
|
store.save();
|
|
2035
2815
|
void this.#cancelRemote(rec.approvalId, "\u667A\u80FD\u4F53\u8FDB\u7A0B\u5DF2\u7ED3\u675F");
|
|
2036
2816
|
}
|
|
2037
|
-
|
|
2817
|
+
resolve6({ decision: "deny", reason: "\u5DF2\u53D6\u6D88" });
|
|
2038
2818
|
};
|
|
2039
2819
|
if (signal.aborted) onAbort();
|
|
2040
2820
|
else signal.addEventListener("abort", onAbort, { once: true });
|
|
@@ -2086,7 +2866,7 @@ var ApprovalCoordinator = class {
|
|
|
2086
2866
|
...watchProcs ? { pid, cmd, baseline: new Set(this.#descendants(pid).map((p) => p.pid)) } : {}
|
|
2087
2867
|
});
|
|
2088
2868
|
let settle;
|
|
2089
|
-
const decided = new Promise((
|
|
2869
|
+
const decided = new Promise((resolve6) => settle = resolve6);
|
|
2090
2870
|
this.#waiters.set(rec.approvalId, settle);
|
|
2091
2871
|
try {
|
|
2092
2872
|
await this.#d.backend.create(rec);
|
|
@@ -2102,11 +2882,11 @@ var ApprovalCoordinator = class {
|
|
|
2102
2882
|
}
|
|
2103
2883
|
this.#d.log(`\u7A97\u53E3\u6388\u6743 ${rec.approvalId} \u5DF2\u63A8\u5230\u8BBE\u5907 kind=${rec.kind}`);
|
|
2104
2884
|
if (rec.status !== "pending") void this.#cancelRemote(rec.approvalId, "\u7535\u8111\u4E0A\u5DF2\u5904\u7406");
|
|
2105
|
-
return new Promise((
|
|
2106
|
-
void decided.then(
|
|
2885
|
+
return new Promise((resolve6) => {
|
|
2886
|
+
void decided.then(resolve6);
|
|
2107
2887
|
const onAbort = () => {
|
|
2108
2888
|
if (this.#waiters.has(rec.approvalId)) this.#settleWindow(rec, "hook \u5DF2\u7ED3\u675F");
|
|
2109
|
-
|
|
2889
|
+
resolve6(none);
|
|
2110
2890
|
};
|
|
2111
2891
|
if (signal.aborted) onAbort();
|
|
2112
2892
|
else signal.addEventListener("abort", onAbort, { once: true });
|
|
@@ -2367,13 +3147,13 @@ function realpathNearest(p) {
|
|
|
2367
3147
|
const tail = [];
|
|
2368
3148
|
for (let i = 0; i < 64; i++) {
|
|
2369
3149
|
try {
|
|
2370
|
-
if (
|
|
3150
|
+
if (existsSync12(cur)) return join14(realpathSync2(cur), ...tail.reverse());
|
|
2371
3151
|
} catch {
|
|
2372
3152
|
return p;
|
|
2373
3153
|
}
|
|
2374
|
-
const parent =
|
|
3154
|
+
const parent = dirname8(cur);
|
|
2375
3155
|
if (parent === cur) return p;
|
|
2376
|
-
tail.push(
|
|
3156
|
+
tail.push(basename6(cur));
|
|
2377
3157
|
cur = parent;
|
|
2378
3158
|
}
|
|
2379
3159
|
return p;
|
|
@@ -2390,11 +3170,11 @@ function cwdForms(cwd) {
|
|
|
2390
3170
|
// src/daemon/gate/server.ts
|
|
2391
3171
|
import { randomBytes as randomBytes3, timingSafeEqual } from "crypto";
|
|
2392
3172
|
import { createServer } from "http";
|
|
2393
|
-
import { mkdirSync as
|
|
2394
|
-
import { join as
|
|
3173
|
+
import { mkdirSync as mkdirSync11, renameSync as renameSync8, rmSync as rmSync6, writeFileSync as writeFileSync10 } from "fs";
|
|
3174
|
+
import { join as join15 } from "path";
|
|
2395
3175
|
var MAX_BODY_BYTES = 8 * 1024 * 1024;
|
|
2396
3176
|
function gateConfPath() {
|
|
2397
|
-
return
|
|
3177
|
+
return join15(deskbuddyHome(), "gate.json");
|
|
2398
3178
|
}
|
|
2399
3179
|
function tokenEquals(a, b) {
|
|
2400
3180
|
const x = Buffer.from(a);
|
|
@@ -2405,39 +3185,46 @@ var GateServer = class {
|
|
|
2405
3185
|
#handler;
|
|
2406
3186
|
#token = randomBytes3(32).toString("hex");
|
|
2407
3187
|
#server = null;
|
|
3188
|
+
#inflight = 0;
|
|
3189
|
+
/** 自动升级排空中:所有请求直接 503(远程 gate → exit 2 拦下;窗口 gate → 退回窗口里本地确认) */
|
|
3190
|
+
refusing = false;
|
|
2408
3191
|
constructor(handler) {
|
|
2409
3192
|
this.#handler = handler;
|
|
2410
3193
|
}
|
|
2411
3194
|
get token() {
|
|
2412
3195
|
return this.#token;
|
|
2413
3196
|
}
|
|
3197
|
+
/** 正在处理(多半是挂着等设备批准)的授权请求数。自动升级的空闲判定用(spec 2026-10-01 §4.3) */
|
|
3198
|
+
get inflight() {
|
|
3199
|
+
return this.#inflight;
|
|
3200
|
+
}
|
|
2414
3201
|
/** 起监听并写 gate.json。返回实际端口 */
|
|
2415
3202
|
async start() {
|
|
2416
3203
|
const server = createServer((req2, res) => void this.#serve(req2, res));
|
|
2417
|
-
await new Promise((
|
|
3204
|
+
await new Promise((resolve6, reject) => {
|
|
2418
3205
|
server.once("error", reject);
|
|
2419
|
-
server.listen(0, "127.0.0.1", () =>
|
|
3206
|
+
server.listen(0, "127.0.0.1", () => resolve6());
|
|
2420
3207
|
});
|
|
2421
3208
|
this.#server = server;
|
|
2422
3209
|
const addr = server.address();
|
|
2423
3210
|
const port = typeof addr === "object" && addr ? addr.port : 0;
|
|
2424
|
-
|
|
3211
|
+
mkdirSync11(deskbuddyHome(), { recursive: true });
|
|
2425
3212
|
const tmp = gateConfPath() + ".tmp";
|
|
2426
|
-
|
|
3213
|
+
writeFileSync10(tmp, JSON.stringify({ port, token: this.#token, pid: process.pid }), {
|
|
2427
3214
|
encoding: "utf8",
|
|
2428
3215
|
mode: 384
|
|
2429
3216
|
});
|
|
2430
|
-
|
|
3217
|
+
renameSync8(tmp, gateConfPath());
|
|
2431
3218
|
return port;
|
|
2432
3219
|
}
|
|
2433
3220
|
async stop() {
|
|
2434
3221
|
try {
|
|
2435
|
-
|
|
3222
|
+
rmSync6(gateConfPath(), { force: true });
|
|
2436
3223
|
} catch {
|
|
2437
3224
|
}
|
|
2438
3225
|
const s = this.#server;
|
|
2439
3226
|
this.#server = null;
|
|
2440
|
-
if (s) await new Promise((
|
|
3227
|
+
if (s) await new Promise((resolve6) => s.close(() => resolve6()));
|
|
2441
3228
|
}
|
|
2442
3229
|
async #serve(req2, res) {
|
|
2443
3230
|
const reply = (status, body2) => {
|
|
@@ -2448,6 +3235,7 @@ var GateServer = class {
|
|
|
2448
3235
|
if (req2.method !== "POST" || req2.url !== "/gate") return reply(404);
|
|
2449
3236
|
const token = req2.headers["x-gate-token"];
|
|
2450
3237
|
if (typeof token !== "string" || !tokenEquals(token, this.#token)) return reply(403);
|
|
3238
|
+
if (this.refusing) return reply(503);
|
|
2451
3239
|
let body = "";
|
|
2452
3240
|
let size = 0;
|
|
2453
3241
|
try {
|
|
@@ -2472,6 +3260,7 @@ var GateServer = class {
|
|
|
2472
3260
|
res.on("close", () => {
|
|
2473
3261
|
if (!res.writableEnded) ac.abort();
|
|
2474
3262
|
});
|
|
3263
|
+
this.#inflight++;
|
|
2475
3264
|
try {
|
|
2476
3265
|
const req3 = window ? {
|
|
2477
3266
|
window: true,
|
|
@@ -2482,6 +3271,8 @@ var GateServer = class {
|
|
|
2482
3271
|
reply(200, out);
|
|
2483
3272
|
} catch {
|
|
2484
3273
|
reply(500);
|
|
3274
|
+
} finally {
|
|
3275
|
+
this.#inflight--;
|
|
2485
3276
|
}
|
|
2486
3277
|
}
|
|
2487
3278
|
};
|
|
@@ -2496,6 +3287,7 @@ var PARAM = {
|
|
|
2496
3287
|
deviceName: "ShiFeng-Device-Name",
|
|
2497
3288
|
nonce: "ShiFeng-Nonce"
|
|
2498
3289
|
};
|
|
3290
|
+
var CLI_VERSION_PARAM = "cliVersion";
|
|
2499
3291
|
var WS_PATH = "/deskbuddy";
|
|
2500
3292
|
function sanitizeParamValue(value) {
|
|
2501
3293
|
return (value || "").replace(/[=&]/g, "-");
|
|
@@ -2536,21 +3328,23 @@ function buildHandshakeUrl(cfg, opts = {}) {
|
|
|
2536
3328
|
[PARAM.signature, p.signature],
|
|
2537
3329
|
[PARAM.deviceId, p.deviceId],
|
|
2538
3330
|
[PARAM.deviceName, p.deviceName],
|
|
2539
|
-
[PARAM.nonce, p.nonce]
|
|
3331
|
+
[PARAM.nonce, p.nonce],
|
|
3332
|
+
// 不参与签名,服务端可据此刷新 DeskBuddyClient.cliVersion(自动升级 spec 2026-10-01 §4.6)
|
|
3333
|
+
[CLI_VERSION_PARAM, packageVersion()]
|
|
2540
3334
|
].map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`).join("&");
|
|
2541
3335
|
return `${toWsOrigin(cfg.apiBase)}${WS_PATH}?${query}`;
|
|
2542
3336
|
}
|
|
2543
3337
|
|
|
2544
3338
|
// src/daemon/log.ts
|
|
2545
|
-
import { appendFileSync, mkdirSync as
|
|
2546
|
-
import { join as
|
|
3339
|
+
import { appendFileSync, mkdirSync as mkdirSync12, statSync as statSync4, truncateSync } from "fs";
|
|
3340
|
+
import { join as join16 } from "path";
|
|
2547
3341
|
var MAX_LOG_BYTES = 2 * 1024 * 1024;
|
|
2548
3342
|
function logPath() {
|
|
2549
|
-
return
|
|
3343
|
+
return join16(deskbuddyHome(), "daemon.log");
|
|
2550
3344
|
}
|
|
2551
3345
|
function rotateIfNeeded(path) {
|
|
2552
3346
|
try {
|
|
2553
|
-
if (
|
|
3347
|
+
if (statSync4(path).size > MAX_LOG_BYTES) truncateSync(path, 0);
|
|
2554
3348
|
} catch {
|
|
2555
3349
|
}
|
|
2556
3350
|
}
|
|
@@ -2558,7 +3352,7 @@ function log(line) {
|
|
|
2558
3352
|
const stamped = `${(/* @__PURE__ */ new Date()).toISOString()} ${line}
|
|
2559
3353
|
`;
|
|
2560
3354
|
try {
|
|
2561
|
-
|
|
3355
|
+
mkdirSync12(deskbuddyHome(), { recursive: true });
|
|
2562
3356
|
const p = logPath();
|
|
2563
3357
|
rotateIfNeeded(p);
|
|
2564
3358
|
appendFileSync(p, stamped, "utf8");
|
|
@@ -2616,18 +3410,18 @@ var SessionQueue = class {
|
|
|
2616
3410
|
};
|
|
2617
3411
|
|
|
2618
3412
|
// src/daemon/remote.ts
|
|
2619
|
-
import { mkdirSync as
|
|
2620
|
-
import { join as
|
|
3413
|
+
import { mkdirSync as mkdirSync13, readFileSync as readFileSync12, renameSync as renameSync9, writeFileSync as writeFileSync11 } from "fs";
|
|
3414
|
+
import { join as join17 } from "path";
|
|
2621
3415
|
function handleDir() {
|
|
2622
|
-
return
|
|
3416
|
+
return join17(deskbuddyHome(), "remote");
|
|
2623
3417
|
}
|
|
2624
3418
|
function handlePath(tool) {
|
|
2625
3419
|
const safe = (tool || "").replace(/[^0-9a-zA-Z_-]/g, "");
|
|
2626
|
-
return
|
|
3420
|
+
return join17(handleDir(), `${safe || "unknown"}.json`);
|
|
2627
3421
|
}
|
|
2628
3422
|
function readHandle(tool) {
|
|
2629
3423
|
try {
|
|
2630
|
-
const h = JSON.parse(
|
|
3424
|
+
const h = JSON.parse(readFileSync12(handlePath(tool), "utf8"));
|
|
2631
3425
|
if (typeof h?.sessionId !== "string" || !h.sessionId) return null;
|
|
2632
3426
|
if (typeof h?.cwd !== "string" || !h.cwd) return null;
|
|
2633
3427
|
return h;
|
|
@@ -2637,10 +3431,10 @@ function readHandle(tool) {
|
|
|
2637
3431
|
}
|
|
2638
3432
|
function writeHandle(tool, handle) {
|
|
2639
3433
|
try {
|
|
2640
|
-
|
|
3434
|
+
mkdirSync13(handleDir(), { recursive: true });
|
|
2641
3435
|
const tmp = handlePath(tool) + ".tmp";
|
|
2642
|
-
|
|
2643
|
-
|
|
3436
|
+
writeFileSync11(tmp, JSON.stringify(handle), "utf8");
|
|
3437
|
+
renameSync9(tmp, handlePath(tool));
|
|
2644
3438
|
} catch {
|
|
2645
3439
|
}
|
|
2646
3440
|
}
|
|
@@ -2693,11 +3487,11 @@ function failureMessage(reason) {
|
|
|
2693
3487
|
}
|
|
2694
3488
|
|
|
2695
3489
|
// src/daemon/seen.ts
|
|
2696
|
-
import { mkdirSync as
|
|
2697
|
-
import { join as
|
|
3490
|
+
import { mkdirSync as mkdirSync14, readFileSync as readFileSync13, renameSync as renameSync10, writeFileSync as writeFileSync12 } from "fs";
|
|
3491
|
+
import { join as join18 } from "path";
|
|
2698
3492
|
var SEEN_CAPACITY = 200;
|
|
2699
3493
|
function seenPath() {
|
|
2700
|
-
return
|
|
3494
|
+
return join18(deskbuddyHome(), "seen.json");
|
|
2701
3495
|
}
|
|
2702
3496
|
var SeenIds = class _SeenIds {
|
|
2703
3497
|
#ids = [];
|
|
@@ -2728,7 +3522,7 @@ var SeenIds = class _SeenIds {
|
|
|
2728
3522
|
}
|
|
2729
3523
|
static load() {
|
|
2730
3524
|
try {
|
|
2731
|
-
const raw = JSON.parse(
|
|
3525
|
+
const raw = JSON.parse(readFileSync13(seenPath(), "utf8"));
|
|
2732
3526
|
return new _SeenIds(Array.isArray(raw?.ids) ? raw.ids.filter((x) => typeof x === "string") : []);
|
|
2733
3527
|
} catch {
|
|
2734
3528
|
return new _SeenIds();
|
|
@@ -2736,17 +3530,17 @@ var SeenIds = class _SeenIds {
|
|
|
2736
3530
|
}
|
|
2737
3531
|
save() {
|
|
2738
3532
|
try {
|
|
2739
|
-
|
|
3533
|
+
mkdirSync14(deskbuddyHome(), { recursive: true });
|
|
2740
3534
|
const tmp = seenPath() + ".tmp";
|
|
2741
|
-
|
|
2742
|
-
|
|
3535
|
+
writeFileSync12(tmp, JSON.stringify({ ids: this.#ids }), "utf8");
|
|
3536
|
+
renameSync10(tmp, seenPath());
|
|
2743
3537
|
} catch {
|
|
2744
3538
|
}
|
|
2745
3539
|
}
|
|
2746
3540
|
};
|
|
2747
3541
|
|
|
2748
3542
|
// src/daemon/codexWatch.ts
|
|
2749
|
-
import { execFileSync as
|
|
3543
|
+
import { execFileSync as execFileSync9 } from "child_process";
|
|
2750
3544
|
import WebSocket from "ws";
|
|
2751
3545
|
var TURN_LIMIT_MS = 10 * 60 * 1e3;
|
|
2752
3546
|
var QUEUE_MAX_MS = 10 * 60 * 1e3;
|
|
@@ -2791,7 +3585,7 @@ function discoverAppServers(configured) {
|
|
|
2791
3585
|
return parseAppServerUrls((winProcs() ?? []).map((p) => p.cmdline).join("\n"));
|
|
2792
3586
|
}
|
|
2793
3587
|
try {
|
|
2794
|
-
const out =
|
|
3588
|
+
const out = execFileSync9("ps", ["-A", "-ww", "-o", "args="], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
|
|
2795
3589
|
return parseAppServerUrls(out);
|
|
2796
3590
|
} catch {
|
|
2797
3591
|
return [];
|
|
@@ -2934,13 +3728,13 @@ var CodexWatch = class {
|
|
|
2934
3728
|
}
|
|
2935
3729
|
#request(method, params) {
|
|
2936
3730
|
const id = this.#nextId++;
|
|
2937
|
-
return new Promise((
|
|
3731
|
+
return new Promise((resolve6) => {
|
|
2938
3732
|
const timer = setTimeout(() => {
|
|
2939
3733
|
this.#calls.delete(id);
|
|
2940
|
-
|
|
3734
|
+
resolve6({ error: { message: "timeout" }, local: true });
|
|
2941
3735
|
}, REQUEST_TIMEOUT_MS);
|
|
2942
3736
|
timer.unref?.();
|
|
2943
|
-
this.#calls.set(id, { resolve:
|
|
3737
|
+
this.#calls.set(id, { resolve: resolve6, timer });
|
|
2944
3738
|
this.#send({ id, method, params });
|
|
2945
3739
|
});
|
|
2946
3740
|
}
|
|
@@ -3163,7 +3957,7 @@ var CodexWatch = class {
|
|
|
3163
3957
|
return { ...out, ...merged ? { merged } : {} };
|
|
3164
3958
|
}
|
|
3165
3959
|
#trackTurn(threadId, turnId) {
|
|
3166
|
-
return new Promise((
|
|
3960
|
+
return new Promise((resolve6) => {
|
|
3167
3961
|
const limit = this.#d.turnLimitMs ?? TURN_LIMIT_MS;
|
|
3168
3962
|
const step = this.#d.turnTickMs ?? 1e3;
|
|
3169
3963
|
const t = {
|
|
@@ -3173,7 +3967,7 @@ var CodexWatch = class {
|
|
|
3173
3967
|
used: 0,
|
|
3174
3968
|
timer: null,
|
|
3175
3969
|
grace: null,
|
|
3176
|
-
done: (end, text, error) =>
|
|
3970
|
+
done: (end, text, error) => resolve6({ sent: true, end, turnId, text, ...error ? { error } : {} })
|
|
3177
3971
|
};
|
|
3178
3972
|
this.#turns.set(turnId, t);
|
|
3179
3973
|
const early = this.#early.get(turnId);
|
|
@@ -3220,7 +4014,7 @@ var CodexWatch = class {
|
|
|
3220
4014
|
t.done(end, text || (end === "completed" ? "" : finalReply(t.items)), error);
|
|
3221
4015
|
}
|
|
3222
4016
|
#waitThread(threadId, ms) {
|
|
3223
|
-
return new Promise((
|
|
4017
|
+
return new Promise((resolve6) => {
|
|
3224
4018
|
let set = this.#wake.get(threadId);
|
|
3225
4019
|
if (!set) this.#wake.set(threadId, set = /* @__PURE__ */ new Set());
|
|
3226
4020
|
const s = set;
|
|
@@ -3228,7 +4022,7 @@ var CodexWatch = class {
|
|
|
3228
4022
|
clearTimeout(timer);
|
|
3229
4023
|
s.delete(fire);
|
|
3230
4024
|
if (s.size === 0 && this.#wake.get(threadId) === s) this.#wake.delete(threadId);
|
|
3231
|
-
|
|
4025
|
+
resolve6();
|
|
3232
4026
|
};
|
|
3233
4027
|
const timer = setTimeout(fire, ms);
|
|
3234
4028
|
timer.unref?.();
|
|
@@ -3401,43 +4195,43 @@ var CodexWatch = class {
|
|
|
3401
4195
|
};
|
|
3402
4196
|
|
|
3403
4197
|
// src/core/codexRemote.ts
|
|
3404
|
-
import { mkdirSync as
|
|
3405
|
-
import { join as
|
|
4198
|
+
import { mkdirSync as mkdirSync15, renameSync as renameSync11, rmSync as rmSync7, writeFileSync as writeFileSync13 } from "fs";
|
|
4199
|
+
import { join as join19 } from "path";
|
|
3406
4200
|
function codexRemotePath() {
|
|
3407
|
-
return
|
|
4201
|
+
return join19(deskbuddyHome(), "codex-remote.json");
|
|
3408
4202
|
}
|
|
3409
4203
|
function writeCodexRemote(threads, pid = process.pid, now = Date.now()) {
|
|
3410
4204
|
try {
|
|
3411
|
-
|
|
4205
|
+
mkdirSync15(deskbuddyHome(), { recursive: true });
|
|
3412
4206
|
const p = codexRemotePath();
|
|
3413
|
-
|
|
3414
|
-
|
|
4207
|
+
writeFileSync13(p + ".tmp", JSON.stringify({ pid, threads: [...threads], at: now }), "utf8");
|
|
4208
|
+
renameSync11(p + ".tmp", p);
|
|
3415
4209
|
} catch {
|
|
3416
4210
|
}
|
|
3417
4211
|
}
|
|
3418
4212
|
function removeCodexRemote() {
|
|
3419
4213
|
try {
|
|
3420
|
-
|
|
4214
|
+
rmSync7(codexRemotePath(), { force: true });
|
|
3421
4215
|
} catch {
|
|
3422
4216
|
}
|
|
3423
4217
|
}
|
|
3424
4218
|
|
|
3425
4219
|
// src/wrap/channel.ts
|
|
3426
4220
|
import { randomBytes as randomBytes5 } from "crypto";
|
|
3427
|
-
import { chmodSync as
|
|
4221
|
+
import { chmodSync as chmodSync4, rmSync as rmSync9 } from "fs";
|
|
3428
4222
|
import { connect, createServer as createServer2 } from "net";
|
|
3429
4223
|
|
|
3430
4224
|
// src/core/wake.ts
|
|
3431
|
-
import { execFileSync as
|
|
4225
|
+
import { execFileSync as execFileSync10 } from "child_process";
|
|
3432
4226
|
import { createHash as createHash3, createHmac as createHmac2, timingSafeEqual as timingSafeEqual2 } from "crypto";
|
|
3433
|
-
import { chmodSync as
|
|
3434
|
-
import { tmpdir } from "os";
|
|
3435
|
-
import { join as
|
|
4227
|
+
import { chmodSync as chmodSync3, closeSync as closeSync3, mkdirSync as mkdirSync16, openSync as openSync3, readdirSync as readdirSync4, readFileSync as readFileSync14, readSync as readSync2, renameSync as renameSync12, rmSync as rmSync8, statSync as statSync5, writeFileSync as writeFileSync14 } from "fs";
|
|
4228
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
4229
|
+
import { join as join20 } from "path";
|
|
3436
4230
|
function wakeDir(home = deskbuddyHome()) {
|
|
3437
|
-
return
|
|
4231
|
+
return join20(home, "wake");
|
|
3438
4232
|
}
|
|
3439
4233
|
function marksDir(home = deskbuddyHome()) {
|
|
3440
|
-
return
|
|
4234
|
+
return join20(wakeDir(home), "marks");
|
|
3441
4235
|
}
|
|
3442
4236
|
function safeSid(sessionId) {
|
|
3443
4237
|
return String(sessionId).replace(/[^A-Za-z0-9_-]/g, "");
|
|
@@ -3447,10 +4241,10 @@ function homeTag(home) {
|
|
|
3447
4241
|
}
|
|
3448
4242
|
function wakeChannelPath(platform = process.platform, home = deskbuddyHome()) {
|
|
3449
4243
|
if (platform === "win32") return `\\\\.\\pipe\\deskbuddy-wake-${homeTag(home)}`;
|
|
3450
|
-
const primary =
|
|
4244
|
+
const primary = join20(wakeDir(home), "wake.sock");
|
|
3451
4245
|
if (Buffer.byteLength(primary) <= 103) return primary;
|
|
3452
4246
|
const uid = typeof process.getuid === "function" ? process.getuid() : 0;
|
|
3453
|
-
return
|
|
4247
|
+
return join20(tmpdir2(), `deskbuddy-${uid}`, `wake-${homeTag(home)}.sock`);
|
|
3454
4248
|
}
|
|
3455
4249
|
function pidExists(pid) {
|
|
3456
4250
|
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
@@ -3465,11 +4259,11 @@ function writeWakeMark(sessionId, agentPid, now = Date.now()) {
|
|
|
3465
4259
|
const safe = safeSid(sessionId);
|
|
3466
4260
|
if (!safe) return;
|
|
3467
4261
|
try {
|
|
3468
|
-
|
|
3469
|
-
const file =
|
|
4262
|
+
mkdirSync16(marksDir(), { recursive: true, mode: 448 });
|
|
4263
|
+
const file = join20(marksDir(), `${safe}.json`);
|
|
3470
4264
|
const tmp = `${file}.${process.pid}.tmp`;
|
|
3471
|
-
|
|
3472
|
-
|
|
4265
|
+
writeFileSync14(tmp, JSON.stringify({ agentPid: agentPid ?? 0, daemonPid: process.pid, at: now }), { encoding: "utf8", mode: 384 });
|
|
4266
|
+
renameSync12(tmp, file);
|
|
3473
4267
|
} catch {
|
|
3474
4268
|
}
|
|
3475
4269
|
}
|
|
@@ -3477,20 +4271,20 @@ function removeWakeMark(sessionId) {
|
|
|
3477
4271
|
const safe = safeSid(sessionId);
|
|
3478
4272
|
if (!safe) return;
|
|
3479
4273
|
try {
|
|
3480
|
-
|
|
4274
|
+
rmSync8(join20(marksDir(), `${safe}.json`), { force: true });
|
|
3481
4275
|
} catch {
|
|
3482
4276
|
}
|
|
3483
4277
|
}
|
|
3484
4278
|
function clearWakeMarks() {
|
|
3485
4279
|
let names = [];
|
|
3486
4280
|
try {
|
|
3487
|
-
names =
|
|
4281
|
+
names = readdirSync4(marksDir());
|
|
3488
4282
|
} catch {
|
|
3489
4283
|
return;
|
|
3490
4284
|
}
|
|
3491
4285
|
for (const n of names) {
|
|
3492
4286
|
try {
|
|
3493
|
-
|
|
4287
|
+
rmSync8(join20(marksDir(), n), { force: true });
|
|
3494
4288
|
} catch {
|
|
3495
4289
|
}
|
|
3496
4290
|
}
|
|
@@ -3499,7 +4293,7 @@ function isWakeMarked(sessionId, alive = pidExists) {
|
|
|
3499
4293
|
const safe = safeSid(sessionId);
|
|
3500
4294
|
if (!safe) return false;
|
|
3501
4295
|
try {
|
|
3502
|
-
const m = JSON.parse(
|
|
4296
|
+
const m = JSON.parse(readFileSync14(join20(marksDir(), `${safe}.json`), "utf8"));
|
|
3503
4297
|
const daemonPid = Number(m?.daemonPid) || 0;
|
|
3504
4298
|
if (!daemonPid || !alive(daemonPid)) return false;
|
|
3505
4299
|
const pid = Number(m?.agentPid) || 0;
|
|
@@ -3512,30 +4306,30 @@ function withWake(states, alive) {
|
|
|
3512
4306
|
return states.map((s) => s.remoteWindow === true || !isWakeMarked(s.sessionId, alive) ? s : { ...s, remoteWindow: true });
|
|
3513
4307
|
}
|
|
3514
4308
|
function wakeSecretPath(home = deskbuddyHome()) {
|
|
3515
|
-
return
|
|
4309
|
+
return join20(wakeDir(home), "secret");
|
|
3516
4310
|
}
|
|
3517
4311
|
function writeWakeSecret(hex, platform = process.platform) {
|
|
3518
4312
|
const file = wakeSecretPath();
|
|
3519
|
-
|
|
4313
|
+
mkdirSync16(wakeDir(), { recursive: true, mode: 448 });
|
|
3520
4314
|
const tmp = `${file}.${process.pid}.tmp`;
|
|
3521
|
-
|
|
4315
|
+
writeFileSync14(tmp, hex, { encoding: "utf8", mode: 384 });
|
|
3522
4316
|
if (platform === "win32") {
|
|
3523
4317
|
try {
|
|
3524
|
-
|
|
4318
|
+
execFileSync10("icacls", [tmp, "/inheritance:r", "/grant:r", `${currentUserSid()}:F`], { stdio: "ignore", windowsHide: true });
|
|
3525
4319
|
} catch {
|
|
3526
|
-
|
|
4320
|
+
renameSync12(tmp, file);
|
|
3527
4321
|
return "\u7A97\u53E3\u5524\u9192\u5BC6\u94A5\u6587\u4EF6\u7684\u6743\u9650\u6CA1\u80FD\u6536\u7D27\uFF08icacls \u5931\u8D25\uFF09";
|
|
3528
4322
|
}
|
|
3529
4323
|
} else {
|
|
3530
|
-
|
|
4324
|
+
chmodSync3(tmp, 384);
|
|
3531
4325
|
}
|
|
3532
|
-
|
|
4326
|
+
renameSync12(tmp, file);
|
|
3533
4327
|
return "";
|
|
3534
4328
|
}
|
|
3535
4329
|
function restrictToUser(file, platform = process.platform) {
|
|
3536
4330
|
if (platform !== "win32") return true;
|
|
3537
4331
|
try {
|
|
3538
|
-
|
|
4332
|
+
execFileSync10("icacls", [file, "/inheritance:r", "/grant:r", `${currentUserSid()}:F`], { stdio: "ignore", windowsHide: true });
|
|
3539
4333
|
return true;
|
|
3540
4334
|
} catch {
|
|
3541
4335
|
return false;
|
|
@@ -3543,7 +4337,7 @@ function restrictToUser(file, platform = process.platform) {
|
|
|
3543
4337
|
}
|
|
3544
4338
|
function currentUserSid() {
|
|
3545
4339
|
try {
|
|
3546
|
-
const out =
|
|
4340
|
+
const out = execFileSync10("whoami", ["/user", "/fo", "csv", "/nh"], { encoding: "utf8", windowsHide: true });
|
|
3547
4341
|
const m = /"(S-1-[0-9-]+)"/.exec(out);
|
|
3548
4342
|
if (m) return `*${m[1]}`;
|
|
3549
4343
|
} catch {
|
|
@@ -3578,10 +4372,10 @@ function analyzeTranscript(file) {
|
|
|
3578
4372
|
if (!file) return null;
|
|
3579
4373
|
let fd;
|
|
3580
4374
|
try {
|
|
3581
|
-
const size =
|
|
4375
|
+
const size = statSync5(file).size;
|
|
3582
4376
|
const len = Math.min(size, TRANSCRIPT_TAIL2);
|
|
3583
4377
|
const buf = Buffer.alloc(len);
|
|
3584
|
-
fd =
|
|
4378
|
+
fd = openSync3(file, "r");
|
|
3585
4379
|
readSync2(fd, buf, 0, len, size - len);
|
|
3586
4380
|
const lines2 = buf.toString("utf8").split("\n");
|
|
3587
4381
|
let seg = false;
|
|
@@ -3623,7 +4417,7 @@ function analyzeTranscript(file) {
|
|
|
3623
4417
|
} finally {
|
|
3624
4418
|
if (fd !== void 0) {
|
|
3625
4419
|
try {
|
|
3626
|
-
|
|
4420
|
+
closeSync3(fd);
|
|
3627
4421
|
} catch {
|
|
3628
4422
|
}
|
|
3629
4423
|
}
|
|
@@ -3674,7 +4468,7 @@ function replyMac(secret, nonceC, r) {
|
|
|
3674
4468
|
return wakeMac(secret, "r", nonceC, r.instructionId, r.status, r.reason ?? "");
|
|
3675
4469
|
}
|
|
3676
4470
|
function startChannelServer(path, secret, onRequest, platform = process.platform) {
|
|
3677
|
-
return new Promise((
|
|
4471
|
+
return new Promise((resolve6) => {
|
|
3678
4472
|
const server = createServer2((sock) => {
|
|
3679
4473
|
let open = true;
|
|
3680
4474
|
let phase = "hello";
|
|
@@ -3725,7 +4519,7 @@ function startChannelServer(path, secret, onRequest, platform = process.platform
|
|
|
3725
4519
|
});
|
|
3726
4520
|
if (platform !== "win32") {
|
|
3727
4521
|
try {
|
|
3728
|
-
|
|
4522
|
+
rmSync9(path, { force: true });
|
|
3729
4523
|
} catch {
|
|
3730
4524
|
}
|
|
3731
4525
|
}
|
|
@@ -3735,19 +4529,19 @@ function startChannelServer(path, secret, onRequest, platform = process.platform
|
|
|
3735
4529
|
};
|
|
3736
4530
|
server.once("error", () => {
|
|
3737
4531
|
restore();
|
|
3738
|
-
|
|
4532
|
+
resolve6(null);
|
|
3739
4533
|
});
|
|
3740
4534
|
server.listen(path, () => {
|
|
3741
4535
|
restore();
|
|
3742
4536
|
if (platform !== "win32") {
|
|
3743
4537
|
try {
|
|
3744
|
-
|
|
4538
|
+
chmodSync4(path, 384);
|
|
3745
4539
|
} catch {
|
|
3746
4540
|
}
|
|
3747
4541
|
}
|
|
3748
4542
|
server.on("error", () => {
|
|
3749
4543
|
});
|
|
3750
|
-
|
|
4544
|
+
resolve6(server);
|
|
3751
4545
|
});
|
|
3752
4546
|
});
|
|
3753
4547
|
}
|
|
@@ -3758,16 +4552,16 @@ function closeChannelServer(server, path, platform = process.platform) {
|
|
|
3758
4552
|
}
|
|
3759
4553
|
if (platform !== "win32") {
|
|
3760
4554
|
try {
|
|
3761
|
-
|
|
4555
|
+
rmSync9(path, { force: true });
|
|
3762
4556
|
} catch {
|
|
3763
4557
|
}
|
|
3764
4558
|
}
|
|
3765
4559
|
}
|
|
3766
4560
|
var FINAL = /* @__PURE__ */ new Set(["started", "rejected", "failed", "not_here"]);
|
|
3767
4561
|
function sendToWindow(path, secret, req2, onReply, opts = {}) {
|
|
3768
|
-
return new Promise((
|
|
4562
|
+
return new Promise((resolve6) => {
|
|
3769
4563
|
if (!secret) {
|
|
3770
|
-
|
|
4564
|
+
resolve6("fallback");
|
|
3771
4565
|
return;
|
|
3772
4566
|
}
|
|
3773
4567
|
let settled = false;
|
|
@@ -3780,13 +4574,13 @@ function sendToWindow(path, secret, req2, onReply, opts = {}) {
|
|
|
3780
4574
|
settled = true;
|
|
3781
4575
|
handled = v === "handled";
|
|
3782
4576
|
clearTimeout(timer);
|
|
3783
|
-
|
|
4577
|
+
resolve6(v);
|
|
3784
4578
|
};
|
|
3785
4579
|
let sock;
|
|
3786
4580
|
try {
|
|
3787
4581
|
sock = connect(path);
|
|
3788
4582
|
} catch {
|
|
3789
|
-
|
|
4583
|
+
resolve6("fallback");
|
|
3790
4584
|
return;
|
|
3791
4585
|
}
|
|
3792
4586
|
const timer = setTimeout(() => {
|
|
@@ -3908,29 +4702,29 @@ function boxMatches(view, body) {
|
|
|
3908
4702
|
}
|
|
3909
4703
|
|
|
3910
4704
|
// src/core/wrapWindows.ts
|
|
3911
|
-
import { chmodSync as
|
|
3912
|
-
import { tmpdir as
|
|
3913
|
-
import { join as
|
|
4705
|
+
import { chmodSync as chmodSync5, mkdirSync as mkdirSync17, readdirSync as readdirSync5, readFileSync as readFileSync15, renameSync as renameSync13, rmSync as rmSync10, statSync as statSync6, writeFileSync as writeFileSync15 } from "fs";
|
|
4706
|
+
import { tmpdir as tmpdir3 } from "os";
|
|
4707
|
+
import { join as join21 } from "path";
|
|
3914
4708
|
var WINDOW_ID_RE = /^[a-f0-9]{32}$/;
|
|
3915
4709
|
function windowsDir() {
|
|
3916
|
-
return
|
|
4710
|
+
return join21(deskbuddyHome(), "windows");
|
|
3917
4711
|
}
|
|
3918
4712
|
function bySessionDir() {
|
|
3919
|
-
return
|
|
4713
|
+
return join21(windowsDir(), "by-session");
|
|
3920
4714
|
}
|
|
3921
4715
|
function safeSid2(sessionId) {
|
|
3922
4716
|
return String(sessionId).replace(/[^A-Za-z0-9_-]/g, "");
|
|
3923
4717
|
}
|
|
3924
4718
|
function ensureDir(dir) {
|
|
3925
|
-
|
|
4719
|
+
mkdirSync17(dir, { recursive: true, mode: 448 });
|
|
3926
4720
|
}
|
|
3927
4721
|
function atomicWrite(file, text) {
|
|
3928
4722
|
const tmp = `${file}.${process.pid}.tmp`;
|
|
3929
|
-
|
|
3930
|
-
|
|
4723
|
+
writeFileSync15(tmp, text, { encoding: "utf8", mode: 384 });
|
|
4724
|
+
renameSync13(tmp, file);
|
|
3931
4725
|
}
|
|
3932
4726
|
function windowFile(windowId) {
|
|
3933
|
-
return
|
|
4727
|
+
return join21(windowsDir(), `${windowId}.json`);
|
|
3934
4728
|
}
|
|
3935
4729
|
function writeWindow(windowId, rec) {
|
|
3936
4730
|
if (!WINDOW_ID_RE.test(windowId)) return;
|
|
@@ -3945,7 +4739,7 @@ function windowSecret(windowId) {
|
|
|
3945
4739
|
function readWindow(windowId) {
|
|
3946
4740
|
if (!WINDOW_ID_RE.test(windowId)) return null;
|
|
3947
4741
|
try {
|
|
3948
|
-
const r = JSON.parse(
|
|
4742
|
+
const r = JSON.parse(readFileSync15(windowFile(windowId), "utf8"));
|
|
3949
4743
|
return r && typeof r.pid === "number" ? r : null;
|
|
3950
4744
|
} catch {
|
|
3951
4745
|
return null;
|
|
@@ -3955,20 +4749,20 @@ function removeWindow(windowId) {
|
|
|
3955
4749
|
if (!WINDOW_ID_RE.test(windowId)) return;
|
|
3956
4750
|
for (const f of [windowFile(windowId), lastEventFile(windowId)]) {
|
|
3957
4751
|
try {
|
|
3958
|
-
|
|
4752
|
+
rmSync10(f, { force: true });
|
|
3959
4753
|
} catch {
|
|
3960
4754
|
}
|
|
3961
4755
|
}
|
|
3962
4756
|
let names = [];
|
|
3963
4757
|
try {
|
|
3964
|
-
names =
|
|
4758
|
+
names = readdirSync5(bySessionDir());
|
|
3965
4759
|
} catch {
|
|
3966
4760
|
return;
|
|
3967
4761
|
}
|
|
3968
4762
|
for (const n of names) {
|
|
3969
|
-
const f =
|
|
4763
|
+
const f = join21(bySessionDir(), n);
|
|
3970
4764
|
try {
|
|
3971
|
-
if (
|
|
4765
|
+
if (readFileSync15(f, "utf8").trim() === windowId) rmSync10(f, { force: true });
|
|
3972
4766
|
} catch {
|
|
3973
4767
|
}
|
|
3974
4768
|
}
|
|
@@ -3977,19 +4771,19 @@ function readBySession(sessionId) {
|
|
|
3977
4771
|
const safe = safeSid2(sessionId);
|
|
3978
4772
|
if (!safe) return null;
|
|
3979
4773
|
try {
|
|
3980
|
-
const id =
|
|
4774
|
+
const id = readFileSync15(join21(bySessionDir(), safe), "utf8").trim();
|
|
3981
4775
|
return WINDOW_ID_RE.test(id) ? id : null;
|
|
3982
4776
|
} catch {
|
|
3983
4777
|
return null;
|
|
3984
4778
|
}
|
|
3985
4779
|
}
|
|
3986
4780
|
function lastEventFile(windowId) {
|
|
3987
|
-
return
|
|
4781
|
+
return join21(windowsDir(), `${windowId}.last`);
|
|
3988
4782
|
}
|
|
3989
4783
|
function readLastEvent(windowId) {
|
|
3990
4784
|
if (!WINDOW_ID_RE.test(windowId)) return null;
|
|
3991
4785
|
try {
|
|
3992
|
-
const e = JSON.parse(
|
|
4786
|
+
const e = JSON.parse(readFileSync15(lastEventFile(windowId), "utf8"));
|
|
3993
4787
|
return e && typeof e.ev === "string" && typeof e.at === "number" ? { ev: e.ev, sid: String(e.sid ?? ""), at: e.at } : null;
|
|
3994
4788
|
} catch {
|
|
3995
4789
|
return null;
|
|
@@ -3998,18 +4792,18 @@ function readLastEvent(windowId) {
|
|
|
3998
4792
|
function channelPath(windowId, platform = process.platform, home = deskbuddyHome()) {
|
|
3999
4793
|
if (platform === "win32") return `\\\\.\\pipe\\deskbuddy-${windowId}`;
|
|
4000
4794
|
const name = `${windowId.slice(0, 16)}.sock`;
|
|
4001
|
-
const primary =
|
|
4795
|
+
const primary = join21(home, "win", name);
|
|
4002
4796
|
if (Buffer.byteLength(primary) <= 103) return primary;
|
|
4003
4797
|
const uid = typeof process.getuid === "function" ? process.getuid() : 0;
|
|
4004
|
-
return
|
|
4798
|
+
return join21(tmpdir3(), `deskbuddy-${uid}`, name);
|
|
4005
4799
|
}
|
|
4006
4800
|
function ensureChannelDir(sockPath) {
|
|
4007
|
-
const dir =
|
|
4801
|
+
const dir = join21(sockPath, "..");
|
|
4008
4802
|
try {
|
|
4009
|
-
|
|
4010
|
-
const st =
|
|
4803
|
+
mkdirSync17(dir, { recursive: true, mode: 448 });
|
|
4804
|
+
const st = statSync6(dir);
|
|
4011
4805
|
if (typeof process.getuid === "function" && st.uid !== process.getuid()) return false;
|
|
4012
|
-
|
|
4806
|
+
chmodSync5(dir, 448);
|
|
4013
4807
|
return true;
|
|
4014
4808
|
} catch {
|
|
4015
4809
|
return false;
|
|
@@ -4026,35 +4820,35 @@ function windowForSession(sessionId) {
|
|
|
4026
4820
|
}
|
|
4027
4821
|
|
|
4028
4822
|
// src/core/launch.ts
|
|
4029
|
-
import { mkdirSync as
|
|
4030
|
-
import { join as
|
|
4823
|
+
import { mkdirSync as mkdirSync18, readFileSync as readFileSync16, renameSync as renameSync14, rmSync as rmSync11, writeFileSync as writeFileSync16 } from "fs";
|
|
4824
|
+
import { join as join22 } from "path";
|
|
4031
4825
|
var RUN_ID_RE = /^[a-f0-9]{32}$/;
|
|
4032
4826
|
var SESSION_UUID_RE = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
|
|
4033
4827
|
function launchDir(home = deskbuddyHome()) {
|
|
4034
|
-
return
|
|
4828
|
+
return join22(home, "launch");
|
|
4035
4829
|
}
|
|
4036
4830
|
function planFile(runId) {
|
|
4037
|
-
return
|
|
4831
|
+
return join22(launchDir(), `${runId}.json`);
|
|
4038
4832
|
}
|
|
4039
4833
|
function writeLaunch(runId, plan) {
|
|
4040
4834
|
if (!RUN_ID_RE.test(runId)) throw new Error("bad runId");
|
|
4041
4835
|
if (plan.resume && !SESSION_UUID_RE.test(plan.resume)) throw new Error("bad session id");
|
|
4042
|
-
|
|
4836
|
+
mkdirSync18(launchDir(), { recursive: true, mode: 448 });
|
|
4043
4837
|
const tmp = `${planFile(runId)}.${process.pid}.tmp`;
|
|
4044
|
-
|
|
4838
|
+
writeFileSync16(tmp, JSON.stringify(plan), { encoding: "utf8", mode: 384 });
|
|
4045
4839
|
restrictToUser(tmp);
|
|
4046
|
-
|
|
4840
|
+
renameSync14(tmp, planFile(runId));
|
|
4047
4841
|
}
|
|
4048
4842
|
function claimLaunch(runId) {
|
|
4049
4843
|
if (!RUN_ID_RE.test(runId)) return null;
|
|
4050
|
-
const claimed =
|
|
4844
|
+
const claimed = join22(launchDir(), `${runId}.${process.pid}.claimed`);
|
|
4051
4845
|
try {
|
|
4052
|
-
|
|
4846
|
+
renameSync14(planFile(runId), claimed);
|
|
4053
4847
|
} catch {
|
|
4054
4848
|
return null;
|
|
4055
4849
|
}
|
|
4056
4850
|
try {
|
|
4057
|
-
const p = JSON.parse(
|
|
4851
|
+
const p = JSON.parse(readFileSync16(claimed, "utf8"));
|
|
4058
4852
|
if (!p || typeof p.cwd !== "string" || typeof p.resume !== "string") return null;
|
|
4059
4853
|
if (p.resume && !SESSION_UUID_RE.test(p.resume)) return null;
|
|
4060
4854
|
return p;
|
|
@@ -4062,14 +4856,14 @@ function claimLaunch(runId) {
|
|
|
4062
4856
|
return null;
|
|
4063
4857
|
} finally {
|
|
4064
4858
|
try {
|
|
4065
|
-
|
|
4859
|
+
rmSync11(claimed, { force: true });
|
|
4066
4860
|
} catch {
|
|
4067
4861
|
}
|
|
4068
4862
|
}
|
|
4069
4863
|
}
|
|
4070
4864
|
function launchPending(runId) {
|
|
4071
4865
|
try {
|
|
4072
|
-
|
|
4866
|
+
readFileSync16(planFile(runId));
|
|
4073
4867
|
return true;
|
|
4074
4868
|
} catch {
|
|
4075
4869
|
return false;
|
|
@@ -4077,10 +4871,10 @@ function launchPending(runId) {
|
|
|
4077
4871
|
}
|
|
4078
4872
|
|
|
4079
4873
|
// src/daemon/openWindow.ts
|
|
4080
|
-
import { execFile as
|
|
4081
|
-
import { chmodSync as
|
|
4874
|
+
import { execFile as execFile4, execFileSync as execFileSync11, spawn as spawn3 } from "child_process";
|
|
4875
|
+
import { chmodSync as chmodSync6, mkdirSync as mkdirSync19, rmSync as rmSync12, writeFileSync as writeFileSync17 } from "fs";
|
|
4082
4876
|
import { userInfo } from "os";
|
|
4083
|
-
import { join as
|
|
4877
|
+
import { join as join23 } from "path";
|
|
4084
4878
|
function safePath(p) {
|
|
4085
4879
|
return /^(?:[A-Za-z]:[\\/]|\/)/.test(p) && !/[\r\n\0"'`$%;]/.test(p);
|
|
4086
4880
|
}
|
|
@@ -4128,14 +4922,14 @@ var defaultGuiDeps = () => ({
|
|
|
4128
4922
|
env: process.env,
|
|
4129
4923
|
macManager: () => {
|
|
4130
4924
|
try {
|
|
4131
|
-
return
|
|
4925
|
+
return execFileSync11("launchctl", ["managername"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout: 3e3 });
|
|
4132
4926
|
} catch {
|
|
4133
4927
|
return "";
|
|
4134
4928
|
}
|
|
4135
4929
|
},
|
|
4136
4930
|
macConsoleUser: () => {
|
|
4137
4931
|
try {
|
|
4138
|
-
return
|
|
4932
|
+
return execFileSync11("stat", ["-f", "%Su", "/dev/console"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout: 3e3 });
|
|
4139
4933
|
} catch {
|
|
4140
4934
|
return "";
|
|
4141
4935
|
}
|
|
@@ -4149,7 +4943,7 @@ var defaultGuiDeps = () => ({
|
|
|
4149
4943
|
},
|
|
4150
4944
|
winSessionId: () => {
|
|
4151
4945
|
try {
|
|
4152
|
-
const out =
|
|
4946
|
+
const out = execFileSync11("tasklist", ["/FI", `PID eq ${process.pid}`, "/FO", "CSV", "/NH"], {
|
|
4153
4947
|
encoding: "utf8",
|
|
4154
4948
|
stdio: ["ignore", "pipe", "ignore"],
|
|
4155
4949
|
windowsHide: true,
|
|
@@ -4166,11 +4960,11 @@ var defaultGuiDeps = () => ({
|
|
|
4166
4960
|
var defaultOpenerDeps = (findWt2) => ({
|
|
4167
4961
|
platform: process.platform,
|
|
4168
4962
|
findWt: findWt2,
|
|
4169
|
-
run: (file, args) => new Promise((
|
|
4170
|
-
|
|
4963
|
+
run: (file, args) => new Promise((resolve6, reject) => {
|
|
4964
|
+
execFile4(file, args, { windowsHide: true, timeout: 15e3 }, (err) => err ? reject(err) : resolve6());
|
|
4171
4965
|
}),
|
|
4172
4966
|
spawnDetached: (file, args) => {
|
|
4173
|
-
const c =
|
|
4967
|
+
const c = spawn3(file, args, { detached: true, stdio: "ignore", shell: false, windowsHide: false });
|
|
4174
4968
|
c.on("error", () => {
|
|
4175
4969
|
});
|
|
4176
4970
|
c.unref();
|
|
@@ -4179,10 +4973,10 @@ var defaultOpenerDeps = (findWt2) => ({
|
|
|
4179
4973
|
async function openTerminal(t, d) {
|
|
4180
4974
|
checkTarget(t);
|
|
4181
4975
|
if (d.platform === "darwin") {
|
|
4182
|
-
|
|
4183
|
-
const file =
|
|
4184
|
-
|
|
4185
|
-
|
|
4976
|
+
mkdirSync19(launchDir(t.home), { recursive: true, mode: 448 });
|
|
4977
|
+
const file = join23(launchDir(t.home), `${t.runId}.command`);
|
|
4978
|
+
writeFileSync17(file, macCommandScript(t), { encoding: "utf8", mode: 448 });
|
|
4979
|
+
chmodSync6(file, 448);
|
|
4186
4980
|
await d.run("open", ["-a", "Terminal", file]);
|
|
4187
4981
|
return "terminal";
|
|
4188
4982
|
}
|
|
@@ -4200,14 +4994,14 @@ async function openTerminal(t, d) {
|
|
|
4200
4994
|
function removeCommandFile(home, runId) {
|
|
4201
4995
|
if (!RUN_ID_RE.test(runId)) return;
|
|
4202
4996
|
try {
|
|
4203
|
-
|
|
4997
|
+
rmSync12(join23(launchDir(home), `${runId}.command`), { force: true });
|
|
4204
4998
|
} catch {
|
|
4205
4999
|
}
|
|
4206
5000
|
}
|
|
4207
5001
|
|
|
4208
5002
|
// src/daemon/wake.ts
|
|
4209
5003
|
import { randomBytes as randomBytes6 } from "crypto";
|
|
4210
|
-
import { rmSync as
|
|
5004
|
+
import { rmSync as rmSync13 } from "fs";
|
|
4211
5005
|
import { createServer as createServer3 } from "net";
|
|
4212
5006
|
var BUSY_STATES = /* @__PURE__ */ new Set(["watch", "ask_approval"]);
|
|
4213
5007
|
var STOP_MIN_MS = 1500;
|
|
@@ -4232,6 +5026,14 @@ var WakeHub = class {
|
|
|
4232
5026
|
const s = this.#slots.get(sessionId);
|
|
4233
5027
|
return !!s && (!!s.reg || !!s.flight);
|
|
4234
5028
|
}
|
|
5029
|
+
/**
|
|
5030
|
+
* 有没有 daemon 交出去、还没落定的指令(排队中 / 交付中 / 唤醒后正在跑的那一轮)。
|
|
5031
|
+
* 只登记着等待者不算忙:那是用户自己的窗口。自动升级的空闲判定用(spec 2026-10-01 §4.3)
|
|
5032
|
+
*/
|
|
5033
|
+
get busy() {
|
|
5034
|
+
for (const s of this.#slots.values()) if (s.queue.length > 0 || s.flight || s.reg?.pending) return true;
|
|
5035
|
+
return false;
|
|
5036
|
+
}
|
|
4235
5037
|
/** 交给唤醒路由。返回 false = 这个会话没有等待者,调用方走别的路 */
|
|
4236
5038
|
submit(cmd, sessionId) {
|
|
4237
5039
|
if (cmd.tool !== "claude_code") return false;
|
|
@@ -4494,11 +5296,11 @@ function startWakeServer(path, hub, secret, platform = process.platform) {
|
|
|
4494
5296
|
if (platform !== "win32") {
|
|
4495
5297
|
if (!ensureChannelDir(path)) return Promise.resolve(null);
|
|
4496
5298
|
try {
|
|
4497
|
-
|
|
5299
|
+
rmSync13(path, { force: true });
|
|
4498
5300
|
} catch {
|
|
4499
5301
|
}
|
|
4500
5302
|
}
|
|
4501
|
-
return new Promise((
|
|
5303
|
+
return new Promise((resolve6) => {
|
|
4502
5304
|
const server = createServer3((sock) => {
|
|
4503
5305
|
sock.on("error", () => {
|
|
4504
5306
|
});
|
|
@@ -4584,18 +5386,19 @@ function startWakeServer(path, hub, secret, platform = process.platform) {
|
|
|
4584
5386
|
};
|
|
4585
5387
|
server.once("error", () => {
|
|
4586
5388
|
restore();
|
|
4587
|
-
|
|
5389
|
+
resolve6(null);
|
|
4588
5390
|
});
|
|
4589
5391
|
server.listen(path, () => {
|
|
4590
5392
|
restore();
|
|
4591
5393
|
server.on("error", () => {
|
|
4592
5394
|
});
|
|
4593
|
-
|
|
5395
|
+
resolve6(server);
|
|
4594
5396
|
});
|
|
4595
5397
|
});
|
|
4596
5398
|
}
|
|
4597
5399
|
|
|
4598
5400
|
// src/daemon/index.ts
|
|
5401
|
+
var UPDATING_REASON = "updating";
|
|
4599
5402
|
var IDLE_TIMEOUT_MS = 150 * 1e3;
|
|
4600
5403
|
var POLICY_VIOLATION = 1008;
|
|
4601
5404
|
var PING_FRAME_SERVICE = "ping";
|
|
@@ -4611,7 +5414,7 @@ function defaultCodexAppServers() {
|
|
|
4611
5414
|
return cfg.enabledTools.includes("codex") ? discoverAppServers(cfg.codexAppServer) : [];
|
|
4612
5415
|
}
|
|
4613
5416
|
function opencodeReady() {
|
|
4614
|
-
return isOcPluginInstalled(opencodePluginPath()) &&
|
|
5417
|
+
return isOcPluginInstalled(opencodePluginPath()) && existsSync13(ocPluginModule(installedHookPath()));
|
|
4615
5418
|
}
|
|
4616
5419
|
function orphanResumeText(summary2) {
|
|
4617
5420
|
return `\uFF08\u4F60\u5728\u8BBE\u5907\u4E0A\u5DF2\u6279\u51C6\uFF1A${summary2}\uFF09\u8BF7\u7EE7\u7EED\u521A\u624D\u88AB\u4E2D\u65AD\u7684\u64CD\u4F5C\u3002`;
|
|
@@ -4646,6 +5449,10 @@ var Daemon = class {
|
|
|
4646
5449
|
#codex = /* @__PURE__ */ new Map();
|
|
4647
5450
|
#codexStarted = false;
|
|
4648
5451
|
#codexReportTimer = null;
|
|
5452
|
+
/** daemon 交出去、还没有结局的指令流程数(开窗 / 写进窗口 / 经 app-server 发一轮)。空闲判定用 */
|
|
5453
|
+
#inflight = 0;
|
|
5454
|
+
/** 自动升级排空中:不再接新指令、新授权请求 */
|
|
5455
|
+
#draining = false;
|
|
4649
5456
|
constructor(deps = {}) {
|
|
4650
5457
|
this.#deps = {
|
|
4651
5458
|
connect: deps.connect ?? ((url) => new WebSocket2(url)),
|
|
@@ -4674,6 +5481,8 @@ var Daemon = class {
|
|
|
4674
5481
|
windowReadyMs: deps.windowReadyMs ?? WINDOW_READY_MS,
|
|
4675
5482
|
wakePath: deps.wakePath === void 0 ? wakeChannelPath() : deps.wakePath,
|
|
4676
5483
|
onWindowDone: deps.onWindowDone ?? (() => {
|
|
5484
|
+
}),
|
|
5485
|
+
onLink: deps.onLink ?? (() => {
|
|
4677
5486
|
})
|
|
4678
5487
|
};
|
|
4679
5488
|
this.#wake = new WakeHub({
|
|
@@ -4866,6 +5675,45 @@ var Daemon = class {
|
|
|
4866
5675
|
get coordinator() {
|
|
4867
5676
|
return this.#coord;
|
|
4868
5677
|
}
|
|
5678
|
+
/** 把一段异步的指令流程记进 #inflight,结束(成功或失败)时减掉 */
|
|
5679
|
+
#track(p) {
|
|
5680
|
+
this.#inflight++;
|
|
5681
|
+
return p.finally(() => {
|
|
5682
|
+
this.#inflight--;
|
|
5683
|
+
});
|
|
5684
|
+
}
|
|
5685
|
+
/**
|
|
5686
|
+
* 进入排空(判定空闲之后、交给新版本切换之前):新指令回报 rejected/updating 且不记 seen,
|
|
5687
|
+
* gate 请求直接拒(远程模式本来也没有在跑的进程;窗口模式拒了会退回窗口里本地确认)
|
|
5688
|
+
*/
|
|
5689
|
+
beginDrain() {
|
|
5690
|
+
this.#draining = true;
|
|
5691
|
+
this.#gate.refusing = true;
|
|
5692
|
+
log("\u8FDB\u5165\u5347\u7EA7\u6392\u7A7A\uFF1A\u4E0D\u518D\u63A5\u65B0\u6307\u4EE4");
|
|
5693
|
+
}
|
|
5694
|
+
/** 退出排空(切换失败) */
|
|
5695
|
+
endDrain() {
|
|
5696
|
+
if (!this.#draining) return;
|
|
5697
|
+
this.#draining = false;
|
|
5698
|
+
this.#gate.refusing = false;
|
|
5699
|
+
log("\u9000\u51FA\u5347\u7EA7\u6392\u7A7A\uFF0C\u6062\u590D\u63A5\u6307\u4EE4");
|
|
5700
|
+
}
|
|
5701
|
+
get draining() {
|
|
5702
|
+
return this.#draining;
|
|
5703
|
+
}
|
|
5704
|
+
/**
|
|
5705
|
+
* 能不能现在重启(自动升级 spec 2026-10-01 §4.3):daemon 手上没有任何在途的设备指令、没有挂着的授权请求。
|
|
5706
|
+
* 用户自己窗口里的会话不算忙 —— 它们不是 daemon 的子进程,重启不影响
|
|
5707
|
+
*/
|
|
5708
|
+
idleState() {
|
|
5709
|
+
const busy = [];
|
|
5710
|
+
if (this.#queue.activeSessions > 0) busy.push(`\u540E\u53F0\u6307\u4EE4\u961F\u5217 ${this.#queue.activeSessions} \u4E2A\u4F1A\u8BDD`);
|
|
5711
|
+
if (this.#runsDone.size > 0) busy.push(`${this.#runsDone.size} \u4E2A\u540E\u53F0\u8FDB\u7A0B\u5728\u8DD1`);
|
|
5712
|
+
if (this.#gate.inflight > 0) busy.push(`${this.#gate.inflight} \u4E2A\u6388\u6743\u8BF7\u6C42\u6302\u7740`);
|
|
5713
|
+
if (this.#opening.size > 0 || this.#inflight > 0) busy.push(`${this.#opening.size + this.#inflight} \u6761\u6307\u4EE4\u6B63\u5728\u5F00\u7A97 / \u4EA4\u7ED9\u7A97\u53E3`);
|
|
5714
|
+
if (this.#wake.busy) busy.push("\u5524\u9192\u7A97\u53E3\u91CC\u6709\u6392\u961F\u6216\u5728\u8DD1\u7684\u6307\u4EE4");
|
|
5715
|
+
return busy.length === 0 ? { idle: true, why: "" } : { idle: false, why: busy.join("\uFF0C") };
|
|
5716
|
+
}
|
|
4869
5717
|
stop() {
|
|
4870
5718
|
this.#stopped = true;
|
|
4871
5719
|
this.#clearIdle();
|
|
@@ -4924,6 +5772,7 @@ var Daemon = class {
|
|
|
4924
5772
|
this.#attempt = 0;
|
|
4925
5773
|
log("\u5DF2\u8FDE\u4E0A");
|
|
4926
5774
|
this.#armIdle();
|
|
5775
|
+
this.#deps.onLink(true);
|
|
4927
5776
|
void this.#coord.syncPending();
|
|
4928
5777
|
void this.#reportDirs(true);
|
|
4929
5778
|
});
|
|
@@ -4939,6 +5788,7 @@ var Daemon = class {
|
|
|
4939
5788
|
ws.on("close", (code, reason) => {
|
|
4940
5789
|
this.#clearIdle();
|
|
4941
5790
|
this.#ws = null;
|
|
5791
|
+
this.#deps.onLink(false);
|
|
4942
5792
|
if (code === POLICY_VIOLATION) {
|
|
4943
5793
|
log(`\u670D\u52A1\u7AEF\u62D2\u7EDD\u8FDE\u63A5(${code} ${reason?.toString() || ""})\uFF1A\u6388\u6743\u5DF2\u5931\u6548\uFF0C\u505C\u6B62\u91CD\u8FDE\u3002\u8BF7\u91CD\u65B0\u8FD0\u884C deskbuddy setup`);
|
|
4944
5794
|
this.#stopped = true;
|
|
@@ -4974,6 +5824,10 @@ var Daemon = class {
|
|
|
4974
5824
|
}
|
|
4975
5825
|
const ap = parseApprovalFrame(frame);
|
|
4976
5826
|
if (ap) {
|
|
5827
|
+
if (this.#draining) {
|
|
5828
|
+
log(`\u6B63\u5728\u5347\u7EA7\uFF0C\u6388\u6743\u51B3\u5B9A ${ap.approvalId} \u7559\u7ED9\u91CD\u542F\u540E\u7684\u5B88\u62A4\u8FDB\u7A0B\u8865\u6536`);
|
|
5829
|
+
return;
|
|
5830
|
+
}
|
|
4977
5831
|
if (!this.#seen.remember(ap.instructionId)) return;
|
|
4978
5832
|
this.#seen.save();
|
|
4979
5833
|
log(`\u6536\u5230\u6388\u6743\u51B3\u5B9A ${ap.approvalId} ${ap.decision}`);
|
|
@@ -4982,6 +5836,11 @@ var Daemon = class {
|
|
|
4982
5836
|
}
|
|
4983
5837
|
const cmd = parseCommand(frame);
|
|
4984
5838
|
if (!cmd) return;
|
|
5839
|
+
if (this.#draining) {
|
|
5840
|
+
log(`\u6307\u4EE4 ${cmd.instructionId} \u5230\u8FBE\u65F6\u6B63\u5728\u5347\u7EA7\u91CD\u542F\uFF0C\u62D2\u7EDD\uFF08\u53EF\u91CD\u53D1\uFF09`);
|
|
5841
|
+
this.#report(cmd, cmd.sessionId, "rejected", "", UPDATING_REASON);
|
|
5842
|
+
return;
|
|
5843
|
+
}
|
|
4985
5844
|
if (!this.#seen.remember(cmd.instructionId)) {
|
|
4986
5845
|
log(`\u6307\u4EE4 ${cmd.instructionId} \u5DF2\u5904\u7406\u8FC7\uFF0C\u4E22\u5F03`);
|
|
4987
5846
|
return;
|
|
@@ -5013,7 +5872,7 @@ var Daemon = class {
|
|
|
5013
5872
|
win = null;
|
|
5014
5873
|
}
|
|
5015
5874
|
if (win) {
|
|
5016
|
-
void this.#toWindow(cmd, sid, win);
|
|
5875
|
+
void this.#track(this.#toWindow(cmd, sid, win));
|
|
5017
5876
|
return;
|
|
5018
5877
|
}
|
|
5019
5878
|
if (cmd.tool === "codex" && !this.#queue.busy(sid)) {
|
|
@@ -5029,7 +5888,7 @@ var Daemon = class {
|
|
|
5029
5888
|
return;
|
|
5030
5889
|
}
|
|
5031
5890
|
if (route) {
|
|
5032
|
-
void this.#toCodexWindow(cmd, sid, route);
|
|
5891
|
+
void this.#track(this.#toCodexWindow(cmd, sid, route));
|
|
5033
5892
|
return;
|
|
5034
5893
|
}
|
|
5035
5894
|
}
|
|
@@ -5046,7 +5905,7 @@ var Daemon = class {
|
|
|
5046
5905
|
if (this.#wake.submit(cmd, sid)) return;
|
|
5047
5906
|
}
|
|
5048
5907
|
if (cmd.tool === "claude_code" && !isExpired(cmd, this.#deps.now())) {
|
|
5049
|
-
void this.#openOrDispatch(cmd).catch((e) => {
|
|
5908
|
+
void this.#track(this.#openOrDispatch(cmd)).catch((e) => {
|
|
5050
5909
|
log(`\u6307\u4EE4 ${cmd.instructionId} \u5F00\u7A97\u6D41\u7A0B\u51FA\u9519\uFF1A${e?.stack ?? String(e)}`);
|
|
5051
5910
|
this.#report(cmd, cmd.sessionId, "failed", "", "window_failed");
|
|
5052
5911
|
});
|
|
@@ -5382,7 +6241,7 @@ var Daemon = class {
|
|
|
5382
6241
|
this.#report(cmd, sessionId, "rejected", "", "tool_unsupported");
|
|
5383
6242
|
return;
|
|
5384
6243
|
}
|
|
5385
|
-
log(`\u6267\u884C ${cmd.instructionId} tool=${cmd.tool} ${sessionId ? "resume" : "new"} cwd=\u2026/${
|
|
6244
|
+
log(`\u6267\u884C ${cmd.instructionId} tool=${cmd.tool} ${sessionId ? "resume" : "new"} cwd=\u2026/${basename7(cwd)} len=${cmd.text.length}`);
|
|
5386
6245
|
const result = await this.#run(plan, cmd.tool, runId, sessionId, cwd, () => this.#report(cmd, sessionId, "started", "headless"));
|
|
5387
6246
|
if ("fail" in result) {
|
|
5388
6247
|
log(
|
|
@@ -5456,7 +6315,7 @@ var Daemon = class {
|
|
|
5456
6315
|
* 起不了(关卡不可用 / 找不到可执行文件)返回 {fail};进程起来那一刻调 onStarted
|
|
5457
6316
|
*/
|
|
5458
6317
|
async #run(plan, tool, runId, sessionId, cwd, onStarted) {
|
|
5459
|
-
if (!
|
|
6318
|
+
if (!existsSync13(this.#deps.nodePath) || !existsSync13(this.#deps.gateScript)) {
|
|
5460
6319
|
log(`\u6388\u6743\u5173\u5361\u4E0D\u53EF\u7528\uFF08node \u6216 gate.cjs \u627E\u4E0D\u5230\uFF09\uFF0C\u62D2\u7EDD\u6267\u884C ${tool}`);
|
|
5461
6320
|
return { fail: "gate_unavailable" };
|
|
5462
6321
|
}
|
|
@@ -5501,6 +6360,22 @@ async function runDaemon(deps = {}) {
|
|
|
5501
6360
|
}
|
|
5502
6361
|
|
|
5503
6362
|
// src/commands/daemon.ts
|
|
6363
|
+
var VERIFY_CONNECTED_MS = 3 * 60 * 1e3;
|
|
6364
|
+
function verifyOnLink(version, opts = {}) {
|
|
6365
|
+
let timer = null;
|
|
6366
|
+
return (up) => {
|
|
6367
|
+
if (timer) {
|
|
6368
|
+
clearTimeout(timer);
|
|
6369
|
+
timer = null;
|
|
6370
|
+
}
|
|
6371
|
+
if (!up) return;
|
|
6372
|
+
timer = setTimeout(() => {
|
|
6373
|
+
timer = null;
|
|
6374
|
+
if ((opts.markGood ?? markGood)(version)) log(`[\u81EA\u52A8\u5347\u7EA7] ${version} \u5DF2\u8FDE\u6EE1 3 \u5206\u949F\uFF0C\u9A8C\u8BC1\u901A\u8FC7`);
|
|
6375
|
+
}, opts.delayMs ?? VERIFY_CONNECTED_MS);
|
|
6376
|
+
timer.unref?.();
|
|
6377
|
+
};
|
|
6378
|
+
}
|
|
5504
6379
|
async function runDaemonCommand(opts = {}) {
|
|
5505
6380
|
const cfg = loadConfig();
|
|
5506
6381
|
if (!cfg.boundAt) {
|
|
@@ -5509,23 +6384,37 @@ async function runDaemonCommand(opts = {}) {
|
|
|
5509
6384
|
}
|
|
5510
6385
|
if (opts.foreground) process.env.DESKBUDDY_DAEMON_FOREGROUND = "1";
|
|
5511
6386
|
try {
|
|
5512
|
-
|
|
6387
|
+
writeFileSync18(pidFilePath(), String(process.pid), "utf8");
|
|
5513
6388
|
} catch {
|
|
5514
6389
|
}
|
|
5515
6390
|
let daemon;
|
|
5516
6391
|
try {
|
|
5517
|
-
daemon = await runDaemon();
|
|
6392
|
+
daemon = await runDaemon({ onLink: verifyOnLink(packageVersion()) });
|
|
5518
6393
|
} catch (e) {
|
|
5519
6394
|
log(`\u5B88\u62A4\u8FDB\u7A0B\u542F\u52A8\u5931\u8D25\uFF1A${e.message}`);
|
|
5520
6395
|
process.stderr.write(`\u2717 \u5B88\u62A4\u8FDB\u7A0B\u542F\u52A8\u5931\u8D25\uFF1A${e.message}
|
|
5521
6396
|
`);
|
|
5522
6397
|
return 1;
|
|
5523
6398
|
}
|
|
6399
|
+
const d = daemon;
|
|
6400
|
+
const updater = opts.foreground ? null : new AutoUpdater({
|
|
6401
|
+
currentVersion: packageVersion(),
|
|
6402
|
+
fetchPolicy: async () => (await api.toolRegistry())?.cli,
|
|
6403
|
+
config: () => loadConfig(),
|
|
6404
|
+
install: (target) => installVersion(target, { log: (l) => log(`[\u81EA\u52A8\u5347\u7EA7] ${l}`) }),
|
|
6405
|
+
idle: () => d.idleState(),
|
|
6406
|
+
beginDrain: () => d.beginDrain(),
|
|
6407
|
+
endDrain: () => d.endDrain(),
|
|
6408
|
+
switchTo: (cliPath, _target, onExit) => spawnSwitch(cliPath, daemonLogPath(), { version: packageVersion(), cliPath: defaultCliPath() }, onExit),
|
|
6409
|
+
log: (l) => log(`[\u81EA\u52A8\u5347\u7EA7] ${l}`)
|
|
6410
|
+
});
|
|
6411
|
+
updater?.start();
|
|
5524
6412
|
const shutdown = (sig) => {
|
|
5525
6413
|
log(`\u6536\u5230 ${sig}\uFF0C\u9000\u51FA`);
|
|
6414
|
+
updater?.stop();
|
|
5526
6415
|
daemon.stop();
|
|
5527
6416
|
try {
|
|
5528
|
-
|
|
6417
|
+
rmSync14(pidFilePath(), { force: true });
|
|
5529
6418
|
} catch {
|
|
5530
6419
|
}
|
|
5531
6420
|
process.exit(0);
|
|
@@ -5542,8 +6431,8 @@ async function runDaemonCommand(opts = {}) {
|
|
|
5542
6431
|
}
|
|
5543
6432
|
|
|
5544
6433
|
// src/core/registry.ts
|
|
5545
|
-
import { mkdirSync as
|
|
5546
|
-
import { join as
|
|
6434
|
+
import { mkdirSync as mkdirSync20, readFileSync as readFileSync17, writeFileSync as writeFileSync19 } from "fs";
|
|
6435
|
+
import { join as join24 } from "path";
|
|
5547
6436
|
var FALLBACK = {
|
|
5548
6437
|
version: "builtin",
|
|
5549
6438
|
tools: {
|
|
@@ -5567,8 +6456,8 @@ var FALLBACK = {
|
|
|
5567
6456
|
format: "codex-toml",
|
|
5568
6457
|
processNames: ["codex"],
|
|
5569
6458
|
// codex-thread-name 是机会主义的一级:session_index.jsonl 里的 thread_name
|
|
5570
|
-
//
|
|
5571
|
-
titleSources: ["codex-thread-name", "git-branch", "project"],
|
|
6459
|
+
// 只有 /rename 或 VS Code 扩展起过名的才有,多数是空的,取不到用首条指令
|
|
6460
|
+
titleSources: ["codex-thread-name", "first-prompt", "git-branch", "project"],
|
|
5572
6461
|
events: {
|
|
5573
6462
|
UserPromptSubmit: "watch",
|
|
5574
6463
|
PreToolUse: "watch",
|
|
@@ -5581,7 +6470,9 @@ var FALLBACK = {
|
|
|
5581
6470
|
workbuddy: {
|
|
5582
6471
|
format: "cc-hook",
|
|
5583
6472
|
processNames: ["WorkBuddy"],
|
|
5584
|
-
|
|
6473
|
+
// WorkBuddy 的 transcript 与 Claude Code 同格式,也写 ai-title(2026-10-01 WorkBuddy 5.6.2 实测);
|
|
6474
|
+
// 工作目录是按时间自动建的,目录名不能当标题
|
|
6475
|
+
titleSources: ["cc-ai-title", "first-prompt", "git-branch", "project"],
|
|
5585
6476
|
events: {
|
|
5586
6477
|
UserPromptSubmit: "watch",
|
|
5587
6478
|
PreToolUse: "watch",
|
|
@@ -5594,7 +6485,9 @@ var FALLBACK = {
|
|
|
5594
6485
|
qoder: {
|
|
5595
6486
|
format: "cc-hook",
|
|
5596
6487
|
processNames: ["Qoder CN"],
|
|
5597
|
-
|
|
6488
|
+
// 桌面端会话库里的标题(先是首条指令,后被换成 AI 摘要);qodercli 命令行版的会话不在库里,落到首条指令
|
|
6489
|
+
// (CLI 1.1.61 实测 transcript 里没有 ai-title,UserPromptSubmit 带 prompt)
|
|
6490
|
+
titleSources: ["qoder-session-title", "first-prompt", "git-branch", "project"],
|
|
5598
6491
|
events: {
|
|
5599
6492
|
UserPromptSubmit: "watch",
|
|
5600
6493
|
PreToolUse: "watch",
|
|
@@ -5607,8 +6500,8 @@ var FALLBACK = {
|
|
|
5607
6500
|
opencode: {
|
|
5608
6501
|
format: "oc-plugin",
|
|
5609
6502
|
processNames: ["opencode"],
|
|
5610
|
-
// opencode
|
|
5611
|
-
titleSources: ["git-branch", "project"],
|
|
6503
|
+
// opencode 自己生成的会话标题(插件从 session.updated 取,随事件传 oc_session_title),没有就用首条指令
|
|
6504
|
+
titleSources: ["oc-session-title", "first-prompt", "git-branch", "project"],
|
|
5612
6505
|
events: {
|
|
5613
6506
|
UserPromptSubmit: "watch",
|
|
5614
6507
|
PreToolUse: "watch",
|
|
@@ -5621,11 +6514,11 @@ var FALLBACK = {
|
|
|
5621
6514
|
}
|
|
5622
6515
|
};
|
|
5623
6516
|
function registryCachePath() {
|
|
5624
|
-
return
|
|
6517
|
+
return join24(deskbuddyHome(), "tool-registry.json");
|
|
5625
6518
|
}
|
|
5626
6519
|
function loadRegistry() {
|
|
5627
6520
|
try {
|
|
5628
|
-
return JSON.parse(
|
|
6521
|
+
return JSON.parse(readFileSync17(registryCachePath(), "utf8"));
|
|
5629
6522
|
} catch {
|
|
5630
6523
|
return FALLBACK;
|
|
5631
6524
|
}
|
|
@@ -5633,15 +6526,15 @@ function loadRegistry() {
|
|
|
5633
6526
|
async function refreshRegistry() {
|
|
5634
6527
|
try {
|
|
5635
6528
|
const registry = await api.toolRegistry();
|
|
5636
|
-
|
|
5637
|
-
|
|
6529
|
+
mkdirSync20(deskbuddyHome(), { recursive: true });
|
|
6530
|
+
writeFileSync19(registryCachePath(), JSON.stringify(registry, null, 2), "utf8");
|
|
5638
6531
|
} catch {
|
|
5639
6532
|
}
|
|
5640
6533
|
}
|
|
5641
6534
|
|
|
5642
6535
|
// src/adapters/ccHook.ts
|
|
5643
|
-
import { chmodSync as
|
|
5644
|
-
import { dirname as
|
|
6536
|
+
import { chmodSync as chmodSync7, existsSync as existsSync14, mkdirSync as mkdirSync21, readFileSync as readFileSync18, renameSync as renameSync15, rmSync as rmSync15, statSync as statSync7, writeFileSync as writeFileSync20 } from "fs";
|
|
6537
|
+
import { dirname as dirname9, join as join25 } from "path";
|
|
5645
6538
|
var CC_EVENTS = [
|
|
5646
6539
|
"UserPromptSubmit",
|
|
5647
6540
|
"PreToolUse",
|
|
@@ -5651,8 +6544,8 @@ var CC_EVENTS = [
|
|
|
5651
6544
|
"SessionEnd"
|
|
5652
6545
|
];
|
|
5653
6546
|
function readSettings(path) {
|
|
5654
|
-
if (!
|
|
5655
|
-
const raw =
|
|
6547
|
+
if (!existsSync14(path)) return {};
|
|
6548
|
+
const raw = readFileSync18(path, "utf8");
|
|
5656
6549
|
if (raw.trim() === "") return {};
|
|
5657
6550
|
try {
|
|
5658
6551
|
return JSON.parse(raw);
|
|
@@ -5661,17 +6554,17 @@ function readSettings(path) {
|
|
|
5661
6554
|
}
|
|
5662
6555
|
}
|
|
5663
6556
|
function writeSettings(path, settings) {
|
|
5664
|
-
|
|
6557
|
+
mkdirSync21(dirname9(path), { recursive: true });
|
|
5665
6558
|
let mode;
|
|
5666
6559
|
try {
|
|
5667
|
-
mode =
|
|
6560
|
+
mode = statSync7(path).mode & 511;
|
|
5668
6561
|
} catch {
|
|
5669
6562
|
mode = void 0;
|
|
5670
6563
|
}
|
|
5671
6564
|
const tmp = path + ".deskbuddy.tmp";
|
|
5672
|
-
|
|
5673
|
-
if (mode !== void 0)
|
|
5674
|
-
|
|
6565
|
+
writeFileSync20(tmp, JSON.stringify(settings, null, 2), "utf8");
|
|
6566
|
+
if (mode !== void 0) chmodSync7(tmp, mode);
|
|
6567
|
+
renameSync15(tmp, path);
|
|
5675
6568
|
}
|
|
5676
6569
|
function mentions(command, script) {
|
|
5677
6570
|
return !!command && (command.includes(script) || command.includes(fwdSlash(script)));
|
|
@@ -5774,8 +6667,8 @@ function installCcRewake(settingsPath, wakePath, platform = process.platform) {
|
|
|
5774
6667
|
function uninstallCcHook(settingsPath, hookPath) {
|
|
5775
6668
|
const settings = readSettings(settingsPath);
|
|
5776
6669
|
if (!settings.hooks) return;
|
|
5777
|
-
const gatePath =
|
|
5778
|
-
const wakePath =
|
|
6670
|
+
const gatePath = join25(dirname9(hookPath), "gate.cjs");
|
|
6671
|
+
const wakePath = join25(dirname9(hookPath), "wake.cjs");
|
|
5779
6672
|
for (const event of Object.keys(settings.hooks)) {
|
|
5780
6673
|
const groups = settings.hooks[event] ?? [];
|
|
5781
6674
|
const kept = [];
|
|
@@ -5789,14 +6682,14 @@ function uninstallCcHook(settingsPath, hookPath) {
|
|
|
5789
6682
|
}
|
|
5790
6683
|
writeSettings(settingsPath, settings);
|
|
5791
6684
|
try {
|
|
5792
|
-
|
|
6685
|
+
rmSync15(join25(dirname9(hookPath), "wake", "tokens"), { recursive: true, force: true });
|
|
5793
6686
|
} catch {
|
|
5794
6687
|
}
|
|
5795
6688
|
}
|
|
5796
6689
|
|
|
5797
6690
|
// src/adapters/codexHook.ts
|
|
5798
|
-
import { chmodSync as
|
|
5799
|
-
import { dirname as
|
|
6691
|
+
import { chmodSync as chmodSync8, existsSync as existsSync15, mkdirSync as mkdirSync22, readFileSync as readFileSync19, renameSync as renameSync16, statSync as statSync8, writeFileSync as writeFileSync21 } from "fs";
|
|
6692
|
+
import { dirname as dirname10, join as join26 } from "path";
|
|
5800
6693
|
var CODEX_MARKER_BEGIN = "# >>> deskbuddy >>>";
|
|
5801
6694
|
var CODEX_MARKER_END = "# <<< deskbuddy <<<";
|
|
5802
6695
|
var CODEX_EVENTS = [
|
|
@@ -5809,7 +6702,7 @@ var CODEX_EVENTS = [
|
|
|
5809
6702
|
];
|
|
5810
6703
|
var CODEX_TRUST_NOTICE = "\u26A0\uFE0F \u4E0B\u6B21\u542F\u52A8 Codex \u65F6\u4F1A\u8981\u6C42\u4F60\u786E\u8BA4\u4FE1\u4EFB\u8FD9\u4E2A hook\uFF0C\u8BF7\u9009\u62E9\u540C\u610F\u2014\u2014\u4E0D\u786E\u8BA4\u7684\u8BDD Codex \u4E0D\u4F1A\u6267\u884C\u5B83\u3002\n Codex \u7ECF `--remote` \u8FDE\u5E38\u9A7B app-server \u4F7F\u7528\u65F6\uFF0C\u7A97\u53E3\u91CC\u7684\u6388\u6743\u6846\u4E5F\u80FD\u5728\u8BBE\u5907\u4E0A\u786E\u8BA4\uFF08\u5B88\u62A4\u8FDB\u7A0B\u5728\u8DD1\u65F6\u751F\u6548\uFF09";
|
|
5811
6704
|
function codexConfigPath() {
|
|
5812
|
-
return
|
|
6705
|
+
return join26(codexHome(), "config.toml");
|
|
5813
6706
|
}
|
|
5814
6707
|
function renderBlock(hookPath, events, tool, platform) {
|
|
5815
6708
|
const command = JSON.stringify(`node ${platform === "win32" ? winPsPath(hookPath) : hookPath} ${tool}`);
|
|
@@ -5828,17 +6721,17 @@ function renderBlock(hookPath, events, tool, platform) {
|
|
|
5828
6721
|
return lines2.join("\n");
|
|
5829
6722
|
}
|
|
5830
6723
|
function writeAtomic(path, content) {
|
|
5831
|
-
|
|
6724
|
+
mkdirSync22(dirname10(path), { recursive: true });
|
|
5832
6725
|
let mode;
|
|
5833
6726
|
try {
|
|
5834
|
-
mode =
|
|
6727
|
+
mode = statSync8(path).mode & 511;
|
|
5835
6728
|
} catch {
|
|
5836
6729
|
mode = void 0;
|
|
5837
6730
|
}
|
|
5838
6731
|
const tmp = path + ".deskbuddy.tmp";
|
|
5839
|
-
|
|
5840
|
-
if (mode !== void 0)
|
|
5841
|
-
|
|
6732
|
+
writeFileSync21(tmp, content, "utf8");
|
|
6733
|
+
if (mode !== void 0) chmodSync8(tmp, mode);
|
|
6734
|
+
renameSync16(tmp, path);
|
|
5842
6735
|
}
|
|
5843
6736
|
function assertSafeToAppend(raw, path) {
|
|
5844
6737
|
for (const quote of ['"""', "'''"]) {
|
|
@@ -5848,7 +6741,7 @@ function assertSafeToAppend(raw, path) {
|
|
|
5848
6741
|
}
|
|
5849
6742
|
}
|
|
5850
6743
|
function installCodexHook(configPath2, hookPath, events = CODEX_EVENTS, tool = "codex", platform = process.platform) {
|
|
5851
|
-
const raw =
|
|
6744
|
+
const raw = existsSync15(configPath2) ? readFileSync19(configPath2, "utf8") : "";
|
|
5852
6745
|
if (raw.includes(CODEX_MARKER_BEGIN)) return;
|
|
5853
6746
|
assertSafeToAppend(raw, configPath2);
|
|
5854
6747
|
const block = renderBlock(hookPath, events, tool, platform);
|
|
@@ -5856,8 +6749,8 @@ function installCodexHook(configPath2, hookPath, events = CODEX_EVENTS, tool = "
|
|
|
5856
6749
|
writeAtomic(configPath2, head + block + "\n");
|
|
5857
6750
|
}
|
|
5858
6751
|
function uninstallCodexHook(configPath2) {
|
|
5859
|
-
if (!
|
|
5860
|
-
const raw =
|
|
6752
|
+
if (!existsSync15(configPath2)) return;
|
|
6753
|
+
const raw = readFileSync19(configPath2, "utf8");
|
|
5861
6754
|
const lines2 = raw.split("\n");
|
|
5862
6755
|
const begin = lines2.findIndex((l) => l.trim() === CODEX_MARKER_BEGIN);
|
|
5863
6756
|
if (begin === -1) return;
|
|
@@ -5871,11 +6764,11 @@ function uninstallCodexHook(configPath2) {
|
|
|
5871
6764
|
}
|
|
5872
6765
|
|
|
5873
6766
|
// src/commands/tools.ts
|
|
5874
|
-
import { existsSync as
|
|
6767
|
+
import { existsSync as existsSync16, readFileSync as readFileSync20 } from "fs";
|
|
5875
6768
|
function fileContains(path, needle) {
|
|
5876
6769
|
try {
|
|
5877
|
-
if (!
|
|
5878
|
-
return
|
|
6770
|
+
if (!existsSync16(path)) return false;
|
|
6771
|
+
return readFileSync20(path, "utf8").includes(needle);
|
|
5879
6772
|
} catch {
|
|
5880
6773
|
return false;
|
|
5881
6774
|
}
|
|
@@ -5949,13 +6842,13 @@ function subscribedEvents(registry, tool, fallback) {
|
|
|
5949
6842
|
}
|
|
5950
6843
|
|
|
5951
6844
|
// src/commands/enable.ts
|
|
5952
|
-
import { dirname as
|
|
6845
|
+
import { dirname as dirname11, join as join28 } from "path";
|
|
5953
6846
|
|
|
5954
6847
|
// src/wrap/shim.ts
|
|
5955
|
-
import { execFileSync as
|
|
5956
|
-
import { chmodSync as
|
|
5957
|
-
import { homedir as
|
|
5958
|
-
import { delimiter as delimiter2, join as
|
|
6848
|
+
import { execFileSync as execFileSync12 } from "child_process";
|
|
6849
|
+
import { chmodSync as chmodSync9, existsSync as existsSync17, mkdirSync as mkdirSync23, readdirSync as readdirSync6, readFileSync as readFileSync21, rmdirSync, rmSync as rmSync16, writeFileSync as writeFileSync22 } from "fs";
|
|
6850
|
+
import { homedir as homedir6 } from "os";
|
|
6851
|
+
import { delimiter as delimiter2, join as join27, resolve as resolve3 } from "path";
|
|
5959
6852
|
var SHIM_FALLBACK_EXIT = 213;
|
|
5960
6853
|
var WIN_SHIM_ENABLED = false;
|
|
5961
6854
|
var SHIM_COMMANDS = { claude_code: "claude", codex: "codex" };
|
|
@@ -5965,10 +6858,10 @@ var RC_END = "# <<< deskbuddy <<<";
|
|
|
5965
6858
|
var RC_FILES = [".zshrc", ".bashrc"];
|
|
5966
6859
|
var WIN_PENDING = "Windows \u7A97\u53E3\u63A5\u7BA1\u5F85\u9A8C\u8BC1\uFF0C\u6682\u672A\u5F00\u542F";
|
|
5967
6860
|
function shimDir() {
|
|
5968
|
-
return
|
|
6861
|
+
return join27(deskbuddyHome(), "bin");
|
|
5969
6862
|
}
|
|
5970
6863
|
function shimFile(binDir, command, platform = process.platform) {
|
|
5971
|
-
return
|
|
6864
|
+
return join27(binDir, platform === "win32" ? `${command}.cmd` : command);
|
|
5972
6865
|
}
|
|
5973
6866
|
function shQuote3(s) {
|
|
5974
6867
|
return `'${s.replace(/'/g, `'\\''`)}'`;
|
|
@@ -6056,7 +6949,7 @@ function cmdScript(tool, command, node, cli, bin) {
|
|
|
6056
6949
|
].join("\r\n");
|
|
6057
6950
|
}
|
|
6058
6951
|
function rcBlock(binDir, rcHome) {
|
|
6059
|
-
const std =
|
|
6952
|
+
const std = resolve3(rcHome, ".deskbuddy", "bin") === resolve3(binDir);
|
|
6060
6953
|
const dir = std ? "$HOME/.deskbuddy/bin" : binDir.replace(/(["\\$`])/g, "\\$1");
|
|
6061
6954
|
return [
|
|
6062
6955
|
RC_BEGIN,
|
|
@@ -6097,10 +6990,10 @@ function removeRcBlock(content) {
|
|
|
6097
6990
|
return content.slice(0, start) + content.slice(end);
|
|
6098
6991
|
}
|
|
6099
6992
|
function readRc(p) {
|
|
6100
|
-
return
|
|
6993
|
+
return readFileSync21(p).toString("latin1");
|
|
6101
6994
|
}
|
|
6102
6995
|
function writeRc(p, latin1) {
|
|
6103
|
-
|
|
6996
|
+
writeFileSync22(p, Buffer.from(latin1, "latin1"));
|
|
6104
6997
|
}
|
|
6105
6998
|
function toLatin1(utf8) {
|
|
6106
6999
|
return Buffer.from(utf8, "utf8").toString("latin1");
|
|
@@ -6122,7 +7015,7 @@ function removePathEntry(value, dir) {
|
|
|
6122
7015
|
var PS_READ = "$k=[Microsoft.Win32.Registry]::CurrentUser.OpenSubKey('Environment');$v='';if($k){$v=$k.GetValue('Path','',[Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames)};[Console]::Out.Write([Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes([string]$v)))";
|
|
6123
7016
|
var PS_WRITE = "$k=[Microsoft.Win32.Registry]::CurrentUser.CreateSubKey('Environment');$kind=[Microsoft.Win32.RegistryValueKind]::ExpandString;try{$kind=$k.GetValueKind('Path')}catch{};if([string]::IsNullOrEmpty($env:DESKBUDDY_NEW_PATH)){$k.DeleteValue('Path',$false)}else{$k.SetValue('Path',$env:DESKBUDDY_NEW_PATH,$kind)};[Environment]::SetEnvironmentVariable('DESKBUDDY_PATH_REFRESH','1','User');[Environment]::SetEnvironmentVariable('DESKBUDDY_PATH_REFRESH',$null,'User')";
|
|
6124
7017
|
function psWinPath() {
|
|
6125
|
-
const ps = (script, env) =>
|
|
7018
|
+
const ps = (script, env) => execFileSync12("powershell.exe", ["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command", script], {
|
|
6126
7019
|
encoding: "utf8",
|
|
6127
7020
|
stdio: ["ignore", "pipe", "ignore"],
|
|
6128
7021
|
windowsHide: true,
|
|
@@ -6145,7 +7038,7 @@ function psWinPath() {
|
|
|
6145
7038
|
function defaultShimEnv(nodePath, cliPath) {
|
|
6146
7039
|
const env = process.env;
|
|
6147
7040
|
const isolated = !!env.VITEST || !!env.DESKBUDDY_HOME;
|
|
6148
|
-
const rcHome = env.DESKBUDDY_RC_HOME || (isolated ? null :
|
|
7041
|
+
const rcHome = env.DESKBUDDY_RC_HOME || (isolated ? null : homedir6());
|
|
6149
7042
|
return {
|
|
6150
7043
|
platform: process.platform,
|
|
6151
7044
|
binDir: shimDir(),
|
|
@@ -6169,12 +7062,12 @@ function installShim(tool, e) {
|
|
|
6169
7062
|
if (!node || !cli || !bin) {
|
|
6170
7063
|
return { ok: false, message: "\u7A97\u53E3\u63A5\u7BA1\u6CA1\u88C5\uFF1A\u5B89\u88C5\u8DEF\u5F84\u91CC\u6709\u975E ASCII \u5B57\u7B26\uFF0CWindows \u7684 .cmd \u8BFB\u4E0D\u4E86\uFF08\u4E0D\u5F71\u54CD\u5176\u5B83\u529F\u80FD\uFF09" };
|
|
6171
7064
|
}
|
|
6172
|
-
|
|
6173
|
-
|
|
7065
|
+
mkdirSync23(e.binDir, { recursive: true });
|
|
7066
|
+
writeFileSync22(file, cmdScript(tool, command, node, cli, bin), "utf8");
|
|
6174
7067
|
} else {
|
|
6175
|
-
|
|
6176
|
-
|
|
6177
|
-
|
|
7068
|
+
mkdirSync23(e.binDir, { recursive: true, mode: 448 });
|
|
7069
|
+
writeFileSync22(file, shScript(tool, command, e.nodePath, e.cliPath, e.binDir), { encoding: "utf8", mode: 493 });
|
|
7070
|
+
chmodSync9(file, 493);
|
|
6178
7071
|
}
|
|
6179
7072
|
const touched = [];
|
|
6180
7073
|
if (e.platform === "win32") {
|
|
@@ -6188,8 +7081,8 @@ function installShim(tool, e) {
|
|
|
6188
7081
|
} else if (e.rcHome) {
|
|
6189
7082
|
const block = toLatin1(rcBlock(e.binDir, e.rcHome));
|
|
6190
7083
|
for (const name of RC_FILES) {
|
|
6191
|
-
const rc =
|
|
6192
|
-
if (!
|
|
7084
|
+
const rc = join27(e.rcHome, name);
|
|
7085
|
+
if (!existsSync17(rc)) continue;
|
|
6193
7086
|
const next = addRcBlock(readRc(rc), block);
|
|
6194
7087
|
if (next !== null) writeRc(rc, next);
|
|
6195
7088
|
touched.push(`~/${name}`);
|
|
@@ -6209,13 +7102,13 @@ function installShim(tool, e) {
|
|
|
6209
7102
|
function ourShims(binDir) {
|
|
6210
7103
|
let names;
|
|
6211
7104
|
try {
|
|
6212
|
-
names =
|
|
7105
|
+
names = readdirSync6(binDir);
|
|
6213
7106
|
} catch {
|
|
6214
7107
|
return [];
|
|
6215
7108
|
}
|
|
6216
7109
|
return names.filter((n) => {
|
|
6217
7110
|
try {
|
|
6218
|
-
return
|
|
7111
|
+
return readFileSync21(join27(binDir, n), "utf8").includes(SHIM_MARKER);
|
|
6219
7112
|
} catch {
|
|
6220
7113
|
return false;
|
|
6221
7114
|
}
|
|
@@ -6227,8 +7120,8 @@ function uninstallShim(tool, e) {
|
|
|
6227
7120
|
let removed = false;
|
|
6228
7121
|
const file = shimFile(e.binDir, command, e.platform);
|
|
6229
7122
|
try {
|
|
6230
|
-
if (
|
|
6231
|
-
|
|
7123
|
+
if (existsSync17(file) && readFileSync21(file, "utf8").includes(SHIM_MARKER)) {
|
|
7124
|
+
rmSync16(file, { force: true });
|
|
6232
7125
|
removed = true;
|
|
6233
7126
|
}
|
|
6234
7127
|
} catch {
|
|
@@ -6246,8 +7139,8 @@ function uninstallShim(tool, e) {
|
|
|
6246
7139
|
}
|
|
6247
7140
|
} else if (e.rcHome) {
|
|
6248
7141
|
for (const name of RC_FILES) {
|
|
6249
|
-
const rc =
|
|
6250
|
-
if (!
|
|
7142
|
+
const rc = join27(e.rcHome, name);
|
|
7143
|
+
if (!existsSync17(rc)) continue;
|
|
6251
7144
|
const next = removeRcBlock(readRc(rc));
|
|
6252
7145
|
if (next !== null) {
|
|
6253
7146
|
writeRc(rc, next);
|
|
@@ -6256,7 +7149,7 @@ function uninstallShim(tool, e) {
|
|
|
6256
7149
|
}
|
|
6257
7150
|
}
|
|
6258
7151
|
try {
|
|
6259
|
-
if (
|
|
7152
|
+
if (existsSync17(e.binDir) && readdirSync6(e.binDir).length === 0) rmdirSync(e.binDir);
|
|
6260
7153
|
} catch {
|
|
6261
7154
|
}
|
|
6262
7155
|
if (!removed && touched.length === 0) return { ok: true, message: "" };
|
|
@@ -6268,7 +7161,7 @@ function shimState(tool, binDir, pathEnv, platform = process.platform) {
|
|
|
6268
7161
|
const file = shimFile(binDir, command, platform);
|
|
6269
7162
|
let installed = false;
|
|
6270
7163
|
try {
|
|
6271
|
-
installed =
|
|
7164
|
+
installed = readFileSync21(file, "utf8").includes(SHIM_MARKER);
|
|
6272
7165
|
} catch {
|
|
6273
7166
|
installed = false;
|
|
6274
7167
|
}
|
|
@@ -6278,72 +7171,6 @@ function shimState(tool, binDir, pathEnv, platform = process.platform) {
|
|
|
6278
7171
|
return { installed, inPath };
|
|
6279
7172
|
}
|
|
6280
7173
|
|
|
6281
|
-
// src/wrap/pty.ts
|
|
6282
|
-
import { chmodSync as chmodSync9, existsSync as existsSync15, statSync as statSync8 } from "fs";
|
|
6283
|
-
import { createRequire as createRequire2 } from "module";
|
|
6284
|
-
import { dirname as dirname8, join as join25 } from "path";
|
|
6285
|
-
var req = createRequire2(import.meta.url);
|
|
6286
|
-
function fixSpawnHelper() {
|
|
6287
|
-
if (process.platform !== "darwin") return;
|
|
6288
|
-
try {
|
|
6289
|
-
const dir = join25(dirname8(req.resolve("node-pty/package.json")), "prebuilds", `darwin-${process.arch}`);
|
|
6290
|
-
const helper = join25(dir, "spawn-helper");
|
|
6291
|
-
if (existsSync15(helper) && (statSync8(helper).mode & 73) === 0) chmodSync9(helper, 493);
|
|
6292
|
-
} catch {
|
|
6293
|
-
}
|
|
6294
|
-
}
|
|
6295
|
-
function loadPty() {
|
|
6296
|
-
try {
|
|
6297
|
-
fixSpawnHelper();
|
|
6298
|
-
const m = req("node-pty");
|
|
6299
|
-
return typeof m?.spawn === "function" ? m : null;
|
|
6300
|
-
} catch {
|
|
6301
|
-
return null;
|
|
6302
|
-
}
|
|
6303
|
-
}
|
|
6304
|
-
function loadHeadless() {
|
|
6305
|
-
try {
|
|
6306
|
-
const m = req("@xterm/headless");
|
|
6307
|
-
return typeof m?.Terminal === "function" ? m.Terminal : null;
|
|
6308
|
-
} catch {
|
|
6309
|
-
return null;
|
|
6310
|
-
}
|
|
6311
|
-
}
|
|
6312
|
-
function screenLines(term) {
|
|
6313
|
-
const b = term.buffer.active;
|
|
6314
|
-
const out = [];
|
|
6315
|
-
for (let i = b.viewportY; i < b.viewportY + term.rows; i++) {
|
|
6316
|
-
const l = b.getLine(i);
|
|
6317
|
-
out.push(l ? l.translateToString(true) : "");
|
|
6318
|
-
}
|
|
6319
|
-
return out;
|
|
6320
|
-
}
|
|
6321
|
-
function screenLinesUndimmed(term) {
|
|
6322
|
-
const b = term.buffer.active;
|
|
6323
|
-
const out = [];
|
|
6324
|
-
for (let i = b.viewportY; i < b.viewportY + term.rows; i++) {
|
|
6325
|
-
const l = b.getLine(i);
|
|
6326
|
-
if (!l) {
|
|
6327
|
-
out.push("");
|
|
6328
|
-
continue;
|
|
6329
|
-
}
|
|
6330
|
-
if (typeof l.getCell !== "function") {
|
|
6331
|
-
out.push(l.translateToString(true));
|
|
6332
|
-
continue;
|
|
6333
|
-
}
|
|
6334
|
-
let s = "";
|
|
6335
|
-
for (let x = 0; x < term.cols; x++) {
|
|
6336
|
-
const c = l.getCell(x);
|
|
6337
|
-
if (!c) break;
|
|
6338
|
-
const w = c.getWidth();
|
|
6339
|
-
if (w === 0) continue;
|
|
6340
|
-
s += c.isDim() ? " ".repeat(w) : c.getChars() || " ";
|
|
6341
|
-
}
|
|
6342
|
-
out.push(s.trimEnd());
|
|
6343
|
-
}
|
|
6344
|
-
return out;
|
|
6345
|
-
}
|
|
6346
|
-
|
|
6347
7174
|
// src/commands/enable.ts
|
|
6348
7175
|
function stdout(line) {
|
|
6349
7176
|
process.stdout.write(line + "\n");
|
|
@@ -6408,14 +7235,14 @@ function installTools(tools, out, shim = defaultShimEnvForInstall()) {
|
|
|
6408
7235
|
installed.push(tool);
|
|
6409
7236
|
out(`\u2705 ${tool}\uFF1Ahook \u5DF2\u88C5\u8FDB ${configPath2}\uFF08${events.length} \u4E2A\u4E8B\u4EF6\uFF09`);
|
|
6410
7237
|
try {
|
|
6411
|
-
if (adapter.installWindowGate?.(configPath2,
|
|
7238
|
+
if (adapter.installWindowGate?.(configPath2, join28(dirname11(hookPath), "gate.cjs"), tool)) {
|
|
6412
7239
|
out(` \u7A97\u53E3\u91CC\u5F39\u6388\u6743\u6846\u65F6\uFF0C\u8BBE\u5907\u4E0A\u4E5F\u80FD\u786E\u8BA4\uFF08\u5B88\u62A4\u8FDB\u7A0B\u5728\u8DD1\u65F6\u751F\u6548\uFF09`);
|
|
6413
7240
|
}
|
|
6414
7241
|
} catch (e) {
|
|
6415
7242
|
out(`\u26A0\uFE0F ${tool}\uFF1A\u8BBE\u5907\u786E\u8BA4\u6388\u6743\u6CA1\u88C5\u4E0A\uFF1A${e.message}`);
|
|
6416
7243
|
}
|
|
6417
7244
|
try {
|
|
6418
|
-
if (adapter.installRewake?.(configPath2,
|
|
7245
|
+
if (adapter.installRewake?.(configPath2, join28(dirname11(hookPath), "wake.cjs"), tool)) {
|
|
6419
7246
|
out(` \u7A97\u53E3\u5F00\u7740\u65F6\uFF0C\u8BBE\u5907\u6307\u4EE4\u76F4\u63A5\u5728\u8FD9\u4E2A\u7A97\u53E3\u91CC\u6267\u884C\uFF08\u5B88\u62A4\u8FDB\u7A0B\u5728\u8DD1\u65F6\u751F\u6548\uFF09`);
|
|
6420
7247
|
}
|
|
6421
7248
|
} catch (e) {
|
|
@@ -6442,8 +7269,9 @@ async function runEnable(tools, opts = {}) {
|
|
|
6442
7269
|
if (!opts.skipRefresh) await refreshRegistry();
|
|
6443
7270
|
const { failed } = installTools(tools, out, opts.shim === void 0 ? defaultShimEnvForInstall() : opts.shim);
|
|
6444
7271
|
if (!opts.skipAutostart) {
|
|
6445
|
-
const r = await installAutostart({ execPath: process.execPath, cliPath: defaultCliPath() });
|
|
6446
7272
|
out("");
|
|
7273
|
+
const target = autostartTarget(out);
|
|
7274
|
+
const r = process.env.DESKBUDDY_SKIP_AUTOSTART === "1" ? { ok: false, message: "\u5DF2\u6309 DESKBUDDY_SKIP_AUTOSTART \u8DF3\u8FC7\u5F00\u673A\u81EA\u542F" } : await installAutostart(target);
|
|
6447
7275
|
out(r.ok ? `\u2705 ${r.message}` : `\u26A0\uFE0F ${r.message}`);
|
|
6448
7276
|
}
|
|
6449
7277
|
out("");
|
|
@@ -6515,12 +7343,12 @@ async function runDisable(tools, opts = {}) {
|
|
|
6515
7343
|
}
|
|
6516
7344
|
|
|
6517
7345
|
// src/commands/install.ts
|
|
6518
|
-
import { mkdirSync as
|
|
6519
|
-
import { join as
|
|
7346
|
+
import { mkdirSync as mkdirSync24, rmSync as rmSync17, writeFileSync as writeFileSync23 } from "fs";
|
|
7347
|
+
import { join as join29 } from "path";
|
|
6520
7348
|
|
|
6521
7349
|
// src/core/detect.ts
|
|
6522
|
-
import { existsSync as
|
|
6523
|
-
import { dirname as
|
|
7350
|
+
import { existsSync as existsSync18 } from "fs";
|
|
7351
|
+
import { dirname as dirname12 } from "path";
|
|
6524
7352
|
var ALL_TOOLS = ["claude_code", "codex", "workbuddy", "qoder", "opencode"];
|
|
6525
7353
|
function toolConfigDir(tool) {
|
|
6526
7354
|
switch (tool) {
|
|
@@ -6529,18 +7357,18 @@ function toolConfigDir(tool) {
|
|
|
6529
7357
|
case "codex":
|
|
6530
7358
|
return codexHome();
|
|
6531
7359
|
case "workbuddy":
|
|
6532
|
-
return
|
|
7360
|
+
return dirname12(workbuddySettingsPath());
|
|
6533
7361
|
case "qoder":
|
|
6534
|
-
return
|
|
7362
|
+
return dirname12(qoderSettingsPath());
|
|
6535
7363
|
case "opencode":
|
|
6536
|
-
return
|
|
7364
|
+
return dirname12(dirname12(opencodePluginPath()));
|
|
6537
7365
|
default:
|
|
6538
7366
|
return null;
|
|
6539
7367
|
}
|
|
6540
7368
|
}
|
|
6541
7369
|
var defaultDetectDeps = () => ({
|
|
6542
7370
|
located: (tool) => locate(tool) != null,
|
|
6543
|
-
exists:
|
|
7371
|
+
exists: existsSync18,
|
|
6544
7372
|
env: process.env
|
|
6545
7373
|
});
|
|
6546
7374
|
function callerTool(env) {
|
|
@@ -6561,19 +7389,19 @@ function detectTools(d = defaultDetectDeps()) {
|
|
|
6561
7389
|
import qrcode from "qrcode-terminal";
|
|
6562
7390
|
|
|
6563
7391
|
// src/commands/remoteCwd.ts
|
|
6564
|
-
import { existsSync as
|
|
7392
|
+
import { existsSync as existsSync19, statSync as statSync9 } from "fs";
|
|
6565
7393
|
import { createInterface } from "readline";
|
|
6566
|
-
import { homedir as
|
|
6567
|
-
import { isAbsolute as isAbsolute2, resolve as
|
|
6568
|
-
function normalizePath(raw, home =
|
|
7394
|
+
import { homedir as homedir7 } from "os";
|
|
7395
|
+
import { isAbsolute as isAbsolute2, resolve as resolve4 } from "path";
|
|
7396
|
+
function normalizePath(raw, home = homedir7()) {
|
|
6569
7397
|
const s = (raw || "").trim();
|
|
6570
7398
|
if (!s) return null;
|
|
6571
|
-
const expanded = s === "~" ? home : s.startsWith("~/") || process.platform === "win32" && s.startsWith("~\\") ?
|
|
6572
|
-
const abs = isAbsolute2(expanded) ? expanded :
|
|
7399
|
+
const expanded = s === "~" ? home : s.startsWith("~/") || process.platform === "win32" && s.startsWith("~\\") ? resolve4(home, s.slice(2)) : s;
|
|
7400
|
+
const abs = isAbsolute2(expanded) ? expanded : resolve4(process.cwd(), expanded);
|
|
6573
7401
|
return abs;
|
|
6574
7402
|
}
|
|
6575
7403
|
function checkPath(abs) {
|
|
6576
|
-
if (!
|
|
7404
|
+
if (!existsSync19(abs)) return "missing";
|
|
6577
7405
|
try {
|
|
6578
7406
|
return statSync9(abs).isDirectory() ? "ok" : "not_a_dir";
|
|
6579
7407
|
} catch {
|
|
@@ -6747,8 +7575,8 @@ var TOOL_LABELS = {
|
|
|
6747
7575
|
opencode: "opencode"
|
|
6748
7576
|
};
|
|
6749
7577
|
function checkApiBase(raw, env = process.env) {
|
|
6750
|
-
const
|
|
6751
|
-
return
|
|
7578
|
+
const norm3 = raw.replace(/\/+$/, "");
|
|
7579
|
+
return norm3 === PROD_API_BASE || norm3 === TEST_API_BASE || env.DESKBUDDY_ALLOW_CUSTOM_API === "1";
|
|
6752
7580
|
}
|
|
6753
7581
|
function parseCode(argv) {
|
|
6754
7582
|
const eq = argv.find((a) => a.startsWith("--code="));
|
|
@@ -6779,11 +7607,11 @@ function inSandbox(env) {
|
|
|
6779
7607
|
}
|
|
6780
7608
|
function precheckWritable(dirs) {
|
|
6781
7609
|
for (const dir of dirs) {
|
|
6782
|
-
const probe =
|
|
7610
|
+
const probe = join29(dir, `.deskbuddy-probe-${process.pid}`);
|
|
6783
7611
|
try {
|
|
6784
|
-
|
|
6785
|
-
|
|
6786
|
-
|
|
7612
|
+
mkdirSync24(dir, { recursive: true });
|
|
7613
|
+
writeFileSync23(probe, "");
|
|
7614
|
+
rmSync17(probe, { force: true });
|
|
6787
7615
|
} catch (e) {
|
|
6788
7616
|
return { ok: false, dir, error: e.message };
|
|
6789
7617
|
}
|
|
@@ -6800,7 +7628,7 @@ var defaultInstallDeps = () => ({
|
|
|
6800
7628
|
refreshRegistry: () => refreshRegistry().catch(() => void 0),
|
|
6801
7629
|
installTools,
|
|
6802
7630
|
// DESKBUDDY_SKIP_AUTOSTART=1 只给端到端测试用:自启会真去动 launchd / systemd,并停掉本机正在跑的 daemon
|
|
6803
|
-
autostart: () => process.env.DESKBUDDY_SKIP_AUTOSTART === "1" ? Promise.resolve({ ok: false, message: "\u5DF2\u6309 DESKBUDDY_SKIP_AUTOSTART \u8DF3\u8FC7" }) : installAutostart(
|
|
7631
|
+
autostart: () => process.env.DESKBUDDY_SKIP_AUTOSTART === "1" ? Promise.resolve({ ok: false, message: "\u5DF2\u6309 DESKBUDDY_SKIP_AUTOSTART \u8DF3\u8FC7" }) : installAutostart(autostartTarget())
|
|
6804
7632
|
});
|
|
6805
7633
|
function summary(out, lines2) {
|
|
6806
7634
|
out("");
|
|
@@ -6912,16 +7740,16 @@ async function runInstall(opts, deps = defaultInstallDeps()) {
|
|
|
6912
7740
|
|
|
6913
7741
|
// src/commands/logout.ts
|
|
6914
7742
|
import { createInterface as createInterface2 } from "readline";
|
|
6915
|
-
import { existsSync as
|
|
7743
|
+
import { existsSync as existsSync20, rmSync as rmSync18 } from "fs";
|
|
6916
7744
|
function stdout4(line) {
|
|
6917
7745
|
process.stdout.write(line + "\n");
|
|
6918
7746
|
}
|
|
6919
7747
|
function askYesNo(question) {
|
|
6920
|
-
return new Promise((
|
|
7748
|
+
return new Promise((resolve6) => {
|
|
6921
7749
|
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
6922
7750
|
rl.question(question, (answer) => {
|
|
6923
7751
|
rl.close();
|
|
6924
|
-
|
|
7752
|
+
resolve6(/^(y|yes)$/i.test(answer.trim()));
|
|
6925
7753
|
});
|
|
6926
7754
|
});
|
|
6927
7755
|
}
|
|
@@ -6943,8 +7771,8 @@ async function runLogout(opts = {}) {
|
|
|
6943
7771
|
}
|
|
6944
7772
|
}
|
|
6945
7773
|
await runDisable([], { out, brief: true, keepAutostart: opts.keepAutostart });
|
|
6946
|
-
if (
|
|
6947
|
-
|
|
7774
|
+
if (existsSync20(home)) {
|
|
7775
|
+
rmSync18(home, { recursive: true, force: true });
|
|
6948
7776
|
}
|
|
6949
7777
|
out("");
|
|
6950
7778
|
out("\u2705 \u5DF2\u6E05\u9664\u672C\u673A\u7684 DeskBuddy \u6570\u636E\u3002");
|
|
@@ -7088,6 +7916,7 @@ function stdout6(line) {
|
|
|
7088
7916
|
process.stdout.write(line + "\n");
|
|
7089
7917
|
}
|
|
7090
7918
|
async function runUpdate(opts = {}) {
|
|
7919
|
+
if (opts.auto) return runAutoSwitch(opts);
|
|
7091
7920
|
const out = opts.out ?? stdout6;
|
|
7092
7921
|
const cfg = loadConfig();
|
|
7093
7922
|
if (!cfg.boundAt) {
|
|
@@ -7102,11 +7931,35 @@ async function runUpdate(opts = {}) {
|
|
|
7102
7931
|
out("");
|
|
7103
7932
|
return runEnable([...cfg.enabledTools], opts);
|
|
7104
7933
|
}
|
|
7934
|
+
async function runAutoSwitch(opts) {
|
|
7935
|
+
const out = opts.out ?? ((line) => log(`[\u81EA\u52A8\u5347\u7EA7] ${line}`));
|
|
7936
|
+
const cfg = loadConfig();
|
|
7937
|
+
if (!cfg.boundAt) {
|
|
7938
|
+
out("\u8FD8\u6CA1\u6709\u6388\u6743\uFF0C\u4E0D\u5207\u6362");
|
|
7939
|
+
return 1;
|
|
7940
|
+
}
|
|
7941
|
+
out(`\u5207\u6362\u5230 ${packageVersion()}`);
|
|
7942
|
+
if (cfg.enabledTools.length > 0) {
|
|
7943
|
+
try {
|
|
7944
|
+
installTools([...cfg.enabledTools], out, opts.shim);
|
|
7945
|
+
} catch (e) {
|
|
7946
|
+
out(`\u91CD\u88C5 hook \u51FA\u9519\uFF1A${e.message}\uFF08\u7EE7\u7EED\u5207\u6362\uFF09`);
|
|
7947
|
+
}
|
|
7948
|
+
}
|
|
7949
|
+
const from = switchFromEnv();
|
|
7950
|
+
delete process.env[FROM_VERSION_ENV];
|
|
7951
|
+
delete process.env[FROM_CLI_ENV];
|
|
7952
|
+
const target = autostartTarget(out, from);
|
|
7953
|
+
if (opts.skipAutostart || process.env.DESKBUDDY_SKIP_AUTOSTART === "1") return 0;
|
|
7954
|
+
const r = await installAutostart(target, process.platform, { auto: true });
|
|
7955
|
+
out(r.message);
|
|
7956
|
+
return r.ok ? 0 : 1;
|
|
7957
|
+
}
|
|
7105
7958
|
|
|
7106
7959
|
// src/wrap/run.ts
|
|
7107
7960
|
import { randomBytes as randomBytes8 } from "crypto";
|
|
7108
|
-
import { appendFileSync as appendFileSync2, mkdirSync as
|
|
7109
|
-
import { join as
|
|
7961
|
+
import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync26, statSync as statSync11, truncateSync as truncateSync3 } from "fs";
|
|
7962
|
+
import { join as join31 } from "path";
|
|
7110
7963
|
import { StringDecoder } from "string_decoder";
|
|
7111
7964
|
|
|
7112
7965
|
// src/wrap/injector.ts
|
|
@@ -7354,10 +8207,10 @@ var Injector = class {
|
|
|
7354
8207
|
};
|
|
7355
8208
|
|
|
7356
8209
|
// src/wrap/passthrough.ts
|
|
7357
|
-
import { spawn as
|
|
8210
|
+
import { spawn as spawn4 } from "child_process";
|
|
7358
8211
|
import { realpathSync as realpathSync3 } from "fs";
|
|
7359
8212
|
import { constants as constants2 } from "os";
|
|
7360
|
-
import { dirname as
|
|
8213
|
+
import { dirname as dirname13, resolve as resolve5 } from "path";
|
|
7361
8214
|
var CLAUDE_SUBCOMMANDS = /* @__PURE__ */ new Set([
|
|
7362
8215
|
"agents",
|
|
7363
8216
|
"attach",
|
|
@@ -7407,8 +8260,8 @@ function passthroughReason(i) {
|
|
|
7407
8260
|
if (firstPositional !== null && CLAUDE_SUBCOMMANDS.has(firstPositional)) return "subcommand";
|
|
7408
8261
|
return null;
|
|
7409
8262
|
}
|
|
7410
|
-
function
|
|
7411
|
-
const r =
|
|
8263
|
+
function norm2(p, win) {
|
|
8264
|
+
const r = resolve5(p).replace(/[\\/]+$/, "");
|
|
7412
8265
|
return win ? r.replace(/\//g, "\\").toLowerCase() : r;
|
|
7413
8266
|
}
|
|
7414
8267
|
function real(p) {
|
|
@@ -7420,8 +8273,8 @@ function real(p) {
|
|
|
7420
8273
|
}
|
|
7421
8274
|
function findReal(tool, d = defaultLocateDeps(), skip = shimDir()) {
|
|
7422
8275
|
const win = (d.platform ?? process.platform) === "win32";
|
|
7423
|
-
const skipKeys = /* @__PURE__ */ new Set([
|
|
7424
|
-
const inSkip = (file) => skipKeys.has(
|
|
8276
|
+
const skipKeys = /* @__PURE__ */ new Set([norm2(skip, win), norm2(real(skip), win)]);
|
|
8277
|
+
const inSkip = (file) => skipKeys.has(norm2(dirname13(file), win)) || skipKeys.has(norm2(dirname13(real(file)), win));
|
|
7425
8278
|
const dd = { ...d, skipDirs: [...d.skipDirs ?? [], skip] };
|
|
7426
8279
|
const name = SHIM_COMMANDS[tool];
|
|
7427
8280
|
if (!name) {
|
|
@@ -7450,7 +8303,7 @@ function runDirect(real2, args, env = process.env) {
|
|
|
7450
8303
|
return new Promise((done) => {
|
|
7451
8304
|
let child;
|
|
7452
8305
|
try {
|
|
7453
|
-
child =
|
|
8306
|
+
child = spawn4(real2.file, [...real2.prefixArgs, ...args], {
|
|
7454
8307
|
stdio: "inherit",
|
|
7455
8308
|
// 🔴 再经过垫片也直接放行,不会绕回来
|
|
7456
8309
|
env: withoutShimFlag({ ...env, DESKBUDDY_WRAPPED: "1" }),
|
|
@@ -7500,21 +8353,21 @@ function runDirect(real2, args, env = process.env) {
|
|
|
7500
8353
|
}
|
|
7501
8354
|
|
|
7502
8355
|
// src/wrap/codexLaunch.ts
|
|
7503
|
-
import { spawn as
|
|
7504
|
-
import { closeSync as
|
|
8356
|
+
import { spawn as spawn5 } from "child_process";
|
|
8357
|
+
import { closeSync as closeSync4, mkdirSync as mkdirSync25, openSync as openSync4, readdirSync as readdirSync7, readFileSync as readFileSync22, rmSync as rmSync19, statSync as statSync10, truncateSync as truncateSync2, writeFileSync as writeFileSync24, renameSync as renameSync17 } from "fs";
|
|
7505
8358
|
import { request } from "http";
|
|
7506
8359
|
import { createServer as createServer4 } from "net";
|
|
7507
|
-
import { join as
|
|
8360
|
+
import { join as join30 } from "path";
|
|
7508
8361
|
|
|
7509
8362
|
// src/core/codexServer.ts
|
|
7510
|
-
import { execFileSync as
|
|
8363
|
+
import { execFileSync as execFileSync13 } from "child_process";
|
|
7511
8364
|
function isAppServerArgs(args, url) {
|
|
7512
8365
|
return /\bapp-server\b/.test(args) && new RegExp(`--listen[= ]${url.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:\\s|$)`).test(args);
|
|
7513
8366
|
}
|
|
7514
8367
|
function processArgs(pid) {
|
|
7515
8368
|
if (process.platform === "win32") return null;
|
|
7516
8369
|
try {
|
|
7517
|
-
const out =
|
|
8370
|
+
const out = execFileSync13("ps", ["-ww", "-o", "args=", "-p", String(pid)], {
|
|
7518
8371
|
encoding: "utf8",
|
|
7519
8372
|
stdio: ["ignore", "pipe", "ignore"]
|
|
7520
8373
|
});
|
|
@@ -7672,12 +8525,12 @@ async function runCodexWindow(real2, args, d) {
|
|
|
7672
8525
|
}
|
|
7673
8526
|
var MAX_LOG_BYTES2 = 1024 * 1024;
|
|
7674
8527
|
function appServerLogPath() {
|
|
7675
|
-
return
|
|
8528
|
+
return join30(deskbuddyHome(), "codex-app-server.log");
|
|
7676
8529
|
}
|
|
7677
8530
|
function windowsDir2() {
|
|
7678
|
-
return
|
|
8531
|
+
return join30(deskbuddyHome(), "codex-windows");
|
|
7679
8532
|
}
|
|
7680
|
-
function
|
|
8533
|
+
function pidAlive2(pid) {
|
|
7681
8534
|
try {
|
|
7682
8535
|
process.kill(pid, 0);
|
|
7683
8536
|
return true;
|
|
@@ -7688,50 +8541,50 @@ function pidAlive(pid) {
|
|
|
7688
8541
|
function daemonAlive(pidFile = pidFilePath(), args = processArgs) {
|
|
7689
8542
|
let pid = 0;
|
|
7690
8543
|
try {
|
|
7691
|
-
pid = Number(
|
|
8544
|
+
pid = Number(readFileSync22(pidFile, "utf8").trim());
|
|
7692
8545
|
} catch {
|
|
7693
8546
|
return false;
|
|
7694
8547
|
}
|
|
7695
|
-
if (!Number.isInteger(pid) || pid <= 0 || !
|
|
8548
|
+
if (!Number.isInteger(pid) || pid <= 0 || !pidAlive2(pid)) return false;
|
|
7696
8549
|
const a = args(pid);
|
|
7697
8550
|
return !!a && /cli\.js daemon(?:\s|$)/.test(a);
|
|
7698
8551
|
}
|
|
7699
8552
|
function probeReady(url) {
|
|
7700
8553
|
const port = Number(url.slice(url.lastIndexOf(":") + 1));
|
|
7701
|
-
return new Promise((
|
|
8554
|
+
return new Promise((resolve6) => {
|
|
7702
8555
|
const req2 = request({ host: "127.0.0.1", port, path: "/readyz", method: "GET", timeout: 800 }, (res) => {
|
|
7703
8556
|
res.resume();
|
|
7704
|
-
|
|
8557
|
+
resolve6((res.statusCode ?? 0) >= 200 && (res.statusCode ?? 0) < 300);
|
|
7705
8558
|
});
|
|
7706
8559
|
req2.on("timeout", () => req2.destroy());
|
|
7707
|
-
req2.on("error", () =>
|
|
8560
|
+
req2.on("error", () => resolve6(false));
|
|
7708
8561
|
req2.end();
|
|
7709
8562
|
});
|
|
7710
8563
|
}
|
|
7711
8564
|
function freePort() {
|
|
7712
|
-
return new Promise((
|
|
8565
|
+
return new Promise((resolve6, reject) => {
|
|
7713
8566
|
const srv = createServer4();
|
|
7714
8567
|
srv.unref();
|
|
7715
8568
|
srv.on("error", reject);
|
|
7716
8569
|
srv.listen(0, "127.0.0.1", () => {
|
|
7717
8570
|
const addr = srv.address();
|
|
7718
8571
|
const port = typeof addr === "object" && addr ? addr.port : 0;
|
|
7719
|
-
srv.close(() => port ?
|
|
8572
|
+
srv.close(() => port ? resolve6(port) : reject(new Error("no port")));
|
|
7720
8573
|
});
|
|
7721
8574
|
});
|
|
7722
8575
|
}
|
|
7723
8576
|
function startServer(real2, url, env, cwd) {
|
|
7724
8577
|
const logPath2 = appServerLogPath();
|
|
7725
|
-
|
|
8578
|
+
mkdirSync25(deskbuddyHome(), { recursive: true });
|
|
7726
8579
|
try {
|
|
7727
8580
|
if (statSync10(logPath2).size > MAX_LOG_BYTES2) truncateSync2(logPath2, 0);
|
|
7728
8581
|
} catch {
|
|
7729
8582
|
}
|
|
7730
|
-
const fd =
|
|
8583
|
+
const fd = openSync4(logPath2, "a");
|
|
7731
8584
|
try {
|
|
7732
8585
|
const childEnv2 = { ...env, DESKBUDDY_WRAPPED: "1" };
|
|
7733
8586
|
delete childEnv2.DESKBUDDY_SHIM;
|
|
7734
|
-
const child =
|
|
8587
|
+
const child = spawn5(real2.file, [...real2.prefixArgs, "app-server", "--listen", url], {
|
|
7735
8588
|
detached: true,
|
|
7736
8589
|
stdio: ["ignore", fd, fd],
|
|
7737
8590
|
cwd,
|
|
@@ -7742,7 +8595,7 @@ function startServer(real2, url, env, cwd) {
|
|
|
7742
8595
|
child.unref();
|
|
7743
8596
|
return child.pid ?? null;
|
|
7744
8597
|
} finally {
|
|
7745
|
-
|
|
8598
|
+
closeSync4(fd);
|
|
7746
8599
|
}
|
|
7747
8600
|
}
|
|
7748
8601
|
function killServer(pid) {
|
|
@@ -7756,41 +8609,41 @@ function killOwnServer(pid, url) {
|
|
|
7756
8609
|
if (a && isAppServerArgs(a, url)) killServer(pid);
|
|
7757
8610
|
}
|
|
7758
8611
|
function recordPath(rec) {
|
|
7759
|
-
return
|
|
8612
|
+
return join30(windowsDir2(), `${rec.server}.json`);
|
|
7760
8613
|
}
|
|
7761
8614
|
function writeRecord(rec) {
|
|
7762
8615
|
try {
|
|
7763
|
-
|
|
8616
|
+
mkdirSync25(windowsDir2(), { recursive: true, mode: 448 });
|
|
7764
8617
|
const p = recordPath(rec);
|
|
7765
|
-
|
|
7766
|
-
|
|
8618
|
+
writeFileSync24(p + ".tmp", JSON.stringify(rec), { encoding: "utf8", mode: 384 });
|
|
8619
|
+
renameSync17(p + ".tmp", p);
|
|
7767
8620
|
} catch {
|
|
7768
8621
|
}
|
|
7769
8622
|
}
|
|
7770
8623
|
function removeRecord(rec) {
|
|
7771
8624
|
try {
|
|
7772
|
-
|
|
8625
|
+
rmSync19(recordPath(rec), { force: true });
|
|
7773
8626
|
} catch {
|
|
7774
8627
|
}
|
|
7775
8628
|
}
|
|
7776
|
-
function reapWindows(dir = windowsDir2(), alive =
|
|
8629
|
+
function reapWindows(dir = windowsDir2(), alive = pidAlive2, args = processArgs, kill = killServer) {
|
|
7777
8630
|
let names = [];
|
|
7778
8631
|
try {
|
|
7779
|
-
names =
|
|
8632
|
+
names = readdirSync7(dir).filter((n) => n.endsWith(".json"));
|
|
7780
8633
|
} catch {
|
|
7781
8634
|
return;
|
|
7782
8635
|
}
|
|
7783
8636
|
for (const n of names) {
|
|
7784
|
-
const p =
|
|
8637
|
+
const p = join30(dir, n);
|
|
7785
8638
|
let rec;
|
|
7786
8639
|
try {
|
|
7787
|
-
rec = JSON.parse(
|
|
8640
|
+
rec = JSON.parse(readFileSync22(p, "utf8"));
|
|
7788
8641
|
} catch {
|
|
7789
|
-
|
|
8642
|
+
rmSync19(p, { force: true });
|
|
7790
8643
|
continue;
|
|
7791
8644
|
}
|
|
7792
8645
|
if (typeof rec.supervisor !== "number" || typeof rec.server !== "number" || typeof rec.url !== "string") {
|
|
7793
|
-
|
|
8646
|
+
rmSync19(p, { force: true });
|
|
7794
8647
|
continue;
|
|
7795
8648
|
}
|
|
7796
8649
|
if (alive(rec.supervisor)) continue;
|
|
@@ -7798,13 +8651,13 @@ function reapWindows(dir = windowsDir2(), alive = pidAlive, args = processArgs,
|
|
|
7798
8651
|
const a = args(rec.server);
|
|
7799
8652
|
if (a && isAppServerArgs(a, rec.url)) kill(rec.server);
|
|
7800
8653
|
}
|
|
7801
|
-
|
|
8654
|
+
rmSync19(p, { force: true });
|
|
7802
8655
|
}
|
|
7803
8656
|
}
|
|
7804
8657
|
function launchLog(line) {
|
|
7805
8658
|
try {
|
|
7806
|
-
|
|
7807
|
-
|
|
8659
|
+
mkdirSync25(deskbuddyHome(), { recursive: true });
|
|
8660
|
+
writeFileSync24(join30(deskbuddyHome(), "wrap.log"), `${(/* @__PURE__ */ new Date()).toISOString()} [codex ${process.pid}] ${line}
|
|
7808
8661
|
`, { flag: "a" });
|
|
7809
8662
|
} catch {
|
|
7810
8663
|
}
|
|
@@ -7844,8 +8697,8 @@ function childEnv(src, windowId) {
|
|
|
7844
8697
|
}
|
|
7845
8698
|
function wrapLog(line) {
|
|
7846
8699
|
try {
|
|
7847
|
-
const p =
|
|
7848
|
-
|
|
8700
|
+
const p = join31(deskbuddyHome(), "wrap.log");
|
|
8701
|
+
mkdirSync26(deskbuddyHome(), { recursive: true });
|
|
7849
8702
|
try {
|
|
7850
8703
|
if (statSync11(p).size > MAX_LOG_BYTES3) truncateSync3(p, 0);
|
|
7851
8704
|
} catch {
|
|
@@ -8135,7 +8988,7 @@ function runAttached(tool, p, term0, windowId, deps, launched = false) {
|
|
|
8135
8988
|
injector.tick().catch(() => {
|
|
8136
8989
|
});
|
|
8137
8990
|
}, TICK_MS);
|
|
8138
|
-
return new Promise((
|
|
8991
|
+
return new Promise((resolve6) => {
|
|
8139
8992
|
let degraded = false;
|
|
8140
8993
|
const onFatal = (e) => {
|
|
8141
8994
|
deps.log(`\u5305\u88C5\u5C42\u5F02\u5E38\uFF1A${e?.stack ?? String(e)}`);
|
|
@@ -8186,8 +9039,8 @@ ${WRAP_HINT}\uFF08\u8BBE\u5907\u56DE\u590D\u6682\u65F6\u5199\u4E0D\u8FDB\u8FD9\u
|
|
|
8186
9039
|
restoreTerminal();
|
|
8187
9040
|
process.removeListener("uncaughtException", onFatal);
|
|
8188
9041
|
for (const [s, h] of sigHandlers) process.removeListener(s, h);
|
|
8189
|
-
if (pending > 0) setTimeout(() =>
|
|
8190
|
-
else
|
|
9042
|
+
if (pending > 0) setTimeout(() => resolve6(code), 150);
|
|
9043
|
+
else resolve6(code);
|
|
8191
9044
|
}
|
|
8192
9045
|
process.on("uncaughtException", onFatal);
|
|
8193
9046
|
for (const [s, h] of sigHandlers) {
|
|
@@ -8214,7 +9067,8 @@ var USAGE = `deskbuddy \u2014\u2014 \u628A AI \u7F16\u7801\u667A\u80FD\u4F53\u6B
|
|
|
8214
9067
|
deskbuddy enable <tool[,tool]> \u5B89\u88C5\u91C7\u96C6 hook\uFF08\u5982 claude_code,codex\uFF09
|
|
8215
9068
|
deskbuddy disable [tool[,tool]] \u79FB\u9664\u91C7\u96C6 hook\uFF08\u4E0D\u5E26\u53C2\u6570=\u5168\u90E8\uFF09
|
|
8216
9069
|
deskbuddy update \u5347\u7EA7\u5230\u5F53\u524D\u8FD9\u7248\uFF1A\u91CD\u88C5\u5DF2\u542F\u7528\u5DE5\u5177\u7684 hook\u3001\u91CD\u542F daemon
|
|
8217
|
-
\uFF08npx deskbuddy@<\u7248\u672C> update\
|
|
9070
|
+
\uFF08npx deskbuddy@<\u7248\u672C> update\uFF1B\u4E4B\u540E\u7531 daemon \u81EA\u52A8\u5347\u7EA7\uFF0C
|
|
9071
|
+
config.json \u91CC "autoUpdate": false \u53EF\u5173\u95ED\uFF09
|
|
8218
9072
|
deskbuddy daemon [--foreground] \u5E38\u9A7B\u6536\u53D6\u8BBE\u5907\u4E0B\u53D1\u7684\u6307\u4EE4\uFF08\u5F00\u673A\u81EA\u542F\u4F1A\u81EA\u52A8\u62C9\u8D77\uFF09
|
|
8219
9073
|
deskbuddy status \u770B\u91C7\u96C6\u5230\u4E86\u4EC0\u4E48\u3001\u4E0A\u62A5\u4E86\u54EA\u4E9B\u5B57\u6BB5
|
|
8220
9074
|
deskbuddy logout [--yes] \u79FB\u9664 hook \u5E76\u6E05\u9664\u672C\u673A\u6570\u636E
|
|
@@ -8254,7 +9108,7 @@ async function main(argv) {
|
|
|
8254
9108
|
case "disable":
|
|
8255
9109
|
return runDisable(parseTools(arg));
|
|
8256
9110
|
case "update":
|
|
8257
|
-
return runUpdate();
|
|
9111
|
+
return runUpdate({ auto: argv.includes("--auto") });
|
|
8258
9112
|
case "daemon":
|
|
8259
9113
|
return runDaemonCommand({ foreground: argv.includes("--foreground") || argv.includes("-f") });
|
|
8260
9114
|
case "status":
|