deskbuddy 0.2.0-beta.33 → 0.2.0-beta.35

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.
@@ -162,6 +162,7 @@ function loadConfig() {
162
162
  dumateCdpPort: portOrUndefined(parsed.dumateCdpPort),
163
163
  dumateShortcuts: typeof parsed.dumateShortcuts === "boolean" ? parsed.dumateShortcuts : void 0,
164
164
  qoderShortcuts: typeof parsed.qoderShortcuts === "boolean" ? parsed.qoderShortcuts : void 0,
165
+ qoderLaunchPort: typeof parsed.qoderLaunchPort === "boolean" ? parsed.qoderLaunchPort : void 0,
165
166
  cozeApp: typeof parsed.cozeApp === "boolean" ? parsed.cozeApp : void 0,
166
167
  cozeCdpPort: portOrUndefined(parsed.cozeCdpPort)
167
168
  };
@@ -2570,8 +2571,11 @@ function renderAmendmentQuoted(v) {
2570
2571
  if (!Array.isArray(v) || v.length === 0 || v.some((x) => typeof x !== "string" || x === "")) return null;
2571
2572
  const out = [];
2572
2573
  for (const x of v) {
2573
- if (/["\u0000-\u001f\u007f-\u009f​-‏‪-‮⁦-⁩]/.test(x)) return null;
2574
- out.push(/^[A-Za-z0-9_@%+=,./-]+$/.test(x) ? x : `"${x}"`);
2574
+ if (/[\u0000-\u001f\u007f-\u009f​-‏‪-‮⁦-⁩]/.test(x)) return null;
2575
+ if (/^[A-Za-z0-9_@%+=,./-]+$/.test(x)) out.push(x);
2576
+ else if (!x.includes('"')) out.push(`"${x}"`);
2577
+ else if (!x.includes("'")) out.push(`'${x}'`);
2578
+ else out.push(`"${x.replace(/"/g, '\\"')}"`);
2575
2579
  }
2576
2580
  return out.join(" ");
2577
2581
  }
@@ -5604,10 +5608,13 @@ var defaultFetchJson = async (url, timeoutMs) => {
5604
5608
  clearTimeout(t);
5605
5609
  }
5606
5610
  };
5607
- function pickMainTarget(list) {
5608
- if (!Array.isArray(list)) return null;
5611
+ function mainTargets(list) {
5612
+ if (!Array.isArray(list)) return [];
5609
5613
  const pages = list.filter((t) => t && t.type === "page" && typeof t.webSocketDebuggerUrl === "string" && !/^devtools:/.test(t.url ?? ""));
5610
- return pages.find((t) => /renderer\/index\.html/.test(t.url ?? "")) ?? null;
5614
+ return pages.filter((t) => /renderer\/index\.html/.test(t.url ?? ""));
5615
+ }
5616
+ function pickMainTarget(list) {
5617
+ return mainTargets(list)[0] ?? null;
5611
5618
  }
5612
5619
  function loopbackWsUrl(wsUrl, port) {
5613
5620
  try {
@@ -5620,16 +5627,18 @@ function loopbackWsUrl(wsUrl, port) {
5620
5627
  return null;
5621
5628
  }
5622
5629
  }
5623
- async function connectCdp(port, d = {}) {
5624
- const fetchJson = d.fetchJson ?? defaultFetchJson;
5625
- const target = (d.pick ?? pickMainTarget)(await fetchJson(`http://127.0.0.1:${port}/json/list`, 3e3));
5626
- if (!target?.webSocketDebuggerUrl) throw new Error("\u627E\u4E0D\u5230 App \u4E3B\u7A97\u53E3");
5630
+ var HAS_SDK_EXPR = "!!(globalThis.wb && globalThis.wb.conversations)";
5631
+ function pageName(url) {
5632
+ return String(url || "").split(/[?#]/)[0].split(/[\\/]/).slice(-2).join("/");
5633
+ }
5634
+ function openWs(target, port, d, info) {
5635
+ if (!target.webSocketDebuggerUrl) return Promise.reject(new Error("\u627E\u4E0D\u5230 App \u4E3B\u7A97\u53E3"));
5627
5636
  const url = loopbackWsUrl(target.webSocketDebuggerUrl, port);
5628
- if (!url) throw new Error("\u4E3B\u7A97\u53E3\u7684\u8C03\u8BD5\u5730\u5740\u4E0D\u5BF9");
5637
+ if (!url) return Promise.reject(new Error("\u4E3B\u7A97\u53E3\u7684\u8C03\u8BD5\u5730\u5740\u4E0D\u5BF9"));
5629
5638
  const ws = (d.ws ?? ((u) => new WebSocket(u, { handshakeTimeout: 5e3 })))(url);
5630
- return await new Promise((resolve6, reject) => {
5639
+ return new Promise((resolve6, reject) => {
5631
5640
  let opened = false;
5632
- const client = new WsCdpClient(ws);
5641
+ const client = new WsCdpClient(ws, info);
5633
5642
  ws.on("open", () => {
5634
5643
  opened = true;
5635
5644
  resolve6(client);
@@ -5644,13 +5653,53 @@ async function connectCdp(port, d = {}) {
5644
5653
  ws.on("message", (data) => client.onMessage(data));
5645
5654
  });
5646
5655
  }
5656
+ async function connectCdp(port, d = {}) {
5657
+ const fetchJson = d.fetchJson ?? defaultFetchJson;
5658
+ const list = await fetchJson(`http://127.0.0.1:${port}/json/list`, 3e3);
5659
+ if (!d.pickSdk) {
5660
+ const target = (d.pick ?? pickMainTarget)(list);
5661
+ if (!target?.webSocketDebuggerUrl) throw new Error("\u627E\u4E0D\u5230 App \u4E3B\u7A97\u53E3");
5662
+ return openWs(target, port, d);
5663
+ }
5664
+ const all = mainTargets(list);
5665
+ if (all.length === 0) throw new Error("\u627E\u4E0D\u5230 App \u4E3B\u7A97\u53E3");
5666
+ let ua = "";
5667
+ try {
5668
+ const v = await fetchJson(`http://127.0.0.1:${port}/json/version`, 2e3);
5669
+ const m = typeof v?.["User-Agent"] === "string" ? /\bWorkBuddy\/[\w.-]+/.exec(v["User-Agent"]) : null;
5670
+ ua = [m?.[0], typeof v?.Browser === "string" ? v.Browser : ""].filter(Boolean).join(" ");
5671
+ } catch {
5672
+ ua = "";
5673
+ }
5674
+ const infoOf = (i, sdk) => ({ url: pageName(String(all[i].url ?? "")), title: "", candidates: all.length, index: i, sdk, ...ua ? { ua } : {} });
5675
+ if (all.length === 1) return openWs(all[0], port, d, infoOf(0, null));
5676
+ for (let i = 0; i < all.length; i++) {
5677
+ let c;
5678
+ try {
5679
+ c = await openWs(all[i], port, d, infoOf(i, true));
5680
+ } catch {
5681
+ continue;
5682
+ }
5683
+ let has = false;
5684
+ try {
5685
+ has = await c.evaluate(HAS_SDK_EXPR, 2e3) === true;
5686
+ } catch {
5687
+ has = false;
5688
+ }
5689
+ if (has) return c;
5690
+ c.close();
5691
+ }
5692
+ return openWs(all[0], port, d, infoOf(0, false));
5693
+ }
5647
5694
  var WsCdpClient = class {
5648
5695
  #ws;
5696
+ target;
5649
5697
  #id = 0;
5650
5698
  #closed = false;
5651
5699
  #waiting = /* @__PURE__ */ new Map();
5652
- constructor(ws) {
5700
+ constructor(ws, target) {
5653
5701
  this.#ws = ws;
5702
+ if (target) this.target = target;
5654
5703
  }
5655
5704
  get closed() {
5656
5705
  return this.#closed;
@@ -5854,6 +5903,7 @@ var HungTracker = class {
5854
5903
  }
5855
5904
  };
5856
5905
  var APP_PORT_HUNG_REASON = "app_port_unresponsive";
5906
+ var STARTUP_GRACE_MS = 90 * 1e3;
5857
5907
  var FORCE_QUIT_WAIT_MS = 10 * 1e3;
5858
5908
  function isTimeoutError(e) {
5859
5909
  const err = e;
@@ -6001,6 +6051,16 @@ var CdpKeeper = class {
6001
6051
  #hung = new HungTracker();
6002
6052
  /** 这一段卡死里「暂不重启」的原因记过哪条(同一条只记一次) */
6003
6053
  #hungNote = "";
6054
+ /** 启动宽限期到什么时候(0 = 不在宽限期) */
6055
+ #graceUntil = 0;
6056
+ /** 宽限期内超时记过一行没有(每段宽限只记一次) */
6057
+ #graceNoted = false;
6058
+ /** 上一轮端口检查的结果(判「刚从不通变成通」) */
6059
+ #lastPort = null;
6060
+ /** 这一段端口不通里「不重启」的原因记过哪条(同一条只记一次;端口通了 / 转成卡死时清掉。258:原先这些分支一行不记) */
6061
+ #downNote = "";
6062
+ /** 最近一轮端口不通的细分(日志 / 设备任务的等待原因用):fail = 没在监听或回包不是 WorkBuddy;timeout = 超时且监听者不是本机回环的 WorkBuddy */
6063
+ #downWhy = "";
6004
6064
  constructor(deps) {
6005
6065
  this.#d = deps;
6006
6066
  }
@@ -6141,6 +6201,30 @@ var CdpKeeper = class {
6141
6201
  this.#hung.clear();
6142
6202
  this.#hungNote = "";
6143
6203
  }
6204
+ /** 进入启动宽限期(拉起 / 重启 / 端口刚从不通变成通) */
6205
+ #startGrace(now) {
6206
+ this.#graceUntil = now + STARTUP_GRACE_MS;
6207
+ this.#graceNoted = false;
6208
+ }
6209
+ #inGrace(now) {
6210
+ return this.#graceUntil > 0 && now < this.#graceUntil;
6211
+ }
6212
+ /** 宽限期内超时:记一行(每段一次) */
6213
+ #noteGrace() {
6214
+ if (this.#graceNoted) return;
6215
+ this.#graceNoted = true;
6216
+ this.#d.log(`WorkBuddy \u8C03\u8BD5\u7AEF\u53E3 ${this.#d.port()} \u8D85\u65F6\uFF0C\u4F46 App \u521A\u542F\u52A8 / \u521A\u6062\u590D\uFF08${STARTUP_GRACE_MS / 1e3} \u79D2\u5BBD\u9650\u671F\u5185\uFF09\uFF0C\u5148\u4E0D\u5224\u65E0\u54CD\u5E94`);
6217
+ }
6218
+ /** 最近一轮端口不通的细分(没不通过返回 '') */
6219
+ get downWhy() {
6220
+ return this.#downWhy;
6221
+ }
6222
+ /** 端口不通期间不重启的原因:同一段里同一条只记一次 */
6223
+ #noteDown(why) {
6224
+ if (this.#downNote === why) return;
6225
+ this.#downNote = why;
6226
+ this.#d.log(`WorkBuddy \u8C03\u8BD5\u7AEF\u53E3 ${this.#d.port()} \u4E0D\u901A\uFF0C${why}`);
6227
+ }
6144
6228
  /** 卡死期间暂不重启的原因:同一段卡死里同一条只记一次 */
6145
6229
  #noteHung(why) {
6146
6230
  if (this.#hungNote === why) return;
@@ -6182,19 +6266,34 @@ var CdpKeeper = class {
6182
6266
  await this.ensureEnv().catch(() => {
6183
6267
  });
6184
6268
  const pr = await this.#probe();
6269
+ const st = await this.#tickInner(pr);
6270
+ this.#lastPort = st;
6271
+ return st;
6272
+ }
6273
+ async #tickInner(pr) {
6185
6274
  if (pr === "ok") {
6275
+ if (this.#lastPort === "down") this.#startGrace(this.#now());
6186
6276
  if (this.#hung.since) this.#d.log(`WorkBuddy \u8C03\u8BD5\u7AEF\u53E3 ${this.#d.port()} \u6062\u590D\u54CD\u5E94`);
6187
6277
  this.#markHung(false, 0);
6188
6278
  this.#downRounds = 0;
6279
+ this.#downNote = "";
6280
+ this.#downWhy = "";
6189
6281
  const ok = await this.ownerOk().catch(() => false);
6190
6282
  if (ok) await this.#rememberExe().catch(() => {
6191
6283
  });
6192
6284
  return ok ? "up" : "untrusted";
6193
6285
  }
6286
+ if (pr === "timeout" && this.#inGrace(this.#now()) && await this.ownerOk().catch(() => false)) {
6287
+ this.#noteGrace();
6288
+ this.#downRounds = 0;
6289
+ return this.#lastPort === "up" ? "up" : "down";
6290
+ }
6194
6291
  if (await this.#isHung(pr)) {
6195
6292
  const now = this.#now();
6196
6293
  this.#markHung(true, now);
6197
6294
  this.#downRounds = 0;
6295
+ this.#downNote = "";
6296
+ this.#downWhy = "";
6198
6297
  if (this.#hung.confirmed(now)) {
6199
6298
  await this.#maybeRestart(true).catch((e) => this.#d.log(`WorkBuddy \u8C03\u8BD5\u7AEF\u53E3\uFF1A\u91CD\u542F\u51FA\u9519 ${e?.message ?? e}`));
6200
6299
  }
@@ -6202,6 +6301,7 @@ var CdpKeeper = class {
6202
6301
  }
6203
6302
  this.#markHung(false, 0);
6204
6303
  this.#downRounds++;
6304
+ this.#downWhy = pr === "timeout" ? "timeout" : "fail";
6205
6305
  await this.#maybeRestart().catch((e) => this.#d.log(`WorkBuddy \u8C03\u8BD5\u7AEF\u53E3\uFF1A\u91CD\u542F\u51FA\u9519 ${e?.message ?? e}`));
6206
6306
  return "down";
6207
6307
  }
@@ -6222,18 +6322,27 @@ var CdpKeeper = class {
6222
6322
  }
6223
6323
  if (this.#restarts >= RESTART_DAILY_MAX) {
6224
6324
  if (hung) this.#noteHung(`\u4ECA\u5929\u5DF2\u91CD\u542F ${RESTART_DAILY_MAX} \u6B21\uFF0C\u4E0D\u518D\u91CD\u542F\uFF1B\u8BF7\u5728\u7535\u8111\u4E0A\u91CD\u5F00 WorkBuddy`);
6325
+ else this.#noteDown(`\u4ECA\u5929\u5DF2\u91CD\u542F ${RESTART_DAILY_MAX} \u6B21\uFF0C\u4E0D\u518D\u5E26\u7AEF\u53E3\u91CD\u542F`);
6326
+ return;
6327
+ }
6328
+ if (this.#lastRestart && now - this.#lastRestart < RESTART_GAP_MS) {
6329
+ if (!hung) this.#noteDown("30 \u5206\u949F\u5185\u521A\u91CD\u542F\u8FC7\uFF0C\u5148\u4E0D\u518D\u91CD\u542F");
6225
6330
  return;
6226
6331
  }
6227
- if (this.#lastRestart && now - this.#lastRestart < RESTART_GAP_MS) return;
6228
6332
  if (this.#lastWakeLaunch && now - this.#lastWakeLaunch < WAKE_LAUNCH_GAP_MS) return;
6229
6333
  const running = await (this.#d.appRunning ?? defaultAppRunning)();
6230
- if (!running) return;
6334
+ if (!running) {
6335
+ if (!hung) this.#noteDown("WorkBuddy App \u6CA1\u5728\u8FD0\u884C");
6336
+ return;
6337
+ }
6231
6338
  if (this.#isBusy(now)) {
6232
6339
  if (hung) this.#noteHung("\u4E14\u6709\u4EFB\u52A1\u5728\u8DD1 / \u6709\u7B49\u8BBE\u5907\u7684\u5361\uFF0C\u4E0D\u91CD\u542F\uFF1B\u8BF7\u5728\u7535\u8111\u4E0A\u91CD\u5F00 WorkBuddy");
6340
+ else this.#noteDown("App \u5728\u8FD0\u884C\u4F46\u6CA1\u5E26\u8C03\u8BD5\u7AEF\u53E3\uFF08\u53EF\u80FD\u662F\u8BBE\u7F6E\u73AF\u5883\u53D8\u91CF\u4E4B\u524D\u6253\u5F00\u7684\uFF09\uFF0C\u4E14\u6709\u4EFB\u52A1\u5728\u8DD1 / \u6709\u7B49\u8BBE\u5907\u7684\u5361\uFF0C\u4E0D\u91CD\u542F");
6233
6341
  return;
6234
6342
  }
6235
6343
  if (!await this.#userAway()) {
6236
6344
  if (hung) this.#noteHung("\u7528\u6237\u5728\u7535\u8111\u524D\uFF0C\u4E0D\u6253\u65AD\uFF1B\u8BF7\u5728\u7535\u8111\u4E0A\u91CD\u5F00 WorkBuddy");
6345
+ else this.#noteDown("App \u5728\u8FD0\u884C\u4F46\u6CA1\u5E26\u8C03\u8BD5\u7AEF\u53E3\uFF08\u53EF\u80FD\u662F\u8BBE\u7F6E\u73AF\u5883\u53D8\u91CF\u4E4B\u524D\u6253\u5F00\u7684\uFF09\uFF0C\u7528\u6237\u5728\u7535\u8111\u524D\uFF0C\u4E0D\u91CD\u542F");
6237
6346
  return;
6238
6347
  }
6239
6348
  await this.#doRestart(true, hung);
@@ -6245,6 +6354,7 @@ var CdpKeeper = class {
6245
6354
  async #doRestart(verify, hung = false) {
6246
6355
  this.#restarting = true;
6247
6356
  this.#lastRestart = this.#now();
6357
+ this.#startGrace(this.#lastRestart);
6248
6358
  this.#restarts++;
6249
6359
  this.#saveThrottle();
6250
6360
  try {
@@ -6336,6 +6446,10 @@ var CdpKeeper = class {
6336
6446
  this.#d.log(`\u5524\u8D77 WorkBuddy\uFF1A${Math.round((now - this.#lastWakeLaunch) / 1e3)} \u79D2\u524D\u521A\u62C9\u8D77\u8FC7\uFF08\u8282\u6D41 60 \u79D2\uFF09\uFF0C\u4E0D\u518D\u62C9\u8D77\uFF0C\u7B49\u5B83\u8D77\u6765`);
6337
6447
  return "launched";
6338
6448
  }
6449
+ if (pr === "timeout" && this.#inGrace(now) && await this.ownerOk().catch(() => false)) {
6450
+ this.#noteGrace();
6451
+ return "running";
6452
+ }
6339
6453
  if (await this.#isHung(pr)) return this.#wakeHung(now);
6340
6454
  if (this.#lastRestart && now - this.#lastRestart < RESTART_GAP_MS) {
6341
6455
  this.#d.log(`\u5524\u8D77 WorkBuddy\uFF1A${Math.round((now - this.#lastRestart) / 6e4)} \u5206\u949F\u524D\u521A\u91CD\u542F\u8FC7\uFF08\u8282\u6D41 30 \u5206\u949F\uFF09\uFF0C\u4E0D\u518D\u91CD\u542F\uFF0C\u7B49\u7AEF\u53E3\u8D77\u6765`);
@@ -6352,6 +6466,7 @@ var CdpKeeper = class {
6352
6466
  return "not_installed";
6353
6467
  }
6354
6468
  this.#lastWakeLaunch = now;
6469
+ this.#startGrace(now);
6355
6470
  this.#saveThrottle();
6356
6471
  await this.ensureEnv().catch(() => {
6357
6472
  });
@@ -8277,6 +8392,10 @@ var RESTART_DAILY_MAX2 = 3;
8277
8392
  var BUSY_WINDOW_MS2 = 30 * 60 * 1e3;
8278
8393
  var QUIT_WAIT_MS2 = 20 * 1e3;
8279
8394
  var USER_IDLE_MS = 5 * 60 * 1e3;
8395
+ var LAUNCH_FRESH_MS = 10 * 1e3;
8396
+ var LAUNCH_PORT_WINDOW_MS = 10 * 60 * 1e3;
8397
+ var LAUNCH_PORT_MAX = 2;
8398
+ var LAUNCH_SEEN_MAX = 32;
8280
8399
  function winBrandMainProc(procs, exe) {
8281
8400
  const want = exe.toLowerCase();
8282
8401
  for (const p of procs) {
@@ -8328,6 +8447,24 @@ function parseLsappBundle(out) {
8328
8447
  const m = /"CFBundleIdentifier"\s*=\s*"([^"]+)"/.exec(out);
8329
8448
  return m ? m[1] : null;
8330
8449
  }
8450
+ function parseEtime(s) {
8451
+ const m = /^(?:(?:(\d+)-)?(\d+):)?(\d+):(\d+)$/.exec(s.trim());
8452
+ if (!m) return null;
8453
+ const [d, h, mi, se] = [m[1], m[2], m[3], m[4]].map((x) => Number(x ?? 0));
8454
+ return (((d * 24 + h) * 60 + mi) * 60 + se) * 1e3;
8455
+ }
8456
+ function hasPortArg(args) {
8457
+ return /(^|\s|")--remote-debugging-port(=|\s|"|$)/.test(args);
8458
+ }
8459
+ function macBrandMains(psOut, macExe) {
8460
+ const out = [];
8461
+ for (const line of psOut.split(/\r?\n/)) {
8462
+ const m = /^\s*(\d+)\s+(\S+)\s+(.*)$/.exec(line);
8463
+ if (!m || !isMainArgs(m[3], macExe)) continue;
8464
+ out.push({ pid: Number(m[1]), ageMs: parseEtime(m[2]), hasPort: hasPortArg(m[3].slice(macExe.length)) });
8465
+ }
8466
+ return out;
8467
+ }
8331
8468
  function toolBusy(tool, now = Date.now(), dir2 = stateDir(), alive = isAlive) {
8332
8469
  if (!existsSync20(dir2)) return false;
8333
8470
  for (const f of readdirSync12(dir2)) {
@@ -8358,6 +8495,8 @@ var QoderCdpKeeper = class {
8358
8495
  #hung = new HungTracker();
8359
8496
  /** 这一段卡死里「暂不重启」的原因记过哪条(同一条只记一次) */
8360
8497
  #hungNote = "";
8498
+ /** 启动瞬间补端口处理过的主进程号(每个启动只做一次) */
8499
+ #launchSeen = [];
8361
8500
  constructor(deps) {
8362
8501
  this.#d = deps;
8363
8502
  }
@@ -8440,6 +8579,7 @@ var QoderCdpKeeper = class {
8440
8579
  const t = all?.[this.#d.brand.tool] ?? {};
8441
8580
  const out = { lastLaunch: Number(t.lastLaunch) || 0, lastRestart: Number(t.lastRestart) || 0, day: typeof t.day === "string" ? t.day : "", restarts: Number(t.restarts) || 0 };
8442
8581
  if (this.#d.brand.win && isWinAppExe(t.appExe, this.#d.brand.win.exe)) out.appExe = t.appExe;
8582
+ if (Array.isArray(t.launchPorts)) out.launchPorts = t.launchPorts.map(Number).filter((x) => Number.isFinite(x) && x > 0);
8443
8583
  return out;
8444
8584
  } catch {
8445
8585
  return { lastLaunch: 0, lastRestart: 0, day: "", restarts: 0 };
@@ -8609,6 +8749,87 @@ var QoderCdpKeeper = class {
8609
8749
  const r = await this.#exec()("ioreg", ["-c", "IOHIDSystem", "-d", "4"]);
8610
8750
  return r.code === 0 ? parseHidIdleMs2(r.stdout) : null;
8611
8751
  }
8752
+ /** 主进程(macOS;只认唯一一个;没有 / 认不出 / 多个 → null) */
8753
+ async #brandMain() {
8754
+ const r = await this.#exec()("ps", ["-axww", "-o", "pid=,etime=,args="]);
8755
+ if (r.code !== 0) return null;
8756
+ const mains = macBrandMains(r.stdout, this.#d.brand.macExe);
8757
+ return mains.length === 1 ? mains[0] : null;
8758
+ }
8759
+ /**
8760
+ * 启动瞬间补端口(spec 2026-10-12 qoder-launch-port):主进程刚起(不到 LAUNCH_FRESH_MS)、命令行没带调试端口、没有会话在跑 / 待答题 →
8761
+ * 不管用户在不在,正常退出并带端口拉起。每个启动(主进程号)只看一次:带了端口 / 已超过 10 秒 / 处理过(成败都算)都不再动;
8762
+ * 单独限流 LAUNCH_PORT_MAX 次 / LAUNCH_PORT_WINDOW_MS(落盘),不占每天 3 次的重启额度。
8763
+ * 🔴 macOS 只正常退出(osascript quit),20 秒没退就放弃、绝不强杀;Windows 关闭按钮只会缩到托盘,按原有规则结束这一个主进程树(刚起、没会话)
8764
+ */
8765
+ async fixFreshLaunch() {
8766
+ if (this.#platform() !== "darwin" || this.#skip() || this.#d.noRestart) return "skipped";
8767
+ if (this.#restarting || this.#waking || this.#launchChecking) return "skipped";
8768
+ this.#launchChecking = true;
8769
+ try {
8770
+ return await this.#fixFresh();
8771
+ } finally {
8772
+ this.#launchChecking = false;
8773
+ if (this.#launchRestarting) {
8774
+ this.#launchRestarting = false;
8775
+ this.#restarting = false;
8776
+ }
8777
+ }
8778
+ }
8779
+ /** 补端口检测进行中(只看进程表,不挡唤起) */
8780
+ #launchChecking = false;
8781
+ /** 这次 #restarting 是补端口占的(finally 里释放) */
8782
+ #launchRestarting = false;
8783
+ /** 正在启动补端口 / 重启(常开据此跳过) */
8784
+ get restarting() {
8785
+ return this.#restarting;
8786
+ }
8787
+ async #fixFresh() {
8788
+ const main = await this.#brandMain();
8789
+ if (!main) return "none";
8790
+ if (this.#launchSeen.includes(main.pid)) return "seen";
8791
+ this.#launchSeen.push(main.pid);
8792
+ if (this.#launchSeen.length > LAUNCH_SEEN_MAX) this.#launchSeen.shift();
8793
+ if (main.hasPort) return "has_port";
8794
+ if (main.ageMs === null || main.ageMs < 0 || main.ageMs >= LAUNCH_FRESH_MS) return "old";
8795
+ const b = this.#d.brand;
8796
+ const now = this.#now();
8797
+ if ((this.#d.busy ?? (() => toolBusy(b.tool, now)))()) {
8798
+ this.#d.log(`${b.label} \u521A\u542F\u52A8\u6CA1\u5E26\u8C03\u8BD5\u7AEF\u53E3\uFF0C\u4F46\u6709\u4F1A\u8BDD\u5728\u8DD1\u6216\u6709\u5F85\u7B54\u9898\uFF0C\u4E0D\u91CD\u5F00`);
8799
+ return "busy";
8800
+ }
8801
+ const t = this.#loadThrottle();
8802
+ const recent = (t.launchPorts ?? []).filter((x) => now - x < LAUNCH_PORT_WINDOW_MS && x <= now);
8803
+ if (recent.length >= LAUNCH_PORT_MAX) {
8804
+ this.#d.log(`${b.label} \u521A\u542F\u52A8\u6CA1\u5E26\u8C03\u8BD5\u7AEF\u53E3\uFF0C\u4F46 ${LAUNCH_PORT_WINDOW_MS / 6e4} \u5206\u949F\u5185\u5DF2\u91CD\u5F00 ${recent.length} \u6B21\uFF0C\u8FD9\u6B21\u4E0D\u91CD\u5F00`);
8805
+ return "limited";
8806
+ }
8807
+ const app = this.appPath();
8808
+ if (!app) return "failed";
8809
+ if (this.#restarting || this.#waking) return "skipped";
8810
+ this.#restarting = true;
8811
+ this.#launchRestarting = true;
8812
+ this.#saveThrottle({ ...t, launchPorts: [...recent, now], lastLaunch: now });
8813
+ await this.#exec()("osascript", ["-e", `quit app ${JSON.stringify(b.appName)}`], 15e3);
8814
+ let gone = false;
8815
+ for (let waited = 0; waited < QUIT_WAIT_MS2; waited += 1e3) {
8816
+ if (!await this.appRunning()) {
8817
+ gone = true;
8818
+ break;
8819
+ }
8820
+ await this.#sleep(1e3);
8821
+ }
8822
+ if (!gone) {
8823
+ this.#d.log(`${b.label} \u521A\u542F\u52A8\u6CA1\u5E26\u8C03\u8BD5\u7AEF\u53E3\uFF0C${QUIT_WAIT_MS2 / 1e3} \u79D2\u5185\u6CA1\u6709\u9000\u51FA\uFF0C\u653E\u5F03\u91CD\u5F00\uFF08\u4E0D\u5F3A\u5236\u7ED3\u675F\uFF09`);
8824
+ return "failed";
8825
+ }
8826
+ const port = this.#d.port();
8827
+ const launch = this.#d.launch ?? defaultLaunch;
8828
+ const env = (this.#d.launchEnv ?? cleanLaunchEnv)();
8829
+ const ok = await launch("open", ["-a", app, "--args", `--remote-debugging-port=${port}`], env);
8830
+ this.#d.log(ok ? `${b.label} \u521A\u542F\u52A8\u6CA1\u5E26\u8C03\u8BD5\u7AEF\u53E3\uFF0C\u5DF2\u5E26\u7AEF\u53E3\u91CD\u5F00\uFF08\u7AEF\u53E3 ${port}\uFF09` : `${b.label} \u521A\u542F\u52A8\u6CA1\u5E26\u8C03\u8BD5\u7AEF\u53E3\uFF0C\u5DF2\u9000\u51FA\u4F46\u5E26\u7AEF\u53E3\u62C9\u8D77\u5931\u8D25\uFF08\u4E0D\u518D\u91CD\u8BD5\uFF09`);
8831
+ return ok ? "reopened" : "failed";
8832
+ }
8612
8833
  /**
8613
8834
  * 设备任务到达、App 不可用时唤起(spec §3.1)。不等端口起来(调用方等连上):
8614
8835
  * - 端口已活 → running;
@@ -8616,9 +8837,9 @@ var QoderCdpKeeper = class {
8616
8837
  * - App 开着没带端口:用户在场 → present、正在跑任务 → busy(都不动 App);用户不在场且空闲 → 退出重启 → restarted;
8617
8838
  * - 没装 → not_installed;当天重启次数用完 / 拉不起来 → failed;非 macOS / 测试兜底 → skipped
8618
8839
  */
8619
- async wakeForTask() {
8840
+ async wakeForTask(source = "task") {
8620
8841
  if (this.#waking) return this.#waking;
8621
- const p = this.#wake();
8842
+ const p = this.#wake(source);
8622
8843
  this.#waking = p;
8623
8844
  try {
8624
8845
  return await p;
@@ -8626,7 +8847,7 @@ var QoderCdpKeeper = class {
8626
8847
  if (this.#waking === p) this.#waking = null;
8627
8848
  }
8628
8849
  }
8629
- async #wake() {
8850
+ async #wake(source) {
8630
8851
  if (this.#skip() || !this.#canManage() || this.#d.noRestart) return "skipped";
8631
8852
  if (this.#restarting) return "restarted";
8632
8853
  const pr = await this.#probe();
@@ -8657,7 +8878,7 @@ var QoderCdpKeeper = class {
8657
8878
  if (restarts >= RESTART_DAILY_MAX2) return "failed";
8658
8879
  const app = this.appPath();
8659
8880
  if (!app) return "not_installed";
8660
- return await this.#restart(app, t, now, restarts, false) ? "restarted" : "failed";
8881
+ return await this.#restart(app, t, now, restarts, false, source) ? "restarted" : "failed";
8661
8882
  }
8662
8883
  /**
8663
8884
  * 设备任务到达、端口在监听但无响应:空闲、用户不在场、已连续无响应满 HUNG_RESTART_MS、没超节流 → 结束并带端口重启(restarted);
@@ -8693,7 +8914,7 @@ var QoderCdpKeeper = class {
8693
8914
  * hung=false(没带端口):正常退出 20 秒没反应就放弃——🔴 可能有确认弹窗,不强杀;
8694
8915
  * hung=true(端口无响应、App 卡死):正常退出没反应时结束端口监听者(确认是这个 App 的主进程才动)
8695
8916
  */
8696
- async #restart(app, t, now, restarts, hung) {
8917
+ async #restart(app, t, now, restarts, hung, source = "task") {
8697
8918
  if (this.#restarting) return false;
8698
8919
  this.#restarting = true;
8699
8920
  const b = this.#d.brand;
@@ -8701,7 +8922,7 @@ var QoderCdpKeeper = class {
8701
8922
  try {
8702
8923
  this.#saveThrottle({ ...this.#loadThrottle(), lastRestart: now, day: localDay2(now), restarts: restarts + 1 });
8703
8924
  this.#d.log(
8704
- hung ? `${b.label} \u8C03\u8BD5\u7AEF\u53E3 ${port} \u65E0\u54CD\u5E94 ${this.#hung.minutes(now)} \u5206\u949F\u3001App \u7A7A\u95F2\u3001\u7528\u6237\u4E0D\u5728\u573A\uFF1A\u9000\u51FA\u5E76\u5E26\u7AEF\u53E3\u91CD\u542F\uFF08\u4ECA\u5929\u7B2C ${restarts + 1}/${RESTART_DAILY_MAX2} \u6B21\uFF09` : `\u8BBE\u5907\u4EFB\u52A1\u5230\u8FBE\uFF1A${b.label} \u5F00\u7740\u4F46\u6CA1\u5E26\u8C03\u8BD5\u7AEF\u53E3\u3001\u7528\u6237\u4E0D\u5728\u573A\uFF1A\u9000\u51FA\u5E76\u5E26\u7AEF\u53E3\u91CD\u542F\uFF08\u4ECA\u5929\u7B2C ${restarts + 1}/${RESTART_DAILY_MAX2} \u6B21\uFF09`
8925
+ hung ? `${b.label} \u8C03\u8BD5\u7AEF\u53E3 ${port} \u65E0\u54CD\u5E94 ${this.#hung.minutes(now)} \u5206\u949F\u3001App \u7A7A\u95F2\u3001\u7528\u6237\u4E0D\u5728\u573A\uFF1A\u9000\u51FA\u5E76\u5E26\u7AEF\u53E3\u91CD\u542F\uFF08\u4ECA\u5929\u7B2C ${restarts + 1}/${RESTART_DAILY_MAX2} \u6B21\uFF09` : source === "keep_open" ? `${b.label} \u7AEF\u53E3\u4E0D\u5728\u3001\u7528\u6237\u4E0D\u5728\u4E14\u7A7A\u95F2\uFF0C\u5E26\u7AEF\u53E3\u91CD\u5F00\uFF08\u5E38\u5F00\uFF0C\u4ECA\u5929\u7B2C ${restarts + 1}/${RESTART_DAILY_MAX2} \u6B21\uFF09` : `\u8BBE\u5907\u4EFB\u52A1\u5230\u8FBE\uFF1A${b.label} \u5F00\u7740\u4F46\u6CA1\u5E26\u8C03\u8BD5\u7AEF\u53E3\u3001\u7528\u6237\u4E0D\u5728\u573A\uFF1A\u9000\u51FA\u5E76\u5E26\u7AEF\u53E3\u91CD\u542F\uFF08\u4ECA\u5929\u7B2C ${restarts + 1}/${RESTART_DAILY_MAX2} \u6B21\uFF09`
8705
8926
  );
8706
8927
  const exec = this.#exec();
8707
8928
  if (this.#isWin()) return await this.#restartWin(app, hung);
@@ -10510,7 +10731,7 @@ var defaultLoginOpenDeps = () => ({
10510
10731
  });
10511
10732
  async function installCodebuddyHooks(log) {
10512
10733
  try {
10513
- const { installTools: installTools2 } = await import("./enable-NGQ3J3NW.js");
10734
+ const { installTools: installTools2 } = await import("./enable-UNMUBPWP.js");
10514
10735
  installTools2(["workbuddy"], () => {
10515
10736
  }, null, { auto: true });
10516
10737
  } catch (e) {
@@ -11024,7 +11245,7 @@ function defaultOfferDeps(log) {
11024
11245
  }),
11025
11246
  openLogin: () => defaultOpenLogin(platform),
11026
11247
  installHooks: async () => {
11027
- const { installTools: installTools2 } = await import("./enable-NGQ3J3NW.js");
11248
+ const { installTools: installTools2 } = await import("./enable-UNMUBPWP.js");
11028
11249
  installTools2(["workbuddy"], (l) => log(`[WorkBuddy \u72EC\u7ACB\u7248] ${l}`), null, { auto: true });
11029
11250
  },
11030
11251
  log: (l) => log(`[WorkBuddy \u72EC\u7ACB\u7248] ${l}`),