deskbuddy 0.2.0-beta.2 → 0.2.0-beta.4

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 writeFileSync17, rmSync as rmSync12 } 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 randomBytes7 } from "crypto";
410
410
  import { existsSync as existsSync10 } from "fs";
411
411
  import { basename as basename6 } from "path";
412
412
  import WebSocket2 from "ws";
@@ -454,8 +454,8 @@ function isOcPluginInstalled(pluginPath) {
454
454
  var OC_NOTICE = " \u5DF2\u5F00\u7740\u7684 opencode \u7A97\u53E3\u91CD\u5F00\u540E\u751F\u6548";
455
455
 
456
456
  // src/core/state.ts
457
- import { existsSync as existsSync4, mkdirSync as mkdirSync4, readdirSync, readFileSync as readFileSync4, renameSync as renameSync3, rmSync as rmSync2, writeFileSync as writeFileSync4 } from "fs";
458
- import { join as join4 } from "path";
457
+ import { existsSync as existsSync4, mkdirSync as mkdirSync5, readdirSync as readdirSync2, readFileSync as readFileSync5, renameSync as renameSync4, rmSync as rmSync3, writeFileSync as writeFileSync5 } from "fs";
458
+ import { join as join5 } from "path";
459
459
 
460
460
  // src/core/process.ts
461
461
  import { execFileSync as execFileSync2 } from "child_process";
@@ -491,42 +491,167 @@ function isAlive(pid, expectName) {
491
491
  return name != null && name.toLowerCase().includes(expectName.toLowerCase());
492
492
  }
493
493
 
494
+ // src/core/ended.ts
495
+ import { closeSync, mkdirSync as mkdirSync4, openSync, readdirSync, readFileSync as readFileSync4, readSync, realpathSync, renameSync as renameSync3, rmSync as rmSync2, statSync, writeFileSync as writeFileSync4 } from "fs";
496
+ import { join as join4 } from "path";
497
+ var ENDED_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
498
+ var SID_RE = /^[A-Za-z0-9_-]{1,64}$/;
499
+ function endedDir(home = deskbuddyHome()) {
500
+ return join4(home, "ended");
501
+ }
502
+ function writeEnded(s, now = Date.now()) {
503
+ if (!SID_RE.test(s.sessionId)) return;
504
+ try {
505
+ const dir = endedDir();
506
+ mkdirSync4(dir, { recursive: true, mode: 448 });
507
+ const rec = { sessionId: s.sessionId, tool: s.tool, cwd: s.cwd ?? "", interactive: s.interactive === true, endedAt: now };
508
+ const file = join4(dir, `${s.sessionId}.json`);
509
+ const tmp = `${file}.${process.pid}.tmp`;
510
+ writeFileSync4(tmp, JSON.stringify(rec), { encoding: "utf8", mode: 384 });
511
+ renameSync3(tmp, file);
512
+ for (const n of readdirSync(dir)) {
513
+ const f = join4(dir, n);
514
+ try {
515
+ if (now - statSync(f).mtimeMs > ENDED_TTL_MS) rmSync2(f, { force: true });
516
+ } catch {
517
+ }
518
+ }
519
+ } catch {
520
+ }
521
+ }
522
+ function readEnded(sessionId, now = Date.now()) {
523
+ if (!SID_RE.test(sessionId)) return null;
524
+ try {
525
+ const r = JSON.parse(readFileSync4(join4(endedDir(), `${sessionId}.json`), "utf8"));
526
+ if (!r || r.sessionId !== sessionId || typeof r.endedAt !== "number" || now - r.endedAt > ENDED_TTL_MS) return null;
527
+ return r;
528
+ } catch {
529
+ return null;
530
+ }
531
+ }
532
+ var TRANSCRIPT_TAIL = 512 * 1024;
533
+ var TRANSCRIPT_HEAD = 64 * 1024;
534
+ function projectDirName(cwd) {
535
+ return cwd.replace(/[^A-Za-z0-9]/g, "-");
536
+ }
537
+ function readChunk(file, fromEnd, max) {
538
+ let fd;
539
+ try {
540
+ const size = statSync(file).size;
541
+ const len = Math.min(size, max);
542
+ const buf = Buffer.alloc(len);
543
+ fd = openSync(file, "r");
544
+ readSync(fd, buf, 0, len, fromEnd ? size - len : 0);
545
+ return buf.toString("utf8");
546
+ } finally {
547
+ if (fd !== void 0) closeSync(fd);
548
+ }
549
+ }
550
+ function cwdsIn(text) {
551
+ const out = [];
552
+ for (const line of text.split("\n")) {
553
+ if (line.indexOf('"cwd"') === -1) continue;
554
+ try {
555
+ const o = JSON.parse(line);
556
+ if (typeof o.cwd === "string" && o.cwd) out.push(o.cwd);
557
+ } catch {
558
+ }
559
+ }
560
+ return out;
561
+ }
562
+ function claudeTranscriptCwd(sessionId, home = claudeHome()) {
563
+ if (!SID_RE.test(sessionId)) return "";
564
+ let dirs = [];
565
+ try {
566
+ dirs = readdirSync(join4(home, "projects"));
567
+ } catch {
568
+ return "";
569
+ }
570
+ for (const d of dirs) {
571
+ const file = join4(home, "projects", d, `${sessionId}.jsonl`);
572
+ try {
573
+ statSync(file);
574
+ } catch {
575
+ continue;
576
+ }
577
+ try {
578
+ const tail = cwdsIn(readChunk(file, true, TRANSCRIPT_TAIL));
579
+ for (let i = tail.length - 1; i >= 0; i--) if (projectDirName(tail[i]).toLowerCase() === d.toLowerCase()) return tail[i];
580
+ return cwdsIn(readChunk(file, false, TRANSCRIPT_HEAD))[0] ?? "";
581
+ } catch {
582
+ return "";
583
+ }
584
+ }
585
+ return "";
586
+ }
587
+ function canonDir(p, platform = process.platform) {
588
+ if (platform === "win32") return p.replace(/\//g, "\\").replace(/\\+$/, "").toLowerCase();
589
+ let r = p;
590
+ try {
591
+ r = realpathSync(p);
592
+ } catch {
593
+ }
594
+ return r.length > 1 ? r.replace(/\/+$/, "") : r;
595
+ }
596
+ function resolveEndedCwd(sessionId, tool, d) {
597
+ const rec = d.readEnded(sessionId);
598
+ const a = rec && rec.tool === tool ? rec.cwd || "" : "";
599
+ const b = tool === "claude_code" ? d.transcriptCwd(sessionId) : "";
600
+ const canon = d.canon ?? ((p) => canonDir(p));
601
+ if (a && b && canon(a) !== canon(b)) return { ok: false, reason: "no_cwd" };
602
+ const cwd = a || b;
603
+ if (!cwd) return { ok: false, reason: "no_cwd" };
604
+ if (!d.isDir(cwd)) return { ok: false, reason: "cwd_gone" };
605
+ return { ok: true, cwd };
606
+ }
607
+ function isDirSync(p) {
608
+ try {
609
+ return statSync(p).isDirectory();
610
+ } catch {
611
+ return false;
612
+ }
613
+ }
614
+
494
615
  // src/core/state.ts
495
616
  function stateDir() {
496
- return join4(deskbuddyHome(), "state");
617
+ return join5(deskbuddyHome(), "state");
497
618
  }
498
619
  function statePath(sessionId) {
499
620
  const safe = sessionId.replace(/[^A-Za-z0-9_-]/g, "");
500
- return join4(stateDir(), `${safe || "unknown"}.json`);
621
+ return join5(stateDir(), `${safe || "unknown"}.json`);
501
622
  }
502
623
  function readState(sessionId) {
503
624
  try {
504
- return JSON.parse(readFileSync4(statePath(sessionId), "utf8"));
625
+ return JSON.parse(readFileSync5(statePath(sessionId), "utf8"));
505
626
  } catch {
506
627
  return null;
507
628
  }
508
629
  }
630
+ function retireState(s, now = Date.now()) {
631
+ writeEnded(s, now);
632
+ removeState(s.sessionId);
633
+ }
509
634
  function removeState(sessionId) {
510
635
  try {
511
- rmSync2(statePath(sessionId), { force: true });
636
+ rmSync3(statePath(sessionId), { force: true });
512
637
  } catch {
513
638
  }
514
639
  }
515
640
  function readAliveStates(tool) {
516
641
  if (!existsSync4(stateDir())) return [];
517
642
  const alive = [];
518
- for (const file of readdirSync(stateDir())) {
643
+ for (const file of readdirSync2(stateDir())) {
519
644
  if (!file.endsWith(".json")) continue;
520
645
  let s;
521
646
  try {
522
- s = JSON.parse(readFileSync4(join4(stateDir(), file), "utf8"));
647
+ s = JSON.parse(readFileSync5(join5(stateDir(), file), "utf8"));
523
648
  } catch {
524
- rmSync2(join4(stateDir(), file), { force: true });
649
+ rmSync3(join5(stateDir(), file), { force: true });
525
650
  continue;
526
651
  }
527
652
  if (s.tool !== tool) continue;
528
653
  if (s.pid && !isAlive(s.pid, s.pidName)) {
529
- removeState(s.sessionId);
654
+ retireState(s);
530
655
  continue;
531
656
  }
532
657
  alive.push(s);
@@ -534,16 +659,16 @@ function readAliveStates(tool) {
534
659
  return alive;
535
660
  }
536
661
  function noticeDir() {
537
- return join4(deskbuddyHome(), "notice");
662
+ return join5(deskbuddyHome(), "notice");
538
663
  }
539
664
  function writeNotice(sessionId, text, now = Date.now()) {
540
665
  try {
541
666
  const safe = sessionId.replace(/[^A-Za-z0-9_-]/g, "");
542
667
  if (!safe) return;
543
- mkdirSync4(noticeDir(), { recursive: true });
544
- const file = join4(noticeDir(), `${safe}.json`);
545
- writeFileSync4(file + ".tmp", JSON.stringify({ text, at: now }), "utf8");
546
- renameSync3(file + ".tmp", file);
668
+ mkdirSync5(noticeDir(), { recursive: true });
669
+ const file = join5(noticeDir(), `${safe}.json`);
670
+ writeFileSync5(file + ".tmp", JSON.stringify({ text, at: now }), "utf8");
671
+ renameSync4(file + ".tmp", file);
547
672
  } catch {
548
673
  }
549
674
  }
@@ -552,7 +677,7 @@ var ALIAS_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
552
677
  function resolveAlias(sessionId, now = Date.now()) {
553
678
  let map = {};
554
679
  try {
555
- map = JSON.parse(readFileSync4(join4(deskbuddyHome(), "alias.json"), "utf8")) || {};
680
+ map = JSON.parse(readFileSync5(join5(deskbuddyHome(), "alias.json"), "utf8")) || {};
556
681
  } catch {
557
682
  return sessionId;
558
683
  }
@@ -568,22 +693,22 @@ function resolveAlias(sessionId, now = Date.now()) {
568
693
  // src/core/api.ts
569
694
  import { execFileSync as execFileSync3 } from "child_process";
570
695
  import { createHash, randomBytes, randomUUID as randomUUID2 } from "crypto";
571
- import { readFileSync as readFileSync6 } from "fs";
696
+ import { readFileSync as readFileSync7 } from "fs";
572
697
  import { release } from "os";
573
698
 
574
699
  // src/core/hookFile.ts
575
- import { copyFileSync, existsSync as existsSync5, mkdirSync as mkdirSync5, readFileSync as readFileSync5, renameSync as renameSync4, writeFileSync as writeFileSync5 } from "fs";
700
+ import { copyFileSync, existsSync as existsSync5, mkdirSync as mkdirSync6, readFileSync as readFileSync6, renameSync as renameSync5, writeFileSync as writeFileSync6 } from "fs";
576
701
  import { createRequire } from "module";
577
- import { dirname as dirname3, join as join5 } from "path";
702
+ import { dirname as dirname3, join as join6 } from "path";
578
703
  import { fileURLToPath as fileURLToPath2 } from "url";
579
704
  var here = dirname3(fileURLToPath2(import.meta.url));
580
705
  function packagedHookPath() {
581
706
  const candidates = [
582
- join5(here, "hook.cjs"),
707
+ join6(here, "hook.cjs"),
583
708
  // dist/cli.js 旁边(发布形态)
584
- join5(here, "..", "hook", "hook.cjs"),
709
+ join6(here, "..", "hook", "hook.cjs"),
585
710
  // dist/../hook/
586
- join5(here, "..", "..", "hook", "hook.cjs")
711
+ join6(here, "..", "..", "hook", "hook.cjs")
587
712
  // src/core/../../hook/(开发形态)
588
713
  ];
589
714
  for (const c of candidates) {
@@ -603,11 +728,11 @@ function packageVersion() {
603
728
  return "0.0.0-dev";
604
729
  }
605
730
  function hookVersionPath(target = installedHookPath()) {
606
- return join5(dirname3(target), "hook-version");
731
+ return join6(dirname3(target), "hook-version");
607
732
  }
608
733
  function installedHookVersion(target = installedHookPath()) {
609
734
  try {
610
- return readFileSync5(hookVersionPath(target), "utf8").trim() || null;
735
+ return readFileSync6(hookVersionPath(target), "utf8").trim() || null;
611
736
  } catch {
612
737
  return null;
613
738
  }
@@ -615,29 +740,29 @@ function installedHookVersion(target = installedHookPath()) {
615
740
  function atomicCopy(from, to) {
616
741
  const tmp = `${to}.${process.pid}.tmp`;
617
742
  copyFileSync(from, tmp);
618
- renameSync4(tmp, to);
743
+ renameSync5(tmp, to);
619
744
  }
620
745
  function syncHookFile(opts = {}) {
621
746
  const target = opts.target ?? installedHookPath();
622
747
  const version = opts.version ?? packageVersion();
623
- const SIBLINGS = ["gate.cjs", "opencode.mjs"];
748
+ const SIBLINGS = ["gate.cjs", "opencode.mjs", "wake.cjs"];
624
749
  const siblingMissing = () => SIBLINGS.some((name) => {
625
- if (existsSync5(join5(dirname3(target), name))) return false;
750
+ if (existsSync5(join6(dirname3(target), name))) return false;
626
751
  try {
627
- return existsSync5(join5(dirname3(opts.source ?? packagedHookPath()), name));
752
+ return existsSync5(join6(dirname3(opts.source ?? packagedHookPath()), name));
628
753
  } catch {
629
754
  return false;
630
755
  }
631
756
  });
632
757
  if (installedHookVersion(target) === version && existsSync5(target) && !siblingMissing()) return false;
633
758
  const source = opts.source ?? packagedHookPath();
634
- mkdirSync5(dirname3(target), { recursive: true });
759
+ mkdirSync6(dirname3(target), { recursive: true });
635
760
  atomicCopy(source, target);
636
761
  for (const name of SIBLINGS) {
637
- const from = join5(dirname3(source), name);
638
- if (existsSync5(from)) atomicCopy(from, join5(dirname3(target), name));
762
+ const from = join6(dirname3(source), name);
763
+ if (existsSync5(from)) atomicCopy(from, join6(dirname3(target), name));
639
764
  }
640
- writeFileSync5(hookVersionPath(target), version, "utf8");
765
+ writeFileSync6(hookVersionPath(target), version, "utf8");
641
766
  return true;
642
767
  }
643
768
 
@@ -724,7 +849,7 @@ var defaultOsVersionDeps = {
724
849
  stdio: ["ignore", "pipe", "ignore"],
725
850
  timeout: 2e3
726
851
  }),
727
- readOsRelease: () => readFileSync6("/etc/os-release", "utf8"),
852
+ readOsRelease: () => readFileSync7("/etc/os-release", "utf8"),
728
853
  kernelRelease: () => release()
729
854
  };
730
855
  function productOsVersion(platform = process.platform, deps = defaultOsVersionDeps) {
@@ -808,7 +933,7 @@ async function postReport(payload) {
808
933
  import { basename as basename2 } from "path";
809
934
  var KEEPALIVE_MS = 2 * 60 * 1e3;
810
935
  var MAX_SESSIONS = 20;
811
- var CAPS = ["cmd_result", "window_inject"];
936
+ var CAPS = ["cmd_result", "window_inject", "resume_ended"];
812
937
  function buildSnapshot(states, tool, cfg, nowMs, remote) {
813
938
  const sessions = states.filter((s) => s.tool === tool).sort((a, b) => b.updatedAt - a.updatedAt).slice(0, MAX_SESSIONS).map((s) => ({
814
939
  sessionId: s.sessionId,
@@ -826,23 +951,23 @@ function buildSnapshot(states, tool, cfg, nowMs, remote) {
826
951
 
827
952
  // src/core/dirs.ts
828
953
  import { createHmac, randomBytes as randomBytes2 } from "crypto";
829
- import { chmodSync, existsSync as existsSync6, mkdirSync as mkdirSync6, readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "fs";
830
- import { basename as basename3, join as join6 } from "path";
954
+ import { chmodSync, existsSync as existsSync6, mkdirSync as mkdirSync7, readFileSync as readFileSync8, writeFileSync as writeFileSync7 } from "fs";
955
+ import { basename as basename3, join as join7 } from "path";
831
956
  var DIRS_REPORT_MAX = 10;
832
957
  function dirsPath() {
833
- return join6(deskbuddyHome(), "dirs.json");
958
+ return join7(deskbuddyHome(), "dirs.json");
834
959
  }
835
960
  function dirSecret() {
836
- const file = join6(deskbuddyHome(), "dir-secret");
961
+ const file = join7(deskbuddyHome(), "dir-secret");
837
962
  try {
838
- const s2 = readFileSync7(file, "utf8").trim();
963
+ const s2 = readFileSync8(file, "utf8").trim();
839
964
  if (s2) return s2;
840
965
  } catch {
841
966
  }
842
967
  const s = randomBytes2(32).toString("hex");
843
968
  try {
844
- mkdirSync6(deskbuddyHome(), { recursive: true });
845
- writeFileSync6(file, s, { encoding: "utf8", mode: 384 });
969
+ mkdirSync7(deskbuddyHome(), { recursive: true });
970
+ writeFileSync7(file, s, { encoding: "utf8", mode: 384 });
846
971
  chmodSync(file, 384);
847
972
  } catch {
848
973
  }
@@ -853,7 +978,7 @@ function dirRef(path) {
853
978
  }
854
979
  function readMap() {
855
980
  try {
856
- const m = JSON.parse(readFileSync7(dirsPath(), "utf8"));
981
+ const m = JSON.parse(readFileSync8(dirsPath(), "utf8"));
857
982
  const out = {};
858
983
  for (const [k, v] of Object.entries(m ?? {})) if (typeof v === "number") out[k] = v;
859
984
  return out;
@@ -951,7 +1076,7 @@ function isExpired(cmd, now = Date.now()) {
951
1076
  // src/daemon/executor.ts
952
1077
  import { execFile as execFile2, execFileSync as execFileSync4 } from "child_process";
953
1078
  import { homedir as homedir4 } from "os";
954
- import { join as join8 } from "path";
1079
+ import { join as join9 } from "path";
955
1080
 
956
1081
  // src/daemon/gate/settings.ts
957
1082
  import { existsSync as existsSync7 } from "fs";
@@ -1009,16 +1134,16 @@ function codexGateArgs(t) {
1009
1134
  }
1010
1135
 
1011
1136
  // src/daemon/locate.ts
1012
- import { accessSync, constants, existsSync as existsSync8, readFileSync as readFileSync8, statSync } from "fs";
1137
+ import { accessSync, constants, existsSync as existsSync8, lstatSync, readFileSync as readFileSync9, statSync as statSync2 } from "fs";
1013
1138
  import { homedir as homedir3 } from "os";
1014
- import { delimiter, dirname as dirname4, join as join7 } from "path";
1139
+ import { delimiter, dirname as dirname4, join as join8 } from "path";
1015
1140
  var defaultLocateDeps = () => ({
1016
1141
  home: homedir3(),
1017
1142
  path: process.env.PATH ?? "",
1018
1143
  nodePath: process.execPath,
1019
1144
  isExecutable: (p) => {
1020
1145
  try {
1021
- if (!statSync(p).isFile()) return false;
1146
+ if (!statSync2(p).isFile()) return false;
1022
1147
  accessSync(p, constants.X_OK);
1023
1148
  return true;
1024
1149
  } catch {
@@ -1026,38 +1151,46 @@ var defaultLocateDeps = () => ({
1026
1151
  }
1027
1152
  },
1028
1153
  exists: (p) => existsSync8(p),
1154
+ lexists: (p) => {
1155
+ try {
1156
+ lstatSync(p);
1157
+ return true;
1158
+ } catch {
1159
+ return false;
1160
+ }
1161
+ },
1029
1162
  platform: process.platform,
1030
1163
  appData: process.env.APPDATA ?? "",
1031
1164
  localAppData: process.env.LOCALAPPDATA ?? "",
1032
1165
  readText: (p) => {
1033
1166
  try {
1034
- return readFileSync8(p, "utf8");
1167
+ return readFileSync9(p, "utf8");
1035
1168
  } catch {
1036
1169
  return null;
1037
1170
  }
1038
1171
  },
1039
1172
  // 🔴 daemon 自己也不能找到垫片:远程任务要的是真程序(窗口接管 spec 2026-09-30 §3.1)
1040
- skipDirs: [join7(deskbuddyHome(), "bin")]
1173
+ skipDirs: [join8(deskbuddyHome(), "bin")]
1041
1174
  });
1042
1175
  var isWin = (d) => (d.platform ?? process.platform) === "win32";
1043
1176
  var WIN_EXTS = [".exe", ".cmd", ".bat"];
1044
1177
  function extraBinDirs(home, nodePath = "", win) {
1045
1178
  if (win) {
1046
1179
  return [
1047
- join7(home, ".local", "bin"),
1048
- ...win.appData ? [join7(win.appData, "npm")] : [],
1049
- join7(home, ".bun", "bin"),
1050
- ...win.localAppData ? [join7(win.localAppData, "Volta", "bin")] : [],
1051
- join7(home, "scoop", "shims"),
1180
+ join8(home, ".local", "bin"),
1181
+ ...win.appData ? [join8(win.appData, "npm")] : [],
1182
+ join8(home, ".bun", "bin"),
1183
+ ...win.localAppData ? [join8(win.localAppData, "Volta", "bin")] : [],
1184
+ join8(home, "scoop", "shims"),
1052
1185
  ...nodePath ? [dirname4(nodePath)] : []
1053
1186
  ];
1054
1187
  }
1055
1188
  return [
1056
- join7(home, ".local", "bin"),
1057
- join7(home, ".claude", "local"),
1058
- join7(home, ".npm-global", "bin"),
1059
- join7(home, ".bun", "bin"),
1060
- join7(home, ".volta", "bin"),
1189
+ join8(home, ".local", "bin"),
1190
+ join8(home, ".claude", "local"),
1191
+ join8(home, ".npm-global", "bin"),
1192
+ join8(home, ".bun", "bin"),
1193
+ join8(home, ".volta", "bin"),
1061
1194
  "/opt/homebrew/bin",
1062
1195
  "/usr/local/bin",
1063
1196
  ...nodePath ? [dirname4(nodePath)] : []
@@ -1071,7 +1204,7 @@ function whichAllIn(name, d) {
1071
1204
  const out = [];
1072
1205
  for (const dir of binDirs(d)) {
1073
1206
  for (const ext of exts) {
1074
- const p = join7(dir, name + ext);
1207
+ const p = join8(dir, name + ext);
1075
1208
  if (d.isExecutable(p)) out.push(p);
1076
1209
  }
1077
1210
  }
@@ -1095,7 +1228,7 @@ function binDirs(d) {
1095
1228
  function parseNpmCmdShim(shimPath, text) {
1096
1229
  const m = text.match(/"%~?dp0%?\\?([^"%]+)"\s+%\*/i);
1097
1230
  if (!m) return null;
1098
- return join7(dirname4(shimPath), m[1].replace(/^\\/, ""));
1231
+ return join8(dirname4(shimPath), m[1].replace(/^\\/, ""));
1099
1232
  }
1100
1233
  function resolveWinLaunch(file, d) {
1101
1234
  const lower = file.toLowerCase();
@@ -1113,16 +1246,22 @@ function resolveWinLaunch(file, d) {
1113
1246
  return null;
1114
1247
  }
1115
1248
  function appDirs(home) {
1116
- return ["/Applications", join7(home, "Applications")];
1249
+ return ["/Applications", join8(home, "Applications")];
1117
1250
  }
1118
1251
  function winAppDirs(d, product) {
1119
1252
  return [
1120
- ...d.localAppData ? [join7(d.localAppData, "Programs", product)] : [],
1121
- join7("C:\\Program Files", product)
1253
+ ...d.localAppData ? [join8(d.localAppData, "Programs", product)] : [],
1254
+ join8("C:\\Program Files", product)
1122
1255
  ];
1123
1256
  }
1124
1257
  var WORKBUDDY_CLI_IN_WIN_APP = "resources/app.asar.unpacked/cli/bin/codebuddy";
1125
1258
  var QODER_CN_WORKER_IN_WIN_APP = "resources/app.asar.unpacked/node_modules/@qoder-ai/qoder-cn-agent-sdk/dist/_worker/qoder-worker-runtime.obf.mjs";
1259
+ function winClaudeExeCandidates(d) {
1260
+ return [
1261
+ ...d.home ? [join8(d.home, ".local", "bin", "claude.exe")] : [],
1262
+ ...d.appData ? [join8(d.appData, "npm", "node_modules", "@anthropic-ai", "claude-code", "bin", "claude.exe")] : []
1263
+ ];
1264
+ }
1126
1265
  function locateWin(tool, d) {
1127
1266
  const plain = (name) => {
1128
1267
  for (const f of whichAllIn(name, d)) {
@@ -1133,13 +1272,14 @@ function locateWin(tool, d) {
1133
1272
  };
1134
1273
  switch (tool) {
1135
1274
  case "claude_code":
1275
+ for (const p of winClaudeExeCandidates(d)) if (d.exists(p)) return { file: p, prefixArgs: [] };
1136
1276
  return plain("claude");
1137
1277
  case "codex":
1138
1278
  return plain("codex");
1139
1279
  case "workbuddy": {
1140
1280
  for (const dir of winAppDirs(d, "WorkBuddy")) {
1141
1281
  for (const ext of [".exe", ".cmd", ""]) {
1142
- const p = join7(dir, WORKBUDDY_CLI_IN_WIN_APP + ext);
1282
+ const p = join8(dir, WORKBUDDY_CLI_IN_WIN_APP + ext);
1143
1283
  if (d.exists(p)) {
1144
1284
  const r = resolveWinLaunch(p, d);
1145
1285
  if (r) return r;
@@ -1150,13 +1290,13 @@ function locateWin(tool, d) {
1150
1290
  }
1151
1291
  case "qoder": {
1152
1292
  for (const dir of winAppDirs(d, "Qoder CN")) {
1153
- const p = join7(dir, QODER_CN_WORKER_IN_WIN_APP);
1293
+ const p = join8(dir, QODER_CN_WORKER_IN_WIN_APP);
1154
1294
  if (d.exists(p)) return { file: d.nodePath, prefixArgs: [p] };
1155
1295
  }
1156
1296
  return plain("qodercli");
1157
1297
  }
1158
1298
  case "opencode": {
1159
- const p = join7(d.home, ".opencode", "bin", "opencode.exe");
1299
+ const p = join8(d.home, ".opencode", "bin", "opencode.exe");
1160
1300
  if (d.exists(p)) return { file: p, prefixArgs: [] };
1161
1301
  return plain("opencode");
1162
1302
  }
@@ -1179,20 +1319,20 @@ function locate(tool, d = defaultLocateDeps()) {
1179
1319
  return plain("codex");
1180
1320
  case "workbuddy": {
1181
1321
  for (const dir of appDirs(d.home)) {
1182
- const p = join7(dir, WORKBUDDY_CLI_IN_APP);
1322
+ const p = join8(dir, WORKBUDDY_CLI_IN_APP);
1183
1323
  if (d.isExecutable(p)) return { file: p, prefixArgs: [] };
1184
1324
  }
1185
1325
  return plain("codebuddy");
1186
1326
  }
1187
1327
  case "qoder": {
1188
1328
  for (const dir of appDirs(d.home)) {
1189
- const p = join7(dir, QODER_CN_WORKER_IN_APP);
1329
+ const p = join8(dir, QODER_CN_WORKER_IN_APP);
1190
1330
  if (d.exists(p)) return { file: d.nodePath, prefixArgs: [p] };
1191
1331
  }
1192
1332
  return plain("qodercli");
1193
1333
  }
1194
1334
  case "opencode": {
1195
- const p = join7(d.home, ".opencode", "bin", "opencode");
1335
+ const p = join8(d.home, ".opencode", "bin", "opencode");
1196
1336
  if (d.isExecutable(p)) return { file: p, prefixArgs: [] };
1197
1337
  return plain("opencode");
1198
1338
  }
@@ -1205,6 +1345,13 @@ function childPath(d = defaultLocateDeps()) {
1205
1345
  const key = (p) => isWin(d) ? p.toLowerCase() : p;
1206
1346
  return binDirs(d).filter((p) => p && !seen.has(key(p)) && (seen.add(key(p)), true)).join(isWin(d) ? ";" : delimiter);
1207
1347
  }
1348
+ function findWt(d = defaultLocateDeps()) {
1349
+ if (!isWin(d)) return null;
1350
+ const hit = whichIn("wt", d);
1351
+ if (hit) return hit;
1352
+ const alias = d.localAppData ? join8(d.localAppData, "Microsoft", "WindowsApps", "wt.exe") : "";
1353
+ return alias && (d.exists(alias) || (d.lexists?.(alias) ?? false)) ? alias : null;
1354
+ }
1208
1355
 
1209
1356
  // src/daemon/executor.ts
1210
1357
  var EXEC_TIMEOUT_MS = 10 * 60 * 1e3;
@@ -1284,7 +1431,7 @@ function buildSpawnPlan(cmd, cwd, gate, home = homedir4()) {
1284
1431
  case "codex":
1285
1432
  return { file, args: codexPlan(cmd, gate), cwd };
1286
1433
  case "workbuddy":
1287
- return { file, args: workbuddyPlan(cmd, gate), cwd, env: { CODEBUDDY_CONFIG_DIR: join8(home, ".workbuddy") } };
1434
+ return { file, args: workbuddyPlan(cmd, gate), cwd, env: { CODEBUDDY_CONFIG_DIR: join9(home, ".workbuddy") } };
1288
1435
  case "qoder":
1289
1436
  return gate ? { file, args: qoderPlan(cmd, gate), cwd } : null;
1290
1437
  case "opencode":
@@ -1295,7 +1442,7 @@ function buildSpawnPlan(cmd, cwd, gate, home = homedir4()) {
1295
1442
  env: { DESKBUDDY_GATE_RUN: gate.runId, DESKBUDDY_NODE: gate.nodePath, OPENCODE_CONFIG_CONTENT: OPENCODE_DENY_ALL }
1296
1443
  } : null;
1297
1444
  default:
1298
- return { file, args: claudePlan(cmd, gate), cwd };
1445
+ return { file, args: claudePlan(cmd, gate), cwd, env: { DESKBUDDY_NO_WAKE: "1" } };
1299
1446
  }
1300
1447
  }
1301
1448
  function materialize(plan, tool, d = defaultLocateDeps()) {
@@ -1451,8 +1598,8 @@ function killTree(child) {
1451
1598
 
1452
1599
  // src/daemon/gate/approvals.ts
1453
1600
  import { createHash as createHash2 } from "crypto";
1454
- import { mkdirSync as mkdirSync7, readFileSync as readFileSync9, renameSync as renameSync5, writeFileSync as writeFileSync7 } from "fs";
1455
- import { join as join9 } from "path";
1601
+ import { mkdirSync as mkdirSync8, readFileSync as readFileSync10, renameSync as renameSync6, writeFileSync as writeFileSync8 } from "fs";
1602
+ import { join as join10 } from "path";
1456
1603
  var RETAIN_MS = 7 * 24 * 60 * 60 * 1e3;
1457
1604
  function fingerprint(toolName, toolInput) {
1458
1605
  let input = "";
@@ -1464,7 +1611,7 @@ function fingerprint(toolName, toolInput) {
1464
1611
  return createHash2("sha256").update(toolName).update("\0").update(input).digest("hex");
1465
1612
  }
1466
1613
  function storePath() {
1467
- return join9(deskbuddyHome(), "approvals.json");
1614
+ return join10(deskbuddyHome(), "approvals.json");
1468
1615
  }
1469
1616
  var ApprovalStore = class _ApprovalStore {
1470
1617
  #records;
@@ -1473,7 +1620,7 @@ var ApprovalStore = class _ApprovalStore {
1473
1620
  }
1474
1621
  static load() {
1475
1622
  try {
1476
- const raw = JSON.parse(readFileSync9(storePath(), "utf8"));
1623
+ const raw = JSON.parse(readFileSync10(storePath(), "utf8"));
1477
1624
  return new _ApprovalStore(Array.isArray(raw) ? raw : []);
1478
1625
  } catch {
1479
1626
  return new _ApprovalStore();
@@ -1481,10 +1628,10 @@ var ApprovalStore = class _ApprovalStore {
1481
1628
  }
1482
1629
  save() {
1483
1630
  try {
1484
- mkdirSync7(deskbuddyHome(), { recursive: true });
1631
+ mkdirSync8(deskbuddyHome(), { recursive: true });
1485
1632
  const tmp = storePath() + ".tmp";
1486
- writeFileSync7(tmp, JSON.stringify(this.#records), { encoding: "utf8", mode: 384 });
1487
- renameSync5(tmp, storePath());
1633
+ writeFileSync8(tmp, JSON.stringify(this.#records), { encoding: "utf8", mode: 384 });
1634
+ renameSync6(tmp, storePath());
1488
1635
  } catch {
1489
1636
  }
1490
1637
  }
@@ -1565,8 +1712,8 @@ function mapStatus(s) {
1565
1712
 
1566
1713
  // src/daemon/gate/coordinator.ts
1567
1714
  import { randomUUID as randomUUID3 } from "crypto";
1568
- import { existsSync as existsSync9, realpathSync } from "fs";
1569
- import { basename as basename5, dirname as dirname5, join as join10 } from "path";
1715
+ import { existsSync as existsSync9, realpathSync as realpathSync2 } from "fs";
1716
+ import { basename as basename5, dirname as dirname5, join as join11 } from "path";
1570
1717
 
1571
1718
  // src/daemon/gate/classify.ts
1572
1719
  import { isAbsolute, relative, resolve, sep } from "path";
@@ -1590,11 +1737,11 @@ function str2(v) {
1590
1737
  return typeof v === "string" ? v : "";
1591
1738
  }
1592
1739
  function truncate(s, max = SUMMARY_MAX) {
1593
- const oneLine = s.replace(/\s+/g, " ").trim();
1594
- if (oneLine.length <= max) return oneLine;
1740
+ const oneLine2 = s.replace(/\s+/g, " ").trim();
1741
+ if (oneLine2.length <= max) return oneLine2;
1595
1742
  const keep = max - 3;
1596
1743
  const head = Math.ceil(keep * 0.55);
1597
- return `${oneLine.slice(0, head)} \u2026 ${oneLine.slice(oneLine.length - (keep - head))}`;
1744
+ return `${oneLine2.slice(0, head)} \u2026 ${oneLine2.slice(oneLine2.length - (keep - head))}`;
1598
1745
  }
1599
1746
  var PROTECTED_DIRS = /* @__PURE__ */ new Set([
1600
1747
  ".git",
@@ -2220,7 +2367,7 @@ function realpathNearest(p) {
2220
2367
  const tail = [];
2221
2368
  for (let i = 0; i < 64; i++) {
2222
2369
  try {
2223
- if (existsSync9(cur)) return join10(realpathSync(cur), ...tail.reverse());
2370
+ if (existsSync9(cur)) return join11(realpathSync2(cur), ...tail.reverse());
2224
2371
  } catch {
2225
2372
  return p;
2226
2373
  }
@@ -2233,7 +2380,7 @@ function realpathNearest(p) {
2233
2380
  }
2234
2381
  function cwdForms(cwd) {
2235
2382
  try {
2236
- const real2 = realpathSync(cwd);
2383
+ const real2 = realpathSync2(cwd);
2237
2384
  return real2 === cwd ? [cwd] : [cwd, real2];
2238
2385
  } catch {
2239
2386
  return [cwd];
@@ -2243,11 +2390,11 @@ function cwdForms(cwd) {
2243
2390
  // src/daemon/gate/server.ts
2244
2391
  import { randomBytes as randomBytes3, timingSafeEqual } from "crypto";
2245
2392
  import { createServer } from "http";
2246
- import { mkdirSync as mkdirSync8, renameSync as renameSync6, rmSync as rmSync3, writeFileSync as writeFileSync8 } from "fs";
2247
- import { join as join11 } from "path";
2393
+ import { mkdirSync as mkdirSync9, renameSync as renameSync7, rmSync as rmSync4, writeFileSync as writeFileSync9 } from "fs";
2394
+ import { join as join12 } from "path";
2248
2395
  var MAX_BODY_BYTES = 8 * 1024 * 1024;
2249
2396
  function gateConfPath() {
2250
- return join11(deskbuddyHome(), "gate.json");
2397
+ return join12(deskbuddyHome(), "gate.json");
2251
2398
  }
2252
2399
  function tokenEquals(a, b) {
2253
2400
  const x = Buffer.from(a);
@@ -2274,18 +2421,18 @@ var GateServer = class {
2274
2421
  this.#server = server;
2275
2422
  const addr = server.address();
2276
2423
  const port = typeof addr === "object" && addr ? addr.port : 0;
2277
- mkdirSync8(deskbuddyHome(), { recursive: true });
2424
+ mkdirSync9(deskbuddyHome(), { recursive: true });
2278
2425
  const tmp = gateConfPath() + ".tmp";
2279
- writeFileSync8(tmp, JSON.stringify({ port, token: this.#token, pid: process.pid }), {
2426
+ writeFileSync9(tmp, JSON.stringify({ port, token: this.#token, pid: process.pid }), {
2280
2427
  encoding: "utf8",
2281
2428
  mode: 384
2282
2429
  });
2283
- renameSync6(tmp, gateConfPath());
2430
+ renameSync7(tmp, gateConfPath());
2284
2431
  return port;
2285
2432
  }
2286
2433
  async stop() {
2287
2434
  try {
2288
- rmSync3(gateConfPath(), { force: true });
2435
+ rmSync4(gateConfPath(), { force: true });
2289
2436
  } catch {
2290
2437
  }
2291
2438
  const s = this.#server;
@@ -2395,15 +2542,15 @@ function buildHandshakeUrl(cfg, opts = {}) {
2395
2542
  }
2396
2543
 
2397
2544
  // src/daemon/log.ts
2398
- import { appendFileSync, mkdirSync as mkdirSync9, statSync as statSync2, truncateSync } from "fs";
2399
- import { join as join12 } from "path";
2545
+ import { appendFileSync, mkdirSync as mkdirSync10, statSync as statSync3, truncateSync } from "fs";
2546
+ import { join as join13 } from "path";
2400
2547
  var MAX_LOG_BYTES = 2 * 1024 * 1024;
2401
2548
  function logPath() {
2402
- return join12(deskbuddyHome(), "daemon.log");
2549
+ return join13(deskbuddyHome(), "daemon.log");
2403
2550
  }
2404
2551
  function rotateIfNeeded(path) {
2405
2552
  try {
2406
- if (statSync2(path).size > MAX_LOG_BYTES) truncateSync(path, 0);
2553
+ if (statSync3(path).size > MAX_LOG_BYTES) truncateSync(path, 0);
2407
2554
  } catch {
2408
2555
  }
2409
2556
  }
@@ -2411,7 +2558,7 @@ function log(line) {
2411
2558
  const stamped = `${(/* @__PURE__ */ new Date()).toISOString()} ${line}
2412
2559
  `;
2413
2560
  try {
2414
- mkdirSync9(deskbuddyHome(), { recursive: true });
2561
+ mkdirSync10(deskbuddyHome(), { recursive: true });
2415
2562
  const p = logPath();
2416
2563
  rotateIfNeeded(p);
2417
2564
  appendFileSync(p, stamped, "utf8");
@@ -2437,10 +2584,14 @@ var SessionQueue = class {
2437
2584
  * rejected promise,后续所有指令都会跟着被拒 —— 一次失败让这个会话**永久**
2438
2585
  * 收不到指令,而且没有任何报错。
2439
2586
  */
2440
- enqueue(sessionId, job) {
2587
+ enqueue(sessionId, job, onError) {
2441
2588
  const key = sessionId || "";
2442
2589
  const prev = this.#tails.get(key) ?? Promise.resolve();
2443
- const next = prev.then(job).catch(() => {
2590
+ const next = prev.then(job).catch((e) => {
2591
+ try {
2592
+ onError?.(e);
2593
+ } catch {
2594
+ }
2444
2595
  });
2445
2596
  this.#tails.set(key, next);
2446
2597
  void next.then(() => {
@@ -2465,18 +2616,18 @@ var SessionQueue = class {
2465
2616
  };
2466
2617
 
2467
2618
  // src/daemon/remote.ts
2468
- import { mkdirSync as mkdirSync10, readFileSync as readFileSync10, renameSync as renameSync7, writeFileSync as writeFileSync9 } from "fs";
2469
- import { join as join13 } from "path";
2619
+ import { mkdirSync as mkdirSync11, readFileSync as readFileSync11, renameSync as renameSync8, writeFileSync as writeFileSync10 } from "fs";
2620
+ import { join as join14 } from "path";
2470
2621
  function handleDir() {
2471
- return join13(deskbuddyHome(), "remote");
2622
+ return join14(deskbuddyHome(), "remote");
2472
2623
  }
2473
2624
  function handlePath(tool) {
2474
2625
  const safe = (tool || "").replace(/[^0-9a-zA-Z_-]/g, "");
2475
- return join13(handleDir(), `${safe || "unknown"}.json`);
2626
+ return join14(handleDir(), `${safe || "unknown"}.json`);
2476
2627
  }
2477
2628
  function readHandle(tool) {
2478
2629
  try {
2479
- const h = JSON.parse(readFileSync10(handlePath(tool), "utf8"));
2630
+ const h = JSON.parse(readFileSync11(handlePath(tool), "utf8"));
2480
2631
  if (typeof h?.sessionId !== "string" || !h.sessionId) return null;
2481
2632
  if (typeof h?.cwd !== "string" || !h.cwd) return null;
2482
2633
  return h;
@@ -2486,10 +2637,10 @@ function readHandle(tool) {
2486
2637
  }
2487
2638
  function writeHandle(tool, handle) {
2488
2639
  try {
2489
- mkdirSync10(handleDir(), { recursive: true });
2640
+ mkdirSync11(handleDir(), { recursive: true });
2490
2641
  const tmp = handlePath(tool) + ".tmp";
2491
- writeFileSync9(tmp, JSON.stringify(handle), "utf8");
2492
- renameSync7(tmp, handlePath(tool));
2642
+ writeFileSync10(tmp, JSON.stringify(handle), "utf8");
2643
+ renameSync8(tmp, handlePath(tool));
2493
2644
  } catch {
2494
2645
  }
2495
2646
  }
@@ -2542,11 +2693,11 @@ function failureMessage(reason) {
2542
2693
  }
2543
2694
 
2544
2695
  // src/daemon/seen.ts
2545
- import { mkdirSync as mkdirSync11, readFileSync as readFileSync11, renameSync as renameSync8, writeFileSync as writeFileSync10 } from "fs";
2546
- import { join as join14 } from "path";
2696
+ import { mkdirSync as mkdirSync12, readFileSync as readFileSync12, renameSync as renameSync9, writeFileSync as writeFileSync11 } from "fs";
2697
+ import { join as join15 } from "path";
2547
2698
  var SEEN_CAPACITY = 200;
2548
2699
  function seenPath() {
2549
- return join14(deskbuddyHome(), "seen.json");
2700
+ return join15(deskbuddyHome(), "seen.json");
2550
2701
  }
2551
2702
  var SeenIds = class _SeenIds {
2552
2703
  #ids = [];
@@ -2577,7 +2728,7 @@ var SeenIds = class _SeenIds {
2577
2728
  }
2578
2729
  static load() {
2579
2730
  try {
2580
- const raw = JSON.parse(readFileSync11(seenPath(), "utf8"));
2731
+ const raw = JSON.parse(readFileSync12(seenPath(), "utf8"));
2581
2732
  return new _SeenIds(Array.isArray(raw?.ids) ? raw.ids.filter((x) => typeof x === "string") : []);
2582
2733
  } catch {
2583
2734
  return new _SeenIds();
@@ -2585,10 +2736,10 @@ var SeenIds = class _SeenIds {
2585
2736
  }
2586
2737
  save() {
2587
2738
  try {
2588
- mkdirSync11(deskbuddyHome(), { recursive: true });
2739
+ mkdirSync12(deskbuddyHome(), { recursive: true });
2589
2740
  const tmp = seenPath() + ".tmp";
2590
- writeFileSync10(tmp, JSON.stringify({ ids: this.#ids }), "utf8");
2591
- renameSync8(tmp, seenPath());
2741
+ writeFileSync11(tmp, JSON.stringify({ ids: this.#ids }), "utf8");
2742
+ renameSync9(tmp, seenPath());
2592
2743
  } catch {
2593
2744
  }
2594
2745
  }
@@ -3250,30 +3401,236 @@ var CodexWatch = class {
3250
3401
  };
3251
3402
 
3252
3403
  // src/core/codexRemote.ts
3253
- import { mkdirSync as mkdirSync12, renameSync as renameSync9, rmSync as rmSync4, writeFileSync as writeFileSync11 } from "fs";
3254
- import { join as join15 } from "path";
3404
+ import { mkdirSync as mkdirSync13, renameSync as renameSync10, rmSync as rmSync5, writeFileSync as writeFileSync12 } from "fs";
3405
+ import { join as join16 } from "path";
3255
3406
  function codexRemotePath() {
3256
- return join15(deskbuddyHome(), "codex-remote.json");
3407
+ return join16(deskbuddyHome(), "codex-remote.json");
3257
3408
  }
3258
3409
  function writeCodexRemote(threads, pid = process.pid, now = Date.now()) {
3259
3410
  try {
3260
- mkdirSync12(deskbuddyHome(), { recursive: true });
3411
+ mkdirSync13(deskbuddyHome(), { recursive: true });
3261
3412
  const p = codexRemotePath();
3262
- writeFileSync11(p + ".tmp", JSON.stringify({ pid, threads: [...threads], at: now }), "utf8");
3263
- renameSync9(p + ".tmp", p);
3413
+ writeFileSync12(p + ".tmp", JSON.stringify({ pid, threads: [...threads], at: now }), "utf8");
3414
+ renameSync10(p + ".tmp", p);
3264
3415
  } catch {
3265
3416
  }
3266
3417
  }
3267
3418
  function removeCodexRemote() {
3268
3419
  try {
3269
- rmSync4(codexRemotePath(), { force: true });
3420
+ rmSync5(codexRemotePath(), { force: true });
3270
3421
  } catch {
3271
3422
  }
3272
3423
  }
3273
3424
 
3274
3425
  // src/wrap/channel.ts
3275
- import { chmodSync as chmodSync2, rmSync as rmSync5 } from "fs";
3426
+ import { randomBytes as randomBytes5 } from "crypto";
3427
+ import { chmodSync as chmodSync3, rmSync as rmSync7 } from "fs";
3276
3428
  import { connect, createServer as createServer2 } from "net";
3429
+
3430
+ // src/core/wake.ts
3431
+ import { execFileSync as execFileSync7 } from "child_process";
3432
+ import { createHash as createHash3, createHmac as createHmac2, timingSafeEqual as timingSafeEqual2 } from "crypto";
3433
+ import { chmodSync as chmodSync2, closeSync as closeSync2, mkdirSync as mkdirSync14, openSync as openSync2, readdirSync as readdirSync3, readFileSync as readFileSync13, readSync as readSync2, renameSync as renameSync11, rmSync as rmSync6, statSync as statSync4, writeFileSync as writeFileSync13 } from "fs";
3434
+ import { tmpdir } from "os";
3435
+ import { join as join17 } from "path";
3436
+ function wakeDir(home = deskbuddyHome()) {
3437
+ return join17(home, "wake");
3438
+ }
3439
+ function marksDir(home = deskbuddyHome()) {
3440
+ return join17(wakeDir(home), "marks");
3441
+ }
3442
+ function safeSid(sessionId) {
3443
+ return String(sessionId).replace(/[^A-Za-z0-9_-]/g, "");
3444
+ }
3445
+ function homeTag(home) {
3446
+ return createHash3("sha1").update(home).digest("hex").slice(0, 16);
3447
+ }
3448
+ function wakeChannelPath(platform = process.platform, home = deskbuddyHome()) {
3449
+ if (platform === "win32") return `\\\\.\\pipe\\deskbuddy-wake-${homeTag(home)}`;
3450
+ const primary = join17(wakeDir(home), "wake.sock");
3451
+ if (Buffer.byteLength(primary) <= 103) return primary;
3452
+ const uid = typeof process.getuid === "function" ? process.getuid() : 0;
3453
+ return join17(tmpdir(), `deskbuddy-${uid}`, `wake-${homeTag(home)}.sock`);
3454
+ }
3455
+ function pidExists(pid) {
3456
+ if (!Number.isInteger(pid) || pid <= 0) return false;
3457
+ try {
3458
+ process.kill(pid, 0);
3459
+ return true;
3460
+ } catch {
3461
+ return false;
3462
+ }
3463
+ }
3464
+ function writeWakeMark(sessionId, agentPid, now = Date.now()) {
3465
+ const safe = safeSid(sessionId);
3466
+ if (!safe) return;
3467
+ try {
3468
+ mkdirSync14(marksDir(), { recursive: true, mode: 448 });
3469
+ const file = join17(marksDir(), `${safe}.json`);
3470
+ const tmp = `${file}.${process.pid}.tmp`;
3471
+ writeFileSync13(tmp, JSON.stringify({ agentPid: agentPid ?? 0, daemonPid: process.pid, at: now }), { encoding: "utf8", mode: 384 });
3472
+ renameSync11(tmp, file);
3473
+ } catch {
3474
+ }
3475
+ }
3476
+ function removeWakeMark(sessionId) {
3477
+ const safe = safeSid(sessionId);
3478
+ if (!safe) return;
3479
+ try {
3480
+ rmSync6(join17(marksDir(), `${safe}.json`), { force: true });
3481
+ } catch {
3482
+ }
3483
+ }
3484
+ function clearWakeMarks() {
3485
+ let names = [];
3486
+ try {
3487
+ names = readdirSync3(marksDir());
3488
+ } catch {
3489
+ return;
3490
+ }
3491
+ for (const n of names) {
3492
+ try {
3493
+ rmSync6(join17(marksDir(), n), { force: true });
3494
+ } catch {
3495
+ }
3496
+ }
3497
+ }
3498
+ function isWakeMarked(sessionId, alive = pidExists) {
3499
+ const safe = safeSid(sessionId);
3500
+ if (!safe) return false;
3501
+ try {
3502
+ const m = JSON.parse(readFileSync13(join17(marksDir(), `${safe}.json`), "utf8"));
3503
+ const daemonPid = Number(m?.daemonPid) || 0;
3504
+ if (!daemonPid || !alive(daemonPid)) return false;
3505
+ const pid = Number(m?.agentPid) || 0;
3506
+ return pid > 0 ? alive(pid) : true;
3507
+ } catch {
3508
+ return false;
3509
+ }
3510
+ }
3511
+ function withWake(states, alive) {
3512
+ return states.map((s) => s.remoteWindow === true || !isWakeMarked(s.sessionId, alive) ? s : { ...s, remoteWindow: true });
3513
+ }
3514
+ function wakeSecretPath(home = deskbuddyHome()) {
3515
+ return join17(wakeDir(home), "secret");
3516
+ }
3517
+ function writeWakeSecret(hex, platform = process.platform) {
3518
+ const file = wakeSecretPath();
3519
+ mkdirSync14(wakeDir(), { recursive: true, mode: 448 });
3520
+ const tmp = `${file}.${process.pid}.tmp`;
3521
+ writeFileSync13(tmp, hex, { encoding: "utf8", mode: 384 });
3522
+ if (platform === "win32") {
3523
+ try {
3524
+ execFileSync7("icacls", [tmp, "/inheritance:r", "/grant:r", `${currentUserSid()}:F`], { stdio: "ignore", windowsHide: true });
3525
+ } catch {
3526
+ renameSync11(tmp, file);
3527
+ return "\u7A97\u53E3\u5524\u9192\u5BC6\u94A5\u6587\u4EF6\u7684\u6743\u9650\u6CA1\u80FD\u6536\u7D27\uFF08icacls \u5931\u8D25\uFF09";
3528
+ }
3529
+ } else {
3530
+ chmodSync2(tmp, 384);
3531
+ }
3532
+ renameSync11(tmp, file);
3533
+ return "";
3534
+ }
3535
+ function restrictToUser(file, platform = process.platform) {
3536
+ if (platform !== "win32") return true;
3537
+ try {
3538
+ execFileSync7("icacls", [file, "/inheritance:r", "/grant:r", `${currentUserSid()}:F`], { stdio: "ignore", windowsHide: true });
3539
+ return true;
3540
+ } catch {
3541
+ return false;
3542
+ }
3543
+ }
3544
+ function currentUserSid() {
3545
+ try {
3546
+ const out = execFileSync7("whoami", ["/user", "/fo", "csv", "/nh"], { encoding: "utf8", windowsHide: true });
3547
+ const m = /"(S-1-[0-9-]+)"/.exec(out);
3548
+ if (m) return `*${m[1]}`;
3549
+ } catch {
3550
+ }
3551
+ return String(process.env.USERNAME);
3552
+ }
3553
+ function canonicalJson(v) {
3554
+ if (Array.isArray(v)) return "[" + v.map(canonicalJson).join(",") + "]";
3555
+ if (v && typeof v === "object") {
3556
+ const o = v;
3557
+ return "{" + Object.keys(o).filter((k) => o[k] !== void 0).sort().map((k) => JSON.stringify(k) + ":" + canonicalJson(o[k])).join(",") + "}";
3558
+ }
3559
+ return JSON.stringify(v === void 0 ? null : v);
3560
+ }
3561
+ function wakeMac(secret, ...parts) {
3562
+ return createHmac2("sha256", secret).update(parts.join("|"), "utf8").digest("hex");
3563
+ }
3564
+ var MAC_RE = /^[0-9a-f]{64}$/;
3565
+ function macEqual(a, b) {
3566
+ if (typeof a !== "string" || typeof b !== "string" || !MAC_RE.test(a) || !MAC_RE.test(b)) return false;
3567
+ return timingSafeEqual2(Buffer.from(a, "hex"), Buffer.from(b, "hex"));
3568
+ }
3569
+ var WAKE_MARK_RE = /\[DeskBuddy#([0-9A-Za-z_-]{1,64})(?::[0-9a-f]{32})?\]/;
3570
+ var INTERRUPT_RE = /^\[Request interrupted by user/;
3571
+ var TRANSCRIPT_TAIL2 = 256 * 1024;
3572
+ function userText(content) {
3573
+ if (typeof content === "string") return content;
3574
+ if (!Array.isArray(content)) return "";
3575
+ return content.filter((x) => x && x.type === "text").map((x) => String(x.text ?? "")).join("\n");
3576
+ }
3577
+ function analyzeTranscript(file) {
3578
+ if (!file) return null;
3579
+ let fd;
3580
+ try {
3581
+ const size = statSync4(file).size;
3582
+ const len = Math.min(size, TRANSCRIPT_TAIL2);
3583
+ const buf = Buffer.alloc(len);
3584
+ fd = openSync2(file, "r");
3585
+ readSync2(fd, buf, 0, len, size - len);
3586
+ const lines2 = buf.toString("utf8").split("\n");
3587
+ let seg = false;
3588
+ let reply = null;
3589
+ let humanAfter = false;
3590
+ let interrupted = null;
3591
+ for (let i = lines2.length - 1; i >= 0; i--) {
3592
+ const line = lines2[i];
3593
+ if (line.indexOf('"assistant"') === -1 && line.indexOf('"user"') === -1) continue;
3594
+ let o;
3595
+ try {
3596
+ o = JSON.parse(line);
3597
+ } catch {
3598
+ continue;
3599
+ }
3600
+ if (!o || !o.message) continue;
3601
+ if (o.type === "assistant") {
3602
+ const t = userText(o.message.content).trim();
3603
+ if (t) {
3604
+ seg = true;
3605
+ if (reply === null) reply = t;
3606
+ }
3607
+ continue;
3608
+ }
3609
+ if (o.type !== "user") continue;
3610
+ const text = userText(o.message.content);
3611
+ if (!text) continue;
3612
+ const m = WAKE_MARK_RE.exec(text);
3613
+ if (!m && (o.isMeta === true || text.trim().startsWith("<"))) continue;
3614
+ if (m) return { id: m[1], answered: seg, humanAfter, interrupted: interrupted === true, reply: reply ?? "" };
3615
+ if (interrupted === null) interrupted = INTERRUPT_RE.test(text.trim());
3616
+ humanAfter = true;
3617
+ seg = false;
3618
+ reply = null;
3619
+ }
3620
+ return interrupted === null ? null : { id: "", answered: false, humanAfter, interrupted, reply: "" };
3621
+ } catch {
3622
+ return null;
3623
+ } finally {
3624
+ if (fd !== void 0) {
3625
+ try {
3626
+ closeSync2(fd);
3627
+ } catch {
3628
+ }
3629
+ }
3630
+ }
3631
+ }
3632
+
3633
+ // src/wrap/channel.ts
3277
3634
  var MAX_LINE_BYTES = 64 * 1024;
3278
3635
  function lines(sock, onLine) {
3279
3636
  let buf = "";
@@ -3294,46 +3651,81 @@ function lines(sock, onLine) {
3294
3651
  }
3295
3652
  function parseRequest(line) {
3296
3653
  let m;
3297
- try {
3298
- m = JSON.parse(line);
3299
- } catch {
3300
- return null;
3654
+ if (typeof line === "string") {
3655
+ try {
3656
+ m = JSON.parse(line);
3657
+ } catch {
3658
+ return null;
3659
+ }
3660
+ } else {
3661
+ m = line;
3301
3662
  }
3302
3663
  if (!m || typeof m !== "object") return null;
3303
3664
  const instructionId = typeof m.instructionId === "string" ? m.instructionId : "";
3304
3665
  const sessionId = typeof m.sessionId === "string" ? m.sessionId : "";
3305
- if (!isValidId(instructionId) || !isValidId(sessionId)) return null;
3666
+ if (!isValidId(instructionId) || sessionId !== "" && !isValidId(sessionId)) return null;
3306
3667
  const text = sanitizeText(typeof m.text === "string" ? m.text : "");
3307
3668
  if (!text || text.length > MAX_TEXT_LENGTH) return null;
3308
3669
  const expireAt = typeof m.expireAt === "number" && Number.isFinite(m.expireAt) ? m.expireAt : 0;
3309
3670
  return { instructionId, sessionId, text, expireAt };
3310
3671
  }
3311
- function startChannelServer(path, onRequest, platform = process.platform) {
3672
+ var HEX32 = /^[0-9a-f]{32}$/;
3673
+ function replyMac(secret, nonceC, r) {
3674
+ return wakeMac(secret, "r", nonceC, r.instructionId, r.status, r.reason ?? "");
3675
+ }
3676
+ function startChannelServer(path, secret, onRequest, platform = process.platform) {
3312
3677
  return new Promise((resolve5) => {
3313
3678
  const server = createServer2((sock) => {
3314
3679
  let open = true;
3680
+ let phase = "hello";
3681
+ let nonceC = "";
3682
+ let nonceS = "";
3315
3683
  sock.on("error", () => {
3316
3684
  });
3317
3685
  sock.on("close", () => open = false);
3318
- const reply = (r) => {
3686
+ const authTimer = setTimeout(() => {
3687
+ if (phase === "hello") sock.destroy();
3688
+ }, 5e3);
3689
+ authTimer.unref?.();
3690
+ const raw = (m) => {
3319
3691
  if (sock.destroyed || !sock.writable) return;
3320
3692
  try {
3321
- sock.write(JSON.stringify(r) + "\n");
3693
+ sock.write(JSON.stringify(m) + "\n");
3322
3694
  } catch {
3323
3695
  }
3324
3696
  };
3697
+ const reply = (r) => raw({ ...r, mac: replyMac(secret, nonceC, r) });
3325
3698
  lines(sock, (line) => {
3326
- const req2 = parseRequest(line);
3327
- if (!req2) {
3328
- reply({ instructionId: "", status: "rejected", reason: "bad_request" });
3329
- return;
3699
+ try {
3700
+ const m = JSON.parse(line);
3701
+ if (!m || typeof m !== "object") throw new Error("bad frame");
3702
+ if (phase === "hello") {
3703
+ if (m.t !== "hello" || typeof m.nonceC !== "string" || !HEX32.test(m.nonceC)) throw new Error("bad hello");
3704
+ nonceC = m.nonceC;
3705
+ nonceS = randomBytes5(16).toString("hex");
3706
+ phase = "req";
3707
+ clearTimeout(authTimer);
3708
+ raw({ t: "auth", nonceS, mac: wakeMac(secret, "s", nonceC, nonceS) });
3709
+ return;
3710
+ }
3711
+ if (phase !== "req") return;
3712
+ const { mac, ...body } = m;
3713
+ if (!macEqual(mac, wakeMac(secret, "c", nonceS, canonicalJson(body)))) throw new Error("bad mac");
3714
+ const req2 = parseRequest(body);
3715
+ if (!req2) {
3716
+ reply({ instructionId: typeof body.instructionId === "string" ? body.instructionId : "", status: "rejected", reason: "bad_request" });
3717
+ return;
3718
+ }
3719
+ phase = "done";
3720
+ onRequest(req2, reply, () => open);
3721
+ } catch {
3722
+ sock.destroy();
3330
3723
  }
3331
- onRequest(req2, reply, () => open);
3332
3724
  });
3333
3725
  });
3334
3726
  if (platform !== "win32") {
3335
3727
  try {
3336
- rmSync5(path, { force: true });
3728
+ rmSync7(path, { force: true });
3337
3729
  } catch {
3338
3730
  }
3339
3731
  }
@@ -3349,7 +3741,7 @@ function startChannelServer(path, onRequest, platform = process.platform) {
3349
3741
  restore();
3350
3742
  if (platform !== "win32") {
3351
3743
  try {
3352
- chmodSync2(path, 384);
3744
+ chmodSync3(path, 384);
3353
3745
  } catch {
3354
3746
  }
3355
3747
  }
@@ -3366,17 +3758,23 @@ function closeChannelServer(server, path, platform = process.platform) {
3366
3758
  }
3367
3759
  if (platform !== "win32") {
3368
3760
  try {
3369
- rmSync5(path, { force: true });
3761
+ rmSync7(path, { force: true });
3370
3762
  } catch {
3371
3763
  }
3372
3764
  }
3373
3765
  }
3374
3766
  var FINAL = /* @__PURE__ */ new Set(["started", "rejected", "failed", "not_here"]);
3375
- function sendToWindow(path, req2, onReply, opts = {}) {
3767
+ function sendToWindow(path, secret, req2, onReply, opts = {}) {
3376
3768
  return new Promise((resolve5) => {
3769
+ if (!secret) {
3770
+ resolve5("fallback");
3771
+ return;
3772
+ }
3377
3773
  let settled = false;
3378
3774
  let handled = false;
3379
3775
  let done = false;
3776
+ const nonceC = randomBytes5(16).toString("hex");
3777
+ let nonceS = "";
3380
3778
  const settle = (v) => {
3381
3779
  if (settled) return;
3382
3780
  settled = true;
@@ -3409,16 +3807,33 @@ function sendToWindow(path, req2, onReply, opts = {}) {
3409
3807
  }
3410
3808
  });
3411
3809
  sock.on("connect", () => {
3412
- sock.write(JSON.stringify(req2) + "\n");
3810
+ sock.write(JSON.stringify({ t: "hello", nonceC }) + "\n");
3413
3811
  });
3414
3812
  lines(sock, (line) => {
3415
- let r;
3813
+ let m;
3416
3814
  try {
3417
- r = JSON.parse(line);
3815
+ m = JSON.parse(line);
3418
3816
  } catch {
3817
+ sock.destroy();
3818
+ return;
3819
+ }
3820
+ if (!nonceS) {
3821
+ if (m.t !== "auth" || typeof m.nonceS !== "string" || !HEX32.test(m.nonceS) || !macEqual(m.mac, wakeMac(secret, "s", nonceC, m.nonceS))) {
3822
+ sock.destroy();
3823
+ return;
3824
+ }
3825
+ nonceS = m.nonceS;
3826
+ const body = { ...req2 };
3827
+ sock.write(JSON.stringify({ ...body, mac: wakeMac(secret, "c", nonceS, canonicalJson(body)) }) + "\n");
3419
3828
  return;
3420
3829
  }
3830
+ const { mac, ...rest } = m;
3831
+ const r = rest;
3421
3832
  if (r.instructionId !== req2.instructionId) return;
3833
+ if (!macEqual(mac, replyMac(secret, nonceC, r))) {
3834
+ sock.destroy();
3835
+ return;
3836
+ }
3422
3837
  if (!settled) {
3423
3838
  if (r.status === "ack") {
3424
3839
  settle("handled");
@@ -3440,40 +3855,85 @@ function sendToWindow(path, req2, onReply, opts = {}) {
3440
3855
  });
3441
3856
  }
3442
3857
 
3858
+ // src/wrap/screen.ts
3859
+ var ESC = "\x1B";
3860
+ var PASTE_BEGIN = ESC + "[200~";
3861
+ var PASTE_END = ESC + "[201~";
3862
+ function sanitize(text) {
3863
+ return String(text).replace(/\r\n?/g, "\n").replace(/[\x00-\x08\x0b-\x1f\x7f-\x9f]/g, "");
3864
+ }
3865
+ var MAX_INJECT_CHARS = 800;
3866
+ var TEXT_TOO_LONG = "text_too_long";
3867
+ function oneLine(text) {
3868
+ return sanitize(text).replace(/\s*\n\s*/g, " ").trim();
3869
+ }
3870
+ function tooLongToInject(text) {
3871
+ return oneLine(text).length > MAX_INJECT_CHARS;
3872
+ }
3873
+ function prepare(text) {
3874
+ return " " + oneLine(text);
3875
+ }
3876
+ var PERMISSION_RE = /Do you want to (proceed|make|create|run|allow)|[❯>]\s*1\. Yes/;
3877
+ function inspectLines(lines2) {
3878
+ const text = lines2.join("\n");
3879
+ const permission = PERMISSION_RE.test(text);
3880
+ const rules = [];
3881
+ lines2.forEach((l, i) => {
3882
+ if (/^─{20,}/.test(l.trim())) rules.push(i);
3883
+ });
3884
+ let input = null;
3885
+ if (rules.length >= 2) {
3886
+ const a = rules[rules.length - 2];
3887
+ const b = rules[rules.length - 1];
3888
+ input = lines2.slice(a + 1, b).join("\n").replace(/^\s*[❯>]\s?/, "").trimEnd();
3889
+ }
3890
+ return { permission, input };
3891
+ }
3892
+ function boxMatches(view, body) {
3893
+ if (view.input === null || view.permission) return false;
3894
+ const flat = (x) => x.replace(/\s+/g, "");
3895
+ return flat(view.input) === flat(body) || /\[Pasted text #\d+/.test(view.input);
3896
+ }
3897
+
3443
3898
  // src/core/wrapWindows.ts
3444
- import { chmodSync as chmodSync3, mkdirSync as mkdirSync13, readdirSync as readdirSync2, readFileSync as readFileSync12, renameSync as renameSync10, rmSync as rmSync6, statSync as statSync3, writeFileSync as writeFileSync12 } from "fs";
3445
- import { tmpdir } from "os";
3446
- import { join as join16 } from "path";
3899
+ import { chmodSync as chmodSync4, mkdirSync as mkdirSync15, readdirSync as readdirSync4, readFileSync as readFileSync14, renameSync as renameSync12, rmSync as rmSync8, statSync as statSync5, writeFileSync as writeFileSync14 } from "fs";
3900
+ import { tmpdir as tmpdir2 } from "os";
3901
+ import { join as join18 } from "path";
3447
3902
  var WINDOW_ID_RE = /^[a-f0-9]{32}$/;
3448
3903
  function windowsDir() {
3449
- return join16(deskbuddyHome(), "windows");
3904
+ return join18(deskbuddyHome(), "windows");
3450
3905
  }
3451
3906
  function bySessionDir() {
3452
- return join16(windowsDir(), "by-session");
3907
+ return join18(windowsDir(), "by-session");
3453
3908
  }
3454
- function safeSid(sessionId) {
3909
+ function safeSid2(sessionId) {
3455
3910
  return String(sessionId).replace(/[^A-Za-z0-9_-]/g, "");
3456
3911
  }
3457
3912
  function ensureDir(dir) {
3458
- mkdirSync13(dir, { recursive: true, mode: 448 });
3913
+ mkdirSync15(dir, { recursive: true, mode: 448 });
3459
3914
  }
3460
3915
  function atomicWrite(file, text) {
3461
3916
  const tmp = `${file}.${process.pid}.tmp`;
3462
- writeFileSync12(tmp, text, { encoding: "utf8", mode: 384 });
3463
- renameSync10(tmp, file);
3917
+ writeFileSync14(tmp, text, { encoding: "utf8", mode: 384 });
3918
+ renameSync12(tmp, file);
3464
3919
  }
3465
3920
  function windowFile(windowId) {
3466
- return join16(windowsDir(), `${windowId}.json`);
3921
+ return join18(windowsDir(), `${windowId}.json`);
3467
3922
  }
3468
3923
  function writeWindow(windowId, rec) {
3469
3924
  if (!WINDOW_ID_RE.test(windowId)) return;
3470
3925
  ensureDir(windowsDir());
3471
3926
  atomicWrite(windowFile(windowId), JSON.stringify(rec));
3927
+ restrictToUser(windowFile(windowId));
3928
+ }
3929
+ function windowSecret(windowId) {
3930
+ const s = readWindow(windowId)?.secret;
3931
+ return typeof s === "string" && /^[0-9a-f]{64}$/.test(s) ? Buffer.from(s, "hex") : null;
3472
3932
  }
3473
3933
  function readWindow(windowId) {
3474
3934
  if (!WINDOW_ID_RE.test(windowId)) return null;
3475
3935
  try {
3476
- const r = JSON.parse(readFileSync12(windowFile(windowId), "utf8"));
3936
+ const r = JSON.parse(readFileSync14(windowFile(windowId), "utf8"));
3477
3937
  return r && typeof r.pid === "number" ? r : null;
3478
3938
  } catch {
3479
3939
  return null;
@@ -3483,41 +3943,41 @@ function removeWindow(windowId) {
3483
3943
  if (!WINDOW_ID_RE.test(windowId)) return;
3484
3944
  for (const f of [windowFile(windowId), lastEventFile(windowId)]) {
3485
3945
  try {
3486
- rmSync6(f, { force: true });
3946
+ rmSync8(f, { force: true });
3487
3947
  } catch {
3488
3948
  }
3489
3949
  }
3490
3950
  let names = [];
3491
3951
  try {
3492
- names = readdirSync2(bySessionDir());
3952
+ names = readdirSync4(bySessionDir());
3493
3953
  } catch {
3494
3954
  return;
3495
3955
  }
3496
3956
  for (const n of names) {
3497
- const f = join16(bySessionDir(), n);
3957
+ const f = join18(bySessionDir(), n);
3498
3958
  try {
3499
- if (readFileSync12(f, "utf8").trim() === windowId) rmSync6(f, { force: true });
3959
+ if (readFileSync14(f, "utf8").trim() === windowId) rmSync8(f, { force: true });
3500
3960
  } catch {
3501
3961
  }
3502
3962
  }
3503
3963
  }
3504
3964
  function readBySession(sessionId) {
3505
- const safe = safeSid(sessionId);
3965
+ const safe = safeSid2(sessionId);
3506
3966
  if (!safe) return null;
3507
3967
  try {
3508
- const id = readFileSync12(join16(bySessionDir(), safe), "utf8").trim();
3968
+ const id = readFileSync14(join18(bySessionDir(), safe), "utf8").trim();
3509
3969
  return WINDOW_ID_RE.test(id) ? id : null;
3510
3970
  } catch {
3511
3971
  return null;
3512
3972
  }
3513
3973
  }
3514
3974
  function lastEventFile(windowId) {
3515
- return join16(windowsDir(), `${windowId}.last`);
3975
+ return join18(windowsDir(), `${windowId}.last`);
3516
3976
  }
3517
3977
  function readLastEvent(windowId) {
3518
3978
  if (!WINDOW_ID_RE.test(windowId)) return null;
3519
3979
  try {
3520
- const e = JSON.parse(readFileSync12(lastEventFile(windowId), "utf8"));
3980
+ const e = JSON.parse(readFileSync14(lastEventFile(windowId), "utf8"));
3521
3981
  return e && typeof e.ev === "string" && typeof e.at === "number" ? { ev: e.ev, sid: String(e.sid ?? ""), at: e.at } : null;
3522
3982
  } catch {
3523
3983
  return null;
@@ -3526,18 +3986,18 @@ function readLastEvent(windowId) {
3526
3986
  function channelPath(windowId, platform = process.platform, home = deskbuddyHome()) {
3527
3987
  if (platform === "win32") return `\\\\.\\pipe\\deskbuddy-${windowId}`;
3528
3988
  const name = `${windowId.slice(0, 16)}.sock`;
3529
- const primary = join16(home, "win", name);
3989
+ const primary = join18(home, "win", name);
3530
3990
  if (Buffer.byteLength(primary) <= 103) return primary;
3531
3991
  const uid = typeof process.getuid === "function" ? process.getuid() : 0;
3532
- return join16(tmpdir(), `deskbuddy-${uid}`, name);
3992
+ return join18(tmpdir2(), `deskbuddy-${uid}`, name);
3533
3993
  }
3534
3994
  function ensureChannelDir(sockPath) {
3535
- const dir = join16(sockPath, "..");
3995
+ const dir = join18(sockPath, "..");
3536
3996
  try {
3537
- mkdirSync13(dir, { recursive: true, mode: 448 });
3538
- const st = statSync3(dir);
3997
+ mkdirSync15(dir, { recursive: true, mode: 448 });
3998
+ const st = statSync5(dir);
3539
3999
  if (typeof process.getuid === "function" && st.uid !== process.getuid()) return false;
3540
- chmodSync3(dir, 448);
4000
+ chmodSync4(dir, 448);
3541
4001
  return true;
3542
4002
  } catch {
3543
4003
  return false;
@@ -3553,23 +4013,595 @@ function windowForSession(sessionId) {
3553
4013
  return id;
3554
4014
  }
3555
4015
 
3556
- // src/daemon/index.ts
3557
- var IDLE_TIMEOUT_MS = 150 * 1e3;
3558
- var POLICY_VIOLATION = 1008;
3559
- var PING_FRAME_SERVICE = "ping";
3560
- var PONG_FRAME = JSON.stringify({ service: "pong", version: "1.0" });
3561
- var CODEX_REMOTE_REFRESH_MS = 30 * 1e3;
3562
- var CODEX_WATCH_MS = 5 * 1e3;
3563
- function defaultCodexAppServers() {
3564
- const cfg = loadConfig();
3565
- return cfg.enabledTools.includes("codex") ? discoverAppServers(cfg.codexAppServer) : [];
3566
- }
3567
- function opencodeReady() {
3568
- return isOcPluginInstalled(opencodePluginPath()) && existsSync10(ocPluginModule(installedHookPath()));
3569
- }
3570
- function orphanResumeText(summary2) {
3571
- return `\uFF08\u4F60\u5728\u8BBE\u5907\u4E0A\u5DF2\u6279\u51C6\uFF1A${summary2}\uFF09\u8BF7\u7EE7\u7EED\u521A\u624D\u88AB\u4E2D\u65AD\u7684\u64CD\u4F5C\u3002`;
3572
- }
4016
+ // src/core/launch.ts
4017
+ import { mkdirSync as mkdirSync16, readFileSync as readFileSync15, renameSync as renameSync13, rmSync as rmSync9, writeFileSync as writeFileSync15 } from "fs";
4018
+ import { join as join19 } from "path";
4019
+ var RUN_ID_RE = /^[a-f0-9]{32}$/;
4020
+ var SESSION_UUID_RE = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
4021
+ function launchDir(home = deskbuddyHome()) {
4022
+ return join19(home, "launch");
4023
+ }
4024
+ function planFile(runId) {
4025
+ return join19(launchDir(), `${runId}.json`);
4026
+ }
4027
+ function writeLaunch(runId, plan) {
4028
+ if (!RUN_ID_RE.test(runId)) throw new Error("bad runId");
4029
+ if (plan.resume && !SESSION_UUID_RE.test(plan.resume)) throw new Error("bad session id");
4030
+ mkdirSync16(launchDir(), { recursive: true, mode: 448 });
4031
+ const tmp = `${planFile(runId)}.${process.pid}.tmp`;
4032
+ writeFileSync15(tmp, JSON.stringify(plan), { encoding: "utf8", mode: 384 });
4033
+ restrictToUser(tmp);
4034
+ renameSync13(tmp, planFile(runId));
4035
+ }
4036
+ function claimLaunch(runId) {
4037
+ if (!RUN_ID_RE.test(runId)) return null;
4038
+ const claimed = join19(launchDir(), `${runId}.${process.pid}.claimed`);
4039
+ try {
4040
+ renameSync13(planFile(runId), claimed);
4041
+ } catch {
4042
+ return null;
4043
+ }
4044
+ try {
4045
+ const p = JSON.parse(readFileSync15(claimed, "utf8"));
4046
+ if (!p || typeof p.cwd !== "string" || typeof p.resume !== "string") return null;
4047
+ if (p.resume && !SESSION_UUID_RE.test(p.resume)) return null;
4048
+ return p;
4049
+ } catch {
4050
+ return null;
4051
+ } finally {
4052
+ try {
4053
+ rmSync9(claimed, { force: true });
4054
+ } catch {
4055
+ }
4056
+ }
4057
+ }
4058
+ function launchPending(runId) {
4059
+ try {
4060
+ readFileSync15(planFile(runId));
4061
+ return true;
4062
+ } catch {
4063
+ return false;
4064
+ }
4065
+ }
4066
+
4067
+ // src/daemon/openWindow.ts
4068
+ import { execFile as execFile3, execFileSync as execFileSync8, spawn as spawn2 } from "child_process";
4069
+ import { chmodSync as chmodSync5, mkdirSync as mkdirSync17, rmSync as rmSync10, writeFileSync as writeFileSync16 } from "fs";
4070
+ import { userInfo } from "os";
4071
+ import { join as join20 } from "path";
4072
+ function safePath(p) {
4073
+ return /^(?:[A-Za-z]:[\\/]|\/)/.test(p) && !/[\r\n\0"'`$%;]/.test(p);
4074
+ }
4075
+ function checkTarget(t) {
4076
+ if (!RUN_ID_RE.test(t.runId)) throw new Error("bad runId");
4077
+ for (const p of [t.node, t.cli, t.home]) if (!safePath(p)) throw new Error("\u8DEF\u5F84\u91CC\u6709\u4E0D\u80FD\u8FDB\u547D\u4EE4\u884C\u7684\u5B57\u7B26");
4078
+ }
4079
+ var shq = (s) => `'${s}'`;
4080
+ function macCommandScript(t) {
4081
+ checkTarget(t);
4082
+ return `#!/bin/sh
4083
+ exec ${shq(t.node)} ${shq(t.cli)} wrap --launch ${t.runId} --home ${shq(t.home)}
4084
+ `;
4085
+ }
4086
+ function winWtArgs(t) {
4087
+ checkTarget(t);
4088
+ return ["-w", "new", t.node, t.cli, "wrap", "--launch", t.runId, "--home", t.home];
4089
+ }
4090
+ var psq = (s) => `'${s.replace(/'/g, "''")}'`;
4091
+ function winStartProcessScript(t) {
4092
+ checkTarget(t);
4093
+ const arg = (s) => psq(/\s/.test(s) ? `"${s}"` : s);
4094
+ return `Start-Process -FilePath ${psq(t.node)} -ArgumentList @(${[t.cli, "wrap", "--launch", t.runId, "--home", t.home].map(arg).join(",")})`;
4095
+ }
4096
+ function encodePs(script) {
4097
+ return Buffer.from(script, "utf16le").toString("base64");
4098
+ }
4099
+ function guiAvailable(d) {
4100
+ if (d.env.SSH_CONNECTION || d.env.SSH_TTY) return false;
4101
+ if (d.platform === "darwin") {
4102
+ const m = d.macManager().trim();
4103
+ if (m === "Aqua") return true;
4104
+ if (m !== "Background") return false;
4105
+ const me = d.userName?.() ?? "";
4106
+ return !!me && (d.macConsoleUser?.() ?? "").trim() === me;
4107
+ }
4108
+ if (d.platform === "win32") {
4109
+ const s = d.winSessionId();
4110
+ return s !== null && s > 0;
4111
+ }
4112
+ return false;
4113
+ }
4114
+ var defaultGuiDeps = () => ({
4115
+ platform: process.platform,
4116
+ env: process.env,
4117
+ macManager: () => {
4118
+ try {
4119
+ return execFileSync8("launchctl", ["managername"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout: 3e3 });
4120
+ } catch {
4121
+ return "";
4122
+ }
4123
+ },
4124
+ macConsoleUser: () => {
4125
+ try {
4126
+ return execFileSync8("stat", ["-f", "%Su", "/dev/console"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout: 3e3 });
4127
+ } catch {
4128
+ return "";
4129
+ }
4130
+ },
4131
+ userName: () => {
4132
+ try {
4133
+ return userInfo().username;
4134
+ } catch {
4135
+ return "";
4136
+ }
4137
+ },
4138
+ winSessionId: () => {
4139
+ try {
4140
+ const out = execFileSync8("tasklist", ["/FI", `PID eq ${process.pid}`, "/FO", "CSV", "/NH"], {
4141
+ encoding: "utf8",
4142
+ stdio: ["ignore", "pipe", "ignore"],
4143
+ windowsHide: true,
4144
+ timeout: 5e3
4145
+ });
4146
+ const cols = out.trim().split("\n")[0]?.match(/"([^"]*)"/g)?.map((c) => c.slice(1, -1)) ?? [];
4147
+ const n = Number(cols[3]);
4148
+ return Number.isInteger(n) ? n : null;
4149
+ } catch {
4150
+ return null;
4151
+ }
4152
+ }
4153
+ });
4154
+ var defaultOpenerDeps = (findWt2) => ({
4155
+ platform: process.platform,
4156
+ findWt: findWt2,
4157
+ run: (file, args) => new Promise((resolve5, reject) => {
4158
+ execFile3(file, args, { windowsHide: true, timeout: 15e3 }, (err) => err ? reject(err) : resolve5());
4159
+ }),
4160
+ spawnDetached: (file, args) => {
4161
+ const c = spawn2(file, args, { detached: true, stdio: "ignore", shell: false, windowsHide: false });
4162
+ c.on("error", () => {
4163
+ });
4164
+ c.unref();
4165
+ }
4166
+ });
4167
+ async function openTerminal(t, d) {
4168
+ checkTarget(t);
4169
+ if (d.platform === "darwin") {
4170
+ mkdirSync17(launchDir(t.home), { recursive: true, mode: 448 });
4171
+ const file = join20(launchDir(t.home), `${t.runId}.command`);
4172
+ writeFileSync16(file, macCommandScript(t), { encoding: "utf8", mode: 448 });
4173
+ chmodSync5(file, 448);
4174
+ await d.run("open", ["-a", "Terminal", file]);
4175
+ return "terminal";
4176
+ }
4177
+ if (d.platform === "win32") {
4178
+ const wt = d.findWt();
4179
+ if (wt) {
4180
+ d.spawnDetached(wt, winWtArgs(t));
4181
+ return "wt";
4182
+ }
4183
+ await d.run("powershell.exe", ["-NoProfile", "-NonInteractive", "-EncodedCommand", encodePs(winStartProcessScript(t))]);
4184
+ return "powershell";
4185
+ }
4186
+ throw new Error(`\u6682\u4E0D\u652F\u6301\u5728 ${d.platform} \u4E0A\u5F00\u7A97`);
4187
+ }
4188
+ function removeCommandFile(home, runId) {
4189
+ if (!RUN_ID_RE.test(runId)) return;
4190
+ try {
4191
+ rmSync10(join20(launchDir(home), `${runId}.command`), { force: true });
4192
+ } catch {
4193
+ }
4194
+ }
4195
+
4196
+ // src/daemon/wake.ts
4197
+ import { randomBytes as randomBytes6 } from "crypto";
4198
+ import { rmSync as rmSync11 } from "fs";
4199
+ import { createServer as createServer3 } from "net";
4200
+ var BUSY_STATES = /* @__PURE__ */ new Set(["watch", "ask_approval"]);
4201
+ var STOP_MIN_MS = 1500;
4202
+ var WAKE_MAX_QUEUE_MS = 30 * 60 * 1e3;
4203
+ var WATCH_STALE_MS = 30 * 60 * 1e3;
4204
+ var INTERRUPTED_REASON = "exec_failed";
4205
+ var WakeHub = class {
4206
+ #slots = /* @__PURE__ */ new Map();
4207
+ #d;
4208
+ constructor(deps) {
4209
+ this.#d = {
4210
+ ackTimeoutMs: 3e3,
4211
+ maxQueueMs: WAKE_MAX_QUEUE_MS,
4212
+ flightLimitMs: EXEC_TIMEOUT_MS,
4213
+ watchStaleMs: WATCH_STALE_MS,
4214
+ analyze: analyzeTranscript,
4215
+ ...deps
4216
+ };
4217
+ }
4218
+ /** 这个会话有没有登记中的等待者(或刚唤醒、正在跑的那一轮) */
4219
+ has(sessionId) {
4220
+ const s = this.#slots.get(sessionId);
4221
+ return !!s && (!!s.reg || !!s.flight);
4222
+ }
4223
+ /** 交给唤醒路由。返回 false = 这个会话没有等待者,调用方走别的路 */
4224
+ submit(cmd, sessionId) {
4225
+ if (cmd.tool !== "claude_code") return false;
4226
+ const slot = this.#slots.get(sessionId);
4227
+ if (!slot || !slot.reg && !slot.flight) return false;
4228
+ slot.queue.push({ cmd, sessionId, queuedAt: this.#d.now() });
4229
+ 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`);
4230
+ this.#settle(sessionId, slot);
4231
+ return true;
4232
+ }
4233
+ onHello(peer, h) {
4234
+ const sid = h.sessionId;
4235
+ if (!isValidId(sid)) {
4236
+ peer.close();
4237
+ return;
4238
+ }
4239
+ const slot = this.#slot(sid);
4240
+ if (h.transcript) slot.transcript = h.transcript;
4241
+ if (h.event === "Stop" && !h.re) {
4242
+ slot.lastStopAt = this.#d.now();
4243
+ const f = slot.flight;
4244
+ if (f) this.#onStop(sid, slot, f, h);
4245
+ }
4246
+ if (h.agentPid) {
4247
+ for (const [other, s] of this.#slots) {
4248
+ if (other === sid || !s.reg || s.agentPid !== h.agentPid || s.reg.pending) continue;
4249
+ this.#evict(s);
4250
+ for (const j of s.queue.splice(0)) {
4251
+ j.sessionId = sid;
4252
+ slot.queue.push(j);
4253
+ }
4254
+ this.#settle(other, s);
4255
+ }
4256
+ }
4257
+ const old = slot.reg;
4258
+ if (old && old.peer !== peer) {
4259
+ if (old.pending || h.re) {
4260
+ peer.send({ t: "dup" });
4261
+ peer.close();
4262
+ this.#settle(sid, slot);
4263
+ return;
4264
+ }
4265
+ this.#evict(slot);
4266
+ }
4267
+ slot.reg = { peer, hello: h };
4268
+ if (h.agentPid) slot.agentPid = h.agentPid;
4269
+ peer.send({ t: "ok" });
4270
+ this.#settle(sid, slot);
4271
+ }
4272
+ /**
4273
+ * 🔴 必须确认是被唤醒的那一轮结束了:一轮结束时 hook.cjs 先写 done,tick 可能抢在这一轮的 Stop 等待者登记前
4274
+ * 把排队指令发给上一个等待者,紧接着登记的 Stop 带的是上一轮的回复(2026-09-30 e2e 实测)
4275
+ */
4276
+ #onStop(sid, slot, f, h) {
4277
+ const id = f.job.cmd.instructionId;
4278
+ const t = h.tail;
4279
+ if (!t) {
4280
+ if ((h.stopAt ?? this.#d.now()) - f.firedAt < STOP_MIN_MS) {
4281
+ this.#d.log(`\u6307\u4EE4 ${id} \u5DF2\u5524\u9192\uFF0C\u8FD9\u6B21 Stop \u591A\u534A\u662F\u4E0A\u4E00\u8F6E\u7684\uFF0C\u7EE7\u7EED\u7B49`);
4282
+ return;
4283
+ }
4284
+ } else if (t.id !== id || !t.answered && !t.humanAfter) {
4285
+ this.#d.log(`\u6307\u4EE4 ${id} \u5DF2\u5524\u9192\uFF0C\u8FD9\u6B21 Stop \u662F\u4E0A\u4E00\u8F6E\u7684\uFF0C\u7EE7\u7EED\u7B49`);
4286
+ return;
4287
+ } else if (t.humanAfter) {
4288
+ this.#interrupted(sid, slot, f, "Stop \u663E\u793A\u5524\u9192\u4E4B\u540E\u53C8\u6709\u4EBA\u7C7B\u6D88\u606F");
4289
+ return;
4290
+ }
4291
+ slot.flight = void 0;
4292
+ this.#d.log(`\u6307\u4EE4 ${id} \u7A97\u53E3\u8FD9\u4E00\u8F6E\u5DF2\u7ED3\u675F\uFF0C\u56DE\u590D ${(h.last ?? "").length} \u5B57`);
4293
+ this.#d.done(f.job.cmd, sid, h.last ?? "");
4294
+ }
4295
+ #interrupted(sid, slot, f, why) {
4296
+ slot.flight = void 0;
4297
+ this.#d.log(`\u6307\u4EE4 ${f.job.cmd.instructionId} \u88AB\u5524\u9192\u7684\u90A3\u4E00\u8F6E\u88AB\u6253\u65AD\uFF08${why}\uFF09`);
4298
+ this.#d.report(f.job.cmd, sid, "failed", INTERRUPTED_REASON);
4299
+ }
4300
+ onMessage(peer, m) {
4301
+ const found = this.#find(peer);
4302
+ if (!found) return;
4303
+ const [sid, slot, reg] = found;
4304
+ const p = reg.pending;
4305
+ if (!p || m.id !== p.job.cmd.instructionId) return;
4306
+ clearTimeout(p.timer);
4307
+ reg.pending = void 0;
4308
+ if (m.t === "fired") {
4309
+ const now = this.#d.now();
4310
+ slot.flight = { job: p.job, firedAt: now, paused: 0, lastTick: now };
4311
+ slot.reg = void 0;
4312
+ this.#d.log(`\u6307\u4EE4 ${p.job.cmd.instructionId} \u5DF2\u5524\u9192\u7A97\u53E3 len=${p.job.cmd.text.length}`);
4313
+ this.#d.report(p.job.cmd, sid, "started");
4314
+ } else {
4315
+ slot.reg = void 0;
4316
+ this.#d.log(`\u6307\u4EE4 ${p.job.cmd.instructionId} \u7B49\u5F85\u8005\u8BF4\u7A97\u53E3\u5DF2\u4E0D\u5728\uFF0C\u6539\u8D70\u540E\u53F0`);
4317
+ this.#d.fallback(p.job.cmd);
4318
+ }
4319
+ this.#settle(sid, slot);
4320
+ }
4321
+ onClose(peer) {
4322
+ const found = this.#find(peer);
4323
+ if (!found) return;
4324
+ const [sid, slot, reg] = found;
4325
+ if (reg.pending) {
4326
+ const job = reg.pending.job;
4327
+ clearTimeout(reg.pending.timer);
4328
+ reg.pending = void 0;
4329
+ 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`);
4330
+ this.#d.report(job.cmd, sid, "failed", "window_failed");
4331
+ }
4332
+ slot.reg = void 0;
4333
+ this.#settle(sid, slot);
4334
+ }
4335
+ /** 每秒一次:过期、窗口关闭、打断、等结果超时、忙转闲 */
4336
+ tick() {
4337
+ const now = this.#d.now();
4338
+ for (const [sid, slot] of [...this.#slots]) {
4339
+ if (slot.agentPid && !this.#d.alive(slot.agentPid)) {
4340
+ if (slot.reg) this.#evict(slot, sid);
4341
+ slot.flight = void 0;
4342
+ slot.agentPid = void 0;
4343
+ this.#drain(slot, "\u6392\u961F\u65F6\u7A97\u53E3\u5173\u4E86");
4344
+ }
4345
+ const f = slot.flight;
4346
+ if (f) {
4347
+ const t = slot.transcript ? this.#d.analyze(slot.transcript) : null;
4348
+ if (t && t.id === f.job.cmd.instructionId && t.interrupted) {
4349
+ this.#interrupted(sid, slot, f, "Esc");
4350
+ } else {
4351
+ const st = this.#d.readState(sid);
4352
+ if (st?.state === "ask_approval") f.paused += now - f.lastTick;
4353
+ f.lastTick = now;
4354
+ if (now - f.firedAt - f.paused > this.#d.flightLimitMs) {
4355
+ 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`);
4356
+ slot.flight = void 0;
4357
+ }
4358
+ }
4359
+ }
4360
+ this.#settle(sid, slot);
4361
+ }
4362
+ }
4363
+ logError(msg) {
4364
+ this.#d.log(msg);
4365
+ }
4366
+ /** 关掉全部连接(daemon 退出) */
4367
+ closeAll() {
4368
+ for (const slot of this.#slots.values()) {
4369
+ if (slot.reg?.pending) clearTimeout(slot.reg.pending.timer);
4370
+ slot.reg?.peer.close();
4371
+ }
4372
+ this.#slots.clear();
4373
+ }
4374
+ #slot(sid) {
4375
+ let s = this.#slots.get(sid);
4376
+ if (!s) {
4377
+ s = { queue: [], lastStopAt: 0, marked: false };
4378
+ this.#slots.set(sid, s);
4379
+ }
4380
+ return s;
4381
+ }
4382
+ #find(peer) {
4383
+ for (const [sid, s] of this.#slots) if (s.reg && s.reg.peer === peer) return [sid, s, s.reg];
4384
+ return null;
4385
+ }
4386
+ #evict(slot, sid) {
4387
+ const r = slot.reg;
4388
+ if (!r) return;
4389
+ slot.reg = void 0;
4390
+ if (r.pending) {
4391
+ clearTimeout(r.pending.timer);
4392
+ 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`);
4393
+ this.#d.report(r.pending.job.cmd, sid ?? r.pending.job.sessionId, "failed", "window_failed");
4394
+ r.pending = void 0;
4395
+ }
4396
+ r.peer.send({ t: "bye" });
4397
+ r.peer.close();
4398
+ }
4399
+ /** 没碰过窗口的排队指令按原顺序交回后台 */
4400
+ #drain(slot, why) {
4401
+ for (const j of slot.queue.splice(0)) {
4402
+ this.#d.log(`\u6307\u4EE4 ${j.cmd.instructionId} ${why}\uFF0C\u6539\u8D70\u540E\u53F0`);
4403
+ this.#d.fallback(j.cmd);
4404
+ }
4405
+ }
4406
+ #busy(sid, slot) {
4407
+ if (slot.flight || slot.reg?.pending) return true;
4408
+ const st = this.#d.readState(sid);
4409
+ if (!st || !BUSY_STATES.has(st.state) || st.updatedAt <= slot.lastStopAt) return false;
4410
+ if (slot.transcript && this.#d.analyze(slot.transcript)?.interrupted) return false;
4411
+ return this.#d.now() - st.updatedAt <= this.#d.watchStaleMs;
4412
+ }
4413
+ #expired(job, now) {
4414
+ return job.cmd.expireAt > 0 ? isExpired(job.cmd, now) : now - job.queuedAt > this.#d.maxQueueMs;
4415
+ }
4416
+ /** 排队 / 发出 / 过期 */
4417
+ #pump(sid, slot) {
4418
+ const now = this.#d.now();
4419
+ for (const j of [...slot.queue]) {
4420
+ if (!this.#expired(j, now)) continue;
4421
+ slot.queue.splice(slot.queue.indexOf(j), 1);
4422
+ this.#d.log(`\u6307\u4EE4 ${j.cmd.instructionId} \u6392\u961F\u5230\u8FC7\u671F\u4E5F\u6CA1\u7B49\u5230\u7A97\u53E3\u7A7A\u95F2`);
4423
+ this.#d.report(j.cmd, sid, "rejected", "expired");
4424
+ }
4425
+ if (slot.queue.length === 0) return;
4426
+ if (!slot.reg || this.#busy(sid, slot)) {
4427
+ for (const j of slot.queue) {
4428
+ if (j.queuedSent) continue;
4429
+ j.queuedSent = true;
4430
+ this.#d.log(`\u6307\u4EE4 ${j.cmd.instructionId} ${slot.reg ? "\u7A97\u53E3\u6B63\u5FD9" : "\u6682\u65E0\u7B49\u5F85\u8005"}\uFF0C\u6392\u961F`);
4431
+ this.#d.report(j.cmd, sid, "queued");
4432
+ }
4433
+ return;
4434
+ }
4435
+ const job = slot.queue.shift();
4436
+ const reg = slot.reg;
4437
+ reg.pending = {
4438
+ job,
4439
+ timer: setTimeout(() => {
4440
+ if (reg.pending?.job === job) reg.peer.close();
4441
+ }, this.#d.ackTimeoutMs)
4442
+ };
4443
+ reg.pending.timer.unref?.();
4444
+ reg.peer.send({ t: "fire", id: job.cmd.instructionId, text: job.cmd.text });
4445
+ }
4446
+ /** 等待者没了、又没有在跑的唤醒轮:排队的交回后台;然后排队 / 发出;同步标记;清空闲槽 */
4447
+ #settle(sid, slot) {
4448
+ if (!slot.reg && !slot.flight && slot.queue.length > 0) this.#drain(slot, "\u7B49\u5F85\u8005\u6CA1\u4E86");
4449
+ this.#pump(sid, slot);
4450
+ const on = !!slot.reg || !!slot.flight;
4451
+ if (on !== slot.marked) {
4452
+ slot.marked = on;
4453
+ this.#d.mark(sid, slot.agentPid, on);
4454
+ }
4455
+ if (!on && slot.queue.length === 0 && !slot.reg) this.#slots.delete(sid);
4456
+ }
4457
+ };
4458
+ function parseTail(v) {
4459
+ if (!v || typeof v !== "object") return void 0;
4460
+ const t = v;
4461
+ if (typeof t.id !== "string" || t.id !== "" && !isValidId(t.id)) return void 0;
4462
+ return { id: t.id, answered: t.answered === true, humanAfter: t.humanAfter === true, interrupted: t.interrupted === true };
4463
+ }
4464
+ function parseHello(m) {
4465
+ if (m.t !== "hello" || typeof m.sessionId !== "string") return null;
4466
+ const num = (v) => typeof v === "number" && Number.isInteger(v) && v > 0 ? v : void 0;
4467
+ return {
4468
+ sessionId: m.sessionId,
4469
+ agentPid: num(m.agentPid),
4470
+ waiterPid: num(m.waiterPid),
4471
+ event: typeof m.event === "string" ? m.event : void 0,
4472
+ last: typeof m.last === "string" ? m.last : void 0,
4473
+ tail: parseTail(m.tail),
4474
+ transcript: typeof m.transcript === "string" && m.transcript.endsWith(".jsonl") ? m.transcript : void 0,
4475
+ stopAt: num(m.stopAt),
4476
+ re: m.re === true
4477
+ };
4478
+ }
4479
+ var NONCE_RE = /^[0-9a-f]{32}$/;
4480
+ var AUTH_TIMEOUT_MS = 5e3;
4481
+ function startWakeServer(path, hub, secret, platform = process.platform) {
4482
+ if (platform !== "win32") {
4483
+ if (!ensureChannelDir(path)) return Promise.resolve(null);
4484
+ try {
4485
+ rmSync11(path, { force: true });
4486
+ } catch {
4487
+ }
4488
+ }
4489
+ return new Promise((resolve5) => {
4490
+ const server = createServer3((sock) => {
4491
+ sock.on("error", () => {
4492
+ });
4493
+ let phase = "hello";
4494
+ let nonceW = "";
4495
+ let nonceD = "";
4496
+ let regSid = "";
4497
+ const authTimer = setTimeout(() => {
4498
+ if (phase !== "ready") sock.destroy();
4499
+ }, AUTH_TIMEOUT_MS);
4500
+ authTimer.unref();
4501
+ const raw = (msg) => {
4502
+ if (sock.destroyed || !sock.writable) return;
4503
+ try {
4504
+ sock.write(JSON.stringify(msg) + "\n");
4505
+ } catch {
4506
+ }
4507
+ };
4508
+ const peer = {
4509
+ send(msg) {
4510
+ if (msg.t === "fire") raw({ ...msg, sid: regSid, mac: wakeMac(secret, nonceW, regSid, String(msg.id), String(msg.text)) });
4511
+ else raw(msg);
4512
+ },
4513
+ close() {
4514
+ sock.end();
4515
+ setTimeout(() => sock.destroy(), 200).unref();
4516
+ }
4517
+ };
4518
+ sock.on("close", () => {
4519
+ clearTimeout(authTimer);
4520
+ if (phase === "ready") {
4521
+ try {
4522
+ hub.onClose(peer);
4523
+ } catch (e) {
4524
+ hub.logError(`\u7B49\u5F85\u8005\u65AD\u5F00\u65F6\u5904\u7406\u51FA\u9519\uFF1A${e?.stack ?? String(e)}`);
4525
+ }
4526
+ }
4527
+ });
4528
+ const onLine = (line) => {
4529
+ const m = JSON.parse(line);
4530
+ if (!m || typeof m !== "object") throw new Error("bad frame");
4531
+ if (phase === "hello") {
4532
+ if (m.t !== "hello" || typeof m.nonceW !== "string" || !NONCE_RE.test(m.nonceW)) {
4533
+ sock.destroy();
4534
+ return;
4535
+ }
4536
+ nonceW = m.nonceW;
4537
+ nonceD = randomBytes6(16).toString("hex");
4538
+ phase = "auth2";
4539
+ raw({ t: "auth", nonceD, mac: wakeMac(secret, "d", nonceW, nonceD) });
4540
+ return;
4541
+ }
4542
+ if (phase === "auth2") {
4543
+ const reg = m.reg && typeof m.reg === "object" ? m.reg : null;
4544
+ const hello = reg ? parseHello({ ...reg, t: "hello" }) : null;
4545
+ if (m.t !== "auth2" || !hello || !macEqual(m.mac, wakeMac(secret, "w", nonceD, canonicalJson(reg)))) {
4546
+ sock.destroy();
4547
+ return;
4548
+ }
4549
+ regSid = hello.sessionId;
4550
+ phase = "ready";
4551
+ clearTimeout(authTimer);
4552
+ hub.onHello(peer, hello);
4553
+ return;
4554
+ }
4555
+ if (m.t !== "fired" && m.t !== "refuse" || typeof m.id !== "string" || !macEqual(m.mac, wakeMac(secret, "u", nonceD, m.t, m.id))) {
4556
+ sock.destroy();
4557
+ return;
4558
+ }
4559
+ hub.onMessage(peer, m);
4560
+ };
4561
+ lines(sock, (line) => {
4562
+ try {
4563
+ onLine(line);
4564
+ } catch {
4565
+ sock.destroy();
4566
+ }
4567
+ });
4568
+ });
4569
+ const oldMask = platform === "win32" ? null : process.umask(127);
4570
+ const restore = () => {
4571
+ if (oldMask !== null) process.umask(oldMask);
4572
+ };
4573
+ server.once("error", () => {
4574
+ restore();
4575
+ resolve5(null);
4576
+ });
4577
+ server.listen(path, () => {
4578
+ restore();
4579
+ server.on("error", () => {
4580
+ });
4581
+ resolve5(server);
4582
+ });
4583
+ });
4584
+ }
4585
+
4586
+ // src/daemon/index.ts
4587
+ var IDLE_TIMEOUT_MS = 150 * 1e3;
4588
+ var POLICY_VIOLATION = 1008;
4589
+ var PING_FRAME_SERVICE = "ping";
4590
+ var PONG_FRAME = JSON.stringify({ service: "pong", version: "1.0" });
4591
+ var CODEX_REMOTE_REFRESH_MS = 30 * 1e3;
4592
+ var CLAIM_WAIT_MS = 15 * 1e3;
4593
+ var WINDOW_READY_MS = 30 * 1e3;
4594
+ var CODEX_WATCH_MS = 5 * 1e3;
4595
+ function defaultCodexAppServers() {
4596
+ const cfg = loadConfig();
4597
+ return cfg.enabledTools.includes("codex") ? discoverAppServers(cfg.codexAppServer) : [];
4598
+ }
4599
+ function opencodeReady() {
4600
+ return isOcPluginInstalled(opencodePluginPath()) && existsSync10(ocPluginModule(installedHookPath()));
4601
+ }
4602
+ function orphanResumeText(summary2) {
4603
+ return `\uFF08\u4F60\u5728\u8BBE\u5907\u4E0A\u5DF2\u6279\u51C6\uFF1A${summary2}\uFF09\u8BF7\u7EE7\u7EED\u521A\u624D\u88AB\u4E2D\u65AD\u7684\u64CD\u4F5C\u3002`;
4604
+ }
3573
4605
  var ALIVE_REPORT_MS = 60 * 1e3;
3574
4606
  var Daemon = class {
3575
4607
  #queue = new SessionQueue();
@@ -3587,6 +4619,15 @@ var Daemon = class {
3587
4619
  #windowScan = null;
3588
4620
  #aliveReport = null;
3589
4621
  #codexTimer = null;
4622
+ #wake;
4623
+ /**
4624
+ * 会话号 → 这个会话正在进行的开窗流程(开窗、等认领、交首句;降级后台时一直到后台那次跑完)。
4625
+ * 🔴 同一会话不能被两个进程同时续跑(实测会静默丢一整轮):后到的指令等它结束再路由
4626
+ */
4627
+ #opening = /* @__PURE__ */ new Map();
4628
+ #wakeServer = null;
4629
+ #wakeTimer = null;
4630
+ #wakeReport = null;
3590
4631
  /** url → 连着它的 CodexWatch。插入顺序 = 发现顺序(DeskBuddy 管的在前) */
3591
4632
  #codex = /* @__PURE__ */ new Map();
3592
4633
  #codexStarted = false;
@@ -3607,8 +4648,35 @@ var Daemon = class {
3607
4648
  codexRoute: deps.codexRoute ?? ((tid) => this.#codexRouteFor(tid)),
3608
4649
  report: deps.report ?? ((p) => api.commandResult(p)),
3609
4650
  windowFor: deps.windowFor ?? windowForSession,
3610
- sendToWindow: deps.sendToWindow ?? ((id, req2, onReply) => sendToWindow(channelPath(id), req2, onReply))
4651
+ sendToWindow: deps.sendToWindow ?? ((id, req2, onReply) => sendToWindow(channelPath(id), windowSecret(id), req2, onReply)),
4652
+ gui: deps.gui ?? (() => guiAvailable(defaultGuiDeps())),
4653
+ openWindow: deps.openWindow ?? ((runId) => this.#openTerminal(runId)),
4654
+ endedCwd: deps.endedCwd ?? ((sid, tool, stateCwd) => resolveEndedCwd(sid, tool, {
4655
+ readEnded: (s) => readEnded(s) ?? (stateCwd ? { sessionId: s, tool, cwd: stateCwd, interactive: false, endedAt: 0 } : null),
4656
+ transcriptCwd: (s) => claudeTranscriptCwd(s),
4657
+ isDir: isDirSync
4658
+ })),
4659
+ claimWaitMs: deps.claimWaitMs ?? CLAIM_WAIT_MS,
4660
+ windowReadyMs: deps.windowReadyMs ?? WINDOW_READY_MS,
4661
+ wakePath: deps.wakePath === void 0 ? wakeChannelPath() : deps.wakePath,
4662
+ onWindowDone: deps.onWindowDone ?? (() => {
4663
+ })
3611
4664
  };
4665
+ this.#wake = new WakeHub({
4666
+ now: this.#deps.now,
4667
+ readState,
4668
+ alive: (pid) => isAlive(pid),
4669
+ report: (cmd, sid, status, reason) => this.#report(cmd, sid, status, "window", reason ?? ""),
4670
+ fallback: (cmd) => this.#dispatch(cmd),
4671
+ done: (cmd, sid, text) => this.#deps.onWindowDone({ instructionId: cmd.instructionId, sessionId: sid, tool: cmd.tool, text }),
4672
+ mark: (sid, pid, on) => {
4673
+ if (on) writeWakeMark(sid, pid, this.#deps.now());
4674
+ else removeWakeMark(sid);
4675
+ log(`\u4F1A\u8BDD ${sid} ${on ? "\u53EF\u4EE5" : "\u4E0D\u518D\u80FD"}\u88AB\u8BBE\u5907\u6307\u4EE4\u5524\u9192`);
4676
+ this.#scheduleWakeReport();
4677
+ },
4678
+ log
4679
+ });
3612
4680
  this.#store.prune(this.#deps.now());
3613
4681
  this.#coord = new ApprovalCoordinator({
3614
4682
  store: this.#store,
@@ -3632,8 +4700,55 @@ var Daemon = class {
3632
4700
  this.#codexTick();
3633
4701
  this.#codexTimer = setInterval(() => this.#codexTick(), CODEX_WATCH_MS);
3634
4702
  this.#codexTimer.unref();
4703
+ await this.startWake();
3635
4704
  return port;
3636
4705
  }
4706
+ /**
4707
+ * 起窗口唤醒通道(Claude Code asyncRewake 等待者连这里,见 hook/wake.cjs 与 ./wake.ts)。
4708
+ * 先清上次残留的标记:还活着的等待者几秒内会重连重新登记
4709
+ */
4710
+ async startWake() {
4711
+ const path = this.#deps.wakePath;
4712
+ if (!path) return;
4713
+ clearWakeMarks();
4714
+ const secret = randomBytes7(32);
4715
+ let warn = "";
4716
+ try {
4717
+ warn = writeWakeSecret(secret.toString("hex"));
4718
+ } catch (e) {
4719
+ log(`\u7A97\u53E3\u5524\u9192\u5BC6\u94A5\u5199\u4E0D\u8FDB\u53BB\uFF0C\u4E0D\u8D77\u5524\u9192\u901A\u9053\uFF1A${e.message}`);
4720
+ return;
4721
+ }
4722
+ if (warn) log(`\u26A0\uFE0F \u5B89\u5168\u544A\u8B66\uFF1A${warn}`);
4723
+ this.#wakeServer = await startWakeServer(path, this.#wake, secret);
4724
+ if (this.#wakeServer) log("\u7A97\u53E3\u5524\u9192\u901A\u9053\u5DF2\u5C31\u7EEA");
4725
+ else if (process.platform === "win32")
4726
+ 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");
4727
+ else log("\u7A97\u53E3\u5524\u9192\u901A\u9053\u6CA1\u8D77\u6765\uFF08\u4E0D\u5F71\u54CD\u5176\u5B83\u529F\u80FD\uFF09");
4728
+ this.#wakeTimer = setInterval(() => this.#wake.tick(), 1e3);
4729
+ this.#wakeTimer.unref();
4730
+ }
4731
+ /** 唤醒能力变了:300ms 内合并,报一次 Claude Code 的快照(remoteWindow 跟着变) */
4732
+ #scheduleWakeReport() {
4733
+ if (this.#wakeReport) return;
4734
+ this.#wakeReport = setTimeout(() => {
4735
+ this.#wakeReport = null;
4736
+ void this.#reportTool("claude_code");
4737
+ }, 300);
4738
+ this.#wakeReport.unref();
4739
+ }
4740
+ async #reportTool(tool) {
4741
+ const cfg = loadConfig();
4742
+ if (!cfg.clientId || !cfg.boundAt || !cfg.enabledTools.includes(tool)) return;
4743
+ try {
4744
+ await postReport(buildSnapshot(withWake(readAliveStates(tool)), tool, cfg, this.#deps.now()));
4745
+ } catch (e) {
4746
+ log(`\u4E0A\u62A5 ${tool} \u4F1A\u8BDD\u5931\u8D25\uFF1A${e.message}`);
4747
+ }
4748
+ }
4749
+ get wake() {
4750
+ return this.#wake;
4751
+ }
3637
4752
  /** 找 app-server:新出现的连上,不在了且已断开的丢掉;连着的各自 tick */
3638
4753
  #codexTick() {
3639
4754
  let urls = [];
@@ -3728,7 +4843,9 @@ var Daemon = class {
3728
4843
  const cfg = loadConfig();
3729
4844
  if (!cfg.clientId || !cfg.boundAt) return;
3730
4845
  for (const tool of cfg.enabledTools) {
3731
- await postReport(buildSnapshot(readAliveStates(tool), tool, cfg, this.#deps.now(), tool === "codex" ? this.#codexLoaded() : null));
4846
+ await postReport(
4847
+ buildSnapshot(withWake(readAliveStates(tool)), tool, cfg, this.#deps.now(), tool === "codex" ? this.#codexLoaded() : null)
4848
+ );
3732
4849
  }
3733
4850
  if (++this.#dirsTick % 10 === 0) await this.#reportDirs();
3734
4851
  }
@@ -3741,6 +4858,10 @@ var Daemon = class {
3741
4858
  if (this.#windowScan) clearInterval(this.#windowScan);
3742
4859
  if (this.#aliveReport) clearInterval(this.#aliveReport);
3743
4860
  if (this.#codexTimer) clearInterval(this.#codexTimer);
4861
+ if (this.#wakeTimer) clearInterval(this.#wakeTimer);
4862
+ if (this.#wakeReport) clearTimeout(this.#wakeReport);
4863
+ this.#wake.closeAll();
4864
+ this.#wakeServer?.close();
3744
4865
  if (this.#codexReportTimer) clearTimeout(this.#codexReportTimer);
3745
4866
  for (const w of this.#codex.values()) w.stop();
3746
4867
  this.#codex.clear();
@@ -3794,7 +4915,11 @@ var Daemon = class {
3794
4915
  });
3795
4916
  ws.on("message", (raw) => {
3796
4917
  this.#armIdle();
3797
- this.#onMessage(String(raw));
4918
+ try {
4919
+ this.#onMessage(String(raw));
4920
+ } catch (e) {
4921
+ log(`\u5904\u7406\u4E0B\u884C\u5E27\u51FA\u9519\uFF1A${e.stack ?? e.message}`);
4922
+ }
3798
4923
  });
3799
4924
  ws.on("error", (e) => log(`\u8FDE\u63A5\u51FA\u9519: ${e.message}`));
3800
4925
  ws.on("close", (code, reason) => {
@@ -3848,6 +4973,22 @@ var Daemon = class {
3848
4973
  return;
3849
4974
  }
3850
4975
  this.#seen.save();
4976
+ this.#route(cmd);
4977
+ }
4978
+ /**
4979
+ * 指令分派(去重之后)。同一会话的开窗流程还没走完时,后到的指令等它结束再从头路由一遍
4980
+ * (届时窗口已登记 → 走窗口写入;降级后台了 → 按原来的按会话串行排队)
4981
+ */
4982
+ #route(cmd) {
4983
+ if (cmd.tool === "claude_code" && cmd.sessionId) {
4984
+ const sid = resolveAlias(cmd.sessionId);
4985
+ const pending = this.#opening.get(sid);
4986
+ if (pending) {
4987
+ log(`\u6307\u4EE4 ${cmd.instructionId} \u7B49\u540C\u4E00\u4F1A\u8BDD\u7684\u5F00\u7A97\u6D41\u7A0B\u7ED3\u675F\u540E\u518D\u5206\u6D3E`);
4988
+ void pending.then(() => this.#route(cmd));
4989
+ return;
4990
+ }
4991
+ }
3851
4992
  if (cmd.sessionId && !isExpired(cmd, this.#deps.now())) {
3852
4993
  const sid = resolveAlias(cmd.sessionId);
3853
4994
  let win = null;
@@ -3878,8 +5019,208 @@ var Daemon = class {
3878
5019
  }
3879
5020
  }
3880
5021
  }
5022
+ this.#afterPty(cmd);
5023
+ }
5024
+ /**
5025
+ * pty 窗口接管之外的两条路:窗口里有登记中的唤醒等待者 → 交给它(忙时排队);否则原来的后台路径。
5026
+ * 🔴 一条指令只走一条路:交给等待者之后的任何结局都由 WakeHub 负责,不再回到这里
5027
+ */
5028
+ #afterPty(cmd) {
5029
+ if (cmd.tool === "claude_code" && cmd.sessionId && !isExpired(cmd, this.#deps.now())) {
5030
+ const sid = resolveAlias(cmd.sessionId);
5031
+ if (this.#wake.submit(cmd, sid)) return;
5032
+ }
5033
+ if (cmd.tool === "claude_code" && !isExpired(cmd, this.#deps.now())) {
5034
+ void this.#openOrDispatch(cmd).catch((e) => {
5035
+ log(`\u6307\u4EE4 ${cmd.instructionId} \u5F00\u7A97\u6D41\u7A0B\u51FA\u9519\uFF1A${e?.stack ?? String(e)}`);
5036
+ this.#report(cmd, cmd.sessionId, "failed", "", "window_failed");
5037
+ });
5038
+ return;
5039
+ }
5040
+ if (cmd.sessionId && !isExpired(cmd, this.#deps.now())) {
5041
+ const sid = resolveAlias(cmd.sessionId);
5042
+ if (!readState(sid) && readEnded(sid)?.tool === cmd.tool) {
5043
+ const r = this.#deps.endedCwd(sid, cmd.tool, "");
5044
+ if (!r.ok) {
5045
+ log(`\u6307\u4EE4 ${cmd.instructionId} \u4F1A\u8BDD\u5DF2\u7ED3\u675F\u4F46${r.reason === "no_cwd" ? "\u53D6\u4E0D\u5230\u53EF\u9760\u7684\u5DE5\u4F5C\u76EE\u5F55" : "\u5DE5\u4F5C\u76EE\u5F55\u5DF2\u4E0D\u5728"}\uFF0C\u4E0D\u6267\u884C`);
5046
+ this.#report(cmd, sid, "rejected", "", r.reason);
5047
+ return;
5048
+ }
5049
+ log(`\u6307\u4EE4 ${cmd.instructionId} \u4F1A\u8BDD\u5DF2\u7ED3\u675F\uFF0C\u540E\u53F0\u7EED\u8DD1`);
5050
+ void this.#headless(cmd, sid, r.cwd);
5051
+ return;
5052
+ }
5053
+ }
3881
5054
  this.#dispatch(cmd);
3882
5055
  }
5056
+ /**
5057
+ * 新建任务、会话已结束 → 开窗;会话还活着(后台 -p 在跑、或在用户自己开的窗口里)→ 原来的路径(排队 / window_open)
5058
+ */
5059
+ async #openOrDispatch(cmd) {
5060
+ if (tooLongToInject(cmd.text)) {
5061
+ log(`\u6307\u4EE4 ${cmd.instructionId} \u592A\u957F\uFF08${cmd.text.length} \u5B57\uFF09\uFF0C\u4E0D\u6267\u884C`);
5062
+ this.#report(cmd, cmd.sessionId ? resolveAlias(cmd.sessionId) : "", "rejected", "", TEXT_TOO_LONG);
5063
+ return;
5064
+ }
5065
+ if (!cmd.sessionId) {
5066
+ let cwd = "";
5067
+ if (cmd.dirRef) {
5068
+ cwd = dirOf(cmd.dirRef);
5069
+ if (!cwd) {
5070
+ this.#report(cmd, "", "rejected", "", "dir_gone");
5071
+ return;
5072
+ }
5073
+ } else {
5074
+ cwd = loadConfig().remoteCwd || "";
5075
+ if (!cwd) {
5076
+ this.#report(cmd, "", "rejected", "", "no_remote_cwd");
5077
+ return;
5078
+ }
5079
+ }
5080
+ await this.#openWindowFor(cmd, "", cwd);
5081
+ return;
5082
+ }
5083
+ const sid = resolveAlias(cmd.sessionId);
5084
+ for (let p = this.#opening.get(sid); p; p = this.#opening.get(sid)) await p;
5085
+ let release2;
5086
+ this.#opening.set(sid, new Promise((r) => release2 = r));
5087
+ try {
5088
+ if (this.#queue.busy(sid)) await this.#queue.enqueue(sid, async () => {
5089
+ });
5090
+ const st = readState(sid);
5091
+ if (st && (!st.pid || isAlive(st.pid, st.pidName))) {
5092
+ this.#dispatch(cmd);
5093
+ return;
5094
+ }
5095
+ if (!SESSION_UUID_RE.test(sid)) {
5096
+ this.#dispatch(cmd);
5097
+ return;
5098
+ }
5099
+ const r = this.#deps.endedCwd(sid, cmd.tool, st?.cwd ?? "");
5100
+ if (!r.ok) {
5101
+ log(`\u6307\u4EE4 ${cmd.instructionId} \u4F1A\u8BDD\u5DF2\u7ED3\u675F\u4F46${r.reason === "no_cwd" ? "\u53D6\u4E0D\u5230\u53EF\u9760\u7684\u5DE5\u4F5C\u76EE\u5F55" : "\u5DE5\u4F5C\u76EE\u5F55\u5DF2\u4E0D\u5728"}\uFF0C\u4E0D\u6267\u884C`);
5102
+ this.#report(cmd, sid, "rejected", "", r.reason);
5103
+ return;
5104
+ }
5105
+ await this.#openWindowFor(cmd, sid, r.cwd);
5106
+ } finally {
5107
+ this.#opening.delete(sid);
5108
+ release2();
5109
+ }
5110
+ }
5111
+ /**
5112
+ * 开窗 → 等认领 → 首句经通道交给窗口;任何一步起不来 → 后台 -p(回报 started/headless)。
5113
+ * 返回时:窗口已收下首句 / 已按失败回报 / 降级的后台那次已跑完
5114
+ */
5115
+ async #openWindowFor(cmd, sid, cwd) {
5116
+ const headless = (why) => {
5117
+ log(`\u6307\u4EE4 ${cmd.instructionId} ${why}\uFF0C\u6539\u540E\u53F0\u6267\u884C`);
5118
+ return this.#headless(cmd, sid, cwd);
5119
+ };
5120
+ let gui = false;
5121
+ try {
5122
+ gui = this.#deps.gui();
5123
+ } catch {
5124
+ gui = false;
5125
+ }
5126
+ if (!gui) return headless("\u6CA1\u6709\u56FE\u5F62\u4F1A\u8BDD\uFF08SSH / \u9501\u5B9A\u7684\u670D\u52A1\u4F1A\u8BDD / Linux\uFF09");
5127
+ const runId = randomBytes7(16).toString("hex");
5128
+ try {
5129
+ writeLaunch(runId, { tool: "claude_code", cwd, resume: sid, at: this.#deps.now() });
5130
+ } catch (e) {
5131
+ return headless(`\u5199\u4EA4\u63A5\u5355\u5931\u8D25\uFF1A${e.message}`);
5132
+ }
5133
+ try {
5134
+ await this.#deps.openWindow(runId);
5135
+ } catch (e) {
5136
+ if (claimLaunch(runId)) {
5137
+ removeCommandFile(deskbuddyHome(), runId);
5138
+ return headless(`\u5F00\u7A97\u5931\u8D25\uFF1A${e.message}`);
5139
+ }
5140
+ log(`\u6307\u4EE4 ${cmd.instructionId} \u5F00\u7A97\u547D\u4EE4\u62A5\u9519\uFF08${e.message}\uFF09\uFF0C\u4F46\u7A97\u53E3\u5DF2\u8BA4\u9886\uFF0C\u6309\u7A97\u53E3\u7EE7\u7EED`);
5141
+ }
5142
+ const sleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
5143
+ const claimDeadline = this.#deps.now() + this.#deps.claimWaitMs;
5144
+ while (launchPending(runId) && this.#deps.now() < claimDeadline) await sleep2(300);
5145
+ removeCommandFile(deskbuddyHome(), runId);
5146
+ if (launchPending(runId) && claimLaunch(runId)) return headless(`${Math.round(this.#deps.claimWaitMs / 1e3)} \u79D2\u5185\u7A97\u53E3\u6CA1\u8D77\u6765`);
5147
+ log(`\u6307\u4EE4 ${cmd.instructionId} \u7A97\u53E3\u5DF2\u8D77\u6765\uFF08${runId.slice(0, 8)}\uFF09\uFF0C\u7B49\u5B83\u5C31\u7EEA\u540E\u5199\u5165\u9996\u53E5`);
5148
+ const req2 = { instructionId: cmd.instructionId, sessionId: "", text: cmd.text, expireAt: cmd.expireAt };
5149
+ const fb = { p: null, final: false };
5150
+ const readyDeadline = this.#deps.now() + this.#deps.windowReadyMs;
5151
+ while (this.#deps.now() < readyDeadline) {
5152
+ let r = "fallback";
5153
+ try {
5154
+ r = await this.#deps.sendToWindow(runId, req2, (reply) => {
5155
+ if (reply.status === "ack") return;
5156
+ if (reply.status !== "queued") fb.final = true;
5157
+ if (reply.status === "not_here") {
5158
+ if (!fb.p) fb.p = headless("\u9996\u53E5\u5199\u5165\u524D\u7A97\u53E3\u5DF2\u5173\u95ED");
5159
+ return;
5160
+ }
5161
+ log(`\u6307\u4EE4 ${cmd.instructionId} \u7A97\u53E3 ${reply.status}${reply.reason ? " " + reply.reason : ""}`);
5162
+ this.#report(cmd, sid, reply.status, "window", reply.reason ?? "");
5163
+ });
5164
+ } catch {
5165
+ r = "fallback";
5166
+ }
5167
+ if (r === "handled") {
5168
+ log(`\u6307\u4EE4 ${cmd.instructionId} \u9996\u53E5\u5DF2\u4EA4\u7ED9\u65B0\u7A97\u53E3 len=${cmd.text.length}`);
5169
+ if (sid) {
5170
+ const left = cmd.expireAt > 0 ? Math.max(cmd.expireAt - this.#deps.now(), 0) : 10 * 60 * 1e3;
5171
+ const cap = this.#deps.now() + left + this.#deps.windowReadyMs;
5172
+ while (!fb.final && this.#deps.now() < cap) {
5173
+ let found = null;
5174
+ try {
5175
+ found = this.#deps.windowFor(sid);
5176
+ } catch {
5177
+ found = null;
5178
+ }
5179
+ if (found) break;
5180
+ await sleep2(200);
5181
+ }
5182
+ }
5183
+ await sleep2(50);
5184
+ if (fb.p) await fb.p;
5185
+ return;
5186
+ }
5187
+ await sleep2(1e3);
5188
+ }
5189
+ log(`\u6307\u4EE4 ${cmd.instructionId} \u65B0\u7A97\u53E3\u7684\u901A\u9053\u4E00\u76F4\u6CA1\u5C31\u7EEA`);
5190
+ this.#report(cmd, sid, "failed", "window", "window_failed");
5191
+ }
5192
+ /**
5193
+ * 后台 -p:新建任务走原来的路径;续跑已结束会话用解析出的 cwd,不再查状态文件(它已被删)。
5194
+ * 返回的 promise 在后台那次跑完时落定(开窗流程的会话锁要一直占到那时)
5195
+ */
5196
+ #headless(cmd, sid, cwd) {
5197
+ if (isExpired(cmd, this.#deps.now())) {
5198
+ log(`\u6307\u4EE4 ${cmd.instructionId} \u5DF2\u8FC7\u671F\uFF0C\u4E0D\u518D\u540E\u53F0\u6267\u884C`);
5199
+ this.#report(cmd, sid, "rejected", "", "expired");
5200
+ return Promise.resolve();
5201
+ }
5202
+ if (!sid) {
5203
+ this.#dispatch(cmd);
5204
+ return Promise.resolve();
5205
+ }
5206
+ return this.#queue.enqueue(
5207
+ sid,
5208
+ () => this.#execute(cmd, sid, cwd, cmd.tool, true),
5209
+ (e) => {
5210
+ log(`\u6307\u4EE4 ${cmd.instructionId} \u6267\u884C\u65F6\u51FA\u9519\uFF1A${e?.stack ?? String(e)}`);
5211
+ this.#report(cmd, sid, "failed", "", "exec_failed");
5212
+ }
5213
+ );
5214
+ }
5215
+ /** 真的弹窗口(测试环境绝不弹:vitest 下直接当开窗失败) */
5216
+ async #openTerminal(runId) {
5217
+ if (process.env.VITEST) throw new Error("\u6D4B\u8BD5\u73AF\u5883\u4E0D\u5F00\u7A97");
5218
+ const how = await openTerminal(
5219
+ { node: this.#deps.nodePath, cli: defaultCliPath(), home: deskbuddyHome(), runId },
5220
+ defaultOpenerDeps(() => findWt())
5221
+ );
5222
+ log(`\u5F00\u7A97 ${runId.slice(0, 8)} \u65B9\u5F0F=${how}`);
5223
+ }
3883
5224
  /**
3884
5225
  * 经 app-server 往 Codex 会话里发一轮(切片 B)。回报语义与 Claude 窗口一致(mode=window):
3885
5226
  * 排队 queued → 发出 started;排队中过期 rejected/expired。
@@ -3954,7 +5295,7 @@ var Daemon = class {
3954
5295
  const fallback = () => {
3955
5296
  if (fellBack) return;
3956
5297
  fellBack = true;
3957
- this.#dispatch(cmd);
5298
+ this.#afterPty(cmd);
3958
5299
  };
3959
5300
  const req2 = { instructionId: cmd.instructionId, sessionId, text: cmd.text, expireAt: cmd.expireAt };
3960
5301
  let r;
@@ -3998,10 +5339,17 @@ var Daemon = class {
3998
5339
  return;
3999
5340
  }
4000
5341
  if (target.sessionId) this.#coord.cancelSession(target.sessionId);
4001
- this.#queue.enqueue(target.sessionId, () => this.#execute(cmd, target.sessionId, target.cwd, target.handleKey));
5342
+ this.#queue.enqueue(
5343
+ target.sessionId,
5344
+ () => this.#execute(cmd, target.sessionId, target.cwd, target.handleKey),
5345
+ (e) => {
5346
+ log(`\u6307\u4EE4 ${cmd.instructionId} \u6267\u884C\u65F6\u51FA\u9519\uFF1A${e?.stack ?? String(e)}`);
5347
+ this.#report(cmd, target.sessionId, "failed", "", "exec_failed");
5348
+ }
5349
+ );
4002
5350
  }
4003
- async #execute(cmd, sessionId, cwd, handleKey = cmd.tool) {
4004
- if (sessionId) {
5351
+ async #execute(cmd, sessionId, cwd, handleKey = cmd.tool, ended = false) {
5352
+ if (sessionId && !ended) {
4005
5353
  const fresh = readState(sessionId);
4006
5354
  if (!fresh) {
4007
5355
  log(`\u6307\u4EE4 ${cmd.instructionId} \u653E\u5F03\uFF1A\u4F1A\u8BDD ${sessionId} \u5728\u6267\u884C\u524D\u5DF2\u6D88\u5931`);
@@ -4050,7 +5398,13 @@ var Daemon = class {
4050
5398
  return;
4051
5399
  }
4052
5400
  const origin = this.#runsDone.get(r.runId) ?? Promise.resolve();
4053
- void origin.then(() => this.#queue.enqueue(r.sessionId, () => this.#resume(r, kind)));
5401
+ void origin.then(
5402
+ () => this.#queue.enqueue(
5403
+ r.sessionId,
5404
+ () => this.#resume(r, kind),
5405
+ (e) => log(`\u7EED\u8DD1\u4F1A\u8BDD ${r.sessionId} \u51FA\u9519\uFF1A${e?.stack ?? String(e)}`)
5406
+ )
5407
+ );
4054
5408
  }
4055
5409
  async #resume(r, kind) {
4056
5410
  if (kind === "deferred" && r.consumed) {
@@ -4119,7 +5473,7 @@ var Daemon = class {
4119
5473
  }
4120
5474
  };
4121
5475
  function newRunId() {
4122
- return randomBytes5(12).toString("hex");
5476
+ return randomBytes7(12).toString("hex");
4123
5477
  }
4124
5478
  async function runDaemon(deps = {}) {
4125
5479
  const d = new Daemon(deps);
@@ -4137,7 +5491,7 @@ async function runDaemonCommand(opts = {}) {
4137
5491
  }
4138
5492
  if (opts.foreground) process.env.DESKBUDDY_DAEMON_FOREGROUND = "1";
4139
5493
  try {
4140
- writeFileSync13(pidFilePath(), String(process.pid), "utf8");
5494
+ writeFileSync17(pidFilePath(), String(process.pid), "utf8");
4141
5495
  } catch {
4142
5496
  }
4143
5497
  let daemon;
@@ -4153,7 +5507,7 @@ async function runDaemonCommand(opts = {}) {
4153
5507
  log(`\u6536\u5230 ${sig}\uFF0C\u9000\u51FA`);
4154
5508
  daemon.stop();
4155
5509
  try {
4156
- rmSync7(pidFilePath(), { force: true });
5510
+ rmSync12(pidFilePath(), { force: true });
4157
5511
  } catch {
4158
5512
  }
4159
5513
  process.exit(0);
@@ -4170,8 +5524,8 @@ async function runDaemonCommand(opts = {}) {
4170
5524
  }
4171
5525
 
4172
5526
  // src/core/registry.ts
4173
- import { mkdirSync as mkdirSync14, readFileSync as readFileSync13, writeFileSync as writeFileSync14 } from "fs";
4174
- import { join as join17 } from "path";
5527
+ import { mkdirSync as mkdirSync18, readFileSync as readFileSync16, writeFileSync as writeFileSync18 } from "fs";
5528
+ import { join as join21 } from "path";
4175
5529
  var FALLBACK = {
4176
5530
  version: "builtin",
4177
5531
  tools: {
@@ -4249,11 +5603,11 @@ var FALLBACK = {
4249
5603
  }
4250
5604
  };
4251
5605
  function registryCachePath() {
4252
- return join17(deskbuddyHome(), "tool-registry.json");
5606
+ return join21(deskbuddyHome(), "tool-registry.json");
4253
5607
  }
4254
5608
  function loadRegistry() {
4255
5609
  try {
4256
- return JSON.parse(readFileSync13(registryCachePath(), "utf8"));
5610
+ return JSON.parse(readFileSync16(registryCachePath(), "utf8"));
4257
5611
  } catch {
4258
5612
  return FALLBACK;
4259
5613
  }
@@ -4261,15 +5615,15 @@ function loadRegistry() {
4261
5615
  async function refreshRegistry() {
4262
5616
  try {
4263
5617
  const registry = await api.toolRegistry();
4264
- mkdirSync14(deskbuddyHome(), { recursive: true });
4265
- writeFileSync14(registryCachePath(), JSON.stringify(registry, null, 2), "utf8");
5618
+ mkdirSync18(deskbuddyHome(), { recursive: true });
5619
+ writeFileSync18(registryCachePath(), JSON.stringify(registry, null, 2), "utf8");
4266
5620
  } catch {
4267
5621
  }
4268
5622
  }
4269
5623
 
4270
5624
  // 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";
5625
+ import { chmodSync as chmodSync6, existsSync as existsSync11, mkdirSync as mkdirSync19, readFileSync as readFileSync17, renameSync as renameSync14, rmSync as rmSync13, statSync as statSync6, writeFileSync as writeFileSync19 } from "fs";
5626
+ import { dirname as dirname6, join as join22 } from "path";
4273
5627
  var CC_EVENTS = [
4274
5628
  "UserPromptSubmit",
4275
5629
  "PreToolUse",
@@ -4280,7 +5634,7 @@ var CC_EVENTS = [
4280
5634
  ];
4281
5635
  function readSettings(path) {
4282
5636
  if (!existsSync11(path)) return {};
4283
- const raw = readFileSync14(path, "utf8");
5637
+ const raw = readFileSync17(path, "utf8");
4284
5638
  if (raw.trim() === "") return {};
4285
5639
  try {
4286
5640
  return JSON.parse(raw);
@@ -4289,17 +5643,17 @@ function readSettings(path) {
4289
5643
  }
4290
5644
  }
4291
5645
  function writeSettings(path, settings) {
4292
- mkdirSync15(dirname6(path), { recursive: true });
5646
+ mkdirSync19(dirname6(path), { recursive: true });
4293
5647
  let mode;
4294
5648
  try {
4295
- mode = statSync4(path).mode & 511;
5649
+ mode = statSync6(path).mode & 511;
4296
5650
  } catch {
4297
5651
  mode = void 0;
4298
5652
  }
4299
5653
  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);
5654
+ writeFileSync19(tmp, JSON.stringify(settings, null, 2), "utf8");
5655
+ if (mode !== void 0) chmodSync6(tmp, mode);
5656
+ renameSync14(tmp, path);
4303
5657
  }
4304
5658
  function mentions(command, script) {
4305
5659
  return !!command && (command.includes(script) || command.includes(fwdSlash(script)));
@@ -4333,25 +5687,98 @@ function installCcWindowGate(settingsPath, gatePath, tool = "claude_code", platf
4333
5687
  });
4334
5688
  writeSettings(settingsPath, settings);
4335
5689
  }
5690
+ var REWAKE_TIMEOUT_S = 20 * 24 * 60 * 60;
5691
+ 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";
5692
+ var REWAKE_SUMMARY = "DeskBuddy \u8BBE\u5907\u6307\u4EE4";
5693
+ var REWAKE_EVENTS = ["SessionStart", "Stop"];
5694
+ var REWAKE_CONTEXT_TIMEOUT_S = 10;
5695
+ function rewakeCommand(wakePath, platform = process.platform) {
5696
+ if (platform === "win32") return `node ${winCmdPath(wakePath)}; exit $LASTEXITCODE`;
5697
+ return `node ${shQuote2(wakePath)}`;
5698
+ }
5699
+ function rewakeContextCommand(wakePath, platform = process.platform) {
5700
+ return `node ${platform === "win32" ? winCmdPath(wakePath) : shQuote2(wakePath)} --context`;
5701
+ }
5702
+ function rewakeKind(h) {
5703
+ return /\s--context(\s|$)/.test(h.command) ? "context" : "wait";
5704
+ }
5705
+ function installCcRewake(settingsPath, wakePath, platform = process.platform) {
5706
+ const settings = readSettings(settingsPath);
5707
+ settings.hooks ??= {};
5708
+ const entries = {
5709
+ wait: {
5710
+ type: "command",
5711
+ command: rewakeCommand(wakePath, platform),
5712
+ asyncRewake: true,
5713
+ timeout: REWAKE_TIMEOUT_S,
5714
+ rewakeMessage: REWAKE_MESSAGE,
5715
+ rewakeSummary: REWAKE_SUMMARY
5716
+ },
5717
+ context: { type: "command", command: rewakeContextCommand(wakePath, platform), timeout: REWAKE_CONTEXT_TIMEOUT_S }
5718
+ };
5719
+ if (platform === "win32") {
5720
+ entries.wait.shell = "powershell";
5721
+ entries.context.shell = "powershell";
5722
+ }
5723
+ const wanted = { SessionStart: ["context", "wait"], Stop: ["wait"] };
5724
+ let changed = false;
5725
+ for (const event of REWAKE_EVENTS) {
5726
+ const groups = settings.hooks[event] ??= [];
5727
+ const seen = /* @__PURE__ */ new Set();
5728
+ for (const g of groups) {
5729
+ const hooks = g.hooks ?? [];
5730
+ for (let i = hooks.length - 1; i >= 0; i--) {
5731
+ if (!mentions(hooks[i].command, wakePath)) continue;
5732
+ const kind = rewakeKind(hooks[i]);
5733
+ if (!wanted[event].includes(kind) || seen.has(kind)) {
5734
+ hooks.splice(i, 1);
5735
+ changed = true;
5736
+ continue;
5737
+ }
5738
+ seen.add(kind);
5739
+ if (JSON.stringify(hooks[i]) !== JSON.stringify(entries[kind])) {
5740
+ hooks[i] = { ...entries[kind] };
5741
+ changed = true;
5742
+ }
5743
+ }
5744
+ }
5745
+ const before = groups.length;
5746
+ settings.hooks[event] = groups.filter((g) => (g.hooks ?? []).length > 0);
5747
+ if (settings.hooks[event].length !== before) changed = true;
5748
+ for (const kind of wanted[event]) {
5749
+ if (seen.has(kind)) continue;
5750
+ settings.hooks[event].push({ hooks: [{ ...entries[kind] }] });
5751
+ changed = true;
5752
+ }
5753
+ }
5754
+ if (changed) writeSettings(settingsPath, settings);
5755
+ }
4336
5756
  function uninstallCcHook(settingsPath, hookPath) {
4337
5757
  const settings = readSettings(settingsPath);
4338
5758
  if (!settings.hooks) return;
4339
- const gatePath = join18(dirname6(hookPath), "gate.cjs");
5759
+ const gatePath = join22(dirname6(hookPath), "gate.cjs");
5760
+ const wakePath = join22(dirname6(hookPath), "wake.cjs");
4340
5761
  for (const event of Object.keys(settings.hooks)) {
4341
5762
  const groups = settings.hooks[event] ?? [];
4342
5763
  const kept = [];
4343
5764
  for (const g of groups) {
4344
- const hooks = (g.hooks ?? []).filter((h) => !mentions(h.command, hookPath) && !mentions(h.command, gatePath));
5765
+ const hooks = (g.hooks ?? []).filter(
5766
+ (h) => !mentions(h.command, hookPath) && !mentions(h.command, gatePath) && !mentions(h.command, wakePath)
5767
+ );
4345
5768
  if (hooks.length > 0) kept.push({ ...g, hooks });
4346
5769
  }
4347
5770
  settings.hooks[event] = kept;
4348
5771
  }
4349
5772
  writeSettings(settingsPath, settings);
5773
+ try {
5774
+ rmSync13(join22(dirname6(hookPath), "wake", "tokens"), { recursive: true, force: true });
5775
+ } catch {
5776
+ }
4350
5777
  }
4351
5778
 
4352
5779
  // 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";
5780
+ import { chmodSync as chmodSync7, existsSync as existsSync12, mkdirSync as mkdirSync20, readFileSync as readFileSync18, renameSync as renameSync15, statSync as statSync7, writeFileSync as writeFileSync20 } from "fs";
5781
+ import { dirname as dirname7, join as join23 } from "path";
4355
5782
  var CODEX_MARKER_BEGIN = "# >>> deskbuddy >>>";
4356
5783
  var CODEX_MARKER_END = "# <<< deskbuddy <<<";
4357
5784
  var CODEX_EVENTS = [
@@ -4364,7 +5791,7 @@ var CODEX_EVENTS = [
4364
5791
  ];
4365
5792
  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
5793
  function codexConfigPath() {
4367
- return join19(codexHome(), "config.toml");
5794
+ return join23(codexHome(), "config.toml");
4368
5795
  }
4369
5796
  function renderBlock(hookPath, events, tool, platform) {
4370
5797
  const command = JSON.stringify(`node ${platform === "win32" ? winPsPath(hookPath) : hookPath} ${tool}`);
@@ -4383,17 +5810,17 @@ function renderBlock(hookPath, events, tool, platform) {
4383
5810
  return lines2.join("\n");
4384
5811
  }
4385
5812
  function writeAtomic(path, content) {
4386
- mkdirSync16(dirname7(path), { recursive: true });
5813
+ mkdirSync20(dirname7(path), { recursive: true });
4387
5814
  let mode;
4388
5815
  try {
4389
- mode = statSync5(path).mode & 511;
5816
+ mode = statSync7(path).mode & 511;
4390
5817
  } catch {
4391
5818
  mode = void 0;
4392
5819
  }
4393
5820
  const tmp = path + ".deskbuddy.tmp";
4394
- writeFileSync16(tmp, content, "utf8");
4395
- if (mode !== void 0) chmodSync5(tmp, mode);
4396
- renameSync12(tmp, path);
5821
+ writeFileSync20(tmp, content, "utf8");
5822
+ if (mode !== void 0) chmodSync7(tmp, mode);
5823
+ renameSync15(tmp, path);
4397
5824
  }
4398
5825
  function assertSafeToAppend(raw, path) {
4399
5826
  for (const quote of ['"""', "'''"]) {
@@ -4403,7 +5830,7 @@ function assertSafeToAppend(raw, path) {
4403
5830
  }
4404
5831
  }
4405
5832
  function installCodexHook(configPath2, hookPath, events = CODEX_EVENTS, tool = "codex", platform = process.platform) {
4406
- const raw = existsSync12(configPath2) ? readFileSync15(configPath2, "utf8") : "";
5833
+ const raw = existsSync12(configPath2) ? readFileSync18(configPath2, "utf8") : "";
4407
5834
  if (raw.includes(CODEX_MARKER_BEGIN)) return;
4408
5835
  assertSafeToAppend(raw, configPath2);
4409
5836
  const block = renderBlock(hookPath, events, tool, platform);
@@ -4412,7 +5839,7 @@ function installCodexHook(configPath2, hookPath, events = CODEX_EVENTS, tool = "
4412
5839
  }
4413
5840
  function uninstallCodexHook(configPath2) {
4414
5841
  if (!existsSync12(configPath2)) return;
4415
- const raw = readFileSync15(configPath2, "utf8");
5842
+ const raw = readFileSync18(configPath2, "utf8");
4416
5843
  const lines2 = raw.split("\n");
4417
5844
  const begin = lines2.findIndex((l) => l.trim() === CODEX_MARKER_BEGIN);
4418
5845
  if (begin === -1) return;
@@ -4426,11 +5853,11 @@ function uninstallCodexHook(configPath2) {
4426
5853
  }
4427
5854
 
4428
5855
  // src/commands/tools.ts
4429
- import { existsSync as existsSync13, readFileSync as readFileSync16 } from "fs";
5856
+ import { existsSync as existsSync13, readFileSync as readFileSync19 } from "fs";
4430
5857
  function fileContains(path, needle) {
4431
5858
  try {
4432
5859
  if (!existsSync13(path)) return false;
4433
- return readFileSync16(path, "utf8").includes(needle);
5860
+ return readFileSync19(path, "utf8").includes(needle);
4434
5861
  } catch {
4435
5862
  return false;
4436
5863
  }
@@ -4454,6 +5881,12 @@ var ADAPTERS = {
4454
5881
  if (tool !== "claude_code" && tool !== "qoder") return false;
4455
5882
  installCcWindowGate(p, gate, tool);
4456
5883
  return true;
5884
+ },
5885
+ // asyncRewake 只有 Claude Code 认(2.1.280 实测);别家当成同步 hook 会一直等
5886
+ installRewake: (p, wake, tool) => {
5887
+ if (tool !== "claude_code") return false;
5888
+ installCcRewake(p, wake);
5889
+ return true;
4457
5890
  }
4458
5891
  },
4459
5892
  "codex-toml": {
@@ -4498,13 +5931,13 @@ function subscribedEvents(registry, tool, fallback) {
4498
5931
  }
4499
5932
 
4500
5933
  // src/commands/enable.ts
4501
- import { dirname as dirname9, join as join22 } from "path";
5934
+ import { dirname as dirname9, join as join26 } from "path";
4502
5935
 
4503
5936
  // 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";
5937
+ import { execFileSync as execFileSync9 } from "child_process";
5938
+ import { chmodSync as chmodSync8, existsSync as existsSync14, mkdirSync as mkdirSync21, readdirSync as readdirSync5, readFileSync as readFileSync20, rmdirSync, rmSync as rmSync14, writeFileSync as writeFileSync21 } from "fs";
4506
5939
  import { homedir as homedir5 } from "os";
4507
- import { delimiter as delimiter2, join as join20, resolve as resolve2 } from "path";
5940
+ import { delimiter as delimiter2, join as join24, resolve as resolve2 } from "path";
4508
5941
  var SHIM_FALLBACK_EXIT = 213;
4509
5942
  var WIN_SHIM_ENABLED = false;
4510
5943
  var SHIM_COMMANDS = { claude_code: "claude", codex: "codex" };
@@ -4514,10 +5947,10 @@ var RC_END = "# <<< deskbuddy <<<";
4514
5947
  var RC_FILES = [".zshrc", ".bashrc"];
4515
5948
  var WIN_PENDING = "Windows \u7A97\u53E3\u63A5\u7BA1\u5F85\u9A8C\u8BC1\uFF0C\u6682\u672A\u5F00\u542F";
4516
5949
  function shimDir() {
4517
- return join20(deskbuddyHome(), "bin");
5950
+ return join24(deskbuddyHome(), "bin");
4518
5951
  }
4519
5952
  function shimFile(binDir, command, platform = process.platform) {
4520
- return join20(binDir, platform === "win32" ? `${command}.cmd` : command);
5953
+ return join24(binDir, platform === "win32" ? `${command}.cmd` : command);
4521
5954
  }
4522
5955
  function shQuote3(s) {
4523
5956
  return `'${s.replace(/'/g, `'\\''`)}'`;
@@ -4646,10 +6079,10 @@ function removeRcBlock(content) {
4646
6079
  return content.slice(0, start) + content.slice(end);
4647
6080
  }
4648
6081
  function readRc(p) {
4649
- return readFileSync17(p).toString("latin1");
6082
+ return readFileSync20(p).toString("latin1");
4650
6083
  }
4651
6084
  function writeRc(p, latin1) {
4652
- writeFileSync17(p, Buffer.from(latin1, "latin1"));
6085
+ writeFileSync21(p, Buffer.from(latin1, "latin1"));
4653
6086
  }
4654
6087
  function toLatin1(utf8) {
4655
6088
  return Buffer.from(utf8, "utf8").toString("latin1");
@@ -4671,7 +6104,7 @@ function removePathEntry(value, dir) {
4671
6104
  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
6105
  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
6106
  function psWinPath() {
4674
- const ps = (script, env) => execFileSync7("powershell.exe", ["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command", script], {
6107
+ const ps = (script, env) => execFileSync9("powershell.exe", ["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command", script], {
4675
6108
  encoding: "utf8",
4676
6109
  stdio: ["ignore", "pipe", "ignore"],
4677
6110
  windowsHide: true,
@@ -4718,12 +6151,12 @@ function installShim(tool, e) {
4718
6151
  if (!node || !cli || !bin) {
4719
6152
  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
6153
  }
4721
- mkdirSync17(e.binDir, { recursive: true });
4722
- writeFileSync17(file, cmdScript(tool, command, node, cli, bin), "utf8");
6154
+ mkdirSync21(e.binDir, { recursive: true });
6155
+ writeFileSync21(file, cmdScript(tool, command, node, cli, bin), "utf8");
4723
6156
  } 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);
6157
+ mkdirSync21(e.binDir, { recursive: true, mode: 448 });
6158
+ writeFileSync21(file, shScript(tool, command, e.nodePath, e.cliPath, e.binDir), { encoding: "utf8", mode: 493 });
6159
+ chmodSync8(file, 493);
4727
6160
  }
4728
6161
  const touched = [];
4729
6162
  if (e.platform === "win32") {
@@ -4737,7 +6170,7 @@ function installShim(tool, e) {
4737
6170
  } else if (e.rcHome) {
4738
6171
  const block = toLatin1(rcBlock(e.binDir, e.rcHome));
4739
6172
  for (const name of RC_FILES) {
4740
- const rc = join20(e.rcHome, name);
6173
+ const rc = join24(e.rcHome, name);
4741
6174
  if (!existsSync14(rc)) continue;
4742
6175
  const next = addRcBlock(readRc(rc), block);
4743
6176
  if (next !== null) writeRc(rc, next);
@@ -4758,13 +6191,13 @@ function installShim(tool, e) {
4758
6191
  function ourShims(binDir) {
4759
6192
  let names;
4760
6193
  try {
4761
- names = readdirSync3(binDir);
6194
+ names = readdirSync5(binDir);
4762
6195
  } catch {
4763
6196
  return [];
4764
6197
  }
4765
6198
  return names.filter((n) => {
4766
6199
  try {
4767
- return readFileSync17(join20(binDir, n), "utf8").includes(SHIM_MARKER);
6200
+ return readFileSync20(join24(binDir, n), "utf8").includes(SHIM_MARKER);
4768
6201
  } catch {
4769
6202
  return false;
4770
6203
  }
@@ -4776,8 +6209,8 @@ function uninstallShim(tool, e) {
4776
6209
  let removed = false;
4777
6210
  const file = shimFile(e.binDir, command, e.platform);
4778
6211
  try {
4779
- if (existsSync14(file) && readFileSync17(file, "utf8").includes(SHIM_MARKER)) {
4780
- rmSync8(file, { force: true });
6212
+ if (existsSync14(file) && readFileSync20(file, "utf8").includes(SHIM_MARKER)) {
6213
+ rmSync14(file, { force: true });
4781
6214
  removed = true;
4782
6215
  }
4783
6216
  } catch {
@@ -4795,7 +6228,7 @@ function uninstallShim(tool, e) {
4795
6228
  }
4796
6229
  } else if (e.rcHome) {
4797
6230
  for (const name of RC_FILES) {
4798
- const rc = join20(e.rcHome, name);
6231
+ const rc = join24(e.rcHome, name);
4799
6232
  if (!existsSync14(rc)) continue;
4800
6233
  const next = removeRcBlock(readRc(rc));
4801
6234
  if (next !== null) {
@@ -4805,7 +6238,7 @@ function uninstallShim(tool, e) {
4805
6238
  }
4806
6239
  }
4807
6240
  try {
4808
- if (existsSync14(e.binDir) && readdirSync3(e.binDir).length === 0) rmdirSync(e.binDir);
6241
+ if (existsSync14(e.binDir) && readdirSync5(e.binDir).length === 0) rmdirSync(e.binDir);
4809
6242
  } catch {
4810
6243
  }
4811
6244
  if (!removed && touched.length === 0) return { ok: true, message: "" };
@@ -4817,7 +6250,7 @@ function shimState(tool, binDir, pathEnv, platform = process.platform) {
4817
6250
  const file = shimFile(binDir, command, platform);
4818
6251
  let installed = false;
4819
6252
  try {
4820
- installed = readFileSync17(file, "utf8").includes(SHIM_MARKER);
6253
+ installed = readFileSync20(file, "utf8").includes(SHIM_MARKER);
4821
6254
  } catch {
4822
6255
  installed = false;
4823
6256
  }
@@ -4828,16 +6261,16 @@ function shimState(tool, binDir, pathEnv, platform = process.platform) {
4828
6261
  }
4829
6262
 
4830
6263
  // src/wrap/pty.ts
4831
- import { chmodSync as chmodSync7, existsSync as existsSync15, statSync as statSync6 } from "fs";
6264
+ import { chmodSync as chmodSync9, existsSync as existsSync15, statSync as statSync8 } from "fs";
4832
6265
  import { createRequire as createRequire2 } from "module";
4833
- import { dirname as dirname8, join as join21 } from "path";
6266
+ import { dirname as dirname8, join as join25 } from "path";
4834
6267
  var req = createRequire2(import.meta.url);
4835
6268
  function fixSpawnHelper() {
4836
6269
  if (process.platform !== "darwin") return;
4837
6270
  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);
6271
+ const dir = join25(dirname8(req.resolve("node-pty/package.json")), "prebuilds", `darwin-${process.arch}`);
6272
+ const helper = join25(dir, "spawn-helper");
6273
+ if (existsSync15(helper) && (statSync8(helper).mode & 73) === 0) chmodSync9(helper, 493);
4841
6274
  } catch {
4842
6275
  }
4843
6276
  }
@@ -4932,12 +6365,19 @@ function installTools(tools, out, shim = defaultShimEnvForInstall()) {
4932
6365
  installed.push(tool);
4933
6366
  out(`\u2705 ${tool}\uFF1Ahook \u5DF2\u88C5\u8FDB ${configPath2}\uFF08${events.length} \u4E2A\u4E8B\u4EF6\uFF09`);
4934
6367
  try {
4935
- if (adapter.installWindowGate?.(configPath2, join22(dirname9(hookPath), "gate.cjs"), tool)) {
6368
+ if (adapter.installWindowGate?.(configPath2, join26(dirname9(hookPath), "gate.cjs"), tool)) {
4936
6369
  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
6370
  }
4938
6371
  } catch (e) {
4939
6372
  out(`\u26A0\uFE0F ${tool}\uFF1A\u8BBE\u5907\u786E\u8BA4\u6388\u6743\u6CA1\u88C5\u4E0A\uFF1A${e.message}`);
4940
6373
  }
6374
+ try {
6375
+ if (adapter.installRewake?.(configPath2, join26(dirname9(hookPath), "wake.cjs"), tool)) {
6376
+ 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`);
6377
+ }
6378
+ } catch (e) {
6379
+ 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`);
6380
+ }
4941
6381
  if (adapter.notice) out(adapter.notice);
4942
6382
  }
4943
6383
  cfg.enabledTools = [...enabled];
@@ -5032,8 +6472,8 @@ async function runDisable(tools, opts = {}) {
5032
6472
  }
5033
6473
 
5034
6474
  // src/commands/install.ts
5035
- import { mkdirSync as mkdirSync18, rmSync as rmSync9, writeFileSync as writeFileSync18 } from "fs";
5036
- import { join as join23 } from "path";
6475
+ import { mkdirSync as mkdirSync22, rmSync as rmSync15, writeFileSync as writeFileSync22 } from "fs";
6476
+ import { join as join27 } from "path";
5037
6477
 
5038
6478
  // src/core/detect.ts
5039
6479
  import { existsSync as existsSync16 } from "fs";
@@ -5078,7 +6518,7 @@ function detectTools(d = defaultDetectDeps()) {
5078
6518
  import qrcode from "qrcode-terminal";
5079
6519
 
5080
6520
  // src/commands/remoteCwd.ts
5081
- import { existsSync as existsSync17, statSync as statSync7 } from "fs";
6521
+ import { existsSync as existsSync17, statSync as statSync9 } from "fs";
5082
6522
  import { createInterface } from "readline";
5083
6523
  import { homedir as homedir6 } from "os";
5084
6524
  import { isAbsolute as isAbsolute2, resolve as resolve3 } from "path";
@@ -5092,7 +6532,7 @@ function normalizePath(raw, home = homedir6()) {
5092
6532
  function checkPath(abs) {
5093
6533
  if (!existsSync17(abs)) return "missing";
5094
6534
  try {
5095
- return statSync7(abs).isDirectory() ? "ok" : "not_a_dir";
6535
+ return statSync9(abs).isDirectory() ? "ok" : "not_a_dir";
5096
6536
  } catch {
5097
6537
  return "missing";
5098
6538
  }
@@ -5294,11 +6734,11 @@ function inSandbox(env) {
5294
6734
  }
5295
6735
  function precheckWritable(dirs) {
5296
6736
  for (const dir of dirs) {
5297
- const probe = join23(dir, `.deskbuddy-probe-${process.pid}`);
6737
+ const probe = join27(dir, `.deskbuddy-probe-${process.pid}`);
5298
6738
  try {
5299
- mkdirSync18(dir, { recursive: true });
5300
- writeFileSync18(probe, "");
5301
- rmSync9(probe, { force: true });
6739
+ mkdirSync22(dir, { recursive: true });
6740
+ writeFileSync22(probe, "");
6741
+ rmSync15(probe, { force: true });
5302
6742
  } catch (e) {
5303
6743
  return { ok: false, dir, error: e.message };
5304
6744
  }
@@ -5427,7 +6867,7 @@ async function runInstall(opts, deps = defaultInstallDeps()) {
5427
6867
 
5428
6868
  // src/commands/logout.ts
5429
6869
  import { createInterface as createInterface2 } from "readline";
5430
- import { existsSync as existsSync18, rmSync as rmSync10 } from "fs";
6870
+ import { existsSync as existsSync18, rmSync as rmSync16 } from "fs";
5431
6871
  function stdout4(line) {
5432
6872
  process.stdout.write(line + "\n");
5433
6873
  }
@@ -5459,7 +6899,7 @@ async function runLogout(opts = {}) {
5459
6899
  }
5460
6900
  await runDisable([], { out, brief: true, keepAutostart: opts.keepAutostart });
5461
6901
  if (existsSync18(home)) {
5462
- rmSync10(home, { recursive: true, force: true });
6902
+ rmSync16(home, { recursive: true, force: true });
5463
6903
  }
5464
6904
  out("");
5465
6905
  out("\u2705 \u5DF2\u6E05\u9664\u672C\u673A\u7684 DeskBuddy \u6570\u636E\u3002");
@@ -5619,45 +7059,15 @@ async function runUpdate(opts = {}) {
5619
7059
  }
5620
7060
 
5621
7061
  // 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";
7062
+ import { randomBytes as randomBytes8 } from "crypto";
7063
+ import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync24, statSync as statSync11, truncateSync as truncateSync3 } from "fs";
7064
+ import { join as join29 } from "path";
5625
7065
  import { StringDecoder } from "string_decoder";
5626
7066
 
5627
- // src/wrap/screen.ts
5628
- var ESC = "\x1B";
5629
- var PASTE_BEGIN = ESC + "[200~";
5630
- var PASTE_END = ESC + "[201~";
5631
- function sanitize(text) {
5632
- return String(text).replace(/\r\n?/g, "\n").replace(/[\x00-\x08\x0b-\x1f\x7f-\x9f]/g, "");
5633
- }
5634
- function prepare(text) {
5635
- return " " + sanitize(text).trim();
5636
- }
5637
- var PERMISSION_RE = /Do you want to (proceed|make|create|run|allow)|[❯>]\s*1\. Yes/;
5638
- function inspectLines(lines2) {
5639
- const text = lines2.join("\n");
5640
- const permission = PERMISSION_RE.test(text);
5641
- const rules = [];
5642
- lines2.forEach((l, i) => {
5643
- if (/^─{20,}/.test(l.trim())) rules.push(i);
5644
- });
5645
- let input = null;
5646
- if (rules.length >= 2) {
5647
- const a = rules[rules.length - 2];
5648
- const b = rules[rules.length - 1];
5649
- input = lines2.slice(a + 1, b).join("\n").replace(/^\s*[❯>]\s?/, "").trimEnd();
5650
- }
5651
- return { permission, input };
5652
- }
5653
- function boxMatches(view, body) {
5654
- if (view.input === null || view.permission) return false;
5655
- const flat = (x) => x.replace(/\s+/g, "");
5656
- return flat(view.input) === flat(body) || /\[Pasted text #\d+/.test(view.input);
5657
- }
5658
-
5659
7067
  // src/wrap/injector.ts
7068
+ var SEEN_MAX = 200;
5660
7069
  var QUIET_MS = 1500;
7070
+ var STARTUP_QUIET_MS = 3e3;
5661
7071
  var KEY_QUIET_MS = 3e3;
5662
7072
  var PASTE_SETTLE_MS = 400;
5663
7073
  var SUBMIT_WAIT_MS = 1e4;
@@ -5669,6 +7079,8 @@ var Injector = class {
5669
7079
  #lastKey = 0;
5670
7080
  #busy = false;
5671
7081
  #closed = false;
7082
+ /** instructionId → 那一条(去重:daemon 等 ack 超时重送时,不能写进窗口两遍) */
7083
+ #seen = /* @__PURE__ */ new Map();
5672
7084
  constructor(deps) {
5673
7085
  this.#d = deps;
5674
7086
  this.#lastOut = deps.now();
@@ -5688,7 +7100,27 @@ var Injector = class {
5688
7100
  if (i >= 0) this.#queue.splice(i, 1);
5689
7101
  if (job.done) return;
5690
7102
  job.done = true;
5691
- job.reply({ instructionId: job.instructionId, ...msg });
7103
+ job.finalMsg = { instructionId: job.instructionId, ...msg };
7104
+ job.reply(job.finalMsg);
7105
+ }
7106
+ /** 记下这条,超过上限丢最早的 */
7107
+ #remember(job) {
7108
+ this.#seen.set(job.instructionId, job);
7109
+ while (this.#seen.size > SEEN_MAX) this.#seen.delete(this.#seen.keys().next().value);
7110
+ }
7111
+ /**
7112
+ * 同一 instructionId 又送来了(daemon 没等到 ack 重送、而第一次其实已收下):不再排队,
7113
+ * 把后续进展改回给新连接;已有终态的原样再回一次
7114
+ */
7115
+ #rebind(old, job) {
7116
+ job.reply({ instructionId: job.instructionId, status: "ack" });
7117
+ if (old.done) {
7118
+ if (old.finalMsg) job.reply(old.finalMsg);
7119
+ return;
7120
+ }
7121
+ old.reply = job.reply;
7122
+ old.alive = job.alive;
7123
+ if (old.queuedSent) job.reply({ instructionId: job.instructionId, status: "queued", mode: "window", reason: "" });
5692
7124
  }
5693
7125
  /**
5694
7126
  * 这个会话还是不是本窗口当前的会话。
@@ -5699,19 +7131,35 @@ var Injector = class {
5699
7131
  #owns(sessionId) {
5700
7132
  const last = this.#d.lastEvent();
5701
7133
  if (!last) return true;
7134
+ if (!sessionId) return last.ev !== "SessionEnd";
5702
7135
  return last.sid === sessionId && last.ev !== "SessionEnd";
5703
7136
  }
5704
7137
  /** 通道收到一条指令 */
5705
7138
  submit(job) {
7139
+ const seen = this.#seen.get(job.instructionId);
7140
+ if (seen) {
7141
+ this.#rebind(seen, job);
7142
+ return;
7143
+ }
5706
7144
  if (this.#closed) {
5707
7145
  job.reply({ instructionId: job.instructionId, status: "not_here" });
5708
7146
  return;
5709
7147
  }
7148
+ if (tooLongToInject(job.text)) {
7149
+ this.#remember(job);
7150
+ job.done = true;
7151
+ job.finalMsg = { instructionId: job.instructionId, status: "rejected", mode: "window", reason: TEXT_TOO_LONG };
7152
+ job.reply(job.finalMsg);
7153
+ return;
7154
+ }
5710
7155
  if (!this.#owns(job.sessionId)) {
7156
+ this.#remember(job);
5711
7157
  job.done = true;
5712
- job.reply({ instructionId: job.instructionId, status: "rejected", mode: "window", reason: "session_gone" });
7158
+ job.finalMsg = { instructionId: job.instructionId, status: "rejected", mode: "window", reason: "session_gone" };
7159
+ job.reply(job.finalMsg);
5713
7160
  return;
5714
7161
  }
7162
+ this.#remember(job);
5715
7163
  job.phase = "queued";
5716
7164
  job.reply({ instructionId: job.instructionId, status: "ack" });
5717
7165
  this.#queue.push(job);
@@ -5732,10 +7180,11 @@ var Injector = class {
5732
7180
  if (v.input === null) why.push("no-input-box");
5733
7181
  else if (v.input !== "") why.push("user-typing");
5734
7182
  const last = this.#d.lastEvent();
5735
- if (!last) why.push("hook:none");
5736
- else if (!IDLE_EVENTS.has(last.ev)) why.push("hook:" + last.ev);
7183
+ if (!last) {
7184
+ if (!this.#d.startupIdle) why.push("hook:none");
7185
+ } else if (!IDLE_EVENTS.has(last.ev)) why.push("hook:" + last.ev);
5737
7186
  const now = this.#d.now();
5738
- if (now - this.#lastOut < QUIET_MS) why.push("screen-busy");
7187
+ if (now - this.#lastOut < (last ? QUIET_MS : STARTUP_QUIET_MS)) why.push("screen-busy");
5739
7188
  if (now - this.#lastKey < KEY_QUIET_MS) why.push("recent-key");
5740
7189
  return why;
5741
7190
  }
@@ -5746,6 +7195,7 @@ var Injector = class {
5746
7195
  if (job.alive && !job.alive()) {
5747
7196
  this.#queue.shift();
5748
7197
  job.done = true;
7198
+ this.#seen.delete(job.instructionId);
5749
7199
  this.#d.log?.("daemon \u8FDE\u63A5\u5DF2\u65AD\uFF0C\u4E22\u5F03\u672A\u5199\u5165\u7684\u4E00\u6761");
5750
7200
  return;
5751
7201
  }
@@ -5832,8 +7282,8 @@ var Injector = class {
5832
7282
  };
5833
7283
 
5834
7284
  // src/wrap/passthrough.ts
5835
- import { spawn as spawn2 } from "child_process";
5836
- import { realpathSync as realpathSync2 } from "fs";
7285
+ import { spawn as spawn3 } from "child_process";
7286
+ import { realpathSync as realpathSync3 } from "fs";
5837
7287
  import { constants as constants2 } from "os";
5838
7288
  import { dirname as dirname11, resolve as resolve4 } from "path";
5839
7289
  var CLAUDE_SUBCOMMANDS = /* @__PURE__ */ new Set([
@@ -5891,7 +7341,7 @@ function norm(p, win) {
5891
7341
  }
5892
7342
  function real(p) {
5893
7343
  try {
5894
- return realpathSync2(p);
7344
+ return realpathSync3(p);
5895
7345
  } catch {
5896
7346
  return p;
5897
7347
  }
@@ -5906,6 +7356,9 @@ function findReal(tool, d = defaultLocateDeps(), skip = shimDir()) {
5906
7356
  const loc = locate(tool, dd);
5907
7357
  return loc && !inSkip(loc.prefixArgs[0] && /\.(?:c|m)?js$/i.test(loc.prefixArgs[0]) ? loc.prefixArgs[0] : loc.file) ? loc : null;
5908
7358
  }
7359
+ if (win && tool === "claude_code") {
7360
+ for (const p of winClaudeExeCandidates(dd)) if (dd.exists(p) && !inSkip(p)) return { file: p, prefixArgs: [] };
7361
+ }
5909
7362
  for (const f of whichAllIn(name, dd)) {
5910
7363
  if (inSkip(f)) continue;
5911
7364
  const loc = win ? resolveWinLaunch(f, dd) : { file: f, prefixArgs: [] };
@@ -5925,7 +7378,7 @@ function runDirect(real2, args, env = process.env) {
5925
7378
  return new Promise((done) => {
5926
7379
  let child;
5927
7380
  try {
5928
- child = spawn2(real2.file, [...real2.prefixArgs, ...args], {
7381
+ child = spawn3(real2.file, [...real2.prefixArgs, ...args], {
5929
7382
  stdio: "inherit",
5930
7383
  // 🔴 再经过垫片也直接放行,不会绕回来
5931
7384
  env: withoutShimFlag({ ...env, DESKBUDDY_WRAPPED: "1" }),
@@ -5975,21 +7428,21 @@ function runDirect(real2, args, env = process.env) {
5975
7428
  }
5976
7429
 
5977
7430
  // src/wrap/codexLaunch.ts
5978
- 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";
7431
+ import { spawn as spawn4 } from "child_process";
7432
+ import { closeSync as closeSync3, mkdirSync as mkdirSync23, openSync as openSync3, readdirSync as readdirSync6, readFileSync as readFileSync21, rmSync as rmSync17, statSync as statSync10, truncateSync as truncateSync2, writeFileSync as writeFileSync23, renameSync as renameSync16 } from "fs";
5980
7433
  import { request } from "http";
5981
- import { createServer as createServer3 } from "net";
5982
- import { join as join24 } from "path";
7434
+ import { createServer as createServer4 } from "net";
7435
+ import { join as join28 } from "path";
5983
7436
 
5984
7437
  // src/core/codexServer.ts
5985
- import { execFileSync as execFileSync8 } from "child_process";
7438
+ import { execFileSync as execFileSync10 } from "child_process";
5986
7439
  function isAppServerArgs(args, url) {
5987
7440
  return /\bapp-server\b/.test(args) && new RegExp(`--listen[= ]${url.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:\\s|$)`).test(args);
5988
7441
  }
5989
7442
  function processArgs(pid) {
5990
7443
  if (process.platform === "win32") return null;
5991
7444
  try {
5992
- const out = execFileSync8("ps", ["-ww", "-o", "args=", "-p", String(pid)], {
7445
+ const out = execFileSync10("ps", ["-ww", "-o", "args=", "-p", String(pid)], {
5993
7446
  encoding: "utf8",
5994
7447
  stdio: ["ignore", "pipe", "ignore"]
5995
7448
  });
@@ -6147,10 +7600,10 @@ async function runCodexWindow(real2, args, d) {
6147
7600
  }
6148
7601
  var MAX_LOG_BYTES2 = 1024 * 1024;
6149
7602
  function appServerLogPath() {
6150
- return join24(deskbuddyHome(), "codex-app-server.log");
7603
+ return join28(deskbuddyHome(), "codex-app-server.log");
6151
7604
  }
6152
7605
  function windowsDir2() {
6153
- return join24(deskbuddyHome(), "codex-windows");
7606
+ return join28(deskbuddyHome(), "codex-windows");
6154
7607
  }
6155
7608
  function pidAlive(pid) {
6156
7609
  try {
@@ -6163,7 +7616,7 @@ function pidAlive(pid) {
6163
7616
  function daemonAlive(pidFile = pidFilePath(), args = processArgs) {
6164
7617
  let pid = 0;
6165
7618
  try {
6166
- pid = Number(readFileSync18(pidFile, "utf8").trim());
7619
+ pid = Number(readFileSync21(pidFile, "utf8").trim());
6167
7620
  } catch {
6168
7621
  return false;
6169
7622
  }
@@ -6185,7 +7638,7 @@ function probeReady(url) {
6185
7638
  }
6186
7639
  function freePort() {
6187
7640
  return new Promise((resolve5, reject) => {
6188
- const srv = createServer3();
7641
+ const srv = createServer4();
6189
7642
  srv.unref();
6190
7643
  srv.on("error", reject);
6191
7644
  srv.listen(0, "127.0.0.1", () => {
@@ -6197,16 +7650,16 @@ function freePort() {
6197
7650
  }
6198
7651
  function startServer(real2, url, env, cwd) {
6199
7652
  const logPath2 = appServerLogPath();
6200
- mkdirSync19(deskbuddyHome(), { recursive: true });
7653
+ mkdirSync23(deskbuddyHome(), { recursive: true });
6201
7654
  try {
6202
- if (statSync8(logPath2).size > MAX_LOG_BYTES2) truncateSync2(logPath2, 0);
7655
+ if (statSync10(logPath2).size > MAX_LOG_BYTES2) truncateSync2(logPath2, 0);
6203
7656
  } catch {
6204
7657
  }
6205
- const fd = openSync(logPath2, "a");
7658
+ const fd = openSync3(logPath2, "a");
6206
7659
  try {
6207
7660
  const childEnv2 = { ...env, DESKBUDDY_WRAPPED: "1" };
6208
7661
  delete childEnv2.DESKBUDDY_SHIM;
6209
- const child = spawn3(real2.file, [...real2.prefixArgs, "app-server", "--listen", url], {
7662
+ const child = spawn4(real2.file, [...real2.prefixArgs, "app-server", "--listen", url], {
6210
7663
  detached: true,
6211
7664
  stdio: ["ignore", fd, fd],
6212
7665
  cwd,
@@ -6217,7 +7670,7 @@ function startServer(real2, url, env, cwd) {
6217
7670
  child.unref();
6218
7671
  return child.pid ?? null;
6219
7672
  } finally {
6220
- closeSync(fd);
7673
+ closeSync3(fd);
6221
7674
  }
6222
7675
  }
6223
7676
  function killServer(pid) {
@@ -6231,41 +7684,41 @@ function killOwnServer(pid, url) {
6231
7684
  if (a && isAppServerArgs(a, url)) killServer(pid);
6232
7685
  }
6233
7686
  function recordPath(rec) {
6234
- return join24(windowsDir2(), `${rec.server}.json`);
7687
+ return join28(windowsDir2(), `${rec.server}.json`);
6235
7688
  }
6236
7689
  function writeRecord(rec) {
6237
7690
  try {
6238
- mkdirSync19(windowsDir2(), { recursive: true, mode: 448 });
7691
+ mkdirSync23(windowsDir2(), { recursive: true, mode: 448 });
6239
7692
  const p = recordPath(rec);
6240
- writeFileSync19(p + ".tmp", JSON.stringify(rec), { encoding: "utf8", mode: 384 });
6241
- renameSync13(p + ".tmp", p);
7693
+ writeFileSync23(p + ".tmp", JSON.stringify(rec), { encoding: "utf8", mode: 384 });
7694
+ renameSync16(p + ".tmp", p);
6242
7695
  } catch {
6243
7696
  }
6244
7697
  }
6245
7698
  function removeRecord(rec) {
6246
7699
  try {
6247
- rmSync11(recordPath(rec), { force: true });
7700
+ rmSync17(recordPath(rec), { force: true });
6248
7701
  } catch {
6249
7702
  }
6250
7703
  }
6251
7704
  function reapWindows(dir = windowsDir2(), alive = pidAlive, args = processArgs, kill = killServer) {
6252
7705
  let names = [];
6253
7706
  try {
6254
- names = readdirSync4(dir).filter((n) => n.endsWith(".json"));
7707
+ names = readdirSync6(dir).filter((n) => n.endsWith(".json"));
6255
7708
  } catch {
6256
7709
  return;
6257
7710
  }
6258
7711
  for (const n of names) {
6259
- const p = join24(dir, n);
7712
+ const p = join28(dir, n);
6260
7713
  let rec;
6261
7714
  try {
6262
- rec = JSON.parse(readFileSync18(p, "utf8"));
7715
+ rec = JSON.parse(readFileSync21(p, "utf8"));
6263
7716
  } catch {
6264
- rmSync11(p, { force: true });
7717
+ rmSync17(p, { force: true });
6265
7718
  continue;
6266
7719
  }
6267
7720
  if (typeof rec.supervisor !== "number" || typeof rec.server !== "number" || typeof rec.url !== "string") {
6268
- rmSync11(p, { force: true });
7721
+ rmSync17(p, { force: true });
6269
7722
  continue;
6270
7723
  }
6271
7724
  if (alive(rec.supervisor)) continue;
@@ -6273,13 +7726,13 @@ function reapWindows(dir = windowsDir2(), alive = pidAlive, args = processArgs,
6273
7726
  const a = args(rec.server);
6274
7727
  if (a && isAppServerArgs(a, rec.url)) kill(rec.server);
6275
7728
  }
6276
- rmSync11(p, { force: true });
7729
+ rmSync17(p, { force: true });
6277
7730
  }
6278
7731
  }
6279
7732
  function launchLog(line) {
6280
7733
  try {
6281
- mkdirSync19(deskbuddyHome(), { recursive: true });
6282
- writeFileSync19(join24(deskbuddyHome(), "wrap.log"), `${(/* @__PURE__ */ new Date()).toISOString()} [codex ${process.pid}] ${line}
7734
+ mkdirSync23(deskbuddyHome(), { recursive: true });
7735
+ writeFileSync23(join28(deskbuddyHome(), "wrap.log"), `${(/* @__PURE__ */ new Date()).toISOString()} [codex ${process.pid}] ${line}
6283
7736
  `, { flag: "a" });
6284
7737
  } catch {
6285
7738
  }
@@ -6319,10 +7772,10 @@ function childEnv(src, windowId) {
6319
7772
  }
6320
7773
  function wrapLog(line) {
6321
7774
  try {
6322
- const p = join25(deskbuddyHome(), "wrap.log");
6323
- mkdirSync20(deskbuddyHome(), { recursive: true });
7775
+ const p = join29(deskbuddyHome(), "wrap.log");
7776
+ mkdirSync24(deskbuddyHome(), { recursive: true });
6324
7777
  try {
6325
- if (statSync9(p).size > MAX_LOG_BYTES3) truncateSync3(p, 0);
7778
+ if (statSync11(p).size > MAX_LOG_BYTES3) truncateSync3(p, 0);
6326
7779
  } catch {
6327
7780
  }
6328
7781
  appendFileSync2(p, `${(/* @__PURE__ */ new Date()).toISOString()} [${process.pid}] ${line}
@@ -6344,6 +7797,7 @@ var defaultWrapDeps = () => ({
6344
7797
  log: wrapLog
6345
7798
  });
6346
7799
  async function runWrap(argv, deps = defaultWrapDeps()) {
7800
+ if (argv[0] === "--launch") return runLaunch(argv.slice(1), deps);
6347
7801
  const viaShim = deps.env.DESKBUDDY_SHIM === "1";
6348
7802
  const r = await runWrapInner(argv, deps);
6349
7803
  if (r === "not_found") {
@@ -6401,7 +7855,59 @@ async function runWrapInner(argv, deps) {
6401
7855
  if (r !== null) return r;
6402
7856
  return deps.runDirect(real2, args);
6403
7857
  }
6404
- async function wrapInPty(tool, real2, args, deps) {
7858
+ var defaultLaunchDeps = () => ({
7859
+ claim: claimLaunch,
7860
+ isDir: isDirSync,
7861
+ chdir: (p) => process.chdir(p)
7862
+ });
7863
+ async function runLaunch(argv, deps = defaultWrapDeps(), ld = defaultLaunchDeps()) {
7864
+ const runId = argv[0] ?? "";
7865
+ const hi = argv.indexOf("--home");
7866
+ const home = hi >= 0 ? argv[hi + 1] ?? "" : "";
7867
+ if (!RUN_ID_RE.test(runId)) {
7868
+ deps.stderr.write("deskbuddy: \u542F\u52A8\u53C2\u6570\u4E0D\u5BF9\n");
7869
+ return 2;
7870
+ }
7871
+ if (home) {
7872
+ if (!/^(?:[A-Za-z]:[\\/]|\/)/.test(home)) {
7873
+ deps.stderr.write("deskbuddy: \u542F\u52A8\u53C2\u6570\u4E0D\u5BF9\n");
7874
+ return 2;
7875
+ }
7876
+ process.env.DESKBUDDY_HOME = home;
7877
+ deps.env.DESKBUDDY_HOME = home;
7878
+ }
7879
+ const plan = ld.claim(runId);
7880
+ if (!plan) {
7881
+ deps.stderr.write("deskbuddy: \u8FD9\u4E2A\u7A97\u53E3\u8981\u505A\u7684\u4E8B\u5DF2\u7ECF\u53D6\u6D88\u6216\u6539\u5728\u540E\u53F0\u6267\u884C\u4E86\uFF0C\u53EF\u4EE5\u5173\u6389\u8FD9\u4E2A\u7A97\u53E3\n");
7882
+ return 1;
7883
+ }
7884
+ if (!ld.isDir(plan.cwd)) {
7885
+ deps.stderr.write("deskbuddy: \u5DE5\u4F5C\u76EE\u5F55\u5DF2\u7ECF\u4E0D\u5728\u4E86\n");
7886
+ return 1;
7887
+ }
7888
+ ld.chdir(plan.cwd);
7889
+ const args = plan.resume ? ["--resume", plan.resume] : [];
7890
+ let real2 = null;
7891
+ try {
7892
+ real2 = deps.findReal("claude_code");
7893
+ } catch {
7894
+ real2 = null;
7895
+ }
7896
+ if (!real2) {
7897
+ deps.stderr.write("deskbuddy: \u627E\u4E0D\u5230 claude\n");
7898
+ return 127;
7899
+ }
7900
+ const d2 = { ...deps, cwd: () => plan.cwd };
7901
+ let r = null;
7902
+ try {
7903
+ r = await wrapInPty("claude_code", real2, args, d2, { windowId: runId, launched: true });
7904
+ } catch (e) {
7905
+ deps.log(`\u5F00\u7A97\u5305\u88C5\u5931\u8D25\uFF0C\u76F4\u63A5\u8FD0\u884C\uFF1A${e.message}`);
7906
+ }
7907
+ if (r !== null) return r;
7908
+ return deps.runDirect(real2, args);
7909
+ }
7910
+ async function wrapInPty(tool, real2, args, deps, opts = {}) {
6405
7911
  const pty = deps.loadPty();
6406
7912
  const Headless = deps.loadHeadless();
6407
7913
  if (!pty || !Headless) {
@@ -6409,7 +7915,7 @@ async function wrapInPty(tool, real2, args, deps) {
6409
7915
  return null;
6410
7916
  }
6411
7917
  const { stdout: stdout7 } = deps;
6412
- const windowId = randomBytes6(16).toString("hex");
7918
+ const windowId = opts.windowId ?? randomBytes8(16).toString("hex");
6413
7919
  const cols = stdout7.columns || 80;
6414
7920
  const rows = stdout7.rows || 24;
6415
7921
  const term = new Headless({ cols, rows, allowProposedApi: true });
@@ -6426,9 +7932,9 @@ async function wrapInPty(tool, real2, args, deps) {
6426
7932
  deps.log(`pty \u8D77\u4E0D\u6765\uFF0C\u653E\u884C\u76F4\u8DD1\uFF1A${e.message}`);
6427
7933
  return null;
6428
7934
  }
6429
- return runAttached(tool, p, term, windowId, deps);
7935
+ return runAttached(tool, p, term, windowId, deps, opts.launched === true);
6430
7936
  }
6431
- function runAttached(tool, p, term0, windowId, deps) {
7937
+ function runAttached(tool, p, term0, windowId, deps, launched = false) {
6432
7938
  const { stdin, stdout: stdout7 } = deps;
6433
7939
  let term = term0;
6434
7940
  let finished = false;
@@ -6458,7 +7964,8 @@ function runAttached(tool, p, term0, windowId, deps) {
6458
7964
  held = null;
6459
7965
  for (const s of h) toPty(s);
6460
7966
  },
6461
- log: (m) => deps.log(`[${windowId.slice(0, 8)}] ${m}`)
7967
+ log: (m) => deps.log(`[${windowId.slice(0, 8)}] ${m}`),
7968
+ startupIdle: launched
6462
7969
  });
6463
7970
  p.onData((d) => {
6464
7971
  try {
@@ -6515,8 +8022,18 @@ function runAttached(tool, p, term0, windowId, deps) {
6515
8022
  } catch (e) {
6516
8023
  deps.log(`\u63A5\u7BA1\u7EC8\u7AEF\u51FA\u9519\uFF1A${e.message}`);
6517
8024
  }
8025
+ const secret = randomBytes8(32);
6518
8026
  try {
6519
- writeWindow(windowId, { pid: process.pid, childPid: p.pid, sock, tool, cwd: deps.cwd(), at: Date.now() });
8027
+ writeWindow(windowId, {
8028
+ pid: process.pid,
8029
+ childPid: p.pid,
8030
+ sock,
8031
+ tool,
8032
+ cwd: deps.cwd(),
8033
+ at: Date.now(),
8034
+ secret: secret.toString("hex"),
8035
+ ...launched ? { launched: true } : {}
8036
+ });
6520
8037
  } catch (e) {
6521
8038
  deps.log(`\u767B\u8BB0\u7A97\u53E3\u5931\u8D25\uFF1A${e.message}`);
6522
8039
  }
@@ -6524,6 +8041,7 @@ function runAttached(tool, p, term0, windowId, deps) {
6524
8041
  if (channelOk) {
6525
8042
  void startChannelServer(
6526
8043
  sock,
8044
+ secret,
6527
8045
  (req2, reply, alive) => {
6528
8046
  try {
6529
8047
  injector.submit({ ...req2, expireAt: req2.expireAt || Date.now() + DEFAULT_TTL_MS, reply, alive });