deskbuddy 0.2.0-beta.2 → 0.2.0-beta.3

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,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/commands/daemon.ts
4
- import { writeFileSync as writeFileSync13, rmSync as rmSync7 } from "fs";
4
+ import { writeFileSync as writeFileSync14, rmSync as rmSync9 } from "fs";
5
5
 
6
6
  // src/core/config.ts
7
7
  import { randomUUID } from "crypto";
@@ -406,7 +406,7 @@ function pidFilePath() {
406
406
  }
407
407
 
408
408
  // src/daemon/index.ts
409
- import { randomBytes as randomBytes5 } from "crypto";
409
+ import { randomBytes as randomBytes6 } from "crypto";
410
410
  import { existsSync as existsSync10 } from "fs";
411
411
  import { basename as basename6 } from "path";
412
412
  import WebSocket2 from "ws";
@@ -620,7 +620,7 @@ function atomicCopy(from, to) {
620
620
  function syncHookFile(opts = {}) {
621
621
  const target = opts.target ?? installedHookPath();
622
622
  const version = opts.version ?? packageVersion();
623
- const SIBLINGS = ["gate.cjs", "opencode.mjs"];
623
+ const SIBLINGS = ["gate.cjs", "opencode.mjs", "wake.cjs"];
624
624
  const siblingMissing = () => SIBLINGS.some((name) => {
625
625
  if (existsSync5(join5(dirname3(target), name))) return false;
626
626
  try {
@@ -1295,7 +1295,7 @@ function buildSpawnPlan(cmd, cwd, gate, home = homedir4()) {
1295
1295
  env: { DESKBUDDY_GATE_RUN: gate.runId, DESKBUDDY_NODE: gate.nodePath, OPENCODE_CONFIG_CONTENT: OPENCODE_DENY_ALL }
1296
1296
  } : null;
1297
1297
  default:
1298
- return { file, args: claudePlan(cmd, gate), cwd };
1298
+ return { file, args: claudePlan(cmd, gate), cwd, env: { DESKBUDDY_NO_WAKE: "1" } };
1299
1299
  }
1300
1300
  }
1301
1301
  function materialize(plan, tool, d = defaultLocateDeps()) {
@@ -2437,10 +2437,14 @@ var SessionQueue = class {
2437
2437
  * rejected promise,后续所有指令都会跟着被拒 —— 一次失败让这个会话**永久**
2438
2438
  * 收不到指令,而且没有任何报错。
2439
2439
  */
2440
- enqueue(sessionId, job) {
2440
+ enqueue(sessionId, job, onError) {
2441
2441
  const key = sessionId || "";
2442
2442
  const prev = this.#tails.get(key) ?? Promise.resolve();
2443
- const next = prev.then(job).catch(() => {
2443
+ const next = prev.then(job).catch((e) => {
2444
+ try {
2445
+ onError?.(e);
2446
+ } catch {
2447
+ }
2444
2448
  });
2445
2449
  this.#tails.set(key, next);
2446
2450
  void next.then(() => {
@@ -3553,6 +3557,590 @@ function windowForSession(sessionId) {
3553
3557
  return id;
3554
3558
  }
3555
3559
 
3560
+ // src/core/wake.ts
3561
+ import { execFileSync as execFileSync7 } from "child_process";
3562
+ import { createHash as createHash3, createHmac as createHmac2, timingSafeEqual as timingSafeEqual2 } from "crypto";
3563
+ import { chmodSync as chmodSync4, closeSync, mkdirSync as mkdirSync14, openSync, readdirSync as readdirSync3, readFileSync as readFileSync13, readSync, renameSync as renameSync11, rmSync as rmSync7, statSync as statSync4, writeFileSync as writeFileSync13 } from "fs";
3564
+ import { tmpdir as tmpdir2 } from "os";
3565
+ import { join as join17 } from "path";
3566
+ function wakeDir(home = deskbuddyHome()) {
3567
+ return join17(home, "wake");
3568
+ }
3569
+ function marksDir(home = deskbuddyHome()) {
3570
+ return join17(wakeDir(home), "marks");
3571
+ }
3572
+ function safeSid2(sessionId) {
3573
+ return String(sessionId).replace(/[^A-Za-z0-9_-]/g, "");
3574
+ }
3575
+ function homeTag(home) {
3576
+ return createHash3("sha1").update(home).digest("hex").slice(0, 16);
3577
+ }
3578
+ function wakeChannelPath(platform = process.platform, home = deskbuddyHome()) {
3579
+ if (platform === "win32") return `\\\\.\\pipe\\deskbuddy-wake-${homeTag(home)}`;
3580
+ const primary = join17(wakeDir(home), "wake.sock");
3581
+ if (Buffer.byteLength(primary) <= 103) return primary;
3582
+ const uid = typeof process.getuid === "function" ? process.getuid() : 0;
3583
+ return join17(tmpdir2(), `deskbuddy-${uid}`, `wake-${homeTag(home)}.sock`);
3584
+ }
3585
+ function pidExists(pid) {
3586
+ if (!Number.isInteger(pid) || pid <= 0) return false;
3587
+ try {
3588
+ process.kill(pid, 0);
3589
+ return true;
3590
+ } catch {
3591
+ return false;
3592
+ }
3593
+ }
3594
+ function writeWakeMark(sessionId, agentPid, now = Date.now()) {
3595
+ const safe = safeSid2(sessionId);
3596
+ if (!safe) return;
3597
+ try {
3598
+ mkdirSync14(marksDir(), { recursive: true, mode: 448 });
3599
+ const file = join17(marksDir(), `${safe}.json`);
3600
+ const tmp = `${file}.${process.pid}.tmp`;
3601
+ writeFileSync13(tmp, JSON.stringify({ agentPid: agentPid ?? 0, daemonPid: process.pid, at: now }), { encoding: "utf8", mode: 384 });
3602
+ renameSync11(tmp, file);
3603
+ } catch {
3604
+ }
3605
+ }
3606
+ function removeWakeMark(sessionId) {
3607
+ const safe = safeSid2(sessionId);
3608
+ if (!safe) return;
3609
+ try {
3610
+ rmSync7(join17(marksDir(), `${safe}.json`), { force: true });
3611
+ } catch {
3612
+ }
3613
+ }
3614
+ function clearWakeMarks() {
3615
+ let names = [];
3616
+ try {
3617
+ names = readdirSync3(marksDir());
3618
+ } catch {
3619
+ return;
3620
+ }
3621
+ for (const n of names) {
3622
+ try {
3623
+ rmSync7(join17(marksDir(), n), { force: true });
3624
+ } catch {
3625
+ }
3626
+ }
3627
+ }
3628
+ function isWakeMarked(sessionId, alive = pidExists) {
3629
+ const safe = safeSid2(sessionId);
3630
+ if (!safe) return false;
3631
+ try {
3632
+ const m = JSON.parse(readFileSync13(join17(marksDir(), `${safe}.json`), "utf8"));
3633
+ const daemonPid = Number(m?.daemonPid) || 0;
3634
+ if (!daemonPid || !alive(daemonPid)) return false;
3635
+ const pid = Number(m?.agentPid) || 0;
3636
+ return pid > 0 ? alive(pid) : true;
3637
+ } catch {
3638
+ return false;
3639
+ }
3640
+ }
3641
+ function withWake(states, alive) {
3642
+ return states.map((s) => s.remoteWindow === true || !isWakeMarked(s.sessionId, alive) ? s : { ...s, remoteWindow: true });
3643
+ }
3644
+ function wakeSecretPath(home = deskbuddyHome()) {
3645
+ return join17(wakeDir(home), "secret");
3646
+ }
3647
+ function writeWakeSecret(hex, platform = process.platform) {
3648
+ const file = wakeSecretPath();
3649
+ mkdirSync14(wakeDir(), { recursive: true, mode: 448 });
3650
+ const tmp = `${file}.${process.pid}.tmp`;
3651
+ writeFileSync13(tmp, hex, { encoding: "utf8", mode: 384 });
3652
+ if (platform === "win32") {
3653
+ try {
3654
+ execFileSync7("icacls", [tmp, "/inheritance:r", "/grant:r", `${currentUserSid()}:F`], { stdio: "ignore", windowsHide: true });
3655
+ } catch {
3656
+ renameSync11(tmp, file);
3657
+ return "\u7A97\u53E3\u5524\u9192\u5BC6\u94A5\u6587\u4EF6\u7684\u6743\u9650\u6CA1\u80FD\u6536\u7D27\uFF08icacls \u5931\u8D25\uFF09";
3658
+ }
3659
+ } else {
3660
+ chmodSync4(tmp, 384);
3661
+ }
3662
+ renameSync11(tmp, file);
3663
+ return "";
3664
+ }
3665
+ function currentUserSid() {
3666
+ try {
3667
+ const out = execFileSync7("whoami", ["/user", "/fo", "csv", "/nh"], { encoding: "utf8", windowsHide: true });
3668
+ const m = /"(S-1-[0-9-]+)"/.exec(out);
3669
+ if (m) return `*${m[1]}`;
3670
+ } catch {
3671
+ }
3672
+ return String(process.env.USERNAME);
3673
+ }
3674
+ function canonicalJson(v) {
3675
+ if (Array.isArray(v)) return "[" + v.map(canonicalJson).join(",") + "]";
3676
+ if (v && typeof v === "object") {
3677
+ const o = v;
3678
+ return "{" + Object.keys(o).filter((k) => o[k] !== void 0).sort().map((k) => JSON.stringify(k) + ":" + canonicalJson(o[k])).join(",") + "}";
3679
+ }
3680
+ return JSON.stringify(v === void 0 ? null : v);
3681
+ }
3682
+ function wakeMac(secret, ...parts) {
3683
+ return createHmac2("sha256", secret).update(parts.join("|"), "utf8").digest("hex");
3684
+ }
3685
+ var MAC_RE = /^[0-9a-f]{64}$/;
3686
+ function macEqual(a, b) {
3687
+ if (typeof a !== "string" || typeof b !== "string" || !MAC_RE.test(a) || !MAC_RE.test(b)) return false;
3688
+ return timingSafeEqual2(Buffer.from(a, "hex"), Buffer.from(b, "hex"));
3689
+ }
3690
+ var WAKE_MARK_RE = /\[DeskBuddy#([0-9A-Za-z_-]{1,64})(?::[0-9a-f]{32})?\]/;
3691
+ var INTERRUPT_RE = /^\[Request interrupted by user/;
3692
+ var TRANSCRIPT_TAIL = 256 * 1024;
3693
+ function userText(content) {
3694
+ if (typeof content === "string") return content;
3695
+ if (!Array.isArray(content)) return "";
3696
+ return content.filter((x) => x && x.type === "text").map((x) => String(x.text ?? "")).join("\n");
3697
+ }
3698
+ function analyzeTranscript(file) {
3699
+ if (!file) return null;
3700
+ let fd;
3701
+ try {
3702
+ const size = statSync4(file).size;
3703
+ const len = Math.min(size, TRANSCRIPT_TAIL);
3704
+ const buf = Buffer.alloc(len);
3705
+ fd = openSync(file, "r");
3706
+ readSync(fd, buf, 0, len, size - len);
3707
+ const lines2 = buf.toString("utf8").split("\n");
3708
+ let seg = false;
3709
+ let reply = null;
3710
+ let humanAfter = false;
3711
+ let interrupted = null;
3712
+ for (let i = lines2.length - 1; i >= 0; i--) {
3713
+ const line = lines2[i];
3714
+ if (line.indexOf('"assistant"') === -1 && line.indexOf('"user"') === -1) continue;
3715
+ let o;
3716
+ try {
3717
+ o = JSON.parse(line);
3718
+ } catch {
3719
+ continue;
3720
+ }
3721
+ if (!o || !o.message) continue;
3722
+ if (o.type === "assistant") {
3723
+ const t = userText(o.message.content).trim();
3724
+ if (t) {
3725
+ seg = true;
3726
+ if (reply === null) reply = t;
3727
+ }
3728
+ continue;
3729
+ }
3730
+ if (o.type !== "user") continue;
3731
+ const text = userText(o.message.content);
3732
+ if (!text) continue;
3733
+ const m = WAKE_MARK_RE.exec(text);
3734
+ if (!m && (o.isMeta === true || text.trim().startsWith("<"))) continue;
3735
+ if (m) return { id: m[1], answered: seg, humanAfter, interrupted: interrupted === true, reply: reply ?? "" };
3736
+ if (interrupted === null) interrupted = INTERRUPT_RE.test(text.trim());
3737
+ humanAfter = true;
3738
+ seg = false;
3739
+ reply = null;
3740
+ }
3741
+ return interrupted === null ? null : { id: "", answered: false, humanAfter, interrupted, reply: "" };
3742
+ } catch {
3743
+ return null;
3744
+ } finally {
3745
+ if (fd !== void 0) {
3746
+ try {
3747
+ closeSync(fd);
3748
+ } catch {
3749
+ }
3750
+ }
3751
+ }
3752
+ }
3753
+
3754
+ // src/daemon/wake.ts
3755
+ import { randomBytes as randomBytes5 } from "crypto";
3756
+ import { rmSync as rmSync8 } from "fs";
3757
+ import { createServer as createServer3 } from "net";
3758
+ var BUSY_STATES = /* @__PURE__ */ new Set(["watch", "ask_approval"]);
3759
+ var STOP_MIN_MS = 1500;
3760
+ var WAKE_MAX_QUEUE_MS = 30 * 60 * 1e3;
3761
+ var WATCH_STALE_MS = 30 * 60 * 1e3;
3762
+ var INTERRUPTED_REASON = "exec_failed";
3763
+ var WakeHub = class {
3764
+ #slots = /* @__PURE__ */ new Map();
3765
+ #d;
3766
+ constructor(deps) {
3767
+ this.#d = {
3768
+ ackTimeoutMs: 3e3,
3769
+ maxQueueMs: WAKE_MAX_QUEUE_MS,
3770
+ flightLimitMs: EXEC_TIMEOUT_MS,
3771
+ watchStaleMs: WATCH_STALE_MS,
3772
+ analyze: analyzeTranscript,
3773
+ ...deps
3774
+ };
3775
+ }
3776
+ /** 这个会话有没有登记中的等待者(或刚唤醒、正在跑的那一轮) */
3777
+ has(sessionId) {
3778
+ const s = this.#slots.get(sessionId);
3779
+ return !!s && (!!s.reg || !!s.flight);
3780
+ }
3781
+ /** 交给唤醒路由。返回 false = 这个会话没有等待者,调用方走别的路 */
3782
+ submit(cmd, sessionId) {
3783
+ if (cmd.tool !== "claude_code") return false;
3784
+ const slot = this.#slots.get(sessionId);
3785
+ if (!slot || !slot.reg && !slot.flight) return false;
3786
+ slot.queue.push({ cmd, sessionId, queuedAt: this.#d.now() });
3787
+ this.#d.log(`\u6307\u4EE4 ${cmd.instructionId} \u4EA4\u7ED9\u7A97\u53E3\u5524\u9192\uFF08${slot.flight ? "\u4E0A\u4E00\u6761\u8FD8\u5728\u8DD1" : slot.reg ? "\u6709\u7B49\u5F85\u8005" : "\u65E0\u7B49\u5F85\u8005"}\uFF09`);
3788
+ this.#settle(sessionId, slot);
3789
+ return true;
3790
+ }
3791
+ onHello(peer, h) {
3792
+ const sid = h.sessionId;
3793
+ if (!isValidId(sid)) {
3794
+ peer.close();
3795
+ return;
3796
+ }
3797
+ const slot = this.#slot(sid);
3798
+ if (h.transcript) slot.transcript = h.transcript;
3799
+ if (h.event === "Stop" && !h.re) {
3800
+ slot.lastStopAt = this.#d.now();
3801
+ const f = slot.flight;
3802
+ if (f) this.#onStop(sid, slot, f, h);
3803
+ }
3804
+ if (h.agentPid) {
3805
+ for (const [other, s] of this.#slots) {
3806
+ if (other === sid || !s.reg || s.agentPid !== h.agentPid || s.reg.pending) continue;
3807
+ this.#evict(s);
3808
+ for (const j of s.queue.splice(0)) {
3809
+ j.sessionId = sid;
3810
+ slot.queue.push(j);
3811
+ }
3812
+ this.#settle(other, s);
3813
+ }
3814
+ }
3815
+ const old = slot.reg;
3816
+ if (old && old.peer !== peer) {
3817
+ if (old.pending || h.re) {
3818
+ peer.send({ t: "dup" });
3819
+ peer.close();
3820
+ this.#settle(sid, slot);
3821
+ return;
3822
+ }
3823
+ this.#evict(slot);
3824
+ }
3825
+ slot.reg = { peer, hello: h };
3826
+ if (h.agentPid) slot.agentPid = h.agentPid;
3827
+ peer.send({ t: "ok" });
3828
+ this.#settle(sid, slot);
3829
+ }
3830
+ /**
3831
+ * 🔴 必须确认是被唤醒的那一轮结束了:一轮结束时 hook.cjs 先写 done,tick 可能抢在这一轮的 Stop 等待者登记前
3832
+ * 把排队指令发给上一个等待者,紧接着登记的 Stop 带的是上一轮的回复(2026-09-30 e2e 实测)
3833
+ */
3834
+ #onStop(sid, slot, f, h) {
3835
+ const id = f.job.cmd.instructionId;
3836
+ const t = h.tail;
3837
+ if (!t) {
3838
+ if ((h.stopAt ?? this.#d.now()) - f.firedAt < STOP_MIN_MS) {
3839
+ this.#d.log(`\u6307\u4EE4 ${id} \u5DF2\u5524\u9192\uFF0C\u8FD9\u6B21 Stop \u591A\u534A\u662F\u4E0A\u4E00\u8F6E\u7684\uFF0C\u7EE7\u7EED\u7B49`);
3840
+ return;
3841
+ }
3842
+ } else if (t.id !== id || !t.answered && !t.humanAfter) {
3843
+ this.#d.log(`\u6307\u4EE4 ${id} \u5DF2\u5524\u9192\uFF0C\u8FD9\u6B21 Stop \u662F\u4E0A\u4E00\u8F6E\u7684\uFF0C\u7EE7\u7EED\u7B49`);
3844
+ return;
3845
+ } else if (t.humanAfter) {
3846
+ this.#interrupted(sid, slot, f, "Stop \u663E\u793A\u5524\u9192\u4E4B\u540E\u53C8\u6709\u4EBA\u7C7B\u6D88\u606F");
3847
+ return;
3848
+ }
3849
+ slot.flight = void 0;
3850
+ this.#d.log(`\u6307\u4EE4 ${id} \u7A97\u53E3\u8FD9\u4E00\u8F6E\u5DF2\u7ED3\u675F\uFF0C\u56DE\u590D ${(h.last ?? "").length} \u5B57`);
3851
+ this.#d.done(f.job.cmd, sid, h.last ?? "");
3852
+ }
3853
+ #interrupted(sid, slot, f, why) {
3854
+ slot.flight = void 0;
3855
+ this.#d.log(`\u6307\u4EE4 ${f.job.cmd.instructionId} \u88AB\u5524\u9192\u7684\u90A3\u4E00\u8F6E\u88AB\u6253\u65AD\uFF08${why}\uFF09`);
3856
+ this.#d.report(f.job.cmd, sid, "failed", INTERRUPTED_REASON);
3857
+ }
3858
+ onMessage(peer, m) {
3859
+ const found = this.#find(peer);
3860
+ if (!found) return;
3861
+ const [sid, slot, reg] = found;
3862
+ const p = reg.pending;
3863
+ if (!p || m.id !== p.job.cmd.instructionId) return;
3864
+ clearTimeout(p.timer);
3865
+ reg.pending = void 0;
3866
+ if (m.t === "fired") {
3867
+ const now = this.#d.now();
3868
+ slot.flight = { job: p.job, firedAt: now, paused: 0, lastTick: now };
3869
+ slot.reg = void 0;
3870
+ this.#d.log(`\u6307\u4EE4 ${p.job.cmd.instructionId} \u5DF2\u5524\u9192\u7A97\u53E3 len=${p.job.cmd.text.length}`);
3871
+ this.#d.report(p.job.cmd, sid, "started");
3872
+ } else {
3873
+ slot.reg = void 0;
3874
+ this.#d.log(`\u6307\u4EE4 ${p.job.cmd.instructionId} \u7B49\u5F85\u8005\u8BF4\u7A97\u53E3\u5DF2\u4E0D\u5728\uFF0C\u6539\u8D70\u540E\u53F0`);
3875
+ this.#d.fallback(p.job.cmd);
3876
+ }
3877
+ this.#settle(sid, slot);
3878
+ }
3879
+ onClose(peer) {
3880
+ const found = this.#find(peer);
3881
+ if (!found) return;
3882
+ const [sid, slot, reg] = found;
3883
+ if (reg.pending) {
3884
+ const job = reg.pending.job;
3885
+ clearTimeout(reg.pending.timer);
3886
+ reg.pending = void 0;
3887
+ this.#d.log(`\u6307\u4EE4 ${job.cmd.instructionId} \u4EA4\u7ED9\u7B49\u5F85\u8005\u540E\u6CA1\u6536\u5230\u786E\u8BA4\uFF0C\u6309\u5931\u8D25\u5904\u7406`);
3888
+ this.#d.report(job.cmd, sid, "failed", "window_failed");
3889
+ }
3890
+ slot.reg = void 0;
3891
+ this.#settle(sid, slot);
3892
+ }
3893
+ /** 每秒一次:过期、窗口关闭、打断、等结果超时、忙转闲 */
3894
+ tick() {
3895
+ const now = this.#d.now();
3896
+ for (const [sid, slot] of [...this.#slots]) {
3897
+ if (slot.agentPid && !this.#d.alive(slot.agentPid)) {
3898
+ if (slot.reg) this.#evict(slot, sid);
3899
+ slot.flight = void 0;
3900
+ slot.agentPid = void 0;
3901
+ this.#drain(slot, "\u6392\u961F\u65F6\u7A97\u53E3\u5173\u4E86");
3902
+ }
3903
+ const f = slot.flight;
3904
+ if (f) {
3905
+ const t = slot.transcript ? this.#d.analyze(slot.transcript) : null;
3906
+ if (t && t.id === f.job.cmd.instructionId && t.interrupted) {
3907
+ this.#interrupted(sid, slot, f, "Esc");
3908
+ } else {
3909
+ const st = this.#d.readState(sid);
3910
+ if (st?.state === "ask_approval") f.paused += now - f.lastTick;
3911
+ f.lastTick = now;
3912
+ if (now - f.firedAt - f.paused > this.#d.flightLimitMs) {
3913
+ this.#d.log(`\u6307\u4EE4 ${f.job.cmd.instructionId} \u5524\u9192\u540E\u8FDF\u8FDF\u6CA1\u7B49\u5230\u8FD9\u4E00\u8F6E\u7ED3\u675F\uFF0C\u4E0D\u518D\u7B49\u7ED3\u679C`);
3914
+ slot.flight = void 0;
3915
+ }
3916
+ }
3917
+ }
3918
+ this.#settle(sid, slot);
3919
+ }
3920
+ }
3921
+ logError(msg) {
3922
+ this.#d.log(msg);
3923
+ }
3924
+ /** 关掉全部连接(daemon 退出) */
3925
+ closeAll() {
3926
+ for (const slot of this.#slots.values()) {
3927
+ if (slot.reg?.pending) clearTimeout(slot.reg.pending.timer);
3928
+ slot.reg?.peer.close();
3929
+ }
3930
+ this.#slots.clear();
3931
+ }
3932
+ #slot(sid) {
3933
+ let s = this.#slots.get(sid);
3934
+ if (!s) {
3935
+ s = { queue: [], lastStopAt: 0, marked: false };
3936
+ this.#slots.set(sid, s);
3937
+ }
3938
+ return s;
3939
+ }
3940
+ #find(peer) {
3941
+ for (const [sid, s] of this.#slots) if (s.reg && s.reg.peer === peer) return [sid, s, s.reg];
3942
+ return null;
3943
+ }
3944
+ #evict(slot, sid) {
3945
+ const r = slot.reg;
3946
+ if (!r) return;
3947
+ slot.reg = void 0;
3948
+ if (r.pending) {
3949
+ clearTimeout(r.pending.timer);
3950
+ this.#d.log(`\u6307\u4EE4 ${r.pending.job.cmd.instructionId} \u4EA4\u4ED8\u4E2D\u7B49\u5F85\u8005\u88AB\u8BF7\u8D70\uFF0C\u6309\u5931\u8D25\u5904\u7406`);
3951
+ this.#d.report(r.pending.job.cmd, sid ?? r.pending.job.sessionId, "failed", "window_failed");
3952
+ r.pending = void 0;
3953
+ }
3954
+ r.peer.send({ t: "bye" });
3955
+ r.peer.close();
3956
+ }
3957
+ /** 没碰过窗口的排队指令按原顺序交回后台 */
3958
+ #drain(slot, why) {
3959
+ for (const j of slot.queue.splice(0)) {
3960
+ this.#d.log(`\u6307\u4EE4 ${j.cmd.instructionId} ${why}\uFF0C\u6539\u8D70\u540E\u53F0`);
3961
+ this.#d.fallback(j.cmd);
3962
+ }
3963
+ }
3964
+ #busy(sid, slot) {
3965
+ if (slot.flight || slot.reg?.pending) return true;
3966
+ const st = this.#d.readState(sid);
3967
+ if (!st || !BUSY_STATES.has(st.state) || st.updatedAt <= slot.lastStopAt) return false;
3968
+ if (slot.transcript && this.#d.analyze(slot.transcript)?.interrupted) return false;
3969
+ return this.#d.now() - st.updatedAt <= this.#d.watchStaleMs;
3970
+ }
3971
+ #expired(job, now) {
3972
+ return job.cmd.expireAt > 0 ? isExpired(job.cmd, now) : now - job.queuedAt > this.#d.maxQueueMs;
3973
+ }
3974
+ /** 排队 / 发出 / 过期 */
3975
+ #pump(sid, slot) {
3976
+ const now = this.#d.now();
3977
+ for (const j of [...slot.queue]) {
3978
+ if (!this.#expired(j, now)) continue;
3979
+ slot.queue.splice(slot.queue.indexOf(j), 1);
3980
+ this.#d.log(`\u6307\u4EE4 ${j.cmd.instructionId} \u6392\u961F\u5230\u8FC7\u671F\u4E5F\u6CA1\u7B49\u5230\u7A97\u53E3\u7A7A\u95F2`);
3981
+ this.#d.report(j.cmd, sid, "rejected", "expired");
3982
+ }
3983
+ if (slot.queue.length === 0) return;
3984
+ if (!slot.reg || this.#busy(sid, slot)) {
3985
+ for (const j of slot.queue) {
3986
+ if (j.queuedSent) continue;
3987
+ j.queuedSent = true;
3988
+ this.#d.log(`\u6307\u4EE4 ${j.cmd.instructionId} ${slot.reg ? "\u7A97\u53E3\u6B63\u5FD9" : "\u6682\u65E0\u7B49\u5F85\u8005"}\uFF0C\u6392\u961F`);
3989
+ this.#d.report(j.cmd, sid, "queued");
3990
+ }
3991
+ return;
3992
+ }
3993
+ const job = slot.queue.shift();
3994
+ const reg = slot.reg;
3995
+ reg.pending = {
3996
+ job,
3997
+ timer: setTimeout(() => {
3998
+ if (reg.pending?.job === job) reg.peer.close();
3999
+ }, this.#d.ackTimeoutMs)
4000
+ };
4001
+ reg.pending.timer.unref?.();
4002
+ reg.peer.send({ t: "fire", id: job.cmd.instructionId, text: job.cmd.text });
4003
+ }
4004
+ /** 等待者没了、又没有在跑的唤醒轮:排队的交回后台;然后排队 / 发出;同步标记;清空闲槽 */
4005
+ #settle(sid, slot) {
4006
+ if (!slot.reg && !slot.flight && slot.queue.length > 0) this.#drain(slot, "\u7B49\u5F85\u8005\u6CA1\u4E86");
4007
+ this.#pump(sid, slot);
4008
+ const on = !!slot.reg || !!slot.flight;
4009
+ if (on !== slot.marked) {
4010
+ slot.marked = on;
4011
+ this.#d.mark(sid, slot.agentPid, on);
4012
+ }
4013
+ if (!on && slot.queue.length === 0 && !slot.reg) this.#slots.delete(sid);
4014
+ }
4015
+ };
4016
+ function parseTail(v) {
4017
+ if (!v || typeof v !== "object") return void 0;
4018
+ const t = v;
4019
+ if (typeof t.id !== "string" || t.id !== "" && !isValidId(t.id)) return void 0;
4020
+ return { id: t.id, answered: t.answered === true, humanAfter: t.humanAfter === true, interrupted: t.interrupted === true };
4021
+ }
4022
+ function parseHello(m) {
4023
+ if (m.t !== "hello" || typeof m.sessionId !== "string") return null;
4024
+ const num = (v) => typeof v === "number" && Number.isInteger(v) && v > 0 ? v : void 0;
4025
+ return {
4026
+ sessionId: m.sessionId,
4027
+ agentPid: num(m.agentPid),
4028
+ waiterPid: num(m.waiterPid),
4029
+ event: typeof m.event === "string" ? m.event : void 0,
4030
+ last: typeof m.last === "string" ? m.last : void 0,
4031
+ tail: parseTail(m.tail),
4032
+ transcript: typeof m.transcript === "string" && m.transcript.endsWith(".jsonl") ? m.transcript : void 0,
4033
+ stopAt: num(m.stopAt),
4034
+ re: m.re === true
4035
+ };
4036
+ }
4037
+ var NONCE_RE = /^[0-9a-f]{32}$/;
4038
+ var AUTH_TIMEOUT_MS = 5e3;
4039
+ function startWakeServer(path, hub, secret, platform = process.platform) {
4040
+ if (platform !== "win32") {
4041
+ if (!ensureChannelDir(path)) return Promise.resolve(null);
4042
+ try {
4043
+ rmSync8(path, { force: true });
4044
+ } catch {
4045
+ }
4046
+ }
4047
+ return new Promise((resolve5) => {
4048
+ const server = createServer3((sock) => {
4049
+ sock.on("error", () => {
4050
+ });
4051
+ let phase = "hello";
4052
+ let nonceW = "";
4053
+ let nonceD = "";
4054
+ let regSid = "";
4055
+ const authTimer = setTimeout(() => {
4056
+ if (phase !== "ready") sock.destroy();
4057
+ }, AUTH_TIMEOUT_MS);
4058
+ authTimer.unref();
4059
+ const raw = (msg) => {
4060
+ if (sock.destroyed || !sock.writable) return;
4061
+ try {
4062
+ sock.write(JSON.stringify(msg) + "\n");
4063
+ } catch {
4064
+ }
4065
+ };
4066
+ const peer = {
4067
+ send(msg) {
4068
+ if (msg.t === "fire") raw({ ...msg, sid: regSid, mac: wakeMac(secret, nonceW, regSid, String(msg.id), String(msg.text)) });
4069
+ else raw(msg);
4070
+ },
4071
+ close() {
4072
+ sock.end();
4073
+ setTimeout(() => sock.destroy(), 200).unref();
4074
+ }
4075
+ };
4076
+ sock.on("close", () => {
4077
+ clearTimeout(authTimer);
4078
+ if (phase === "ready") {
4079
+ try {
4080
+ hub.onClose(peer);
4081
+ } catch (e) {
4082
+ hub.logError(`\u7B49\u5F85\u8005\u65AD\u5F00\u65F6\u5904\u7406\u51FA\u9519\uFF1A${e?.stack ?? String(e)}`);
4083
+ }
4084
+ }
4085
+ });
4086
+ const onLine = (line) => {
4087
+ const m = JSON.parse(line);
4088
+ if (!m || typeof m !== "object") throw new Error("bad frame");
4089
+ if (phase === "hello") {
4090
+ if (m.t !== "hello" || typeof m.nonceW !== "string" || !NONCE_RE.test(m.nonceW)) {
4091
+ sock.destroy();
4092
+ return;
4093
+ }
4094
+ nonceW = m.nonceW;
4095
+ nonceD = randomBytes5(16).toString("hex");
4096
+ phase = "auth2";
4097
+ raw({ t: "auth", nonceD, mac: wakeMac(secret, "d", nonceW, nonceD) });
4098
+ return;
4099
+ }
4100
+ if (phase === "auth2") {
4101
+ const reg = m.reg && typeof m.reg === "object" ? m.reg : null;
4102
+ const hello = reg ? parseHello({ ...reg, t: "hello" }) : null;
4103
+ if (m.t !== "auth2" || !hello || !macEqual(m.mac, wakeMac(secret, "w", nonceD, canonicalJson(reg)))) {
4104
+ sock.destroy();
4105
+ return;
4106
+ }
4107
+ regSid = hello.sessionId;
4108
+ phase = "ready";
4109
+ clearTimeout(authTimer);
4110
+ hub.onHello(peer, hello);
4111
+ return;
4112
+ }
4113
+ if (m.t !== "fired" && m.t !== "refuse" || typeof m.id !== "string" || !macEqual(m.mac, wakeMac(secret, "u", nonceD, m.t, m.id))) {
4114
+ sock.destroy();
4115
+ return;
4116
+ }
4117
+ hub.onMessage(peer, m);
4118
+ };
4119
+ lines(sock, (line) => {
4120
+ try {
4121
+ onLine(line);
4122
+ } catch {
4123
+ sock.destroy();
4124
+ }
4125
+ });
4126
+ });
4127
+ const oldMask = platform === "win32" ? null : process.umask(127);
4128
+ const restore = () => {
4129
+ if (oldMask !== null) process.umask(oldMask);
4130
+ };
4131
+ server.once("error", () => {
4132
+ restore();
4133
+ resolve5(null);
4134
+ });
4135
+ server.listen(path, () => {
4136
+ restore();
4137
+ server.on("error", () => {
4138
+ });
4139
+ resolve5(server);
4140
+ });
4141
+ });
4142
+ }
4143
+
3556
4144
  // src/daemon/index.ts
3557
4145
  var IDLE_TIMEOUT_MS = 150 * 1e3;
3558
4146
  var POLICY_VIOLATION = 1008;
@@ -3587,6 +4175,10 @@ var Daemon = class {
3587
4175
  #windowScan = null;
3588
4176
  #aliveReport = null;
3589
4177
  #codexTimer = null;
4178
+ #wake;
4179
+ #wakeServer = null;
4180
+ #wakeTimer = null;
4181
+ #wakeReport = null;
3590
4182
  /** url → 连着它的 CodexWatch。插入顺序 = 发现顺序(DeskBuddy 管的在前) */
3591
4183
  #codex = /* @__PURE__ */ new Map();
3592
4184
  #codexStarted = false;
@@ -3607,8 +4199,26 @@ var Daemon = class {
3607
4199
  codexRoute: deps.codexRoute ?? ((tid) => this.#codexRouteFor(tid)),
3608
4200
  report: deps.report ?? ((p) => api.commandResult(p)),
3609
4201
  windowFor: deps.windowFor ?? windowForSession,
3610
- sendToWindow: deps.sendToWindow ?? ((id, req2, onReply) => sendToWindow(channelPath(id), req2, onReply))
4202
+ sendToWindow: deps.sendToWindow ?? ((id, req2, onReply) => sendToWindow(channelPath(id), req2, onReply)),
4203
+ wakePath: deps.wakePath === void 0 ? wakeChannelPath() : deps.wakePath,
4204
+ onWindowDone: deps.onWindowDone ?? (() => {
4205
+ })
3611
4206
  };
4207
+ this.#wake = new WakeHub({
4208
+ now: this.#deps.now,
4209
+ readState,
4210
+ alive: (pid) => isAlive(pid),
4211
+ report: (cmd, sid, status, reason) => this.#report(cmd, sid, status, "window", reason ?? ""),
4212
+ fallback: (cmd) => this.#dispatch(cmd),
4213
+ done: (cmd, sid, text) => this.#deps.onWindowDone({ instructionId: cmd.instructionId, sessionId: sid, tool: cmd.tool, text }),
4214
+ mark: (sid, pid, on) => {
4215
+ if (on) writeWakeMark(sid, pid, this.#deps.now());
4216
+ else removeWakeMark(sid);
4217
+ log(`\u4F1A\u8BDD ${sid} ${on ? "\u53EF\u4EE5" : "\u4E0D\u518D\u80FD"}\u88AB\u8BBE\u5907\u6307\u4EE4\u5524\u9192`);
4218
+ this.#scheduleWakeReport();
4219
+ },
4220
+ log
4221
+ });
3612
4222
  this.#store.prune(this.#deps.now());
3613
4223
  this.#coord = new ApprovalCoordinator({
3614
4224
  store: this.#store,
@@ -3632,8 +4242,55 @@ var Daemon = class {
3632
4242
  this.#codexTick();
3633
4243
  this.#codexTimer = setInterval(() => this.#codexTick(), CODEX_WATCH_MS);
3634
4244
  this.#codexTimer.unref();
4245
+ await this.startWake();
3635
4246
  return port;
3636
4247
  }
4248
+ /**
4249
+ * 起窗口唤醒通道(Claude Code asyncRewake 等待者连这里,见 hook/wake.cjs 与 ./wake.ts)。
4250
+ * 先清上次残留的标记:还活着的等待者几秒内会重连重新登记
4251
+ */
4252
+ async startWake() {
4253
+ const path = this.#deps.wakePath;
4254
+ if (!path) return;
4255
+ clearWakeMarks();
4256
+ const secret = randomBytes6(32);
4257
+ let warn = "";
4258
+ try {
4259
+ warn = writeWakeSecret(secret.toString("hex"));
4260
+ } catch (e) {
4261
+ log(`\u7A97\u53E3\u5524\u9192\u5BC6\u94A5\u5199\u4E0D\u8FDB\u53BB\uFF0C\u4E0D\u8D77\u5524\u9192\u901A\u9053\uFF1A${e.message}`);
4262
+ return;
4263
+ }
4264
+ if (warn) log(`\u26A0\uFE0F \u5B89\u5168\u544A\u8B66\uFF1A${warn}`);
4265
+ this.#wakeServer = await startWakeServer(path, this.#wake, secret);
4266
+ if (this.#wakeServer) log("\u7A97\u53E3\u5524\u9192\u901A\u9053\u5DF2\u5C31\u7EEA");
4267
+ else if (process.platform === "win32")
4268
+ log("\u26A0\uFE0F \u5B89\u5168\u544A\u8B66\uFF1A\u7A97\u53E3\u5524\u9192\u7BA1\u9053\u76D1\u542C\u5931\u8D25\uFF0C\u53EF\u80FD\u5DF2\u88AB\u5176\u4ED6\u8FDB\u7A0B\u62A2\u6CE8\uFF1B\u5DF2\u505C\u7528\u7A97\u53E3\u5524\u9192\uFF08\u7B49\u5F85\u8005\u8BA4\u8BC1\u4E0D\u8FC7\u4E0D\u4F1A\u6267\u884C\u4EFB\u4F55\u4E1C\u897F\uFF09");
4269
+ else log("\u7A97\u53E3\u5524\u9192\u901A\u9053\u6CA1\u8D77\u6765\uFF08\u4E0D\u5F71\u54CD\u5176\u5B83\u529F\u80FD\uFF09");
4270
+ this.#wakeTimer = setInterval(() => this.#wake.tick(), 1e3);
4271
+ this.#wakeTimer.unref();
4272
+ }
4273
+ /** 唤醒能力变了:300ms 内合并,报一次 Claude Code 的快照(remoteWindow 跟着变) */
4274
+ #scheduleWakeReport() {
4275
+ if (this.#wakeReport) return;
4276
+ this.#wakeReport = setTimeout(() => {
4277
+ this.#wakeReport = null;
4278
+ void this.#reportTool("claude_code");
4279
+ }, 300);
4280
+ this.#wakeReport.unref();
4281
+ }
4282
+ async #reportTool(tool) {
4283
+ const cfg = loadConfig();
4284
+ if (!cfg.clientId || !cfg.boundAt || !cfg.enabledTools.includes(tool)) return;
4285
+ try {
4286
+ await postReport(buildSnapshot(withWake(readAliveStates(tool)), tool, cfg, this.#deps.now()));
4287
+ } catch (e) {
4288
+ log(`\u4E0A\u62A5 ${tool} \u4F1A\u8BDD\u5931\u8D25\uFF1A${e.message}`);
4289
+ }
4290
+ }
4291
+ get wake() {
4292
+ return this.#wake;
4293
+ }
3637
4294
  /** 找 app-server:新出现的连上,不在了且已断开的丢掉;连着的各自 tick */
3638
4295
  #codexTick() {
3639
4296
  let urls = [];
@@ -3728,7 +4385,9 @@ var Daemon = class {
3728
4385
  const cfg = loadConfig();
3729
4386
  if (!cfg.clientId || !cfg.boundAt) return;
3730
4387
  for (const tool of cfg.enabledTools) {
3731
- await postReport(buildSnapshot(readAliveStates(tool), tool, cfg, this.#deps.now(), tool === "codex" ? this.#codexLoaded() : null));
4388
+ await postReport(
4389
+ buildSnapshot(withWake(readAliveStates(tool)), tool, cfg, this.#deps.now(), tool === "codex" ? this.#codexLoaded() : null)
4390
+ );
3732
4391
  }
3733
4392
  if (++this.#dirsTick % 10 === 0) await this.#reportDirs();
3734
4393
  }
@@ -3741,6 +4400,10 @@ var Daemon = class {
3741
4400
  if (this.#windowScan) clearInterval(this.#windowScan);
3742
4401
  if (this.#aliveReport) clearInterval(this.#aliveReport);
3743
4402
  if (this.#codexTimer) clearInterval(this.#codexTimer);
4403
+ if (this.#wakeTimer) clearInterval(this.#wakeTimer);
4404
+ if (this.#wakeReport) clearTimeout(this.#wakeReport);
4405
+ this.#wake.closeAll();
4406
+ this.#wakeServer?.close();
3744
4407
  if (this.#codexReportTimer) clearTimeout(this.#codexReportTimer);
3745
4408
  for (const w of this.#codex.values()) w.stop();
3746
4409
  this.#codex.clear();
@@ -3794,7 +4457,11 @@ var Daemon = class {
3794
4457
  });
3795
4458
  ws.on("message", (raw) => {
3796
4459
  this.#armIdle();
3797
- this.#onMessage(String(raw));
4460
+ try {
4461
+ this.#onMessage(String(raw));
4462
+ } catch (e) {
4463
+ log(`\u5904\u7406\u4E0B\u884C\u5E27\u51FA\u9519\uFF1A${e.stack ?? e.message}`);
4464
+ }
3798
4465
  });
3799
4466
  ws.on("error", (e) => log(`\u8FDE\u63A5\u51FA\u9519: ${e.message}`));
3800
4467
  ws.on("close", (code, reason) => {
@@ -3878,6 +4545,17 @@ var Daemon = class {
3878
4545
  }
3879
4546
  }
3880
4547
  }
4548
+ this.#afterPty(cmd);
4549
+ }
4550
+ /**
4551
+ * pty 窗口接管之外的两条路:窗口里有登记中的唤醒等待者 → 交给它(忙时排队);否则原来的后台路径。
4552
+ * 🔴 一条指令只走一条路:交给等待者之后的任何结局都由 WakeHub 负责,不再回到这里
4553
+ */
4554
+ #afterPty(cmd) {
4555
+ if (cmd.tool === "claude_code" && cmd.sessionId && !isExpired(cmd, this.#deps.now())) {
4556
+ const sid = resolveAlias(cmd.sessionId);
4557
+ if (this.#wake.submit(cmd, sid)) return;
4558
+ }
3881
4559
  this.#dispatch(cmd);
3882
4560
  }
3883
4561
  /**
@@ -3954,7 +4632,7 @@ var Daemon = class {
3954
4632
  const fallback = () => {
3955
4633
  if (fellBack) return;
3956
4634
  fellBack = true;
3957
- this.#dispatch(cmd);
4635
+ this.#afterPty(cmd);
3958
4636
  };
3959
4637
  const req2 = { instructionId: cmd.instructionId, sessionId, text: cmd.text, expireAt: cmd.expireAt };
3960
4638
  let r;
@@ -3998,7 +4676,14 @@ var Daemon = class {
3998
4676
  return;
3999
4677
  }
4000
4678
  if (target.sessionId) this.#coord.cancelSession(target.sessionId);
4001
- this.#queue.enqueue(target.sessionId, () => this.#execute(cmd, target.sessionId, target.cwd, target.handleKey));
4679
+ this.#queue.enqueue(
4680
+ target.sessionId,
4681
+ () => this.#execute(cmd, target.sessionId, target.cwd, target.handleKey),
4682
+ (e) => {
4683
+ log(`\u6307\u4EE4 ${cmd.instructionId} \u6267\u884C\u65F6\u51FA\u9519\uFF1A${e?.stack ?? String(e)}`);
4684
+ this.#report(cmd, target.sessionId, "failed", "", "exec_failed");
4685
+ }
4686
+ );
4002
4687
  }
4003
4688
  async #execute(cmd, sessionId, cwd, handleKey = cmd.tool) {
4004
4689
  if (sessionId) {
@@ -4050,7 +4735,13 @@ var Daemon = class {
4050
4735
  return;
4051
4736
  }
4052
4737
  const origin = this.#runsDone.get(r.runId) ?? Promise.resolve();
4053
- void origin.then(() => this.#queue.enqueue(r.sessionId, () => this.#resume(r, kind)));
4738
+ void origin.then(
4739
+ () => this.#queue.enqueue(
4740
+ r.sessionId,
4741
+ () => this.#resume(r, kind),
4742
+ (e) => log(`\u7EED\u8DD1\u4F1A\u8BDD ${r.sessionId} \u51FA\u9519\uFF1A${e?.stack ?? String(e)}`)
4743
+ )
4744
+ );
4054
4745
  }
4055
4746
  async #resume(r, kind) {
4056
4747
  if (kind === "deferred" && r.consumed) {
@@ -4119,7 +4810,7 @@ var Daemon = class {
4119
4810
  }
4120
4811
  };
4121
4812
  function newRunId() {
4122
- return randomBytes5(12).toString("hex");
4813
+ return randomBytes6(12).toString("hex");
4123
4814
  }
4124
4815
  async function runDaemon(deps = {}) {
4125
4816
  const d = new Daemon(deps);
@@ -4137,7 +4828,7 @@ async function runDaemonCommand(opts = {}) {
4137
4828
  }
4138
4829
  if (opts.foreground) process.env.DESKBUDDY_DAEMON_FOREGROUND = "1";
4139
4830
  try {
4140
- writeFileSync13(pidFilePath(), String(process.pid), "utf8");
4831
+ writeFileSync14(pidFilePath(), String(process.pid), "utf8");
4141
4832
  } catch {
4142
4833
  }
4143
4834
  let daemon;
@@ -4153,7 +4844,7 @@ async function runDaemonCommand(opts = {}) {
4153
4844
  log(`\u6536\u5230 ${sig}\uFF0C\u9000\u51FA`);
4154
4845
  daemon.stop();
4155
4846
  try {
4156
- rmSync7(pidFilePath(), { force: true });
4847
+ rmSync9(pidFilePath(), { force: true });
4157
4848
  } catch {
4158
4849
  }
4159
4850
  process.exit(0);
@@ -4170,8 +4861,8 @@ async function runDaemonCommand(opts = {}) {
4170
4861
  }
4171
4862
 
4172
4863
  // src/core/registry.ts
4173
- import { mkdirSync as mkdirSync14, readFileSync as readFileSync13, writeFileSync as writeFileSync14 } from "fs";
4174
- import { join as join17 } from "path";
4864
+ import { mkdirSync as mkdirSync15, readFileSync as readFileSync14, writeFileSync as writeFileSync15 } from "fs";
4865
+ import { join as join18 } from "path";
4175
4866
  var FALLBACK = {
4176
4867
  version: "builtin",
4177
4868
  tools: {
@@ -4249,11 +4940,11 @@ var FALLBACK = {
4249
4940
  }
4250
4941
  };
4251
4942
  function registryCachePath() {
4252
- return join17(deskbuddyHome(), "tool-registry.json");
4943
+ return join18(deskbuddyHome(), "tool-registry.json");
4253
4944
  }
4254
4945
  function loadRegistry() {
4255
4946
  try {
4256
- return JSON.parse(readFileSync13(registryCachePath(), "utf8"));
4947
+ return JSON.parse(readFileSync14(registryCachePath(), "utf8"));
4257
4948
  } catch {
4258
4949
  return FALLBACK;
4259
4950
  }
@@ -4261,15 +4952,15 @@ function loadRegistry() {
4261
4952
  async function refreshRegistry() {
4262
4953
  try {
4263
4954
  const registry = await api.toolRegistry();
4264
- mkdirSync14(deskbuddyHome(), { recursive: true });
4265
- writeFileSync14(registryCachePath(), JSON.stringify(registry, null, 2), "utf8");
4955
+ mkdirSync15(deskbuddyHome(), { recursive: true });
4956
+ writeFileSync15(registryCachePath(), JSON.stringify(registry, null, 2), "utf8");
4266
4957
  } catch {
4267
4958
  }
4268
4959
  }
4269
4960
 
4270
4961
  // src/adapters/ccHook.ts
4271
- import { chmodSync as chmodSync4, existsSync as existsSync11, mkdirSync as mkdirSync15, readFileSync as readFileSync14, renameSync as renameSync11, statSync as statSync4, writeFileSync as writeFileSync15 } from "fs";
4272
- import { dirname as dirname6, join as join18 } from "path";
4962
+ import { chmodSync as chmodSync5, existsSync as existsSync11, mkdirSync as mkdirSync16, readFileSync as readFileSync15, renameSync as renameSync12, rmSync as rmSync10, statSync as statSync5, writeFileSync as writeFileSync16 } from "fs";
4963
+ import { dirname as dirname6, join as join19 } from "path";
4273
4964
  var CC_EVENTS = [
4274
4965
  "UserPromptSubmit",
4275
4966
  "PreToolUse",
@@ -4280,7 +4971,7 @@ var CC_EVENTS = [
4280
4971
  ];
4281
4972
  function readSettings(path) {
4282
4973
  if (!existsSync11(path)) return {};
4283
- const raw = readFileSync14(path, "utf8");
4974
+ const raw = readFileSync15(path, "utf8");
4284
4975
  if (raw.trim() === "") return {};
4285
4976
  try {
4286
4977
  return JSON.parse(raw);
@@ -4289,17 +4980,17 @@ function readSettings(path) {
4289
4980
  }
4290
4981
  }
4291
4982
  function writeSettings(path, settings) {
4292
- mkdirSync15(dirname6(path), { recursive: true });
4983
+ mkdirSync16(dirname6(path), { recursive: true });
4293
4984
  let mode;
4294
4985
  try {
4295
- mode = statSync4(path).mode & 511;
4986
+ mode = statSync5(path).mode & 511;
4296
4987
  } catch {
4297
4988
  mode = void 0;
4298
4989
  }
4299
4990
  const tmp = path + ".deskbuddy.tmp";
4300
- writeFileSync15(tmp, JSON.stringify(settings, null, 2), "utf8");
4301
- if (mode !== void 0) chmodSync4(tmp, mode);
4302
- renameSync11(tmp, path);
4991
+ writeFileSync16(tmp, JSON.stringify(settings, null, 2), "utf8");
4992
+ if (mode !== void 0) chmodSync5(tmp, mode);
4993
+ renameSync12(tmp, path);
4303
4994
  }
4304
4995
  function mentions(command, script) {
4305
4996
  return !!command && (command.includes(script) || command.includes(fwdSlash(script)));
@@ -4333,25 +5024,98 @@ function installCcWindowGate(settingsPath, gatePath, tool = "claude_code", platf
4333
5024
  });
4334
5025
  writeSettings(settingsPath, settings);
4335
5026
  }
5027
+ var REWAKE_TIMEOUT_S = 20 * 24 * 60 * 60;
5028
+ var REWAKE_MESSAGE = "\u4EE5\u4E0B\u662F\u4E00\u6761 DeskBuddy \u8BBE\u5907\u6307\u4EE4\uFF08\u6309\u4F1A\u8BDD\u5F00\u5934 DeskBuddy \u8BF4\u660E\u91CC\u7684\u53E3\u4EE4\u6838\u5BF9\uFF0C\u53E3\u4EE4\u4E00\u81F4\u624D\u7167\u5E38\u6267\u884C\uFF0C\u5B8C\u6210\u540E\u7B80\u8981\u56DE\u590D\u7ED3\u679C\uFF09\uFF1A";
5029
+ var REWAKE_SUMMARY = "DeskBuddy \u8BBE\u5907\u6307\u4EE4";
5030
+ var REWAKE_EVENTS = ["SessionStart", "Stop"];
5031
+ var REWAKE_CONTEXT_TIMEOUT_S = 10;
5032
+ function rewakeCommand(wakePath, platform = process.platform) {
5033
+ if (platform === "win32") return `node ${winCmdPath(wakePath)}; exit $LASTEXITCODE`;
5034
+ return `node ${shQuote2(wakePath)}`;
5035
+ }
5036
+ function rewakeContextCommand(wakePath, platform = process.platform) {
5037
+ return `node ${platform === "win32" ? winCmdPath(wakePath) : shQuote2(wakePath)} --context`;
5038
+ }
5039
+ function rewakeKind(h) {
5040
+ return /\s--context(\s|$)/.test(h.command) ? "context" : "wait";
5041
+ }
5042
+ function installCcRewake(settingsPath, wakePath, platform = process.platform) {
5043
+ const settings = readSettings(settingsPath);
5044
+ settings.hooks ??= {};
5045
+ const entries = {
5046
+ wait: {
5047
+ type: "command",
5048
+ command: rewakeCommand(wakePath, platform),
5049
+ asyncRewake: true,
5050
+ timeout: REWAKE_TIMEOUT_S,
5051
+ rewakeMessage: REWAKE_MESSAGE,
5052
+ rewakeSummary: REWAKE_SUMMARY
5053
+ },
5054
+ context: { type: "command", command: rewakeContextCommand(wakePath, platform), timeout: REWAKE_CONTEXT_TIMEOUT_S }
5055
+ };
5056
+ if (platform === "win32") {
5057
+ entries.wait.shell = "powershell";
5058
+ entries.context.shell = "powershell";
5059
+ }
5060
+ const wanted = { SessionStart: ["context", "wait"], Stop: ["wait"] };
5061
+ let changed = false;
5062
+ for (const event of REWAKE_EVENTS) {
5063
+ const groups = settings.hooks[event] ??= [];
5064
+ const seen = /* @__PURE__ */ new Set();
5065
+ for (const g of groups) {
5066
+ const hooks = g.hooks ?? [];
5067
+ for (let i = hooks.length - 1; i >= 0; i--) {
5068
+ if (!mentions(hooks[i].command, wakePath)) continue;
5069
+ const kind = rewakeKind(hooks[i]);
5070
+ if (!wanted[event].includes(kind) || seen.has(kind)) {
5071
+ hooks.splice(i, 1);
5072
+ changed = true;
5073
+ continue;
5074
+ }
5075
+ seen.add(kind);
5076
+ if (JSON.stringify(hooks[i]) !== JSON.stringify(entries[kind])) {
5077
+ hooks[i] = { ...entries[kind] };
5078
+ changed = true;
5079
+ }
5080
+ }
5081
+ }
5082
+ const before = groups.length;
5083
+ settings.hooks[event] = groups.filter((g) => (g.hooks ?? []).length > 0);
5084
+ if (settings.hooks[event].length !== before) changed = true;
5085
+ for (const kind of wanted[event]) {
5086
+ if (seen.has(kind)) continue;
5087
+ settings.hooks[event].push({ hooks: [{ ...entries[kind] }] });
5088
+ changed = true;
5089
+ }
5090
+ }
5091
+ if (changed) writeSettings(settingsPath, settings);
5092
+ }
4336
5093
  function uninstallCcHook(settingsPath, hookPath) {
4337
5094
  const settings = readSettings(settingsPath);
4338
5095
  if (!settings.hooks) return;
4339
- const gatePath = join18(dirname6(hookPath), "gate.cjs");
5096
+ const gatePath = join19(dirname6(hookPath), "gate.cjs");
5097
+ const wakePath = join19(dirname6(hookPath), "wake.cjs");
4340
5098
  for (const event of Object.keys(settings.hooks)) {
4341
5099
  const groups = settings.hooks[event] ?? [];
4342
5100
  const kept = [];
4343
5101
  for (const g of groups) {
4344
- const hooks = (g.hooks ?? []).filter((h) => !mentions(h.command, hookPath) && !mentions(h.command, gatePath));
5102
+ const hooks = (g.hooks ?? []).filter(
5103
+ (h) => !mentions(h.command, hookPath) && !mentions(h.command, gatePath) && !mentions(h.command, wakePath)
5104
+ );
4345
5105
  if (hooks.length > 0) kept.push({ ...g, hooks });
4346
5106
  }
4347
5107
  settings.hooks[event] = kept;
4348
5108
  }
4349
5109
  writeSettings(settingsPath, settings);
5110
+ try {
5111
+ rmSync10(join19(dirname6(hookPath), "wake", "tokens"), { recursive: true, force: true });
5112
+ } catch {
5113
+ }
4350
5114
  }
4351
5115
 
4352
5116
  // src/adapters/codexHook.ts
4353
- import { chmodSync as chmodSync5, existsSync as existsSync12, mkdirSync as mkdirSync16, readFileSync as readFileSync15, renameSync as renameSync12, statSync as statSync5, writeFileSync as writeFileSync16 } from "fs";
4354
- import { dirname as dirname7, join as join19 } from "path";
5117
+ import { chmodSync as chmodSync6, existsSync as existsSync12, mkdirSync as mkdirSync17, readFileSync as readFileSync16, renameSync as renameSync13, statSync as statSync6, writeFileSync as writeFileSync17 } from "fs";
5118
+ import { dirname as dirname7, join as join20 } from "path";
4355
5119
  var CODEX_MARKER_BEGIN = "# >>> deskbuddy >>>";
4356
5120
  var CODEX_MARKER_END = "# <<< deskbuddy <<<";
4357
5121
  var CODEX_EVENTS = [
@@ -4364,7 +5128,7 @@ var CODEX_EVENTS = [
4364
5128
  ];
4365
5129
  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";
4366
5130
  function codexConfigPath() {
4367
- return join19(codexHome(), "config.toml");
5131
+ return join20(codexHome(), "config.toml");
4368
5132
  }
4369
5133
  function renderBlock(hookPath, events, tool, platform) {
4370
5134
  const command = JSON.stringify(`node ${platform === "win32" ? winPsPath(hookPath) : hookPath} ${tool}`);
@@ -4383,17 +5147,17 @@ function renderBlock(hookPath, events, tool, platform) {
4383
5147
  return lines2.join("\n");
4384
5148
  }
4385
5149
  function writeAtomic(path, content) {
4386
- mkdirSync16(dirname7(path), { recursive: true });
5150
+ mkdirSync17(dirname7(path), { recursive: true });
4387
5151
  let mode;
4388
5152
  try {
4389
- mode = statSync5(path).mode & 511;
5153
+ mode = statSync6(path).mode & 511;
4390
5154
  } catch {
4391
5155
  mode = void 0;
4392
5156
  }
4393
5157
  const tmp = path + ".deskbuddy.tmp";
4394
- writeFileSync16(tmp, content, "utf8");
4395
- if (mode !== void 0) chmodSync5(tmp, mode);
4396
- renameSync12(tmp, path);
5158
+ writeFileSync17(tmp, content, "utf8");
5159
+ if (mode !== void 0) chmodSync6(tmp, mode);
5160
+ renameSync13(tmp, path);
4397
5161
  }
4398
5162
  function assertSafeToAppend(raw, path) {
4399
5163
  for (const quote of ['"""', "'''"]) {
@@ -4403,7 +5167,7 @@ function assertSafeToAppend(raw, path) {
4403
5167
  }
4404
5168
  }
4405
5169
  function installCodexHook(configPath2, hookPath, events = CODEX_EVENTS, tool = "codex", platform = process.platform) {
4406
- const raw = existsSync12(configPath2) ? readFileSync15(configPath2, "utf8") : "";
5170
+ const raw = existsSync12(configPath2) ? readFileSync16(configPath2, "utf8") : "";
4407
5171
  if (raw.includes(CODEX_MARKER_BEGIN)) return;
4408
5172
  assertSafeToAppend(raw, configPath2);
4409
5173
  const block = renderBlock(hookPath, events, tool, platform);
@@ -4412,7 +5176,7 @@ function installCodexHook(configPath2, hookPath, events = CODEX_EVENTS, tool = "
4412
5176
  }
4413
5177
  function uninstallCodexHook(configPath2) {
4414
5178
  if (!existsSync12(configPath2)) return;
4415
- const raw = readFileSync15(configPath2, "utf8");
5179
+ const raw = readFileSync16(configPath2, "utf8");
4416
5180
  const lines2 = raw.split("\n");
4417
5181
  const begin = lines2.findIndex((l) => l.trim() === CODEX_MARKER_BEGIN);
4418
5182
  if (begin === -1) return;
@@ -4426,11 +5190,11 @@ function uninstallCodexHook(configPath2) {
4426
5190
  }
4427
5191
 
4428
5192
  // src/commands/tools.ts
4429
- import { existsSync as existsSync13, readFileSync as readFileSync16 } from "fs";
5193
+ import { existsSync as existsSync13, readFileSync as readFileSync17 } from "fs";
4430
5194
  function fileContains(path, needle) {
4431
5195
  try {
4432
5196
  if (!existsSync13(path)) return false;
4433
- return readFileSync16(path, "utf8").includes(needle);
5197
+ return readFileSync17(path, "utf8").includes(needle);
4434
5198
  } catch {
4435
5199
  return false;
4436
5200
  }
@@ -4454,6 +5218,12 @@ var ADAPTERS = {
4454
5218
  if (tool !== "claude_code" && tool !== "qoder") return false;
4455
5219
  installCcWindowGate(p, gate, tool);
4456
5220
  return true;
5221
+ },
5222
+ // asyncRewake 只有 Claude Code 认(2.1.280 实测);别家当成同步 hook 会一直等
5223
+ installRewake: (p, wake, tool) => {
5224
+ if (tool !== "claude_code") return false;
5225
+ installCcRewake(p, wake);
5226
+ return true;
4457
5227
  }
4458
5228
  },
4459
5229
  "codex-toml": {
@@ -4498,13 +5268,13 @@ function subscribedEvents(registry, tool, fallback) {
4498
5268
  }
4499
5269
 
4500
5270
  // src/commands/enable.ts
4501
- import { dirname as dirname9, join as join22 } from "path";
5271
+ import { dirname as dirname9, join as join23 } from "path";
4502
5272
 
4503
5273
  // src/wrap/shim.ts
4504
- import { execFileSync as execFileSync7 } from "child_process";
4505
- import { chmodSync as chmodSync6, existsSync as existsSync14, mkdirSync as mkdirSync17, readdirSync as readdirSync3, readFileSync as readFileSync17, rmdirSync, rmSync as rmSync8, writeFileSync as writeFileSync17 } from "fs";
5274
+ import { execFileSync as execFileSync8 } from "child_process";
5275
+ import { chmodSync as chmodSync7, existsSync as existsSync14, mkdirSync as mkdirSync18, readdirSync as readdirSync4, readFileSync as readFileSync18, rmdirSync, rmSync as rmSync11, writeFileSync as writeFileSync18 } from "fs";
4506
5276
  import { homedir as homedir5 } from "os";
4507
- import { delimiter as delimiter2, join as join20, resolve as resolve2 } from "path";
5277
+ import { delimiter as delimiter2, join as join21, resolve as resolve2 } from "path";
4508
5278
  var SHIM_FALLBACK_EXIT = 213;
4509
5279
  var WIN_SHIM_ENABLED = false;
4510
5280
  var SHIM_COMMANDS = { claude_code: "claude", codex: "codex" };
@@ -4514,10 +5284,10 @@ var RC_END = "# <<< deskbuddy <<<";
4514
5284
  var RC_FILES = [".zshrc", ".bashrc"];
4515
5285
  var WIN_PENDING = "Windows \u7A97\u53E3\u63A5\u7BA1\u5F85\u9A8C\u8BC1\uFF0C\u6682\u672A\u5F00\u542F";
4516
5286
  function shimDir() {
4517
- return join20(deskbuddyHome(), "bin");
5287
+ return join21(deskbuddyHome(), "bin");
4518
5288
  }
4519
5289
  function shimFile(binDir, command, platform = process.platform) {
4520
- return join20(binDir, platform === "win32" ? `${command}.cmd` : command);
5290
+ return join21(binDir, platform === "win32" ? `${command}.cmd` : command);
4521
5291
  }
4522
5292
  function shQuote3(s) {
4523
5293
  return `'${s.replace(/'/g, `'\\''`)}'`;
@@ -4646,10 +5416,10 @@ function removeRcBlock(content) {
4646
5416
  return content.slice(0, start) + content.slice(end);
4647
5417
  }
4648
5418
  function readRc(p) {
4649
- return readFileSync17(p).toString("latin1");
5419
+ return readFileSync18(p).toString("latin1");
4650
5420
  }
4651
5421
  function writeRc(p, latin1) {
4652
- writeFileSync17(p, Buffer.from(latin1, "latin1"));
5422
+ writeFileSync18(p, Buffer.from(latin1, "latin1"));
4653
5423
  }
4654
5424
  function toLatin1(utf8) {
4655
5425
  return Buffer.from(utf8, "utf8").toString("latin1");
@@ -4671,7 +5441,7 @@ function removePathEntry(value, dir) {
4671
5441
  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)))";
4672
5442
  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')";
4673
5443
  function psWinPath() {
4674
- const ps = (script, env) => execFileSync7("powershell.exe", ["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command", script], {
5444
+ const ps = (script, env) => execFileSync8("powershell.exe", ["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command", script], {
4675
5445
  encoding: "utf8",
4676
5446
  stdio: ["ignore", "pipe", "ignore"],
4677
5447
  windowsHide: true,
@@ -4718,12 +5488,12 @@ function installShim(tool, e) {
4718
5488
  if (!node || !cli || !bin) {
4719
5489
  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" };
4720
5490
  }
4721
- mkdirSync17(e.binDir, { recursive: true });
4722
- writeFileSync17(file, cmdScript(tool, command, node, cli, bin), "utf8");
5491
+ mkdirSync18(e.binDir, { recursive: true });
5492
+ writeFileSync18(file, cmdScript(tool, command, node, cli, bin), "utf8");
4723
5493
  } else {
4724
- mkdirSync17(e.binDir, { recursive: true, mode: 448 });
4725
- writeFileSync17(file, shScript(tool, command, e.nodePath, e.cliPath, e.binDir), { encoding: "utf8", mode: 493 });
4726
- chmodSync6(file, 493);
5494
+ mkdirSync18(e.binDir, { recursive: true, mode: 448 });
5495
+ writeFileSync18(file, shScript(tool, command, e.nodePath, e.cliPath, e.binDir), { encoding: "utf8", mode: 493 });
5496
+ chmodSync7(file, 493);
4727
5497
  }
4728
5498
  const touched = [];
4729
5499
  if (e.platform === "win32") {
@@ -4737,7 +5507,7 @@ function installShim(tool, e) {
4737
5507
  } else if (e.rcHome) {
4738
5508
  const block = toLatin1(rcBlock(e.binDir, e.rcHome));
4739
5509
  for (const name of RC_FILES) {
4740
- const rc = join20(e.rcHome, name);
5510
+ const rc = join21(e.rcHome, name);
4741
5511
  if (!existsSync14(rc)) continue;
4742
5512
  const next = addRcBlock(readRc(rc), block);
4743
5513
  if (next !== null) writeRc(rc, next);
@@ -4758,13 +5528,13 @@ function installShim(tool, e) {
4758
5528
  function ourShims(binDir) {
4759
5529
  let names;
4760
5530
  try {
4761
- names = readdirSync3(binDir);
5531
+ names = readdirSync4(binDir);
4762
5532
  } catch {
4763
5533
  return [];
4764
5534
  }
4765
5535
  return names.filter((n) => {
4766
5536
  try {
4767
- return readFileSync17(join20(binDir, n), "utf8").includes(SHIM_MARKER);
5537
+ return readFileSync18(join21(binDir, n), "utf8").includes(SHIM_MARKER);
4768
5538
  } catch {
4769
5539
  return false;
4770
5540
  }
@@ -4776,8 +5546,8 @@ function uninstallShim(tool, e) {
4776
5546
  let removed = false;
4777
5547
  const file = shimFile(e.binDir, command, e.platform);
4778
5548
  try {
4779
- if (existsSync14(file) && readFileSync17(file, "utf8").includes(SHIM_MARKER)) {
4780
- rmSync8(file, { force: true });
5549
+ if (existsSync14(file) && readFileSync18(file, "utf8").includes(SHIM_MARKER)) {
5550
+ rmSync11(file, { force: true });
4781
5551
  removed = true;
4782
5552
  }
4783
5553
  } catch {
@@ -4795,7 +5565,7 @@ function uninstallShim(tool, e) {
4795
5565
  }
4796
5566
  } else if (e.rcHome) {
4797
5567
  for (const name of RC_FILES) {
4798
- const rc = join20(e.rcHome, name);
5568
+ const rc = join21(e.rcHome, name);
4799
5569
  if (!existsSync14(rc)) continue;
4800
5570
  const next = removeRcBlock(readRc(rc));
4801
5571
  if (next !== null) {
@@ -4805,7 +5575,7 @@ function uninstallShim(tool, e) {
4805
5575
  }
4806
5576
  }
4807
5577
  try {
4808
- if (existsSync14(e.binDir) && readdirSync3(e.binDir).length === 0) rmdirSync(e.binDir);
5578
+ if (existsSync14(e.binDir) && readdirSync4(e.binDir).length === 0) rmdirSync(e.binDir);
4809
5579
  } catch {
4810
5580
  }
4811
5581
  if (!removed && touched.length === 0) return { ok: true, message: "" };
@@ -4817,7 +5587,7 @@ function shimState(tool, binDir, pathEnv, platform = process.platform) {
4817
5587
  const file = shimFile(binDir, command, platform);
4818
5588
  let installed = false;
4819
5589
  try {
4820
- installed = readFileSync17(file, "utf8").includes(SHIM_MARKER);
5590
+ installed = readFileSync18(file, "utf8").includes(SHIM_MARKER);
4821
5591
  } catch {
4822
5592
  installed = false;
4823
5593
  }
@@ -4828,16 +5598,16 @@ function shimState(tool, binDir, pathEnv, platform = process.platform) {
4828
5598
  }
4829
5599
 
4830
5600
  // src/wrap/pty.ts
4831
- import { chmodSync as chmodSync7, existsSync as existsSync15, statSync as statSync6 } from "fs";
5601
+ import { chmodSync as chmodSync8, existsSync as existsSync15, statSync as statSync7 } from "fs";
4832
5602
  import { createRequire as createRequire2 } from "module";
4833
- import { dirname as dirname8, join as join21 } from "path";
5603
+ import { dirname as dirname8, join as join22 } from "path";
4834
5604
  var req = createRequire2(import.meta.url);
4835
5605
  function fixSpawnHelper() {
4836
5606
  if (process.platform !== "darwin") return;
4837
5607
  try {
4838
- const dir = join21(dirname8(req.resolve("node-pty/package.json")), "prebuilds", `darwin-${process.arch}`);
4839
- const helper = join21(dir, "spawn-helper");
4840
- if (existsSync15(helper) && (statSync6(helper).mode & 73) === 0) chmodSync7(helper, 493);
5608
+ const dir = join22(dirname8(req.resolve("node-pty/package.json")), "prebuilds", `darwin-${process.arch}`);
5609
+ const helper = join22(dir, "spawn-helper");
5610
+ if (existsSync15(helper) && (statSync7(helper).mode & 73) === 0) chmodSync8(helper, 493);
4841
5611
  } catch {
4842
5612
  }
4843
5613
  }
@@ -4932,12 +5702,19 @@ function installTools(tools, out, shim = defaultShimEnvForInstall()) {
4932
5702
  installed.push(tool);
4933
5703
  out(`\u2705 ${tool}\uFF1Ahook \u5DF2\u88C5\u8FDB ${configPath2}\uFF08${events.length} \u4E2A\u4E8B\u4EF6\uFF09`);
4934
5704
  try {
4935
- if (adapter.installWindowGate?.(configPath2, join22(dirname9(hookPath), "gate.cjs"), tool)) {
5705
+ if (adapter.installWindowGate?.(configPath2, join23(dirname9(hookPath), "gate.cjs"), tool)) {
4936
5706
  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`);
4937
5707
  }
4938
5708
  } catch (e) {
4939
5709
  out(`\u26A0\uFE0F ${tool}\uFF1A\u8BBE\u5907\u786E\u8BA4\u6388\u6743\u6CA1\u88C5\u4E0A\uFF1A${e.message}`);
4940
5710
  }
5711
+ try {
5712
+ if (adapter.installRewake?.(configPath2, join23(dirname9(hookPath), "wake.cjs"), tool)) {
5713
+ 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`);
5714
+ }
5715
+ } catch (e) {
5716
+ out(`\u26A0\uFE0F ${tool}\uFF1A\u7A97\u53E3\u5524\u9192\u6CA1\u88C5\u4E0A\uFF1A${e.message}\uFF08\u8BBE\u5907\u6307\u4EE4\u4ECD\u8D70\u540E\u53F0\uFF09`);
5717
+ }
4941
5718
  if (adapter.notice) out(adapter.notice);
4942
5719
  }
4943
5720
  cfg.enabledTools = [...enabled];
@@ -5032,8 +5809,8 @@ async function runDisable(tools, opts = {}) {
5032
5809
  }
5033
5810
 
5034
5811
  // src/commands/install.ts
5035
- import { mkdirSync as mkdirSync18, rmSync as rmSync9, writeFileSync as writeFileSync18 } from "fs";
5036
- import { join as join23 } from "path";
5812
+ import { mkdirSync as mkdirSync19, rmSync as rmSync12, writeFileSync as writeFileSync19 } from "fs";
5813
+ import { join as join24 } from "path";
5037
5814
 
5038
5815
  // src/core/detect.ts
5039
5816
  import { existsSync as existsSync16 } from "fs";
@@ -5078,7 +5855,7 @@ function detectTools(d = defaultDetectDeps()) {
5078
5855
  import qrcode from "qrcode-terminal";
5079
5856
 
5080
5857
  // src/commands/remoteCwd.ts
5081
- import { existsSync as existsSync17, statSync as statSync7 } from "fs";
5858
+ import { existsSync as existsSync17, statSync as statSync8 } from "fs";
5082
5859
  import { createInterface } from "readline";
5083
5860
  import { homedir as homedir6 } from "os";
5084
5861
  import { isAbsolute as isAbsolute2, resolve as resolve3 } from "path";
@@ -5092,7 +5869,7 @@ function normalizePath(raw, home = homedir6()) {
5092
5869
  function checkPath(abs) {
5093
5870
  if (!existsSync17(abs)) return "missing";
5094
5871
  try {
5095
- return statSync7(abs).isDirectory() ? "ok" : "not_a_dir";
5872
+ return statSync8(abs).isDirectory() ? "ok" : "not_a_dir";
5096
5873
  } catch {
5097
5874
  return "missing";
5098
5875
  }
@@ -5294,11 +6071,11 @@ function inSandbox(env) {
5294
6071
  }
5295
6072
  function precheckWritable(dirs) {
5296
6073
  for (const dir of dirs) {
5297
- const probe = join23(dir, `.deskbuddy-probe-${process.pid}`);
6074
+ const probe = join24(dir, `.deskbuddy-probe-${process.pid}`);
5298
6075
  try {
5299
- mkdirSync18(dir, { recursive: true });
5300
- writeFileSync18(probe, "");
5301
- rmSync9(probe, { force: true });
6076
+ mkdirSync19(dir, { recursive: true });
6077
+ writeFileSync19(probe, "");
6078
+ rmSync12(probe, { force: true });
5302
6079
  } catch (e) {
5303
6080
  return { ok: false, dir, error: e.message };
5304
6081
  }
@@ -5427,7 +6204,7 @@ async function runInstall(opts, deps = defaultInstallDeps()) {
5427
6204
 
5428
6205
  // src/commands/logout.ts
5429
6206
  import { createInterface as createInterface2 } from "readline";
5430
- import { existsSync as existsSync18, rmSync as rmSync10 } from "fs";
6207
+ import { existsSync as existsSync18, rmSync as rmSync13 } from "fs";
5431
6208
  function stdout4(line) {
5432
6209
  process.stdout.write(line + "\n");
5433
6210
  }
@@ -5459,7 +6236,7 @@ async function runLogout(opts = {}) {
5459
6236
  }
5460
6237
  await runDisable([], { out, brief: true, keepAutostart: opts.keepAutostart });
5461
6238
  if (existsSync18(home)) {
5462
- rmSync10(home, { recursive: true, force: true });
6239
+ rmSync13(home, { recursive: true, force: true });
5463
6240
  }
5464
6241
  out("");
5465
6242
  out("\u2705 \u5DF2\u6E05\u9664\u672C\u673A\u7684 DeskBuddy \u6570\u636E\u3002");
@@ -5619,9 +6396,9 @@ async function runUpdate(opts = {}) {
5619
6396
  }
5620
6397
 
5621
6398
  // src/wrap/run.ts
5622
- import { randomBytes as randomBytes6 } from "crypto";
5623
- import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync20, statSync as statSync9, truncateSync as truncateSync3 } from "fs";
5624
- import { join as join25 } from "path";
6399
+ import { randomBytes as randomBytes7 } from "crypto";
6400
+ import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync21, statSync as statSync10, truncateSync as truncateSync3 } from "fs";
6401
+ import { join as join26 } from "path";
5625
6402
  import { StringDecoder } from "string_decoder";
5626
6403
 
5627
6404
  // src/wrap/screen.ts
@@ -5976,20 +6753,20 @@ function runDirect(real2, args, env = process.env) {
5976
6753
 
5977
6754
  // src/wrap/codexLaunch.ts
5978
6755
  import { spawn as spawn3 } from "child_process";
5979
- import { closeSync, mkdirSync as mkdirSync19, openSync, readdirSync as readdirSync4, readFileSync as readFileSync18, rmSync as rmSync11, statSync as statSync8, truncateSync as truncateSync2, writeFileSync as writeFileSync19, renameSync as renameSync13 } from "fs";
6756
+ import { closeSync as closeSync2, mkdirSync as mkdirSync20, openSync as openSync2, readdirSync as readdirSync5, readFileSync as readFileSync19, rmSync as rmSync14, statSync as statSync9, truncateSync as truncateSync2, writeFileSync as writeFileSync20, renameSync as renameSync14 } from "fs";
5980
6757
  import { request } from "http";
5981
- import { createServer as createServer3 } from "net";
5982
- import { join as join24 } from "path";
6758
+ import { createServer as createServer4 } from "net";
6759
+ import { join as join25 } from "path";
5983
6760
 
5984
6761
  // src/core/codexServer.ts
5985
- import { execFileSync as execFileSync8 } from "child_process";
6762
+ import { execFileSync as execFileSync9 } from "child_process";
5986
6763
  function isAppServerArgs(args, url) {
5987
6764
  return /\bapp-server\b/.test(args) && new RegExp(`--listen[= ]${url.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:\\s|$)`).test(args);
5988
6765
  }
5989
6766
  function processArgs(pid) {
5990
6767
  if (process.platform === "win32") return null;
5991
6768
  try {
5992
- const out = execFileSync8("ps", ["-ww", "-o", "args=", "-p", String(pid)], {
6769
+ const out = execFileSync9("ps", ["-ww", "-o", "args=", "-p", String(pid)], {
5993
6770
  encoding: "utf8",
5994
6771
  stdio: ["ignore", "pipe", "ignore"]
5995
6772
  });
@@ -6147,10 +6924,10 @@ async function runCodexWindow(real2, args, d) {
6147
6924
  }
6148
6925
  var MAX_LOG_BYTES2 = 1024 * 1024;
6149
6926
  function appServerLogPath() {
6150
- return join24(deskbuddyHome(), "codex-app-server.log");
6927
+ return join25(deskbuddyHome(), "codex-app-server.log");
6151
6928
  }
6152
6929
  function windowsDir2() {
6153
- return join24(deskbuddyHome(), "codex-windows");
6930
+ return join25(deskbuddyHome(), "codex-windows");
6154
6931
  }
6155
6932
  function pidAlive(pid) {
6156
6933
  try {
@@ -6163,7 +6940,7 @@ function pidAlive(pid) {
6163
6940
  function daemonAlive(pidFile = pidFilePath(), args = processArgs) {
6164
6941
  let pid = 0;
6165
6942
  try {
6166
- pid = Number(readFileSync18(pidFile, "utf8").trim());
6943
+ pid = Number(readFileSync19(pidFile, "utf8").trim());
6167
6944
  } catch {
6168
6945
  return false;
6169
6946
  }
@@ -6185,7 +6962,7 @@ function probeReady(url) {
6185
6962
  }
6186
6963
  function freePort() {
6187
6964
  return new Promise((resolve5, reject) => {
6188
- const srv = createServer3();
6965
+ const srv = createServer4();
6189
6966
  srv.unref();
6190
6967
  srv.on("error", reject);
6191
6968
  srv.listen(0, "127.0.0.1", () => {
@@ -6197,12 +6974,12 @@ function freePort() {
6197
6974
  }
6198
6975
  function startServer(real2, url, env, cwd) {
6199
6976
  const logPath2 = appServerLogPath();
6200
- mkdirSync19(deskbuddyHome(), { recursive: true });
6977
+ mkdirSync20(deskbuddyHome(), { recursive: true });
6201
6978
  try {
6202
- if (statSync8(logPath2).size > MAX_LOG_BYTES2) truncateSync2(logPath2, 0);
6979
+ if (statSync9(logPath2).size > MAX_LOG_BYTES2) truncateSync2(logPath2, 0);
6203
6980
  } catch {
6204
6981
  }
6205
- const fd = openSync(logPath2, "a");
6982
+ const fd = openSync2(logPath2, "a");
6206
6983
  try {
6207
6984
  const childEnv2 = { ...env, DESKBUDDY_WRAPPED: "1" };
6208
6985
  delete childEnv2.DESKBUDDY_SHIM;
@@ -6217,7 +6994,7 @@ function startServer(real2, url, env, cwd) {
6217
6994
  child.unref();
6218
6995
  return child.pid ?? null;
6219
6996
  } finally {
6220
- closeSync(fd);
6997
+ closeSync2(fd);
6221
6998
  }
6222
6999
  }
6223
7000
  function killServer(pid) {
@@ -6231,41 +7008,41 @@ function killOwnServer(pid, url) {
6231
7008
  if (a && isAppServerArgs(a, url)) killServer(pid);
6232
7009
  }
6233
7010
  function recordPath(rec) {
6234
- return join24(windowsDir2(), `${rec.server}.json`);
7011
+ return join25(windowsDir2(), `${rec.server}.json`);
6235
7012
  }
6236
7013
  function writeRecord(rec) {
6237
7014
  try {
6238
- mkdirSync19(windowsDir2(), { recursive: true, mode: 448 });
7015
+ mkdirSync20(windowsDir2(), { recursive: true, mode: 448 });
6239
7016
  const p = recordPath(rec);
6240
- writeFileSync19(p + ".tmp", JSON.stringify(rec), { encoding: "utf8", mode: 384 });
6241
- renameSync13(p + ".tmp", p);
7017
+ writeFileSync20(p + ".tmp", JSON.stringify(rec), { encoding: "utf8", mode: 384 });
7018
+ renameSync14(p + ".tmp", p);
6242
7019
  } catch {
6243
7020
  }
6244
7021
  }
6245
7022
  function removeRecord(rec) {
6246
7023
  try {
6247
- rmSync11(recordPath(rec), { force: true });
7024
+ rmSync14(recordPath(rec), { force: true });
6248
7025
  } catch {
6249
7026
  }
6250
7027
  }
6251
7028
  function reapWindows(dir = windowsDir2(), alive = pidAlive, args = processArgs, kill = killServer) {
6252
7029
  let names = [];
6253
7030
  try {
6254
- names = readdirSync4(dir).filter((n) => n.endsWith(".json"));
7031
+ names = readdirSync5(dir).filter((n) => n.endsWith(".json"));
6255
7032
  } catch {
6256
7033
  return;
6257
7034
  }
6258
7035
  for (const n of names) {
6259
- const p = join24(dir, n);
7036
+ const p = join25(dir, n);
6260
7037
  let rec;
6261
7038
  try {
6262
- rec = JSON.parse(readFileSync18(p, "utf8"));
7039
+ rec = JSON.parse(readFileSync19(p, "utf8"));
6263
7040
  } catch {
6264
- rmSync11(p, { force: true });
7041
+ rmSync14(p, { force: true });
6265
7042
  continue;
6266
7043
  }
6267
7044
  if (typeof rec.supervisor !== "number" || typeof rec.server !== "number" || typeof rec.url !== "string") {
6268
- rmSync11(p, { force: true });
7045
+ rmSync14(p, { force: true });
6269
7046
  continue;
6270
7047
  }
6271
7048
  if (alive(rec.supervisor)) continue;
@@ -6273,13 +7050,13 @@ function reapWindows(dir = windowsDir2(), alive = pidAlive, args = processArgs,
6273
7050
  const a = args(rec.server);
6274
7051
  if (a && isAppServerArgs(a, rec.url)) kill(rec.server);
6275
7052
  }
6276
- rmSync11(p, { force: true });
7053
+ rmSync14(p, { force: true });
6277
7054
  }
6278
7055
  }
6279
7056
  function launchLog(line) {
6280
7057
  try {
6281
- mkdirSync19(deskbuddyHome(), { recursive: true });
6282
- writeFileSync19(join24(deskbuddyHome(), "wrap.log"), `${(/* @__PURE__ */ new Date()).toISOString()} [codex ${process.pid}] ${line}
7058
+ mkdirSync20(deskbuddyHome(), { recursive: true });
7059
+ writeFileSync20(join25(deskbuddyHome(), "wrap.log"), `${(/* @__PURE__ */ new Date()).toISOString()} [codex ${process.pid}] ${line}
6283
7060
  `, { flag: "a" });
6284
7061
  } catch {
6285
7062
  }
@@ -6319,10 +7096,10 @@ function childEnv(src, windowId) {
6319
7096
  }
6320
7097
  function wrapLog(line) {
6321
7098
  try {
6322
- const p = join25(deskbuddyHome(), "wrap.log");
6323
- mkdirSync20(deskbuddyHome(), { recursive: true });
7099
+ const p = join26(deskbuddyHome(), "wrap.log");
7100
+ mkdirSync21(deskbuddyHome(), { recursive: true });
6324
7101
  try {
6325
- if (statSync9(p).size > MAX_LOG_BYTES3) truncateSync3(p, 0);
7102
+ if (statSync10(p).size > MAX_LOG_BYTES3) truncateSync3(p, 0);
6326
7103
  } catch {
6327
7104
  }
6328
7105
  appendFileSync2(p, `${(/* @__PURE__ */ new Date()).toISOString()} [${process.pid}] ${line}
@@ -6409,7 +7186,7 @@ async function wrapInPty(tool, real2, args, deps) {
6409
7186
  return null;
6410
7187
  }
6411
7188
  const { stdout: stdout7 } = deps;
6412
- const windowId = randomBytes6(16).toString("hex");
7189
+ const windowId = randomBytes7(16).toString("hex");
6413
7190
  const cols = stdout7.columns || 80;
6414
7191
  const rows = stdout7.rows || 24;
6415
7192
  const term = new Headless({ cols, rows, allowProposedApi: true });