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 CHANGED
@@ -1,7 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/commands/daemon.ts
4
- import { writeFileSync as writeFileSync17, rmSync as rmSync12 } from "fs";
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 existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, rmSync, writeFileSync as writeFileSync2 } from "fs";
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 join2 } from "path";
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 = execFileSync("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", WIN_PS_SCRIPT], {
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 = fileURLToPath(new URL("./" + CLI_ENTRY, import.meta.url));
239
- if (typeof argv1 === "string" && basename(argv1) === CLI_ENTRY) return argv1;
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 ?? join2(homedir2(), "AppData", "Roaming")) {
279
- return join2(appData, "Microsoft", "Windows", "Start Menu", "Programs", "Startup", "DeskBuddyDaemon.vbs");
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(readFileSync2(pidFilePath(), "utf8").trim());
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 || !isOurDaemonCmdline(p.cmdline)) return;
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 join2(home, "Library", "LaunchAgents", `${LAUNCH_LABEL}.plist`);
853
+ return join4(home, "Library", "LaunchAgents", `${LAUNCH_LABEL}.plist`);
323
854
  }
324
855
  function systemdUnitPath(home = homedir2()) {
325
- return join2(home, ".config", "systemd", "user", SYSTEMD_UNIT);
856
+ return join4(home, ".config", "systemd", "user", SYSTEMD_UNIT);
326
857
  }
327
858
  function write(path, content) {
328
- mkdirSync2(dirname(path), { recursive: true });
329
- writeFileSync2(path, content, "utf8");
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
- async function installAutostart(t, platform = process.platform) {
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
- for (let i = 0; ; i++) {
341
- try {
342
- await run("launchctl", ["bootstrap", `gui/${process.getuid?.() ?? 501}`, p]);
343
- break;
344
- } catch (e) {
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
- mkdirSync2(dirname(p), { recursive: true });
361
- writeFileSync2(p, "\uFEFF" + winStartupVbs(t), "utf16le");
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 (existsSync2(p)) rmSync(p, { force: true });
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 (existsSync2(p)) rmSync(p, { force: true });
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 (existsSync2(p)) rmSync(p, { force: true });
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 { ok: true, message: "" };
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
- function pidFilePath() {
405
- return join2(deskbuddyHome(), "daemon.pid");
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 existsSync10 } from "fs";
411
- import { basename as basename6 } from "path";
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 existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync3, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync3 } from "fs";
416
- import { dirname as dirname2, join as join3 } from "path";
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 join3(dirname2(hookPath), "opencode.mjs");
1361
+ return join7(dirname6(hookPath), "opencode.mjs");
431
1362
  }
432
1363
  function installOcPlugin(pluginPath, hookPath, nodePath) {
433
- if (existsSync3(pluginPath) && !readFileSync3(pluginPath, "utf8").includes(OC_MARKER)) {
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
- mkdirSync3(dirname2(pluginPath), { recursive: true });
1367
+ mkdirSync6(dirname6(pluginPath), { recursive: true });
437
1368
  const tmp = pluginPath + ".deskbuddy.tmp";
438
- writeFileSync3(tmp, ocPluginSource(ocPluginModule(hookPath), nodePath), "utf8");
439
- renameSync2(tmp, pluginPath);
1369
+ writeFileSync5(tmp, ocPluginSource(ocPluginModule(hookPath), nodePath), "utf8");
1370
+ renameSync4(tmp, pluginPath);
440
1371
  }
441
1372
  function uninstallOcPlugin(pluginPath) {
442
1373
  try {
443
- if (readFileSync3(pluginPath, "utf8").includes(OC_MARKER)) unlinkSync(pluginPath);
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 readFileSync3(pluginPath, "utf8").includes(OC_MARKER);
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 existsSync4, mkdirSync as mkdirSync5, readdirSync as readdirSync2, readFileSync as readFileSync5, renameSync as renameSync4, rmSync as rmSync3, writeFileSync as writeFileSync5 } from "fs";
458
- import { join as join5 } from "path";
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 execFileSync2 } from "child_process";
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 = execFileSync2("ps", ["-p", String(pid), "-o", "comm="], {
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 mkdirSync4, openSync, readdirSync, readFileSync as readFileSync4, readSync, realpathSync, renameSync as renameSync3, rmSync as rmSync2, statSync, writeFileSync as writeFileSync4 } from "fs";
496
- import { join as join4 } from "path";
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 join4(home, "ended");
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
- mkdirSync4(dir, { recursive: true, mode: 448 });
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 = join4(dir, `${s.sessionId}.json`);
1439
+ const file = join8(dir, `${s.sessionId}.json`);
509
1440
  const tmp = `${file}.${process.pid}.tmp`;
510
- writeFileSync4(tmp, JSON.stringify(rec), { encoding: "utf8", mode: 384 });
511
- renameSync3(tmp, file);
512
- for (const n of readdirSync(dir)) {
513
- const f = join4(dir, n);
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 - statSync(f).mtimeMs > ENDED_TTL_MS) rmSync2(f, { force: true });
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(readFileSync4(join4(endedDir(), `${sessionId}.json`), "utf8"));
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 = statSync(file).size;
1471
+ const size = statSync2(file).size;
541
1472
  const len = Math.min(size, max);
542
1473
  const buf = Buffer.alloc(len);
543
- fd = openSync(file, "r");
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) closeSync(fd);
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 = readdirSync(join4(home, "projects"));
1497
+ dirs = readdirSync2(join8(home, "projects"));
567
1498
  } catch {
568
1499
  return "";
569
1500
  }
570
1501
  for (const d of dirs) {
571
- const file = join4(home, "projects", d, `${sessionId}.jsonl`);
1502
+ const file = join8(home, "projects", d, `${sessionId}.jsonl`);
572
1503
  try {
573
- statSync(file);
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 statSync(p).isDirectory();
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 join5(deskbuddyHome(), "state");
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 join5(stateDir(), `${safe || "unknown"}.json`);
1552
+ return join9(stateDir(), `${safe || "unknown"}.json`);
622
1553
  }
623
1554
  function readState(sessionId) {
624
1555
  try {
625
- return JSON.parse(readFileSync5(statePath(sessionId), "utf8"));
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
- rmSync3(statePath(sessionId), { force: true });
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
- var machineInfoCache = null;
876
- function machineInfo() {
877
- if (!machineInfoCache) {
878
- machineInfoCache = {
879
- os: humanOs(),
880
- osVersion: productOsVersion(),
881
- arch: process.arch,
882
- cliVersion: packageVersion()
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 machineInfoCache;
1590
+ return alive;
886
1591
  }
887
- async function call(service, data, timeoutMs = DEFAULT_TIMEOUT_MS) {
888
- const cfg = loadConfig();
889
- const body = buildGatewayBody(service, data);
890
- const controller = new AbortController();
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 res = await fetch(`${cfg.apiBase}/gateway`, {
894
- method: "POST",
895
- headers: buildHeaders(cfg, body),
896
- body,
897
- // 与签名用的是同一个字符串
898
- signal: controller.signal
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 api = {
910
- authCode: (clientId, hostName) => call(SERVICES.authCode, { clientId, hostName, ...machineInfo() }),
911
- authToken: (authCode) => call(SERVICES.authToken, { authCode }),
912
- toolRegistry: () => call(SERVICES.toolRegistry, {}),
913
- // 🔴 2 秒超时:这条调用发生在 hook 链路上,慢一点就是拖慢用户的 agent
914
- report: (payload) => call(SERVICES.sessionReport, payload, 2e3),
915
- approvalCreate: (payload) => call(APPROVAL_SERVICES.create, payload),
916
- approvalCancel: (approvalId, reason) => call(APPROVAL_SERVICES.cancel, { approvalId, reason }),
917
- approvalSync: (approvalIds) => call(APPROVAL_SERVICES.sync, { approvalIds }),
918
- dirsReport: (payload) => call(DIRS_SERVICE, payload),
919
- installRedeem: (installCode, clientId, hostName) => call(INSTALL_REDEEM_SERVICE, { installCode, clientId, hostName, ...machineInfo() }),
920
- // 2 秒超时、失败不重试(spec §4.1):回报丢了只是设备少一句提示,任务状态仍由会话快照负责
921
- commandResult: (payload) => call(CMD_RESULT_SERVICE, payload, 2e3)
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 basename2 } from "path";
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: basename2(s.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 existsSync6, mkdirSync as mkdirSync7, readFileSync as readFileSync8, writeFileSync as writeFileSync7 } from "fs";
955
- import { basename as basename3, join as join7 } from "path";
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 join7(deskbuddyHome(), "dirs.json");
1694
+ return join10(deskbuddyHome(), "dirs.json");
959
1695
  }
960
1696
  function dirSecret() {
961
- const file = join7(deskbuddyHome(), "dir-secret");
1697
+ const file = join10(deskbuddyHome(), "dir-secret");
962
1698
  try {
963
- const s2 = readFileSync8(file, "utf8").trim();
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
- mkdirSync7(deskbuddyHome(), { recursive: true });
970
- writeFileSync7(file, s, { encoding: "utf8", mode: 384 });
971
- chmodSync(file, 384);
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(readFileSync8(dirsPath(), "utf8"));
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 = existsSync6) {
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(basename3(p), (counts.get(basename3(p)) ?? 0) + 1);
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 = basename3(p) || "/";
996
- return { path: p, ref, name: (counts.get(basename3(p)) ?? 0) > 1 ? `${name} \xB7 ${ref.slice(0, 4)}` : name, lastUsedAt: at };
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 = existsSync6) {
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 execFile2, execFileSync as execFileSync4 } from "child_process";
1078
- import { homedir as homedir4 } from "os";
1079
- import { join as join9 } from "path";
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 existsSync7 } from "fs";
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) => existsSync7(p)) ?? candidates[0];
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 { accessSync, constants, existsSync as existsSync8, lstatSync, readFileSync as readFileSync9, statSync as statSync2 } from "fs";
1138
- import { homedir as homedir3 } from "os";
1139
- import { delimiter, dirname as dirname4, join as join8 } from "path";
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: homedir3(),
1878
+ home: homedir4(),
1142
1879
  path: process.env.PATH ?? "",
1143
1880
  nodePath: process.execPath,
1144
1881
  isExecutable: (p) => {
1145
1882
  try {
1146
- if (!statSync2(p).isFile()) return false;
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) => existsSync8(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 readFileSync9(p, "utf8");
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: [join8(deskbuddyHome(), "bin")]
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
- join8(home, ".local", "bin"),
1181
- ...win.appData ? [join8(win.appData, "npm")] : [],
1182
- join8(home, ".bun", "bin"),
1183
- ...win.localAppData ? [join8(win.localAppData, "Volta", "bin")] : [],
1184
- join8(home, "scoop", "shims"),
1185
- ...nodePath ? [dirname4(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
- join8(home, ".local", "bin"),
1190
- join8(home, ".claude", "local"),
1191
- join8(home, ".npm-global", "bin"),
1192
- join8(home, ".bun", "bin"),
1193
- join8(home, ".volta", "bin"),
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 ? [dirname4(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 = join8(dir, name + ext);
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 sep2 = win ? ";" : delimiter;
1991
+ const sep3 = win ? ";" : delimiter;
1216
1992
  const all = [
1217
- ...d.path.split(sep2).filter(Boolean),
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 join8(dirname4(shimPath), m[1].replace(/^\\/, ""));
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", join8(home, "Applications")];
2025
+ return ["/Applications", join11(home, "Applications")];
1250
2026
  }
1251
2027
  function winAppDirs(d, product) {
1252
2028
  return [
1253
- ...d.localAppData ? [join8(d.localAppData, "Programs", product)] : [],
1254
- join8("C:\\Program Files", product)
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 ? [join8(d.home, ".local", "bin", "claude.exe")] : [],
1262
- ...d.appData ? [join8(d.appData, "npm", "node_modules", "@anthropic-ai", "claude-code", "bin", "claude.exe")] : []
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
- for (const dir of winAppDirs(d, "WorkBuddy")) {
1281
- for (const ext of [".exe", ".cmd", ""]) {
1282
- const p = join8(dir, WORKBUDDY_CLI_IN_WIN_APP + ext);
1283
- if (d.exists(p)) {
1284
- const r = resolveWinLaunch(p, d);
1285
- if (r) return r;
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
- return plain("codebuddy");
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 = join8(dir, QODER_CN_WORKER_IN_WIN_APP);
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 = join8(d.home, ".opencode", "bin", "opencode.exe");
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 = join8(dir, WORKBUDDY_CLI_IN_APP);
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 = join8(dir, QODER_CN_WORKER_IN_APP);
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 = join8(d.home, ".opencode", "bin", "opencode");
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 ? join8(d.localAppData, "Microsoft", "WindowsApps", "wt.exe") : "";
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 = homedir4()) {
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: join9(home, ".workbuddy") } };
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((resolve5) => {
1535
- child = execFile2(
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
- resolve5({ ok: false, sessionId: parseSessionId(tool, out), stdout: out, error: msg });
2332
+ resolve6({ ok: false, sessionId: parseSessionId(tool, out), stdout: out, error: msg });
1553
2333
  return;
1554
2334
  }
1555
- resolve5({ ok: true, sessionId: parseSessionId(tool, out), stdout: out });
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
- execFileSync4("taskkill", ["/pid", String(child.pid), "/T", "/F"], { stdio: "ignore", windowsHide: true });
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 mkdirSync8, readFileSync as readFileSync10, renameSync as renameSync6, writeFileSync as writeFileSync8 } from "fs";
1602
- import { join as join10 } from "path";
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 join10(deskbuddyHome(), "approvals.json");
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(readFileSync10(storePath(), "utf8"));
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
- mkdirSync8(deskbuddyHome(), { recursive: true });
2411
+ mkdirSync10(deskbuddyHome(), { recursive: true });
1632
2412
  const tmp = storePath() + ".tmp";
1633
- writeFileSync8(tmp, JSON.stringify(this.#records), { encoding: "utf8", mode: 384 });
1634
- renameSync6(tmp, storePath());
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 basename4 } from "path";
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: basename4(r.cwd),
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 existsSync9, realpathSync as realpathSync2 } from "fs";
1716
- import { basename as basename5, dirname as dirname5, join as join11 } from "path";
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 = resolve(cwd || "/", p).split(sep).filter(Boolean).map((s) => s.toLowerCase());
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 = relative(resolve(cwd), resolve(cwd, p));
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(resolve(roots[0] ?? "/", p));
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 relative(resolve(r), resolve(r, p));
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(resolve(roots[0] ?? "/", p))) : false)
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 execFileSync5 } from "child_process";
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 = execFileSync5("ps", ["-A", "-ww", "-o", "pid=,ppid=,args="], {
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((resolve5) => settle = resolve5);
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((resolve5) => {
2028
- void decided.then(resolve5);
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
- resolve5({ decision: "deny", reason: "\u5DF2\u53D6\u6D88" });
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((resolve5) => settle = resolve5);
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((resolve5) => {
2106
- void decided.then(resolve5);
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
- resolve5(none);
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 (existsSync9(cur)) return join11(realpathSync2(cur), ...tail.reverse());
3150
+ if (existsSync12(cur)) return join14(realpathSync2(cur), ...tail.reverse());
2371
3151
  } catch {
2372
3152
  return p;
2373
3153
  }
2374
- const parent = dirname5(cur);
3154
+ const parent = dirname8(cur);
2375
3155
  if (parent === cur) return p;
2376
- tail.push(basename5(cur));
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 mkdirSync9, renameSync as renameSync7, rmSync as rmSync4, writeFileSync as writeFileSync9 } from "fs";
2394
- import { join as join12 } from "path";
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 join12(deskbuddyHome(), "gate.json");
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((resolve5, reject) => {
3204
+ await new Promise((resolve6, reject) => {
2418
3205
  server.once("error", reject);
2419
- server.listen(0, "127.0.0.1", () => resolve5());
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
- mkdirSync9(deskbuddyHome(), { recursive: true });
3211
+ mkdirSync11(deskbuddyHome(), { recursive: true });
2425
3212
  const tmp = gateConfPath() + ".tmp";
2426
- writeFileSync9(tmp, JSON.stringify({ port, token: this.#token, pid: process.pid }), {
3213
+ writeFileSync10(tmp, JSON.stringify({ port, token: this.#token, pid: process.pid }), {
2427
3214
  encoding: "utf8",
2428
3215
  mode: 384
2429
3216
  });
2430
- renameSync7(tmp, gateConfPath());
3217
+ renameSync8(tmp, gateConfPath());
2431
3218
  return port;
2432
3219
  }
2433
3220
  async stop() {
2434
3221
  try {
2435
- rmSync4(gateConfPath(), { force: true });
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((resolve5) => s.close(() => resolve5()));
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 mkdirSync10, statSync as statSync3, truncateSync } from "fs";
2546
- import { join as join13 } from "path";
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 join13(deskbuddyHome(), "daemon.log");
3343
+ return join16(deskbuddyHome(), "daemon.log");
2550
3344
  }
2551
3345
  function rotateIfNeeded(path) {
2552
3346
  try {
2553
- if (statSync3(path).size > MAX_LOG_BYTES) truncateSync(path, 0);
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
- mkdirSync10(deskbuddyHome(), { recursive: true });
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 mkdirSync11, readFileSync as readFileSync11, renameSync as renameSync8, writeFileSync as writeFileSync10 } from "fs";
2620
- import { join as join14 } from "path";
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 join14(deskbuddyHome(), "remote");
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 join14(handleDir(), `${safe || "unknown"}.json`);
3420
+ return join17(handleDir(), `${safe || "unknown"}.json`);
2627
3421
  }
2628
3422
  function readHandle(tool) {
2629
3423
  try {
2630
- const h = JSON.parse(readFileSync11(handlePath(tool), "utf8"));
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
- mkdirSync11(handleDir(), { recursive: true });
3434
+ mkdirSync13(handleDir(), { recursive: true });
2641
3435
  const tmp = handlePath(tool) + ".tmp";
2642
- writeFileSync10(tmp, JSON.stringify(handle), "utf8");
2643
- renameSync8(tmp, handlePath(tool));
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 mkdirSync12, readFileSync as readFileSync12, renameSync as renameSync9, writeFileSync as writeFileSync11 } from "fs";
2697
- import { join as join15 } from "path";
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 join15(deskbuddyHome(), "seen.json");
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(readFileSync12(seenPath(), "utf8"));
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
- mkdirSync12(deskbuddyHome(), { recursive: true });
3533
+ mkdirSync14(deskbuddyHome(), { recursive: true });
2740
3534
  const tmp = seenPath() + ".tmp";
2741
- writeFileSync11(tmp, JSON.stringify({ ids: this.#ids }), "utf8");
2742
- renameSync9(tmp, seenPath());
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 execFileSync6 } from "child_process";
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 = execFileSync6("ps", ["-A", "-ww", "-o", "args="], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
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((resolve5) => {
3731
+ return new Promise((resolve6) => {
2938
3732
  const timer = setTimeout(() => {
2939
3733
  this.#calls.delete(id);
2940
- resolve5({ error: { message: "timeout" }, local: true });
3734
+ resolve6({ error: { message: "timeout" }, local: true });
2941
3735
  }, REQUEST_TIMEOUT_MS);
2942
3736
  timer.unref?.();
2943
- this.#calls.set(id, { resolve: resolve5, timer });
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((resolve5) => {
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) => resolve5({ sent: true, end, turnId, text, ...error ? { 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((resolve5) => {
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
- resolve5();
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 mkdirSync13, renameSync as renameSync10, rmSync as rmSync5, writeFileSync as writeFileSync12 } from "fs";
3405
- import { join as join16 } from "path";
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 join16(deskbuddyHome(), "codex-remote.json");
4201
+ return join19(deskbuddyHome(), "codex-remote.json");
3408
4202
  }
3409
4203
  function writeCodexRemote(threads, pid = process.pid, now = Date.now()) {
3410
4204
  try {
3411
- mkdirSync13(deskbuddyHome(), { recursive: true });
4205
+ mkdirSync15(deskbuddyHome(), { recursive: true });
3412
4206
  const p = codexRemotePath();
3413
- writeFileSync12(p + ".tmp", JSON.stringify({ pid, threads: [...threads], at: now }), "utf8");
3414
- renameSync10(p + ".tmp", p);
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
- rmSync5(codexRemotePath(), { force: true });
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 chmodSync3, rmSync as rmSync7 } from "fs";
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 execFileSync7 } from "child_process";
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 chmodSync2, closeSync as closeSync2, mkdirSync as mkdirSync14, openSync as openSync2, readdirSync as readdirSync3, readFileSync as readFileSync13, readSync as readSync2, renameSync as renameSync11, rmSync as rmSync6, statSync as statSync4, writeFileSync as writeFileSync13 } from "fs";
3434
- import { tmpdir } from "os";
3435
- import { join as join17 } from "path";
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 join17(home, "wake");
4231
+ return join20(home, "wake");
3438
4232
  }
3439
4233
  function marksDir(home = deskbuddyHome()) {
3440
- return join17(wakeDir(home), "marks");
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 = join17(wakeDir(home), "wake.sock");
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 join17(tmpdir(), `deskbuddy-${uid}`, `wake-${homeTag(home)}.sock`);
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
- mkdirSync14(marksDir(), { recursive: true, mode: 448 });
3469
- const file = join17(marksDir(), `${safe}.json`);
4262
+ mkdirSync16(marksDir(), { recursive: true, mode: 448 });
4263
+ const file = join20(marksDir(), `${safe}.json`);
3470
4264
  const tmp = `${file}.${process.pid}.tmp`;
3471
- writeFileSync13(tmp, JSON.stringify({ agentPid: agentPid ?? 0, daemonPid: process.pid, at: now }), { encoding: "utf8", mode: 384 });
3472
- renameSync11(tmp, file);
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
- rmSync6(join17(marksDir(), `${safe}.json`), { force: true });
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 = readdirSync3(marksDir());
4281
+ names = readdirSync4(marksDir());
3488
4282
  } catch {
3489
4283
  return;
3490
4284
  }
3491
4285
  for (const n of names) {
3492
4286
  try {
3493
- rmSync6(join17(marksDir(), n), { force: true });
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(readFileSync13(join17(marksDir(), `${safe}.json`), "utf8"));
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 join17(wakeDir(home), "secret");
4309
+ return join20(wakeDir(home), "secret");
3516
4310
  }
3517
4311
  function writeWakeSecret(hex, platform = process.platform) {
3518
4312
  const file = wakeSecretPath();
3519
- mkdirSync14(wakeDir(), { recursive: true, mode: 448 });
4313
+ mkdirSync16(wakeDir(), { recursive: true, mode: 448 });
3520
4314
  const tmp = `${file}.${process.pid}.tmp`;
3521
- writeFileSync13(tmp, hex, { encoding: "utf8", mode: 384 });
4315
+ writeFileSync14(tmp, hex, { encoding: "utf8", mode: 384 });
3522
4316
  if (platform === "win32") {
3523
4317
  try {
3524
- execFileSync7("icacls", [tmp, "/inheritance:r", "/grant:r", `${currentUserSid()}:F`], { stdio: "ignore", windowsHide: true });
4318
+ execFileSync10("icacls", [tmp, "/inheritance:r", "/grant:r", `${currentUserSid()}:F`], { stdio: "ignore", windowsHide: true });
3525
4319
  } catch {
3526
- renameSync11(tmp, file);
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
- chmodSync2(tmp, 384);
4324
+ chmodSync3(tmp, 384);
3531
4325
  }
3532
- renameSync11(tmp, file);
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
- execFileSync7("icacls", [file, "/inheritance:r", "/grant:r", `${currentUserSid()}:F`], { stdio: "ignore", windowsHide: true });
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 = execFileSync7("whoami", ["/user", "/fo", "csv", "/nh"], { encoding: "utf8", windowsHide: true });
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 = statSync4(file).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 = openSync2(file, "r");
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
- closeSync2(fd);
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((resolve5) => {
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
- rmSync7(path, { force: true });
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
- resolve5(null);
4532
+ resolve6(null);
3739
4533
  });
3740
4534
  server.listen(path, () => {
3741
4535
  restore();
3742
4536
  if (platform !== "win32") {
3743
4537
  try {
3744
- chmodSync3(path, 384);
4538
+ chmodSync4(path, 384);
3745
4539
  } catch {
3746
4540
  }
3747
4541
  }
3748
4542
  server.on("error", () => {
3749
4543
  });
3750
- resolve5(server);
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
- rmSync7(path, { force: true });
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((resolve5) => {
4562
+ return new Promise((resolve6) => {
3769
4563
  if (!secret) {
3770
- resolve5("fallback");
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
- resolve5(v);
4577
+ resolve6(v);
3784
4578
  };
3785
4579
  let sock;
3786
4580
  try {
3787
4581
  sock = connect(path);
3788
4582
  } catch {
3789
- resolve5("fallback");
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 chmodSync4, mkdirSync as mkdirSync15, readdirSync as readdirSync4, readFileSync as readFileSync14, renameSync as renameSync12, rmSync as rmSync8, statSync as statSync5, writeFileSync as writeFileSync14 } from "fs";
3912
- import { tmpdir as tmpdir2 } from "os";
3913
- import { join as join18 } from "path";
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 join18(deskbuddyHome(), "windows");
4710
+ return join21(deskbuddyHome(), "windows");
3917
4711
  }
3918
4712
  function bySessionDir() {
3919
- return join18(windowsDir(), "by-session");
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
- mkdirSync15(dir, { recursive: true, mode: 448 });
4719
+ mkdirSync17(dir, { recursive: true, mode: 448 });
3926
4720
  }
3927
4721
  function atomicWrite(file, text) {
3928
4722
  const tmp = `${file}.${process.pid}.tmp`;
3929
- writeFileSync14(tmp, text, { encoding: "utf8", mode: 384 });
3930
- renameSync12(tmp, file);
4723
+ writeFileSync15(tmp, text, { encoding: "utf8", mode: 384 });
4724
+ renameSync13(tmp, file);
3931
4725
  }
3932
4726
  function windowFile(windowId) {
3933
- return join18(windowsDir(), `${windowId}.json`);
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(readFileSync14(windowFile(windowId), "utf8"));
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
- rmSync8(f, { force: true });
4752
+ rmSync10(f, { force: true });
3959
4753
  } catch {
3960
4754
  }
3961
4755
  }
3962
4756
  let names = [];
3963
4757
  try {
3964
- names = readdirSync4(bySessionDir());
4758
+ names = readdirSync5(bySessionDir());
3965
4759
  } catch {
3966
4760
  return;
3967
4761
  }
3968
4762
  for (const n of names) {
3969
- const f = join18(bySessionDir(), n);
4763
+ const f = join21(bySessionDir(), n);
3970
4764
  try {
3971
- if (readFileSync14(f, "utf8").trim() === windowId) rmSync8(f, { force: true });
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 = readFileSync14(join18(bySessionDir(), safe), "utf8").trim();
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 join18(windowsDir(), `${windowId}.last`);
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(readFileSync14(lastEventFile(windowId), "utf8"));
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 = join18(home, "win", name);
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 join18(tmpdir2(), `deskbuddy-${uid}`, name);
4798
+ return join21(tmpdir3(), `deskbuddy-${uid}`, name);
4005
4799
  }
4006
4800
  function ensureChannelDir(sockPath) {
4007
- const dir = join18(sockPath, "..");
4801
+ const dir = join21(sockPath, "..");
4008
4802
  try {
4009
- mkdirSync15(dir, { recursive: true, mode: 448 });
4010
- const st = statSync5(dir);
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
- chmodSync4(dir, 448);
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 mkdirSync16, readFileSync as readFileSync15, renameSync as renameSync13, rmSync as rmSync9, writeFileSync as writeFileSync15 } from "fs";
4030
- import { join as join19 } from "path";
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 join19(home, "launch");
4828
+ return join22(home, "launch");
4035
4829
  }
4036
4830
  function planFile(runId) {
4037
- return join19(launchDir(), `${runId}.json`);
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
- mkdirSync16(launchDir(), { recursive: true, mode: 448 });
4836
+ mkdirSync18(launchDir(), { recursive: true, mode: 448 });
4043
4837
  const tmp = `${planFile(runId)}.${process.pid}.tmp`;
4044
- writeFileSync15(tmp, JSON.stringify(plan), { encoding: "utf8", mode: 384 });
4838
+ writeFileSync16(tmp, JSON.stringify(plan), { encoding: "utf8", mode: 384 });
4045
4839
  restrictToUser(tmp);
4046
- renameSync13(tmp, planFile(runId));
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 = join19(launchDir(), `${runId}.${process.pid}.claimed`);
4844
+ const claimed = join22(launchDir(), `${runId}.${process.pid}.claimed`);
4051
4845
  try {
4052
- renameSync13(planFile(runId), claimed);
4846
+ renameSync14(planFile(runId), claimed);
4053
4847
  } catch {
4054
4848
  return null;
4055
4849
  }
4056
4850
  try {
4057
- const p = JSON.parse(readFileSync15(claimed, "utf8"));
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
- rmSync9(claimed, { force: true });
4859
+ rmSync11(claimed, { force: true });
4066
4860
  } catch {
4067
4861
  }
4068
4862
  }
4069
4863
  }
4070
4864
  function launchPending(runId) {
4071
4865
  try {
4072
- readFileSync15(planFile(runId));
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 execFile3, execFileSync as execFileSync8, spawn as spawn2 } from "child_process";
4081
- import { chmodSync as chmodSync5, mkdirSync as mkdirSync17, rmSync as rmSync10, writeFileSync as writeFileSync16 } from "fs";
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 join20 } from "path";
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 execFileSync8("launchctl", ["managername"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout: 3e3 });
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 execFileSync8("stat", ["-f", "%Su", "/dev/console"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout: 3e3 });
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 = execFileSync8("tasklist", ["/FI", `PID eq ${process.pid}`, "/FO", "CSV", "/NH"], {
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((resolve5, reject) => {
4170
- execFile3(file, args, { windowsHide: true, timeout: 15e3 }, (err) => err ? reject(err) : resolve5());
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 = spawn2(file, args, { detached: true, stdio: "ignore", shell: false, windowsHide: false });
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
- mkdirSync17(launchDir(t.home), { recursive: true, mode: 448 });
4183
- const file = join20(launchDir(t.home), `${t.runId}.command`);
4184
- writeFileSync16(file, macCommandScript(t), { encoding: "utf8", mode: 448 });
4185
- chmodSync5(file, 448);
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
- rmSync10(join20(launchDir(home), `${runId}.command`), { force: true });
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 rmSync11 } from "fs";
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
- rmSync11(path, { force: true });
5299
+ rmSync13(path, { force: true });
4498
5300
  } catch {
4499
5301
  }
4500
5302
  }
4501
- return new Promise((resolve5) => {
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
- resolve5(null);
5389
+ resolve6(null);
4588
5390
  });
4589
5391
  server.listen(path, () => {
4590
5392
  restore();
4591
5393
  server.on("error", () => {
4592
5394
  });
4593
- resolve5(server);
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()) && existsSync10(ocPluginModule(installedHookPath()));
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/${basename6(cwd)} len=${cmd.text.length}`);
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 (!existsSync10(this.#deps.nodePath) || !existsSync10(this.#deps.gateScript)) {
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
- writeFileSync17(pidFilePath(), String(process.pid), "utf8");
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
- rmSync12(pidFilePath(), { force: true });
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 mkdirSync18, readFileSync as readFileSync16, writeFileSync as writeFileSync18 } from "fs";
5546
- import { join as join21 } from "path";
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
- // 原生只有 /rename 手动设,多数用户是空的,取不到就往下回落
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
- titleSources: ["git-branch", "project"],
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
- titleSources: ["git-branch", "project"],
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 自带的会话标题是模型对提问的摘要,违反隐私约束(spec §6.4),不用
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 join21(deskbuddyHome(), "tool-registry.json");
6517
+ return join24(deskbuddyHome(), "tool-registry.json");
5625
6518
  }
5626
6519
  function loadRegistry() {
5627
6520
  try {
5628
- return JSON.parse(readFileSync16(registryCachePath(), "utf8"));
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
- mkdirSync18(deskbuddyHome(), { recursive: true });
5637
- writeFileSync18(registryCachePath(), JSON.stringify(registry, null, 2), "utf8");
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 chmodSync6, existsSync as existsSync11, mkdirSync as mkdirSync19, readFileSync as readFileSync17, renameSync as renameSync14, rmSync as rmSync13, statSync as statSync6, writeFileSync as writeFileSync19 } from "fs";
5644
- import { dirname as dirname6, join as join22 } from "path";
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 (!existsSync11(path)) return {};
5655
- const raw = readFileSync17(path, "utf8");
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
- mkdirSync19(dirname6(path), { recursive: true });
6557
+ mkdirSync21(dirname9(path), { recursive: true });
5665
6558
  let mode;
5666
6559
  try {
5667
- mode = statSync6(path).mode & 511;
6560
+ mode = statSync7(path).mode & 511;
5668
6561
  } catch {
5669
6562
  mode = void 0;
5670
6563
  }
5671
6564
  const tmp = path + ".deskbuddy.tmp";
5672
- writeFileSync19(tmp, JSON.stringify(settings, null, 2), "utf8");
5673
- if (mode !== void 0) chmodSync6(tmp, mode);
5674
- renameSync14(tmp, path);
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 = join22(dirname6(hookPath), "gate.cjs");
5778
- const wakePath = join22(dirname6(hookPath), "wake.cjs");
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
- rmSync13(join22(dirname6(hookPath), "wake", "tokens"), { recursive: true, force: true });
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 chmodSync7, existsSync as existsSync12, mkdirSync as mkdirSync20, readFileSync as readFileSync18, renameSync as renameSync15, statSync as statSync7, writeFileSync as writeFileSync20 } from "fs";
5799
- import { dirname as dirname7, join as join23 } from "path";
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 join23(codexHome(), "config.toml");
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
- mkdirSync20(dirname7(path), { recursive: true });
6724
+ mkdirSync22(dirname10(path), { recursive: true });
5832
6725
  let mode;
5833
6726
  try {
5834
- mode = statSync7(path).mode & 511;
6727
+ mode = statSync8(path).mode & 511;
5835
6728
  } catch {
5836
6729
  mode = void 0;
5837
6730
  }
5838
6731
  const tmp = path + ".deskbuddy.tmp";
5839
- writeFileSync20(tmp, content, "utf8");
5840
- if (mode !== void 0) chmodSync7(tmp, mode);
5841
- renameSync15(tmp, path);
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 = existsSync12(configPath2) ? readFileSync18(configPath2, "utf8") : "";
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 (!existsSync12(configPath2)) return;
5860
- const raw = readFileSync18(configPath2, "utf8");
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 existsSync13, readFileSync as readFileSync19 } from "fs";
6767
+ import { existsSync as existsSync16, readFileSync as readFileSync20 } from "fs";
5875
6768
  function fileContains(path, needle) {
5876
6769
  try {
5877
- if (!existsSync13(path)) return false;
5878
- return readFileSync19(path, "utf8").includes(needle);
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 dirname9, join as join26 } from "path";
6845
+ import { dirname as dirname11, join as join28 } from "path";
5953
6846
 
5954
6847
  // src/wrap/shim.ts
5955
- import { execFileSync as execFileSync9 } from "child_process";
5956
- import { chmodSync as chmodSync8, existsSync as existsSync14, mkdirSync as mkdirSync21, readdirSync as readdirSync5, readFileSync as readFileSync20, rmdirSync, rmSync as rmSync14, writeFileSync as writeFileSync21 } from "fs";
5957
- import { homedir as homedir5 } from "os";
5958
- import { delimiter as delimiter2, join as join24, resolve as resolve2 } from "path";
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 join24(deskbuddyHome(), "bin");
6861
+ return join27(deskbuddyHome(), "bin");
5969
6862
  }
5970
6863
  function shimFile(binDir, command, platform = process.platform) {
5971
- return join24(binDir, platform === "win32" ? `${command}.cmd` : command);
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 = resolve2(rcHome, ".deskbuddy", "bin") === resolve2(binDir);
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 readFileSync20(p).toString("latin1");
6993
+ return readFileSync21(p).toString("latin1");
6101
6994
  }
6102
6995
  function writeRc(p, latin1) {
6103
- writeFileSync21(p, Buffer.from(latin1, "latin1"));
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) => execFileSync9("powershell.exe", ["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command", script], {
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 : homedir5());
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
- mkdirSync21(e.binDir, { recursive: true });
6173
- writeFileSync21(file, cmdScript(tool, command, node, cli, bin), "utf8");
7065
+ mkdirSync23(e.binDir, { recursive: true });
7066
+ writeFileSync22(file, cmdScript(tool, command, node, cli, bin), "utf8");
6174
7067
  } else {
6175
- mkdirSync21(e.binDir, { recursive: true, mode: 448 });
6176
- writeFileSync21(file, shScript(tool, command, e.nodePath, e.cliPath, e.binDir), { encoding: "utf8", mode: 493 });
6177
- chmodSync8(file, 493);
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 = join24(e.rcHome, name);
6192
- if (!existsSync14(rc)) continue;
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 = readdirSync5(binDir);
7105
+ names = readdirSync6(binDir);
6213
7106
  } catch {
6214
7107
  return [];
6215
7108
  }
6216
7109
  return names.filter((n) => {
6217
7110
  try {
6218
- return readFileSync20(join24(binDir, n), "utf8").includes(SHIM_MARKER);
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 (existsSync14(file) && readFileSync20(file, "utf8").includes(SHIM_MARKER)) {
6231
- rmSync14(file, { force: true });
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 = join24(e.rcHome, name);
6250
- if (!existsSync14(rc)) continue;
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 (existsSync14(e.binDir) && readdirSync5(e.binDir).length === 0) rmdirSync(e.binDir);
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 = readFileSync20(file, "utf8").includes(SHIM_MARKER);
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, join26(dirname9(hookPath), "gate.cjs"), tool)) {
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, join26(dirname9(hookPath), "wake.cjs"), tool)) {
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 mkdirSync22, rmSync as rmSync15, writeFileSync as writeFileSync22 } from "fs";
6519
- import { join as join27 } from "path";
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 existsSync16 } from "fs";
6523
- import { dirname as dirname10 } from "path";
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 dirname10(workbuddySettingsPath());
7360
+ return dirname12(workbuddySettingsPath());
6533
7361
  case "qoder":
6534
- return dirname10(qoderSettingsPath());
7362
+ return dirname12(qoderSettingsPath());
6535
7363
  case "opencode":
6536
- return dirname10(dirname10(opencodePluginPath()));
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: existsSync16,
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 existsSync17, statSync as statSync9 } from "fs";
7392
+ import { existsSync as existsSync19, statSync as statSync9 } from "fs";
6565
7393
  import { createInterface } from "readline";
6566
- import { homedir as homedir6 } from "os";
6567
- import { isAbsolute as isAbsolute2, resolve as resolve3 } from "path";
6568
- function normalizePath(raw, home = homedir6()) {
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("~\\") ? resolve3(home, s.slice(2)) : s;
6572
- const abs = isAbsolute2(expanded) ? expanded : resolve3(process.cwd(), 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 (!existsSync17(abs)) return "missing";
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 norm2 = raw.replace(/\/+$/, "");
6751
- return norm2 === PROD_API_BASE || norm2 === TEST_API_BASE || env.DESKBUDDY_ALLOW_CUSTOM_API === "1";
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 = join27(dir, `.deskbuddy-probe-${process.pid}`);
7610
+ const probe = join29(dir, `.deskbuddy-probe-${process.pid}`);
6783
7611
  try {
6784
- mkdirSync22(dir, { recursive: true });
6785
- writeFileSync22(probe, "");
6786
- rmSync15(probe, { force: true });
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({ execPath: process.execPath, cliPath: defaultCliPath() })
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 existsSync18, rmSync as rmSync16 } from "fs";
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((resolve5) => {
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
- resolve5(/^(y|yes)$/i.test(answer.trim()));
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 (existsSync18(home)) {
6947
- rmSync16(home, { recursive: true, force: true });
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 mkdirSync24, statSync as statSync11, truncateSync as truncateSync3 } from "fs";
7109
- import { join as join29 } from "path";
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 spawn3 } from "child_process";
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 dirname11, resolve as resolve4 } from "path";
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 norm(p, win) {
7411
- const r = resolve4(p).replace(/[\\/]+$/, "");
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([norm(skip, win), norm(real(skip), win)]);
7424
- const inSkip = (file) => skipKeys.has(norm(dirname11(file), win)) || skipKeys.has(norm(dirname11(real(file)), win));
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 = spawn3(real2.file, [...real2.prefixArgs, ...args], {
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 spawn4 } from "child_process";
7504
- import { closeSync as closeSync3, mkdirSync as mkdirSync23, openSync as openSync3, readdirSync as readdirSync6, readFileSync as readFileSync21, rmSync as rmSync17, statSync as statSync10, truncateSync as truncateSync2, writeFileSync as writeFileSync23, renameSync as renameSync16 } from "fs";
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 join28 } from "path";
8360
+ import { join as join30 } from "path";
7508
8361
 
7509
8362
  // src/core/codexServer.ts
7510
- import { execFileSync as execFileSync10 } from "child_process";
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 = execFileSync10("ps", ["-ww", "-o", "args=", "-p", String(pid)], {
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 join28(deskbuddyHome(), "codex-app-server.log");
8528
+ return join30(deskbuddyHome(), "codex-app-server.log");
7676
8529
  }
7677
8530
  function windowsDir2() {
7678
- return join28(deskbuddyHome(), "codex-windows");
8531
+ return join30(deskbuddyHome(), "codex-windows");
7679
8532
  }
7680
- function pidAlive(pid) {
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(readFileSync21(pidFile, "utf8").trim());
8544
+ pid = Number(readFileSync22(pidFile, "utf8").trim());
7692
8545
  } catch {
7693
8546
  return false;
7694
8547
  }
7695
- if (!Number.isInteger(pid) || pid <= 0 || !pidAlive(pid)) return false;
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((resolve5) => {
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
- resolve5((res.statusCode ?? 0) >= 200 && (res.statusCode ?? 0) < 300);
8557
+ resolve6((res.statusCode ?? 0) >= 200 && (res.statusCode ?? 0) < 300);
7705
8558
  });
7706
8559
  req2.on("timeout", () => req2.destroy());
7707
- req2.on("error", () => resolve5(false));
8560
+ req2.on("error", () => resolve6(false));
7708
8561
  req2.end();
7709
8562
  });
7710
8563
  }
7711
8564
  function freePort() {
7712
- return new Promise((resolve5, reject) => {
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 ? resolve5(port) : reject(new Error("no 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
- mkdirSync23(deskbuddyHome(), { recursive: true });
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 = openSync3(logPath2, "a");
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 = spawn4(real2.file, [...real2.prefixArgs, "app-server", "--listen", url], {
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
- closeSync3(fd);
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 join28(windowsDir2(), `${rec.server}.json`);
8612
+ return join30(windowsDir2(), `${rec.server}.json`);
7760
8613
  }
7761
8614
  function writeRecord(rec) {
7762
8615
  try {
7763
- mkdirSync23(windowsDir2(), { recursive: true, mode: 448 });
8616
+ mkdirSync25(windowsDir2(), { recursive: true, mode: 448 });
7764
8617
  const p = recordPath(rec);
7765
- writeFileSync23(p + ".tmp", JSON.stringify(rec), { encoding: "utf8", mode: 384 });
7766
- renameSync16(p + ".tmp", p);
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
- rmSync17(recordPath(rec), { force: true });
8625
+ rmSync19(recordPath(rec), { force: true });
7773
8626
  } catch {
7774
8627
  }
7775
8628
  }
7776
- function reapWindows(dir = windowsDir2(), alive = pidAlive, args = processArgs, kill = killServer) {
8629
+ function reapWindows(dir = windowsDir2(), alive = pidAlive2, args = processArgs, kill = killServer) {
7777
8630
  let names = [];
7778
8631
  try {
7779
- names = readdirSync6(dir).filter((n) => n.endsWith(".json"));
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 = join28(dir, n);
8637
+ const p = join30(dir, n);
7785
8638
  let rec;
7786
8639
  try {
7787
- rec = JSON.parse(readFileSync21(p, "utf8"));
8640
+ rec = JSON.parse(readFileSync22(p, "utf8"));
7788
8641
  } catch {
7789
- rmSync17(p, { force: true });
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
- rmSync17(p, { force: true });
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
- rmSync17(p, { force: true });
8654
+ rmSync19(p, { force: true });
7802
8655
  }
7803
8656
  }
7804
8657
  function launchLog(line) {
7805
8658
  try {
7806
- mkdirSync23(deskbuddyHome(), { recursive: true });
7807
- writeFileSync23(join28(deskbuddyHome(), "wrap.log"), `${(/* @__PURE__ */ new Date()).toISOString()} [codex ${process.pid}] ${line}
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 = join29(deskbuddyHome(), "wrap.log");
7848
- mkdirSync24(deskbuddyHome(), { recursive: true });
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((resolve5) => {
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(() => resolve5(code), 150);
8190
- else resolve5(code);
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\uFF09
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":